33import pgzrun
44from types import SimpleNamespace
55
6- # --- Settings ---
76WIDTH = 800
87HEIGHT = 600
9- TITLE = "Wizarding Duel: Turn-Based Strategy "
8+ TITLE = "Wizarding Duel: The Final Battle "
109
1110# --- State ---
1211hp = {"Harry" : 100 , "Voldemort" : 100 }
1312display = SimpleNamespace (Harry = 100 , Voldemort = 100 )
1413
14+ # Sprites (Ensure harry.png and voldemort.png are in the 'images' folder)
15+ harry_sprite = Actor ('harry' , (200 , 320 ))
16+ voldy_sprite = Actor ('voldemort' , (600 , 150 ))
17+
1518message = "A wild VOLDEMORT appeared!"
1619sub_message = "What will HARRY do?"
17- current_turn = "Harry" # Who is currently attacking
18- waiting_for_input = True # Controls if buttons are visible
20+ waiting_for_input = True
1921game_active = True
2022
2123# --- Load Data ---
22- spells_df = pl .read_csv (" spells.csv" )
24+ spells_df = pl .read_csv (r"C:\Users\alema\Desktop\pythonbiella\LearningPythonWithGames\game11\ spells.csv" )
2325
2426def get_options (character ):
25- return spells_df .filter (pl .col ("character" ) == character ). to_dicts ()
27+ return spells_df .filter (pl .col ("character" ) == character )
2628
27- # --- Battle Logic ---
29+ # --- Visual Effects ---
2830
29- def player_choice (spell_data ):
30- """Triggered when Harry clicks a button."""
31- global waiting_for_input , message , sub_message
32-
33- waiting_for_input = False
34- execute_move ("Harry" , "Voldemort" , spell_data )
35-
36- # If Voldemort survived, schedule his turn in 2 seconds
37- if game_active :
38- clock .schedule_unique (voldemort_turn , 2.0 )
31+ def flash_hurt (sprite ):
32+ """Blinks the sprite and shakes it slightly."""
33+ original_x = sprite .x
34+ # Quick shake
35+ animate (sprite , duration = 0.1 , x = original_x + 10 , tween = 'bounce_end' )
36+ # Blink
37+ for i in range (3 ):
38+ clock .schedule_unique (lambda : setattr (sprite , 'opacity' , 0 ), i * 0.2 )
39+ clock .schedule_unique (lambda : setattr (sprite , 'opacity' , 255 ), i * 0.2 + 0.1 )
40+ # Reset position
41+ clock .schedule_unique (lambda : setattr (sprite , 'x' , original_x ), 0.3 )
3942
40- def voldemort_turn ():
41- """Triggered automatically after Harry's turn."""
42- global message , sub_message , waiting_for_input
43-
44- if not game_active : return
43+ # --- Logic Core ---
4544
46- choices = get_options ("Voldemort" )
47- spell = random .choice (choices )
48-
49- execute_move ("Voldemort" , "Harry" , spell )
50-
51- # After Voldemort attacks, give Harry control back in 1.5 seconds
52- if game_active :
53- clock .schedule_unique (reset_to_player , 1.5 )
54-
55- def execute_move (attacker , defender , spell_data ):
45+ def execute_move (attacker_name , defender_name , spell_df , spell_index ):
5646 global message , sub_message , game_active
5747
58- raw_damage = float (spell_data ["damage" ])
59-
60- # --- HEALING LOGIC ---
61- if raw_damage < 0 :
62- # It's a heal! Target is the attacker, not the defender
63- heal_amount = abs (raw_damage )
64- hp [attacker ] = min (100 , hp [attacker ] + heal_amount ) # Cap at 100
65-
66- message = f"{ attacker .upper ()} used { spell_data ['spell' ].upper ()} !"
67- sub_message = f"It recovered { heal_amount } HP!"
68-
69- # Animate the attacker's bar
70- if attacker == "Harry" :
71- animate (display , duration = 0.6 , Harry = hp ["Harry" ])
72- else :
73- animate (display , duration = 0.6 , Voldemort = hp ["Voldemort" ])
74-
75- # --- ATTACK LOGIC ---
48+ dmg = float (spell_df [spell_index , "damage" ])
49+ precision = float (spell_df [spell_index , "precision" ])
50+ message = f"{ attacker_name .upper ()} used { spell_df [spell_index , 'spell' ].upper ()} !"
51+ a = random .random ()
52+ spell_successful = a < precision
53+ print (a , precision )
54+ if spell_successful :
55+ if dmg < 0 : # Healing
56+ amt = abs (dmg )
57+ hp [attacker_name ] = min (100 , hp [attacker_name ] + amt )
58+ sub_message = f"It recovered { amt } HP!"
59+ animate (display , duration = 0.6 , ** {attacker_name : hp [attacker_name ]})
60+ else : # Attacking
61+ hp [defender_name ] = max (0 , hp [defender_name ] - dmg )
62+ sub_message = f"It dealt { dmg } damage!"
63+ # Visual hurt effect
64+ target_sprite = voldy_sprite if defender_name == "Voldemort" else harry_sprite
65+ flash_hurt (target_sprite )
66+ animate (display , duration = 0.6 , ** {defender_name : hp [defender_name ]})
7667 else :
77- hp [defender ] = max (0 , hp [defender ] - raw_damage )
78- message = f"{ attacker .upper ()} used { spell_data ['spell' ].upper ()} !"
79- sub_message = f"It dealt { raw_damage } damage!"
80-
81- # Animate the defender's bar
82- if defender == "Harry" :
83- animate (display , duration = 0.6 , Harry = hp ["Harry" ])
84- else :
85- animate (display , duration = 0.6 , Voldemort = hp ["Voldemort" ])
86-
87- # Check for win/loss (only matters if damage was dealt)
88- if hp [defender ] <= 0 :
68+ sub_message = f"The spell did not work!"
69+
70+ if hp [defender_name ] <= 0 :
8971 game_active = False
90- message = f"{ defender .upper ()} fainted!"
91- sub_message = f"{ attacker .upper ()} is the winner!"
72+ message = f"{ defender_name .upper ()} fainted!"
73+ sub_message = "The duel is over."
74+
75+ # --- Turn Handlers ---
76+
77+ def voldemort_phase ():
78+ """Voldemort picks a random spell and casts it."""
79+ global message , sub_message
80+ if not game_active : return
81+
82+ options = get_options ("Voldemort" )
83+ spell_index = random .randint (1 , len (options )) - 1
84+ execute_move ("Voldemort" , "Harry" , options , spell_index )
85+
86+ # After Voldemort moves, wait 2 seconds then let Harry play
87+ if game_active :
88+ clock .schedule_unique (ready_harry , 2.0 )
9289
93- def reset_to_player ():
90+ def ready_harry ():
91+ """Resets the UI so Harry can choose a spell."""
9492 global message , sub_message , waiting_for_input
9593 message = "What will HARRY do?"
96- sub_message = "Choose a spell to cast! "
94+ sub_message = "Select a spell... "
9795 waiting_for_input = True
9896
97+ def on_mouse_down (pos ):
98+ global waiting_for_input
99+
100+ if game_active and waiting_for_input :
101+ options = get_options ("Harry" )[:4 ]
102+ for i in range (len (options )):
103+ x = 40 + (i % 2 ) * 380
104+ y = 440 + (i // 2 ) * 60
105+ if Rect ((x , y ), (350 , 50 )).collidepoint (pos ):
106+ # Harry's action
107+ waiting_for_input = False
108+ execute_move ("Harry" , "Voldemort" , options , i )
109+
110+ # If Voldemort is still alive, he takes his turn in 2 seconds
111+ if game_active :
112+ clock .schedule_unique (voldemort_phase , 2.0 )
113+
99114# --- Draw Functions ---
100115
101116def draw ():
117+ screen .clear ()
102118 screen .draw .filled_rect (Rect ((0 , 0 ), (800 , 400 )), (200 , 230 , 255 ))
103119 screen .draw .filled_rect (Rect ((0 , 400 ), (800 , 200 )), (120 , 180 , 120 ))
104120
105- # Status Boxes
121+ voldy_sprite .draw ()
122+ harry_sprite .draw ()
123+
106124 draw_status_box ("VOLDEMORT" , display .Voldemort , 50 , 50 )
107- draw_status_box ("HARRY" , display .Harry , 450 , 300 )
125+ draw_status_box ("HARRY" , display .Harry , 450 , 250 )
108126
109- # UI Box
127+ # Dialogue Box
110128 screen .draw .filled_rect (Rect ((10 , 410 ), (780 , 180 )), (50 , 50 , 60 ))
111129 screen .draw .rect (Rect ((10 , 410 ), (780 , 180 )), "white" )
112130
113131 if waiting_for_input and game_active :
114132 draw_move_menu ()
115133 else :
116- # Show text messages during animations or enemy turn
117134 screen .draw .text (message , (40 , 450 ), fontsize = 40 , color = "white" )
118135 screen .draw .text (sub_message , (40 , 510 ), fontsize = 30 , color = "lightgray" )
119136
120- def draw_status_box (name , current_hp , x , y ):
137+ def draw_status_box (name , val , x , y ):
121138 screen .draw .filled_rect (Rect ((x , y ), (300 , 80 )), "white" )
122139 screen .draw .rect (Rect ((x , y ), (300 , 80 )), "black" )
123- screen .draw .text (name , (x + 20 , y + 15 ), color = "black" , fontsize = 30 )
124- # HP Bar Border
125- screen .draw .rect (Rect ((x + 100 , y + 45 ), (160 , 15 )), "black" )
126- # Fill
127- bar_width = (current_hp / 100 ) * 158
128- color = "green" if current_hp > 50 else "orange" if current_hp > 20 else "red"
129- if bar_width > 0 :
130- screen .draw .filled_rect (Rect ((x + 101 , y + 46 ), (bar_width , 13 )), color )
140+ screen .draw .text (name , (x + 20 , y + 15 ), color = "black" , fontsize = 30 )
141+ screen .draw .rect (Rect ((x + 100 , y + 45 ), (160 , 15 )), "black" )
142+ bw = (val / 100 ) * 158
143+ c = "green" if val > 50 else "orange" if val > 20 else "red"
144+ if bw > 0 : screen .draw .filled_rect (Rect ((x + 101 , y + 46 ), (bw , 13 )), c )
131145
132146def draw_move_menu ():
133- options = get_options ("Harry" )[:4 ]
134- for i , spell in enumerate (options ):
135- x = 40 + (i % 2 ) * 380
136- y = 440 + (i // 2 ) * 60
147+ opts = get_options ("Harry" )[:4 ]
148+ for i in range (len (opts )):
149+ x , y = 40 + (i % 2 )* 380 , 440 + (i // 2 )* 60
137150 screen .draw .rect (Rect ((x , y ), (350 , 50 )), "white" )
138- screen .draw .text (f"> { spell ['spell' ].upper ()} " , (x + 20 , y + 15 ), fontsize = 30 )
139-
140- # --- Input ---
141-
142- def on_mouse_down (pos ):
143- if game_active and waiting_for_input :
144- options = get_options ("Harry" )[:4 ]
145- for i in range (len (options )):
146- x = 40 + (i % 2 ) * 380
147- y = 440 + (i // 2 ) * 60
148- if Rect ((x , y ), (350 , 50 )).collidepoint (pos ):
149- player_choice (options [i ])
151+ screen .draw .text (f"> { opts [i , 'spell' ].upper ()} " , (x + 20 , y + 15 ), fontsize = 30 )
150152
151153pgzrun .go ()
0 commit comments