Spaces:
Running
Running
def update_game(choice, game_state_json): | |
"""Update game based on player choice""" | |
try: # Wrap major logic in try-except | |
if not choice: # Handle empty choice if dropdown value is cleared | |
# Return updates that don't change anything | |
return gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update() | |
# Parse game state from JSON | |
game_state_dict = json.loads(game_state_json) | |
game_state = GameState.from_dict(game_state_dict) | |
# Get current page data | |
current_page = game_state.current_page | |
current_page_str = str(current_page) | |
if current_page_str not in game_data: | |
# Handle invalid page number | |
error_content = f"<p>Error: Reached invalid page number {current_page}. Resetting game.</p>" | |
svg_error = create_svg_illustration("default") | |
new_game_state = GameState() | |
return (svg_error, error_content, gr.Dropdown(choices=["Restart"], label="Error", value=None), new_game_state.to_json(), | |
generate_stats_display(new_game_state), generate_inventory_display(new_game_state), "Error - Resetting") | |
page_data = game_data[current_page_str] | |
# Find the selected option | |
selected_option = None | |
current_options_list = page_data.get("options", []) | |
for opt in current_options_list: | |
# Ensure 'text' key exists in option dictionary | |
if isinstance(opt, dict) and opt.get("text") == choice: | |
selected_option = opt | |
break | |
# Check alternative option if it was displayed and chosen | |
alt_opt_data = page_data.get("alternativeOption") | |
if not selected_option and isinstance(alt_opt_data, dict) and alt_opt_data.get("text") == choice: | |
# Check if the alternative option should have been available | |
# This logic needs refinement: check if primary options were filtered out *and* alt condition met | |
alt_show_if = alt_opt_data.get("showIf") | |
current_inventory = game_state.inventory | |
primary_options_available = False | |
if current_options_list: | |
for opt in current_options_list: | |
is_available = True | |
req_item = opt.get("requireItem") | |
if req_item and req_item not in current_inventory: | |
is_available = False | |
req_any = opt.get("requireAnyItem") | |
if is_available and req_any and not any(i in current_inventory for i in req_any): | |
is_available = False | |
if is_available: | |
primary_options_available = True | |
break # Found at least one available primary option | |
# Select alternative only if no primary options were available AND showIf condition is met | |
if not primary_options_available and (not alt_show_if or any(item in current_inventory for item in alt_show_if)): | |
selected_option = alt_opt_data | |
if selected_option is None: | |
# Handle case where choice might be "Restart" or invalid | |
if choice == "Restart": | |
return initialize_game() | |
else: | |
# Stay on the same page if the choice wasn't found | |
options = [opt.get("text", "Invalid Option") for opt in current_options_list] | |
if alt_opt_data and alt_opt_data.get("text"): # Add alternative option text if it exists | |
# Check showIf condition for alternative option display | |
alt_show_if = alt_opt_data.get("showIf") | |
current_inventory = game_state.inventory | |
if not alt_show_if or any(item in current_inventory for item in alt_show_if): | |
# Check if primary options were filtered out | |
primary_options_available = False | |
if current_options_list: | |
# Re-check primary options availability | |
for opt in current_options_list: | |
is_available = True | |
req_item = opt.get("requireItem") | |
if req_item and req_item not in current_inventory: is_available = False | |
req_any = opt.get("requireAnyItem") | |
if is_available and req_any and not any(i in current_inventory for i in req_any): is_available = False | |
if is_available: | |
primary_options_available = True | |
break | |
if not primary_options_available: | |
options.append(alt_opt_data["text"]) | |
if not options: options = ["Restart"] | |
svg_content = create_svg_illustration(page_data.get("illustration", "default")) | |
content = f"<h2>{page_data.get('title', 'Error')}</h2>" + page_data.get("content", "") + f"<p><i>Debug: Choice '{choice}' not matched to any available option.</i></p>" | |
stats_display = generate_stats_display(game_state) | |
inventory_display = generate_inventory_display(game_state) | |
story_path_display = f"You remain on page {current_page}: {page_data.get('title', 'Error')}" | |
return svg_content, content, gr.Dropdown(choices=options, value=None), game_state.to_json(), stats_display, inventory_display, story_path_display | |
# --- Option requirement checks --- | |
item_required = selected_option.get("requireItem") | |
any_item_required = selected_option.get("requireAnyItem") | |
current_inventory = game_state.inventory # Cache inventory | |
# Check specific item requirement | |
if item_required and item_required not in current_inventory: | |
content = f"<h2>{page_data['title']}</h2>" + page_data["content"] | |
content += f"<p style='color: red;'>You need the <strong>{item_required}</strong> for this option, but you don't have it.</p>" | |
options_texts = [opt.get("text") for opt in page_data.get("options", []) if opt.get("text")] | |
# Check if alt option should be added back | |
if alt_opt_data and alt_opt_data.get("text"): | |
alt_show_if = alt_opt_data.get("showIf") | |
if not alt_show_if or any(item in current_inventory for item in alt_show_if): | |
primary_available_check = any( | |
opt.get("text") and (not opt.get("requireItem") or opt.get("requireItem") in current_inventory) and \ | |
(not opt.get("requireAnyItem") or any(i in current_inventory for i in opt.get("requireAnyItem"))) | |
for opt in page_data.get("options", []) | |
) | |
if not primary_available_check: options_texts.append(alt_opt_data["text"]) | |
if not options_texts: options_texts = ["Restart"] | |
svg_content = create_svg_illustration(page_data.get("illustration", "default")) | |
stats_display = generate_stats_display(game_state) | |
inventory_display = generate_inventory_display(game_state) | |
story_path_display = f"You remain on page {current_page}: {page_data['title']}" | |
return svg_content, content, gr.Dropdown(choices=list(set(options_texts)), value=None), game_state.to_json(), stats_display, inventory_display, story_path_display # Use set to remove duplicates | |
# Check 'any item' requirement | |
if any_item_required and not any(item in current_inventory for item in any_item_required): | |
item_list = ", ".join(f"'{item}'" for item in any_item_required) | |
content = f"<h2>{page_data['title']}</h2>" + page_data["content"] | |
content += f"<p style='color: red;'>You need one of the following items for this option: <strong>{item_list}</strong>, but you don't have any.</p>" | |
options_texts = [opt.get("text") for opt in page_data.get("options", []) if opt.get("text")] | |
# Check if alt option should be added back (similar logic as above) | |
if alt_opt_data and alt_opt_data.get("text"): | |
alt_show_if = alt_opt_data.get("showIf") | |
if not alt_show_if or any(item in current_inventory for item in alt_show_if): | |
primary_available_check = any( | |
opt.get("text") and (not opt.get("requireItem") or opt.get("requireItem") in current_inventory) and \ | |
(not opt.get("requireAnyItem") or any(i in current_inventory for i in opt.get("requireAnyItem"))) | |
for opt in page_data.get("options", []) | |
) | |
if not primary_available_check: options_texts.append(alt_opt_data["text"]) | |
if not options_texts: options_texts = ["Restart"] | |
svg_content = create_svg_illustration(page_data.get("illustration", "default")) | |
stats_display = generate_stats_display(game_state) | |
inventory_display = generate_inventory_display(game_state) | |
story_path_display = f"You remain on page {current_page}: {page_data['title']}" | |
return svg_content, content, gr.Dropdown(choices=list(set(options_texts)), value=None), game_state.to_json(), stats_display, inventory_display, story_path_display | |
# --- Process valid choice --- | |
item_to_add = selected_option.get("addItem") | |
if item_to_add and item_to_add not in game_state.inventory: | |
game_state.inventory.append(item_to_add) | |
item_data = items_data.get(item_to_add, {}) | |
if item_data.get("type") == "consumable" and item_data.get("useOnAdd"): | |
if "hpRestore" in item_data: | |
hp_before = game_state.current_hp | |
game_state.current_hp = min(game_state.max_hp, game_state.current_hp + item_data["hpRestore"]) | |
# Maybe add feedback about using the item? Add later if needed. | |
game_state.inventory.remove(item_to_add) # Remove consumable after use | |
# Move to the next page (initial move before battle/challenge checks) | |
next_page = selected_option["next"] | |
next_page_str = str(next_page) | |
if next_page_str not in game_data: | |
error_content = f"<p>Error: Option leads to invalid page number {next_page}. Resetting game.</p>" | |
svg_error = create_svg_illustration("default") | |
new_game_state = GameState() | |
return (svg_error, error_content, gr.Dropdown(choices=["Restart"], label="Error", value=None), new_game_state.to_json(), | |
generate_stats_display(new_game_state), generate_inventory_display(new_game_state), "Error - Resetting") | |
# --- Battle Check --- | |
battle_occurred = False | |
battle_message = "" | |
battle_result = True | |
# Check random battle flag on the *current* page data (page_data before challenge potentially changes it) | |
if page_data.get("randomBattle", False) and random.random() < 0.2: | |
battle_result, battle_log = simulate_battle(game_state) | |
battle_occurred = True | |
if not battle_result: | |
content = "<h2>Game Over</h2><p>You have been defeated in battle!</p>" + battle_log | |
stats_display = generate_stats_display(game_state) | |
inventory_display = generate_inventory_display(game_state) | |
return create_svg_illustration("game-over"), content, gr.Dropdown(choices=["Restart"], label="Game Over", value=None), game_state.to_json(), stats_display, inventory_display, "Game Over - You were defeated in battle." | |
else: | |
battle_message = f"<div style='border: 1px solid green; padding: 10px; margin-top: 10px; background-color: #e8f5e9;'><strong>Battle Won!</strong>{battle_log}</div>" | |
# --- Set current page to the determined next page (before challenge check) --- | |
game_state.current_page = next_page | |
if next_page not in game_state.visited_pages: | |
game_state.visited_pages.append(next_page) | |
# Update journey progress | |
game_state.journey_progress += 5 | |
if game_state.journey_progress > 100: game_state.journey_progress = 100 | |
# Get data for the potentially final destination page (next_page_str) | |
page_data = game_data[next_page_str] # Now refers to the data of the page we are landing on | |
# --- HP Loss / Stat Increase on Landing Page --- | |
stat_increase = page_data.get("statIncrease") | |
if stat_increase: | |
stat = stat_increase.get("stat") | |
amount = stat_increase.get("amount") | |
if stat and amount and stat in game_state.stats: | |
game_state.stats[stat] += amount | |
hp_loss = page_data.get("hpLoss") | |
if hp_loss: | |
game_state.current_hp -= hp_loss | |
if game_state.current_hp <= 0: | |
game_state.current_hp = 0 | |
content = "<h2>Game Over</h2><p>You have died from your wounds!</p>" | |
stats_display = generate_stats_display(game_state) | |
inventory_display = generate_inventory_display(game_state) | |
return create_svg_illustration("game-over"), content, gr.Dropdown(choices=["Restart"], label="Game Over", value=None), game_state.to_json(), stats_display, inventory_display, "Game Over - You died from your wounds." | |
# --- Challenge Check on Landing Page --- | |
challenge_log = "" | |
challenge_occurred = False | |
challenge = page_data.get("challenge") | |
if challenge: | |
challenge_occurred = True | |
req_stat = challenge.get("stat") | |
req_difficulty = challenge.get("difficulty") | |
req_success_page = challenge.get("success") | |
req_failure_page = challenge.get("failure") | |
if not all([req_stat, req_difficulty, req_success_page is not None, req_failure_page is not None]): # Check pages can be 0 | |
challenge_log = "<div style='color: orange;'>Challenge data incomplete.</div>" | |
challenge = None # Skip challenge processing | |
else: | |
success, roll, total = perform_challenge(game_state, challenge) | |
challenge_log = f"<div style='border: 1px solid #ccc; padding: 10px; margin-top: 10px; background-color: #eee;'>" | |
challenge_log += f"<strong>Challenge: {challenge.get('title', 'Skill Check')}</strong><br>" | |
challenge_log += f"Target Stat: {req_stat}, Difficulty: {req_difficulty}<br>" | |
stat_val_before_roll = total - roll # Stat value used in the roll | |
challenge_log += f"You rolled a {roll} + ({stat_val_before_roll} {req_stat}) = {total}<br>" | |
if success: | |
challenge_log += "<strong style='color: green;'>Success!</strong></div>" | |
next_page = req_success_page | |
else: | |
# *** THIS IS THE CORRECTED LINE (around line 208 of app.py) *** | |
challenge_log += "<strong style='color: red;'>Failure!</strong></div>" | |
# ************************************************************** | |
next_page = req_failure_page | |
# Update game state again based on challenge outcome | |
game_state.current_page = next_page | |
next_page_str = str(next_page) | |
if next_page_str not in game_data: | |
error_content = f"<p>Error: Challenge outcome leads to invalid page {next_page}. Resetting.</p>" | |
svg_error = create_svg_illustration("default") | |
new_game_state = GameState() | |
return (svg_error, error_content, gr.Dropdown(choices=["Restart"], label="Error", value=None), new_game_state.to_json(), | |
generate_stats_display(new_game_state), generate_inventory_display(new_game_state), "Error - Resetting") | |
if next_page not in game_state.visited_pages: | |
game_state.visited_pages.append(next_page) | |
# Load the *final* destination page data after challenge resolution | |
page_data = game_data[next_page_str] | |
# --- Prepare Final Output --- | |
final_page_title = page_data.get('title', 'Untitled') | |
svg_content = create_svg_illustration(page_data.get("illustration", "default")) | |
# Handle game over on the final destination page | |
if page_data.get("gameOver", False): | |
content = f"<h2>{final_page_title}</h2>" | |
content += page_data.get("content", "") | |
if battle_occurred and battle_result: content += battle_message | |
if challenge_occurred: content += challenge_log | |
if "ending" in page_data: | |
content += f"<div style='text-align: center; margin-top: 20px; font-weight: bold; color: #c00;'>THE END</div>" | |
content += f"<p style='font-style: italic;'>{page_data['ending']}</p>" | |
stats_display = generate_stats_display(game_state) | |
inventory_display = generate_inventory_display(game_state) | |
return svg_content, content, gr.Dropdown(choices=["Restart"], label="Game Over", value=None), game_state.to_json(), stats_display, inventory_display, "Game Over - " + final_page_title | |
# Build content for the final destination page (if not game over) | |
content = f"<h2>{final_page_title}</h2>" | |
content += page_data.get("content", "<p>No content.</p>") | |
if battle_occurred and battle_result: content += battle_message | |
if challenge_occurred: content += challenge_log | |
# Build options for the final destination page | |
options_texts = [] | |
current_inventory = game_state.inventory # Cache inventory again | |
final_page_options = page_data.get("options", []) | |
if final_page_options: | |
for opt in final_page_options: | |
option_available = True | |
if isinstance(opt, dict): # Ensure opt is a dictionary | |
req_item = opt.get("requireItem") | |
if req_item and req_item not in current_inventory: | |
option_available = False | |
req_any_item = opt.get("requireAnyItem") | |
if option_available and req_any_item: | |
if not any(item in current_inventory for item in req_any_item): | |
option_available = False | |
if option_available and opt.get("text"): | |
options_texts.append(opt["text"]) | |
# else: skip invalid option format | |
# Handle alternative option if necessary (only if no primary options were available/visible) | |
alt_opt_data = page_data.get("alternativeOption") | |
if isinstance(alt_opt_data, dict) and alt_opt_data.get("text") and not options_texts: # Check if text exists and no primary options shown | |
alt_show_if = alt_opt_data.get("showIf") | |
if not alt_show_if or any(item in current_inventory for item in alt_show_if): | |
options_texts.append(alt_opt_data["text"]) | |
if not options_texts: # If still no options (dead end?) | |
options_texts = ["Restart"] | |
content += "<p><i>There are no further actions you can take from here.</i></p>" | |
# Update story path display text | |
story_path = f"You are on page {next_page}: {final_page_title}" | |
if game_state.journey_progress >= 80: story_path += " (Nearing the conclusion)" | |
elif game_state.journey_progress >= 50: story_path += " (Middle of your journey)" | |
elif game_state.journey_progress >= 25: story_path += " (Adventure beginning)" | |
# Generate final displays | |
stats_display = generate_stats_display(game_state) | |
inventory_display = generate_inventory_display(game_state) | |
# Return final state | |
return (svg_content, content, gr.Dropdown(choices=list(set(options_texts)), label="What will you do?", value=None), | |
game_state.to_json(), stats_display, inventory_display, story_path) | |
except Exception as e: | |
# Generic error handling for the entire update function | |
print(f"Error during update_game: {e}") | |
import traceback | |
traceback.print_exc() # Print detailed traceback to console (if possible in Gradio Lite) | |
error_content = f"<p>A critical error occurred: {e}. Resetting game state.</p>" | |
svg_error = create_svg_illustration("default") | |
new_game_state = GameState() # Reset state | |
return (svg_error, error_content, gr.Dropdown(choices=["Restart"], label="Critical Error", value=None), new_game_state.to_json(), | |
generate_stats_display(new_game_state), generate_inventory_display(new_game_state), "Critical Error - Reset") | |