Spaces:
Sleeping
Sleeping
| """Gradio game UI for Myco โ LLM-powered forest adventure.""" | |
| from typing import Any, cast | |
| import gradio as gr | |
| from game.engine import ( | |
| collect_current, discover_mushroom, eat_current, | |
| follow_whisper, hf_companion_status, myco_reply, | |
| pick_current, study_current, | |
| ) | |
| from game.state import welcome_history | |
| from models.mushroom import Mushroom | |
| from ui.renderers import ( | |
| dex_markdown, forest_scene, game_intro_markdown, | |
| game_status_markdown, hero_markdown, home_forest_markdown, | |
| mushroom_card, play_hook_markdown, progress_markdown, | |
| world_map_markdown, | |
| ) | |
| EMPTY_COLLECTION = None | |
| START_POSITION = (1, 1) | |
| HOME_MARKDOWN = """ | |
| ## Welcome to the Myco forest | |
| Myco is a tiny AI-powered forest game. Open **๐ฎ Play** to search for mushrooms, | |
| visit **๐ MycoDex** to review your collection, and use **๐งญ Guide** for help. | |
| **Goal:** solve one mystery โ why does Myco remember a forest that vanished before it was born? | |
| Follow clues until the Impossible Mushroom blooms inside the MycoDex. | |
| """ | |
| GUIDE_MARKDOWN = """ | |
| ## How to play | |
| 1. Go to **๐ฎ Play** and click **๐ฎ Search Clearing** to discover a mushroom. | |
| 2. **Myco (the AI) reacts** โ read what it senses about the mushroom. | |
| 3. Choose your action: | |
| - **๐ Study** โ Myco examines the mushroom and updates its magic field. | |
| - **๐ Pick** โ grab it as a game item. Poisonous picks = Game Over! | |
| - **๐ Collect** โ add it to the MycoDex for safe keeping and score. | |
| - **๐ Follow Whisper** โ reveal mystery fragments. Risky on unstudied mushrooms! | |
| - **๐ฝ๏ธ Eat?** โ Myco will stop you every time (for good reason). | |
| 4. **Ask Myco** anything in the chat โ it knows the forest, the mystery, and the mushrooms. | |
| 5. Use **โฌ๏ธโฌ๏ธโฌ ๏ธโก๏ธ** to move through the 3ร3 forest grid. | |
| **Scoring:** Common = 10 spores ยท Rare = 35 ยท Legendary = 100 ยท Poison = โ25 + Game Over | |
| **Poisonous mushrooms:** Ghost Gill ยท Pepper Pixie ยท Ruby Knuckle ยท Clockwork Chanterelle | |
| """ | |
| def mushroom_card_from_state(current): | |
| if current is None: | |
| return mushroom_card(None) | |
| return mushroom_card( | |
| Mushroom.from_dict(current), | |
| current.get("clue"), | |
| current.get("secret"), | |
| current, | |
| ) | |
| def search_forest(collection, position): | |
| mushroom, current, history = discover_mushroom(collection) | |
| pos = _safe_pos(position) | |
| return ( | |
| forest_scene(current, collection, pos), | |
| game_status_markdown(current, collection, pos), | |
| mushroom_card_from_state(current), | |
| current, | |
| history, | |
| dex_markdown(collection), | |
| progress_markdown(collection), | |
| ) | |
| def ask_companion(message, history, current, collection, position): | |
| return myco_reply(message, history, current, collection, position) | |
| def collect_discovery(current, collection, history, position): | |
| updated_coll, updated_hist = collect_current(current, collection, history) | |
| pos = _safe_pos(position) | |
| return ( | |
| updated_coll, | |
| forest_scene(current, updated_coll, pos), | |
| game_status_markdown(current, updated_coll, pos), | |
| dex_markdown(updated_coll), | |
| world_map_markdown(updated_coll), | |
| updated_hist, | |
| progress_markdown(updated_coll), | |
| ) | |
| def pick_discovery(current, collection, history, position): | |
| updated_coll, updated_current, updated_hist = pick_current(current, collection, history) | |
| pos = _safe_pos(position) | |
| return ( | |
| updated_coll, | |
| updated_current, | |
| forest_scene(updated_current, updated_coll, pos), | |
| game_status_markdown(updated_current, updated_coll, pos), | |
| mushroom_card_from_state(updated_current), | |
| dex_markdown(updated_coll), | |
| world_map_markdown(updated_coll), | |
| updated_hist, | |
| progress_markdown(updated_coll), | |
| ) | |
| def follow_story_whisper(current, collection, history, position): | |
| updated_current, updated_hist = follow_whisper(current, collection, history) | |
| pos = _safe_pos(position) | |
| return ( | |
| updated_current, | |
| forest_scene(updated_current, collection, pos), | |
| game_status_markdown(updated_current, collection, pos), | |
| mushroom_card_from_state(updated_current), | |
| updated_hist, | |
| ) | |
| def study_discovery(current, history, collection, position): | |
| updated_current, updated_hist = study_current(current, history) | |
| pos = _safe_pos(position) | |
| return ( | |
| updated_current, | |
| forest_scene(updated_current, collection, pos), | |
| game_status_markdown(updated_current, collection, pos), | |
| mushroom_card_from_state(updated_current), | |
| updated_hist, | |
| ) | |
| def move_player(position, direction, current, collection): | |
| x, y = _safe_pos(position) | |
| if direction == "north": y -= 1 | |
| elif direction == "south": y += 1 | |
| elif direction == "west": x -= 1 | |
| elif direction == "east": x += 1 | |
| pos = _safe_pos((x, y)) | |
| return pos, forest_scene(current, collection, pos), game_status_markdown(current, collection, pos) | |
| def block_unsafe_eating(current, history): | |
| return eat_current(current, history) | |
| def open_play_tab(): | |
| return gr.update(selected="play") | |
| def _safe_pos(position): | |
| if position is None: | |
| return START_POSITION | |
| x, y = position | |
| return (max(0, min(2, int(x))), max(0, min(2, int(y)))) | |
| def build_app(): | |
| with gr.Blocks(title="Myco โ AI Forest Game", theme=gr.themes.Soft()) as demo: | |
| # Added 'contain' CSS to prevent layout jumping/flashing | |
| gr.HTML(""" | |
| <style> | |
| .gradio-container { background: #1a1a2e !important; } | |
| #forest-scene, #discovery-card { contain: layout style; content-visibility: auto; } | |
| .progress-level, .progress-level-inner, .generating { display: none !important; } | |
| </style> | |
| """) | |
| current_mushroom = gr.State(None) | |
| collection = gr.State([]) | |
| player_position = gr.State(START_POSITION) | |
| gr.HTML(hero_markdown()) | |
| with gr.Tabs(selected="home") as tabs: | |
| with gr.Tab("๐ Home", id="home"): | |
| gr.Markdown(HOME_MARKDOWN) | |
| gr.HTML(home_forest_markdown()) | |
| start_play_button = gr.Button("๐ Enter the Forest", variant="primary", size="lg") | |
| gr.Markdown(f"*{hf_companion_status()}*") | |
| with gr.Tab("๐ฎ Play", id="play"): | |
| gr.HTML(game_intro_markdown()) | |
| scene = gr.HTML(forest_scene(None, EMPTY_COLLECTION, START_POSITION), elem_id="forest-scene") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| search_button = gr.Button("๐ฎ Search Clearing", variant="primary", size="lg") | |
| with gr.Column(scale=1): | |
| gr.Markdown("**Move Myco**") | |
| with gr.Row(): | |
| gr.HTML("<div></div>") | |
| north_button = gr.Button("โฌ๏ธ", size="sm") | |
| gr.HTML("<div></div>") | |
| with gr.Row(): | |
| west_button = gr.Button("โฌ ๏ธ", size="sm") | |
| pick_button = gr.Button("๐ Pick", variant="primary", size="sm") | |
| east_button = gr.Button("โก๏ธ", size="sm") | |
| with gr.Row(): | |
| gr.HTML("<div></div>") | |
| south_button = gr.Button("โฌ๏ธ", size="sm") | |
| gr.HTML("<div></div>") | |
| status = gr.Markdown(game_status_markdown(None, EMPTY_COLLECTION, START_POSITION)) | |
| with gr.Row(equal_height=True, elem_id="discovery-card"): | |
| with gr.Column(scale=2): | |
| gr.Markdown("### ๐ Current Discovery") | |
| discovery = gr.Markdown(mushroom_card(None)) | |
| with gr.Row(): | |
| study_button = gr.Button("๐ Study", variant="secondary") | |
| collect_button = gr.Button("๐ Collect", variant="secondary") | |
| with gr.Row(): | |
| whisper_button = gr.Button("๐ Follow Whisper", variant="secondary") | |
| eat_button = gr.Button("๐ฝ๏ธ Eat?", variant="stop") | |
| with gr.Column(scale=3): | |
| gr.Markdown("### ๐ฌ Myco Chat") | |
| chat = gr.Chatbot(value=cast(Any, welcome_history()), label="Myco", height=400, show_label=False) | |
| with gr.Row(): | |
| prompt = gr.Textbox(placeholder="Ask Myco anything about the forest...", show_label=False, scale=4, lines=1) | |
| ask_button = gr.Button("Ask ๐", variant="primary", scale=1) | |
| with gr.Tab("๐ MycoDex"): | |
| with gr.Row(): | |
| progress = gr.Markdown(progress_markdown(EMPTY_COLLECTION)) | |
| dex = gr.Markdown(dex_markdown(EMPTY_COLLECTION)) | |
| with gr.Tab("๐บ๏ธ World Map"): | |
| world_map = gr.HTML(world_map_markdown(EMPTY_COLLECTION)) | |
| with gr.Tab("๐งญ Guide"): | |
| gr.Markdown(GUIDE_MARKDOWN) | |
| # โโ Event wiring (show_progress="hidden" applied to all for zero-flash) โโ | |
| start_play_button.click(open_play_tab, outputs=[tabs]) | |
| search_button.click(search_forest, inputs=[collection, player_position], | |
| outputs=[scene, status, discovery, current_mushroom, chat, dex, progress], show_progress="hidden") | |
| ask_button.click(ask_companion, inputs=[prompt, chat, current_mushroom, collection, player_position], | |
| outputs=[prompt, chat], show_progress="hidden") | |
| prompt.submit(ask_companion, inputs=[prompt, chat, current_mushroom, collection, player_position], | |
| outputs=[prompt, chat], show_progress="hidden") | |
| for direction, button in [("north", north_button), ("south", south_button), ("west", west_button), ("east", east_button)]: | |
| button.click(move_player, inputs=[player_position, gr.State(direction), current_mushroom, collection], | |
| outputs=[player_position, scene, status], show_progress="hidden") | |
| study_button.click(study_discovery, inputs=[current_mushroom, chat, collection, player_position], | |
| outputs=[current_mushroom, scene, status, discovery, chat], show_progress="hidden") | |
| collect_button.click(collect_discovery, inputs=[current_mushroom, collection, chat, player_position], | |
| outputs=[collection, scene, status, dex, world_map, chat, progress], show_progress="hidden") | |
| pick_button.click(pick_discovery, inputs=[current_mushroom, collection, chat, player_position], | |
| outputs=[collection, current_mushroom, scene, status, discovery, dex, world_map, chat, progress], show_progress="hidden") | |
| whisper_button.click(follow_story_whisper, inputs=[current_mushroom, collection, chat, player_position], | |
| outputs=[current_mushroom, scene, status, discovery, chat], show_progress="hidden") | |
| eat_button.click(block_unsafe_eating, inputs=[current_mushroom, chat], outputs=[chat], show_progress="hidden") | |
| demo.queue() | |
| return demo | |