Myco / ui /gradio_app.py
byte-vortex's picture
Deploy Myco from CI
e1aa5a8 verified
Raw
History Blame
11.5 kB
"""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