byte-vortex commited on
Commit
da8a529
·
verified ·
1 Parent(s): 2539e4a

Delete ui/2-renderers.py

Browse files
Files changed (1) hide show
  1. ui/2-renderers.py +0 -525
ui/2-renderers.py DELETED
@@ -1,525 +0,0 @@
1
- """Markdown renderers for the Myco Gradio interface."""
2
-
3
- from html import escape
4
- from game.progression import (
5
- AREA_UNLOCKS, current_area, next_area, next_mystery,
6
- revealed_mysteries, unlocked_areas,
7
- )
8
- from game.state import CollectionState, EMPTY_DEX, MushroomState
9
- from models.mushroom import Mushroom
10
-
11
- import json
12
- import ast
13
-
14
-
15
- POISONOUS_MUSHROOM_NAMES = {
16
- "Ghost Gill", "Pepper Pixie", "Ruby Knuckle", "Clockwork Chanterelle",
17
- }
18
-
19
- # ---------------------------------------------------------------------------
20
- # Static UI fragments
21
- # ---------------------------------------------------------------------------
22
-
23
- def hero_markdown() -> str:
24
- return """
25
- <div class="myco-hero">
26
- <div class="myco-mascot">🍄</div>
27
- <div>
28
- <p class="eyebrow">Tiny AI mushroom companion</p>
29
- <h1>Myco</h1>
30
- <p>Follow an AI mushroom companion through a glowing forest. Every discovery starts with Myco's intuition.</p>
31
- </div>
32
- </div>
33
- """
34
-
35
- def bubble(content):
36
- return f"""
37
- <div style="
38
- font-size: 12px;
39
- padding: 12px;
40
- border-radius: 28px;
41
- background: linear-gradient(135deg, #1e1e1e 0%, #0f2027 50%, #203a43 100%);
42
- color: #b6ff9a;
43
- font-family: monospace;
44
- ">
45
- {content} by Myco AI 🍄
46
- </div>
47
- """
48
-
49
-
50
- def home_forest_markdown() -> str:
51
- return """
52
- <div class="home-forest-postcard" aria-label="Animated Myco forest postcard">
53
- <style>
54
- .home-myco-wrapper, .home-brown-wrapper { position:relative; display:inline-block; }
55
- .home-spore {
56
- position:absolute; left:50%; top:20%; width:4px; height:4px;
57
- background:#a8ffb2; border-radius:50%; filter:blur(0.5px);
58
- box-shadow:0 0 6px #52ff6b,0 0 10px #fff;
59
- opacity:0; pointer-events:none;
60
- animation:floatHomeSpore var(--duration) ease-out infinite;
61
- animation-delay:var(--delay);
62
- }
63
- @keyframes floatHomeSpore {
64
- 0% { transform:translate(0,0) scale(1); opacity:0; }
65
- 15% { opacity:1; }
66
- 80% { opacity:0.7; }
67
- 100% { transform:translate(var(--x),var(--y)) scale(0.2); opacity:0; }
68
- }
69
- </style>
70
- <div class="home-sky">
71
- <span class="home-milky-way">🌌</span>
72
- <span class="home-star star-one">✨</span>
73
- <span class="home-star star-two">✦</span>
74
- <span class="home-star star-three">✧</span>
75
- </div>
76
- <div class="home-forest-floor">
77
- <span class="home-tree left">🌲</span>
78
- <span class="home-grass grass-left">🌿</span>
79
- <span class="home-myco-wrapper">
80
- <span class="home-myco">🍄</span>
81
- <span class="home-spore" style="--x:-25px;--y:-45px;--duration:3.5s;--delay:0s;"></span>
82
- <span class="home-spore" style="--x:20px;--y:-35px;--duration:4.2s;--delay:0.8s;"></span>
83
- <span class="home-spore" style="--x:-5px;--y:-60px;--duration:3s;--delay:1.5s;"></span>
84
- </span>
85
- <span class="home-brown-wrapper">
86
- <span class="home-brown-mushroom">🍄‍🟫</span>
87
- <span class="home-spore" style="--x:-15px;--y:-40px;--duration:3.8s;--delay:0.4s;background:#ffeaa7;box-shadow:0 0 6px #fdcb6e,0 0 10px #fff;"></span>
88
- <span class="home-spore" style="--x:25px;--y:-50px;--duration:4.5s;--delay:1.2s;background:#ffeaa7;box-shadow:0 0 6px #fdcb6e,0 0 10px #fff;"></span>
89
- </span>
90
- <span class="home-grass grass-right">🍃</span>
91
- <span class="home-tree right">🌳</span>
92
- <span class="home-moon">🌖</span>
93
- </div>
94
- <p><strong>The Milky Way is awake.</strong> Myco hears a brown mushroom whispering your first clue.</p>
95
- <span class="home-whisper">"Tap Play. Let's find what the forest is hiding."</span>
96
- </div>
97
- """
98
-
99
- def play_hook_markdown() -> str:
100
- return """
101
- <div class="play-hook">
102
- <p class="eyebrow">Mission</p>
103
- <h2>Goal: follow clues until Myco remembers the Impossible Mushroom.</h2>
104
- <div class="hook-steps">
105
- <span>1 · Move through the forest</span>
106
- <span>2 · Search Clearing — mushroom pops up!</span>
107
- <span>3 · Myco reacts · Pick, Study, or Follow Whisper</span>
108
- </div>
109
- <p>Every clue advances one mystery.</p>
110
- </div>
111
- """
112
-
113
- def game_intro_markdown() -> str:
114
- return """
115
- <div style="text-align:center;margin-bottom:.75rem;">
116
- <h3>🌲 The Deep Forest</h3>
117
- <p style="color:#359635;font-size:.9em;">
118
- You step into the glowing undergrowth. Myco hops off your shoulder, its cap faintly pulsing.<br>
119
- Click <strong>Search Clearing</strong> — watch Myco run toward whatever pops up.
120
- <span class="ai-tick-badge">🤖 Myco AI roaming</span>
121
- </p>
122
- </div>
123
- """
124
-
125
- # ---------------------------------------------------------------------------
126
- # Forest scene — accepts optional active_mushrooms list
127
- # ---------------------------------------------------------------------------
128
-
129
- def forest_scene(
130
- current: "MushroomState | None",
131
- collection: "CollectionState | None",
132
- position: "tuple[int,int] | None" = None,
133
- active_mushrooms: "list | None" = None,
134
- ) -> str:
135
- import time
136
- count = len(_safe_collection(collection))
137
- area = current_area(count)
138
- pos_x, pos_y = _safe_position(position)
139
- pos_cls = f"pos-{pos_x}-{pos_y}"
140
-
141
- # Catch active_mushrooms if packed inside the current dictionary
142
- if current is not None and not active_mushrooms:
143
- active_mushrooms = current.get("active_mushrooms", [])
144
-
145
- # Safe defaults
146
- mushroom_name = "Hidden mushroom"
147
- rarity = "common"
148
- sprite = "✨"
149
- status = "The moss is listening. Click Search Clearing to wake the forest."
150
- omen = "Myco is waiting to sense the first spore."
151
- event_label = "🌌 Dreaming Forest"
152
- encounter = "The next clearing may reveal a memory, creature, or poison omen."
153
- mystery_label = "The Wrong Memory"
154
- score_total = "0"
155
- health = "3"
156
- reward_text = "Search a clearing to score spores."
157
- state_cls = "state-waiting"
158
-
159
- if current is not None:
160
- mushroom_name = current.get("name") or "Mystery cluster"
161
-
162
- # SAFEGUARD: Prevent NoneType.lower() crash
163
- raw_rarity = current.get("rarity")
164
- rarity = (raw_rarity if raw_rarity else "Common").lower()
165
-
166
- game_over = current.get("game_over") == "Yes"
167
-
168
- # SAFEGUARD: Handle None values in string formatting safely
169
- emoji = current.get("event_emoji") or "✨"
170
- title = current.get("event_title") or "Strange Omen"
171
- event_label = f"{emoji} {title}"
172
-
173
- encounter = current.get("encounter") or "Go pick them up!"
174
- mystery_label = current.get("mystery_title") or "The Wrong Memory"
175
- score_total = current.get("score_total") or "0"
176
- health = current.get("health") or "3"
177
- reward_text = current.get("reward_text") or "Dodge the poison, collect the rest!"
178
- state_cls = _scene_state_class(current)
179
-
180
- if game_over:
181
- status = "💀 GAME OVER: You stepped on a poisonous mushroom!"
182
- omen = "Press Search Clearing to start a new run."
183
- elif current.get("picked") == "Yes":
184
- status = "✅ Area clear! Move to the next clearing."
185
- omen = "Search the next clearing!"
186
- else:
187
- status = f"A {rarity} {mushroom_name} breaks through the moss!"
188
- omen = "🍄 Study it before picking!"
189
-
190
- active_sprites = _build_active_sprites(active_mushrooms or [])
191
- anim_key = str(int(time.time() * 1000))[-6:]
192
-
193
- return f'''
194
- <div class="forest-scene rarity-{escape(rarity)} {pos_cls} {state_cls}" data-key="{anim_key}">
195
- <div class="scene-hud">
196
- <span>🧭 {escape(area.name)}</span>
197
- <span>🎮 ({pos_x+1},{pos_y+1})</span>
198
- <span>📖 {count} found</span>
199
- <span>⭐ {escape(str(score_total))}</span>
200
- <span>❤️ {escape(str(health))}/3</span>
201
- <span>{escape(event_label)}</span>
202
- <span>🧩 {escape(mystery_label)}</span>
203
- </div>
204
-
205
- <div class="scene-stage" aria-label="Magical forest scene">
206
- <span class="tree tree-left">🌲</span>
207
- <span class="tree tree-right">🌲</span>
208
- <span class="grass grass-one">🌿</span>
209
- <span class="grass grass-two">🍃</span>
210
- <span class="myco-player" title="Myco">🍄</span>
211
- <span class="mushroom-sprite" title="{escape(mushroom_name)}">{escape(sprite)}</span>
212
- {active_sprites}
213
- <span class="sparkle sparkle-one">✨</span>
214
- <span class="sparkle sparkle-two">✦</span>
215
- <span class="wood-log">🪵</span>
216
- </div>
217
-
218
- <div class="forest-map" aria-label="Forest grid">
219
- {_movement_tiles(pos_x, pos_y, active_mushrooms)}
220
- </div>
221
-
222
- <div class="scene-caption">
223
- <strong>{escape(str(status))}</strong>
224
- <span>{escape(str(omen))}</span>
225
- </div>
226
- <div class="score-burst" aria-live="polite">{escape(str(reward_text))}</div>
227
- <div class="story-moment">
228
- <strong>{escape(str(event_label))}</strong>
229
- <span>{escape(str(encounter))}</span>
230
- </div>
231
- </div>'''
232
-
233
-
234
- # ---------------------------------------------------------------------------
235
- # Active mushroom sprites builder
236
- # ---------------------------------------------------------------------------
237
-
238
- def _build_active_sprites(active_mushrooms: list) -> str:
239
- sprites = []
240
- for m in active_mushrooms:
241
- if not isinstance(m, dict):
242
- continue
243
- if m.get("picked") == "Yes":
244
- continue
245
-
246
- # SAFEGUARD: Prevent int(None) crash
247
- raw_x = m.get("mush_x")
248
- raw_y = m.get("mush_y")
249
- mx = int(raw_x) if raw_x is not None else 1
250
- my = int(raw_y) if raw_y is not None else 1
251
-
252
- mx = max(0, min(2, mx))
253
- my = max(0, min(2, my))
254
- is_poison = m.get("poison") == "Yes"
255
- icon = "☠️" if is_poison else ("🍄‍🟫" if m.get("rarity") == "Legendary" else "🍄")
256
- css_class = f"roam-sprite roam-{mx}-{my}" + (" roam-poison" if is_poison else "")
257
- name = escape(str(m.get("name", "?")))
258
- sprites.append(f'<span class="{css_class}" title="{name}">{icon}</span>')
259
- return "\n ".join(sprites)
260
-
261
-
262
- # ---------------------------------------------------------------------------
263
- # Helpers
264
- # ---------------------------------------------------------------------------
265
-
266
- def _safe_position(position):
267
- if position is None:
268
- return (1, 1)
269
- try:
270
- x, y = position
271
- return (max(0, min(2, int(x))), max(0, min(2, int(y))))
272
- except Exception:
273
- return (1, 1)
274
-
275
-
276
- def _movement_tiles(active_x, active_y, active_mushrooms=None):
277
- """Grid map tiles — marks player, mushrooms, and poison cells."""
278
- mush_coords = {}
279
- for m in (active_mushrooms or []):
280
- if not isinstance(m, dict):
281
- continue
282
- if m.get("picked") == "Yes":
283
- continue
284
- mx, my = int(m.get("mush_x", -1)), int(m.get("mush_y", -1))
285
- if 0 <= mx <= 2 and 0 <= my <= 2:
286
- mush_coords[(mx, my)] = m
287
-
288
- tiles = []
289
- for y in range(3):
290
- for x in range(3):
291
- is_player = (x, y) == (int(active_x), int(active_y))
292
- mush = mush_coords.get((x, y))
293
- if is_player:
294
- css = "map-tile active"
295
- icon = "🍄"
296
- elif mush:
297
- is_p = mush.get("poison") == "Yes"
298
- css = "map-tile has-poison" if is_p else "map-tile has-mush"
299
- icon = "☠️" if is_p else "✨"
300
- else:
301
- css = "map-tile"
302
- icon = "·"
303
- tiles.append(f'<span class="{css}">{icon}</span>')
304
- return "".join(tiles)
305
-
306
-
307
- def _scene_state_class(current):
308
- if not current:
309
- return "state-waiting"
310
- if current.get("game_over") == "Yes":
311
- return "state-game-over"
312
- if current.get("picked") == "Yes":
313
- return "state-picked"
314
- if current.get("poison") == "Yes":
315
- return "state-danger"
316
- return "state-discovered"
317
-
318
-
319
- def _safe_collection(collection):
320
- """Return a list of dicts, parsing any string entries that Gradio may have serialized."""
321
- import json as _json
322
- result = []
323
- for entry in list(collection or []):
324
- if isinstance(entry, str):
325
- try:
326
- entry = _json.loads(entry)
327
- except Exception:
328
- continue
329
- if isinstance(entry, dict):
330
- result.append(entry)
331
- return result
332
-
333
-
334
- # ---------------------------------------------------------------------------
335
- # Game status panel
336
- # ---------------------------------------------------------------------------
337
-
338
- def game_status_markdown(
339
- current: "MushroomState | None",
340
- collection: "CollectionState | None",
341
- position: "tuple[int,int] | None" = None,
342
- ) -> str:
343
- count = len(_safe_collection(collection))
344
- pos_x, pos_y = _safe_position(position)
345
- active_mystery = next_mystery(count)
346
- mystery_line = _mystery_goal_line(active_mystery, count)
347
-
348
- if current is None:
349
- objective = "Goal: solve **one mystery** — why does Myco remember a vanished forest?"
350
- discovery = "No active discovery yet. Move Myco, then Search Clearing."
351
- ai_job = "Myco AI is roaming — watch it search, study, and collect automatically."
352
- event = "**Event:** waiting for the first omen"
353
- clue_line = mystery_line
354
- thread_line = "**Mystery thread:** The Wrong Memory — search the first clearing."
355
- bloom_line = f"**Impossible bloom:** {_mystery_bloom_meter(count)}"
356
- score_line = "**Score:** 0 spores · **Health:** 3/3"
357
- else:
358
- name = current.get("name", "Mystery mushroom")
359
- studied = current.get("studied") == "Yes"
360
- game_over = current.get("game_over") == "Yes"
361
- picked = current.get("picked") == "Yes"
362
- if game_over:
363
- objective = "**💀 GAME OVER** — press **Search Clearing** to start a new run."
364
- elif picked:
365
- objective = "Current objective: **move to the next clearing and search again**."
366
- else:
367
- next_step = "Collect it to preserve the clue" if studied else "Study it, then Collect or Pick"
368
- objective = f"Current objective: **{next_step}**."
369
- bloom_line = f"**Impossible bloom:** {_mystery_bloom_meter(count)}"
370
- score_line = (
371
- f"**Score:** {current.get('score_total','0')} spores · "
372
- f"**Health:** {current.get('health','3')}/3 · "
373
- f"**Last:** {current.get('reward_text','—')}"
374
- )
375
- discovery = f"Active discovery: **{name}** · Rarity: **{current.get('rarity','Unknown')}**"
376
- event = f"**Event:** {current.get('event_emoji','✨')} {current.get('event_title','Strange Omen')}"
377
- clue_line = f"**Clue:** {current.get('clue','Myco is still listening.')}"
378
- thread_line = (
379
- f"**Mystery thread:** {current.get('mystery_title','The Wrong Memory')} — "
380
- f"Next: {current.get('mystery_next','follow the whisper.')}"
381
- )
382
- ai_job = "Myco AI is watching — it will study and collect automatically while you explore."
383
-
384
- return "\n".join((
385
- "### 🎮 Quest Log",
386
- f"**MycoDex:** {count} discoveries · **Map:** ({pos_x+1},{pos_y+1})",
387
- discovery, objective, score_line, bloom_line,
388
- event, clue_line, thread_line, mystery_line, ai_job,
389
- ))
390
-
391
-
392
- # ---------------------------------------------------------------------------
393
- # Mushroom card
394
- # ---------------------------------------------------------------------------
395
-
396
- def mushroom_card(
397
- mushroom: "Mushroom | None",
398
- clue: "str | None" = None,
399
- secret: "str | None" = None,
400
- state: "MushroomState | None" = None,
401
- ) -> str:
402
- if mushroom is None:
403
- return "### 🌲 Forest Clearing\nClick **Search Clearing** — watch Myco run toward the mushroom!"
404
- s = state or {}
405
- poison = mushroom.name in POISONOUS_MUSHROOM_NAMES
406
- warning = "\n> ⚠️ **Myco senses danger — do NOT pick this without studying first!**" if poison else ""
407
- return "\n".join((
408
- f"### 🍄‍🟫 {mushroom.name}",
409
- f"**{mushroom.rarity} discovery** · found near **{mushroom.habitat}**",
410
- f"> {mushroom.lore}",
411
- warning, "",
412
- f"* **Edible:** {mushroom.edible}",
413
- f"* **Magic:** {mushroom.magic}",
414
- f"* **Danger:** {mushroom.danger}",
415
- f"* **Value:** {_mushroom_value_label(mushroom.rarity)}",
416
- f"* **Poison risk:** {_poison_risk_label(mushroom.name)}",
417
- f"* **Event:** {s.get('event_emoji','✨')} {s.get('event_title','Strange Omen')}",
418
- f"* **Mystery:** {s.get('mystery_title','The Wrong Memory')}",
419
- f"* **Clue:** {clue or 'Study with Myco to reveal the clue.'}",
420
- f"* **Secret:** {secret or 'Collect it to see how the MycoDex reacts.'}",
421
- f"* **Status:** {'⚠️ POISONOUS' if poison else '✅ Appears safe'}",
422
- ))
423
-
424
- def _mushroom_value_label(rarity):
425
- return {"Legendary": "100 spores", "Rare": "35 spores"}.get(rarity, "10 spores")
426
-
427
- def _poison_risk_label(name):
428
- return "⚠️ POISONOUS — Game Over if picked!" if name in POISONOUS_MUSHROOM_NAMES else "No obvious poison"
429
-
430
-
431
- # ---------------------------------------------------------------------------
432
- # World map / Dex / Progress
433
- # ---------------------------------------------------------------------------
434
-
435
- def world_map_markdown(collection: "CollectionState | None") -> str:
436
- count = len(_safe_collection(collection))
437
- cards = []
438
- for area in AREA_UNLOCKS:
439
- unlocked = count >= area.required_discoveries
440
- state = "unlocked" if unlocked else "locked"
441
- badge = "✅ Unlocked" if unlocked else f"🔒 {area.required_discoveries - count} away"
442
- cards.append("\n".join((
443
- f'<div class="map-card {state}">',
444
- ' <div class="map-card-top">',
445
- f' <strong>{escape(area.name)}</strong><span>{badge}</span>',
446
- ' </div>',
447
- f' <p>{escape(area.description)}</p>',
448
- '</div>',
449
- )))
450
- secrets = tuple(_secret_badge(t.name, count >= t.required_discoveries) for t in revealed_mysteries(count))
451
- if not secrets:
452
- secrets = (_secret_badge("First Spore", False),)
453
- return "\n".join((
454
- '<div class="world-map-panel">',
455
- ' <div class="world-map-header"><p class="eyebrow">World Map</p>',
456
- ' <h2>Forest Atlas</h2>',
457
- f' <p>{count} discoveries logged.</p></div>',
458
- f' <div class="achievement-row">{"".join(secrets)}</div>',
459
- f' <div class="map-grid">{"".join(cards)}</div>',
460
- '</div>',
461
- ))
462
-
463
- def _secret_badge(label, unlocked):
464
- state = "earned" if unlocked else "pending"
465
- icon = "🔎" if unlocked else "◇"
466
- return f'<span class="achievement {state}">{icon} {escape(label)}</span>'
467
-
468
- def dex_markdown(collection: "CollectionState | None") -> str:
469
- current_collection = _safe_collection(collection)
470
- if not current_collection:
471
- return EMPTY_DEX
472
- rows = ["### 📖 MycoDex", ""]
473
- for entry in current_collection:
474
- # Entries can arrive as JSON strings if Gradio serialized the state
475
- if isinstance(entry, str):
476
- try:
477
- import json as _json
478
- entry = _json.loads(entry)
479
- except Exception:
480
- continue
481
- if not isinstance(entry, dict):
482
- continue
483
- poison_flag = " ☠️" if entry.get("poison") == "Yes" else ""
484
- rows.append(
485
- f"* 🍄 **{entry.get('name','Unknown')}**{poison_flag} — "
486
- f"{entry.get('rarity','Unknown')} — "
487
- f"{entry.get('habitat','Unknown habitat')} — clue: {entry.get('clue','not studied yet')}"
488
- )
489
- return "\n".join(rows)
490
-
491
- def progress_markdown(collection: "CollectionState | None") -> str:
492
- count = len(_safe_collection(collection))
493
- active_area = current_area(count)
494
- upcoming_area = next_area(count)
495
- unlocked_names = ", ".join(a.name for a in unlocked_areas(count))
496
- rows = [
497
- "### ✨ Forest Progress",
498
- f"**MycoDex discoveries:** {count}",
499
- f"**Current area:** {active_area.name} — {active_area.description}",
500
- f"**Unlocked:** {unlocked_names}",
501
- ]
502
- upcoming_mystery = next_mystery(count)
503
- if upcoming_area:
504
- rows.append(f"**Next area:** {upcoming_area.required_discoveries - count} more to unlock **{upcoming_area.name}**.")
505
- else:
506
- rows.append(f"**All areas visible.** You reached **{AREA_UNLOCKS[-1].name}**.")
507
- if upcoming_mystery:
508
- rows.append(f"**Next secret:** {upcoming_mystery.required_discoveries - count} more to reveal **{upcoming_mystery.name}**.")
509
- else:
510
- rows.append("**All current secrets found.**")
511
- return "\n\n".join(rows)
512
-
513
- def _mystery_goal_line(active_mystery, count):
514
- if active_mystery is None:
515
- return "**Mystery:** Every known clue is awake. Keep exploring for rare surprises."
516
- remaining = active_mystery.required_discoveries - count
517
- return f"**Mystery:** {remaining} clue discoveries until **{active_mystery.name}** — {active_mystery.clue}"
518
-
519
- def _mystery_bloom_meter(count):
520
- filled = max(0, min(5, count))
521
- petals = "".join("🌕" if i < filled else "🌑" for i in range(5))
522
- if filled >= 5:
523
- return f"{petals} Impossible Bloom visible"
524
- return f"{petals} {5 - filled} clue-whispers remain"
525
-