Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -26,9 +26,21 @@ class Character:
|
|
26 |
|
27 |
def attack_target(self, target):
|
28 |
"""Performs an attack on a target."""
|
29 |
-
# Add
|
|
|
|
|
|
|
|
|
30 |
damage = random.randint(self.attack - 2, self.attack + 2)
|
31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
message += "\n" + target.take_damage(damage)
|
33 |
return message
|
34 |
|
@@ -39,7 +51,8 @@ class PlayerCharacter(Character):
|
|
39 |
super().__init__(name, max_hp, attack, defense, character_type="player")
|
40 |
self.xp = 0
|
41 |
self.level = 1
|
42 |
-
|
|
|
43 |
|
44 |
def gain_xp(self, amount):
|
45 |
"""Awards experience points and checks for level up."""
|
@@ -109,6 +122,14 @@ class ScottPilgrimGame:
|
|
109 |
("The Subspace Highway", EvilEx("Young Neil", 160, 21, 8, 160, 75)) # Fictional final boss
|
110 |
]
|
111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
def reset_game(self):
|
113 |
"""Resets the game state for a new playthrough."""
|
114 |
self.__init__() # Re-initialize the game object to its default state
|
@@ -155,6 +176,7 @@ class ScottPilgrimGame:
|
|
155 |
if not self.player:
|
156 |
return ""
|
157 |
status = f"{self.player.name} (Lvl {self.player.level}) | HP: {self.player.current_hp}/{self.player.max_hp} | XP: {self.player.xp}/{self.player.level * 50}"
|
|
|
158 |
if self.current_enemy:
|
159 |
status += f"\n{self.current_enemy.name} | HP: {self.current_enemy.current_hp}/{self.current_enemy.max_hp}"
|
160 |
return status
|
@@ -231,6 +253,50 @@ class ScottPilgrimGame:
|
|
231 |
# Enemy gets a free hit as a penalty for a failed escape
|
232 |
self.enemy_attack_turn()
|
233 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
234 |
# --- Gradio Interface Logic ---
|
235 |
|
236 |
# Create a single game instance to be managed by gr.State
|
@@ -271,7 +337,8 @@ def _format_status_html(game_state):
|
|
271 |
status_html = [
|
272 |
f"<h4 style='margin:4px 0'>{player.name} (Lvl {player.level})</h4>",
|
273 |
f"HP: <progress value='{player.current_hp}' max='{player.max_hp}' style='width:160px;height:14px;'></progress> {player.current_hp}/{player.max_hp}<br>",
|
274 |
-
f"XP: <progress value='{player.xp}' max='{player.level * 50}' style='width:160px;height:14px;'></progress> {player.xp}/{player.level * 50}<br
|
|
|
275 |
]
|
276 |
|
277 |
if game_state.current_enemy:
|
@@ -309,6 +376,7 @@ def update_ui(game_state):
|
|
309 |
char_buttons_visible = (phase == "start")
|
310 |
action_buttons_visible = (phase == "combat")
|
311 |
play_again_visible = (phase in ["game_over", "win"])
|
|
|
312 |
|
313 |
return (
|
314 |
log_html,
|
@@ -316,6 +384,7 @@ def update_ui(game_state):
|
|
316 |
gr.update(visible=char_buttons_visible),
|
317 |
gr.update(visible=action_buttons_visible),
|
318 |
gr.update(visible=play_again_visible),
|
|
|
319 |
game_state
|
320 |
)
|
321 |
|
@@ -339,6 +408,15 @@ def play_again(game_state):
|
|
339 |
game_state.reset_game()
|
340 |
return update_ui(game_state)
|
341 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
342 |
# --- Gradio UI Layout ---
|
343 |
|
344 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
@@ -362,9 +440,14 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
362 |
|
363 |
play_again_btn = gr.Button("Play Again?", visible=False)
|
364 |
|
|
|
|
|
|
|
|
|
|
|
365 |
# --- Event Handlers ---
|
366 |
# Define a list of all UI components that need to be updated, including the state
|
367 |
-
outputs = [game_output, status_output, character_selection_buttons, game_action_buttons, play_again_btn, game_state]
|
368 |
|
369 |
# Connect buttons to their callback functions
|
370 |
scott_btn.click(choose_character, inputs=[scott_btn, game_state], outputs=outputs)
|
@@ -372,6 +455,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
372 |
attack_btn.click(player_attack, inputs=[game_state], outputs=outputs)
|
373 |
run_btn.click(player_run, inputs=[game_state], outputs=outputs)
|
374 |
play_again_btn.click(play_again, inputs=[game_state], outputs=outputs)
|
|
|
375 |
|
376 |
# Set the initial UI state when the app loads, ensuring the outputs match the function's return values
|
377 |
demo.load(update_ui, inputs=[game_state], outputs=outputs)
|
|
|
26 |
|
27 |
def attack_target(self, target):
|
28 |
"""Performs an attack on a target."""
|
29 |
+
# Add randomness, critical hits, and misses!
|
30 |
+
miss_chance = 0.05 # 5% chance to miss
|
31 |
+
if random.random() < miss_chance:
|
32 |
+
return f"{self.name} swings and MISSES {target.name}!"
|
33 |
+
|
34 |
damage = random.randint(self.attack - 2, self.attack + 2)
|
35 |
+
|
36 |
+
critical_hit = random.random() < 0.1 # 10% chance for a crit
|
37 |
+
if critical_hit:
|
38 |
+
damage = int(damage * 1.5)
|
39 |
+
crit_text = " CRITICAL HIT!"
|
40 |
+
else:
|
41 |
+
crit_text = ""
|
42 |
+
|
43 |
+
message = f"{self.name} attacks {target.name} for {damage} damage!{crit_text}"
|
44 |
message += "\n" + target.take_damage(damage)
|
45 |
return message
|
46 |
|
|
|
51 |
super().__init__(name, max_hp, attack, defense, character_type="player")
|
52 |
self.xp = 0
|
53 |
self.level = 1
|
54 |
+
# Start players with a little spending cash
|
55 |
+
self.money = 25
|
56 |
|
57 |
def gain_xp(self, amount):
|
58 |
"""Awards experience points and checks for level up."""
|
|
|
122 |
("The Subspace Highway", EvilEx("Young Neil", 160, 21, 8, 160, 75)) # Fictional final boss
|
123 |
]
|
124 |
|
125 |
+
# --- In-game shop items ---
|
126 |
+
self.shop_items = {
|
127 |
+
"Energy Drink": {"cost": 10, "type": "heal", "amount": 20, "desc": "Restore 20 HP"},
|
128 |
+
"Protein Bar": {"cost": 15, "type": "attack", "amount": 2, "desc": "+2 Attack"},
|
129 |
+
"Armor Patch": {"cost": 15, "type": "defense", "amount": 1, "desc": "+1 Defense"},
|
130 |
+
"Max Potion": {"cost": 20, "type": "heal_full", "amount": 0, "desc": "Fully heal"},
|
131 |
+
}
|
132 |
+
|
133 |
def reset_game(self):
|
134 |
"""Resets the game state for a new playthrough."""
|
135 |
self.__init__() # Re-initialize the game object to its default state
|
|
|
176 |
if not self.player:
|
177 |
return ""
|
178 |
status = f"{self.player.name} (Lvl {self.player.level}) | HP: {self.player.current_hp}/{self.player.max_hp} | XP: {self.player.xp}/{self.player.level * 50}"
|
179 |
+
status += f" | $: {self.player.money}"
|
180 |
if self.current_enemy:
|
181 |
status += f"\n{self.current_enemy.name} | HP: {self.current_enemy.current_hp}/{self.current_enemy.max_hp}"
|
182 |
return status
|
|
|
253 |
# Enemy gets a free hit as a penalty for a failed escape
|
254 |
self.enemy_attack_turn()
|
255 |
|
256 |
+
# --------- Shop Mechanics ---------
|
257 |
+
def get_shop_choices(self):
|
258 |
+
"""Return list of item names with price for UI dropdown."""
|
259 |
+
return [f"{name} - ${data['cost']}" for name, data in self.shop_items.items()]
|
260 |
+
|
261 |
+
def buy_item(self, item_display_name):
|
262 |
+
"""Process purchasing an item given the dropdown display string."""
|
263 |
+
if not self.player:
|
264 |
+
return
|
265 |
+
|
266 |
+
# Extract item key (before ' - $')
|
267 |
+
item_name = item_display_name.split(" - $")[0]
|
268 |
+
if item_name not in self.shop_items:
|
269 |
+
self.add_message("That item doesn't exist!")
|
270 |
+
return
|
271 |
+
|
272 |
+
item = self.shop_items[item_name]
|
273 |
+
cost = item["cost"]
|
274 |
+
|
275 |
+
if self.player.money < cost:
|
276 |
+
self.add_message("Not enough money!")
|
277 |
+
return
|
278 |
+
|
279 |
+
# Deduct money
|
280 |
+
self.player.money -= cost
|
281 |
+
|
282 |
+
# Apply item effect
|
283 |
+
if item["type"] == "heal":
|
284 |
+
self.player.current_hp = min(self.player.max_hp, self.player.current_hp + item["amount"])
|
285 |
+
self.add_message(f"You used {item_name} and healed {item['amount']} HP!")
|
286 |
+
elif item["type"] == "heal_full":
|
287 |
+
healed = self.player.max_hp - self.player.current_hp
|
288 |
+
self.player.current_hp = self.player.max_hp
|
289 |
+
self.add_message(f"{item_name} fully restored your HP (+{healed})!")
|
290 |
+
elif item["type"] == "attack":
|
291 |
+
self.player.attack += item["amount"]
|
292 |
+
self.add_message(f"{item_name} consumed! Attack increased by {item['amount']}.")
|
293 |
+
elif item["type"] == "defense":
|
294 |
+
self.player.defense += item["amount"]
|
295 |
+
self.add_message(f"{item_name} equipped! Defense increased by {item['amount']}.")
|
296 |
+
|
297 |
+
# Confirm purchase
|
298 |
+
self.add_message(f"You bought {item_name} for ${cost}. Remaining money: ${self.player.money}.")
|
299 |
+
|
300 |
# --- Gradio Interface Logic ---
|
301 |
|
302 |
# Create a single game instance to be managed by gr.State
|
|
|
337 |
status_html = [
|
338 |
f"<h4 style='margin:4px 0'>{player.name} (Lvl {player.level})</h4>",
|
339 |
f"HP: <progress value='{player.current_hp}' max='{player.max_hp}' style='width:160px;height:14px;'></progress> {player.current_hp}/{player.max_hp}<br>",
|
340 |
+
f"XP: <progress value='{player.xp}' max='{player.level * 50}' style='width:160px;height:14px;'></progress> {player.xp}/{player.level * 50}<br>",
|
341 |
+
f"Money: ${player.money}<br><br>"
|
342 |
]
|
343 |
|
344 |
if game_state.current_enemy:
|
|
|
376 |
char_buttons_visible = (phase == "start")
|
377 |
action_buttons_visible = (phase == "combat")
|
378 |
play_again_visible = (phase in ["game_over", "win"])
|
379 |
+
shop_visible = (phase == "combat")
|
380 |
|
381 |
return (
|
382 |
log_html,
|
|
|
384 |
gr.update(visible=char_buttons_visible),
|
385 |
gr.update(visible=action_buttons_visible),
|
386 |
gr.update(visible=play_again_visible),
|
387 |
+
gr.update(visible=shop_visible),
|
388 |
game_state
|
389 |
)
|
390 |
|
|
|
408 |
game_state.reset_game()
|
409 |
return update_ui(game_state)
|
410 |
|
411 |
+
# --- Shop callback ---
|
412 |
+
def buy_item_action(item_choice, game_state):
|
413 |
+
"""Callback for Buy button in the shop."""
|
414 |
+
if item_choice:
|
415 |
+
game_state.buy_item(item_choice)
|
416 |
+
else:
|
417 |
+
game_state.add_message("Select an item first!")
|
418 |
+
return update_ui(game_state)
|
419 |
+
|
420 |
# --- Gradio UI Layout ---
|
421 |
|
422 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
|
440 |
|
441 |
play_again_btn = gr.Button("Play Again?", visible=False)
|
442 |
|
443 |
+
# --- Shop UI Components ---
|
444 |
+
with gr.Row(visible=False) as shop_row:
|
445 |
+
item_dropdown = gr.Dropdown(choices=game_instance.get_shop_choices(), label="Shop Items")
|
446 |
+
buy_btn = gr.Button("Buy")
|
447 |
+
|
448 |
# --- Event Handlers ---
|
449 |
# Define a list of all UI components that need to be updated, including the state
|
450 |
+
outputs = [game_output, status_output, character_selection_buttons, game_action_buttons, play_again_btn, shop_row, game_state]
|
451 |
|
452 |
# Connect buttons to their callback functions
|
453 |
scott_btn.click(choose_character, inputs=[scott_btn, game_state], outputs=outputs)
|
|
|
455 |
attack_btn.click(player_attack, inputs=[game_state], outputs=outputs)
|
456 |
run_btn.click(player_run, inputs=[game_state], outputs=outputs)
|
457 |
play_again_btn.click(play_again, inputs=[game_state], outputs=outputs)
|
458 |
+
buy_btn.click(buy_item_action, inputs=[item_dropdown, game_state], outputs=outputs)
|
459 |
|
460 |
# Set the initial UI state when the app loads, ensuring the outputs match the function's return values
|
461 |
demo.load(update_ui, inputs=[game_state], outputs=outputs)
|