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"

Error: Reached invalid page number {current_page}. Resetting game.

" 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"

{page_data.get('title', 'Error')}

" + page_data.get("content", "") + f"

Debug: Choice '{choice}' not matched to any available option.

" 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"

{page_data['title']}

" + page_data["content"] content += f"

You need the {item_required} for this option, but you don't have it.

" 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"

{page_data['title']}

" + page_data["content"] content += f"

You need one of the following items for this option: {item_list}, but you don't have any.

" 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"

Error: Option leads to invalid page number {next_page}. Resetting game.

" 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 = "

Game Over

You have been defeated in battle!

" + 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"
Battle Won!{battle_log}
" # --- 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 = "

Game Over

You have died from your wounds!

" 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 = "
Challenge data incomplete.
" challenge = None # Skip challenge processing else: success, roll, total = perform_challenge(game_state, challenge) challenge_log = f"
" challenge_log += f"Challenge: {challenge.get('title', 'Skill Check')}
" challenge_log += f"Target Stat: {req_stat}, Difficulty: {req_difficulty}
" 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}
" if success: challenge_log += "Success!
" next_page = req_success_page else: # *** THIS IS THE CORRECTED LINE (around line 208 of app.py) *** challenge_log += "Failure!" # ************************************************************** 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"

Error: Challenge outcome leads to invalid page {next_page}. Resetting.

" 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"

{final_page_title}

" 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"
THE END
" content += f"

{page_data['ending']}

" 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"

{final_page_title}

" content += page_data.get("content", "

No content.

") 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 += "

There are no further actions you can take from here.

" # 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"

A critical error occurred: {e}. Resetting game state.

" 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")