1+ import random
2+ import polars as pl
3+ import pgzrun
4+ from types import SimpleNamespace
5+
6+ WIDTH = 800
7+ HEIGHT = 600
8+ TITLE = "Wizarding Duel: The Final Battle"
9+
10+ # --- State ---
11+ hp = {"Harry" : 100 , "Voldemort" : 100 }
12+ display = SimpleNamespace (Harry = 100 , Voldemort = 100 )
13+
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+
18+ message = "A wild VOLDEMORT appeared!"
19+ sub_message = "What will HARRY do?"
20+ waiting_for_input = True
21+ game_active = True
22+
23+ # --- Load Data ---
24+ spells_df = pl .read_csv (r"C:\Users\alema\Desktop\pythonbiella\LearningPythonWithGames\game11\spells.csv" )
25+
26+ def get_options (character ):
27+ return spells_df .filter (pl .col ("character" ) == character )
28+
29+ # --- Visual Effects ---
30+
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 )
42+
43+ # --- Logic Core ---
44+
45+ def execute_move (attacker_name , defender_name , spell_df , spell_index ):
46+ global message , sub_message , game_active
47+
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 ]})
67+ else :
68+ sub_message = f"The spell did not work!"
69+
70+ if hp [defender_name ] <= 0 :
71+ game_active = False
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 )
89+
90+ def ready_harry ():
91+ """Resets the UI so Harry can choose a spell."""
92+ global message , sub_message , waiting_for_input
93+ message = "What will HARRY do?"
94+ sub_message = "Select a spell..."
95+ waiting_for_input = True
96+
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+
114+ # --- Draw Functions ---
115+
116+ def draw ():
117+ screen .clear ()
118+ screen .draw .filled_rect (Rect ((0 , 0 ), (800 , 400 )), (200 , 230 , 255 ))
119+ screen .draw .filled_rect (Rect ((0 , 400 ), (800 , 200 )), (120 , 180 , 120 ))
120+
121+ voldy_sprite .draw ()
122+ harry_sprite .draw ()
123+
124+ draw_status_box ("VOLDEMORT" , display .Voldemort , 50 , 50 )
125+ draw_status_box ("HARRY" , display .Harry , 450 , 250 )
126+
127+ # Dialogue Box
128+ screen .draw .filled_rect (Rect ((10 , 410 ), (780 , 180 )), (50 , 50 , 60 ))
129+ screen .draw .rect (Rect ((10 , 410 ), (780 , 180 )), "white" )
130+
131+ if waiting_for_input and game_active :
132+ draw_move_menu ()
133+ else :
134+ screen .draw .text (message , (40 , 450 ), fontsize = 40 , color = "white" )
135+ screen .draw .text (sub_message , (40 , 510 ), fontsize = 30 , color = "lightgray" )
136+
137+ def draw_status_box (name , val , x , y ):
138+ screen .draw .filled_rect (Rect ((x , y ), (300 , 80 )), "white" )
139+ screen .draw .rect (Rect ((x , y ), (300 , 80 )), "black" )
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 )
145+
146+ def draw_move_menu ():
147+ opts = get_options ("Harry" )[:4 ]
148+ for i in range (len (opts )):
149+ x , y = 40 + (i % 2 )* 380 , 440 + (i // 2 )* 60
150+ screen .draw .rect (Rect ((x , y ), (350 , 50 )), "white" )
151+ screen .draw .text (f"> { opts [i , 'spell' ].upper ()} " , (x + 20 , y + 15 ), fontsize = 30 )
152+
153+ pgzrun .go ()
0 commit comments