byte-vortex commited on
Commit
c29d463
·
verified ·
1 Parent(s): 50d09fc

Deploy Myco from CI

Browse files
Files changed (1) hide show
  1. ui/renderers.py +62 -184
ui/renderers.py CHANGED
@@ -33,6 +33,7 @@ EMOTION_ANIMATION: dict[str, str] = {
33
  "wonder": "myco-bob 3.2s ease-in-out infinite",
34
  "proud": "myco-bob 2s ease-in-out infinite",
35
  "sad": "myco-fall 0.6s ease both",
 
36
  }
37
 
38
  MYCO_FILTER: dict[str, str] = {
@@ -43,6 +44,7 @@ MYCO_FILTER: dict[str, str] = {
43
  "wonder": "drop-shadow(0 6px 18px rgba(140,100,255,.5))",
44
  "proud": "drop-shadow(0 6px 14px rgba(100,255,160,.4))",
45
  "sad": "drop-shadow(0 6px 14px rgba(100,100,180,.3))",
 
46
  }
47
 
48
  SCENE_BG: dict[str, str] = {
@@ -68,11 +70,15 @@ SCENE_STYLES = """<style>
68
  .sparkle-one { left:34%; top:18%; font-size:1.8rem; animation:twinkle 1.8s ease-in-out infinite; }
69
  .sparkle-two { right:33%; top:24%; font-size:1.6rem; animation:twinkle 2.2s ease-in-out infinite reverse; }
70
  @keyframes twinkle { 0%,100%{opacity:.3;transform:scale(.85);} 50%{opacity:1;transform:scale(1.18);} }
71
- .myco-player { font-size:4rem; z-index:2; transition: left .32s cubic-bezier(.34,1.56,.64,1), bottom .32s cubic-bezier(.34,1.56,.64,1), filter .6s ease; }
 
 
72
  @keyframes myco-bob { 0%,100%{transform:translateY(0);} 50%{transform:translateY(-.45rem);} }
73
  @keyframes myco-recoil { 0%{transform:translateX(0);} 30%{transform:translateX(-1.2rem);} 60%{transform:translateX(.4rem);} 100%{transform:translateX(0);} }
74
  @keyframes myco-run-in { 0%{transform:translateX(-3rem) scaleX(-1);} 100%{transform:translateX(0) scaleX(1);} }
75
  @keyframes myco-fall { 0%{transform:rotate(0) translateY(0); opacity:1;} 100%{transform:rotate(-90deg) translateY(2rem); opacity:.4;} }
 
 
76
  /* Position Grid */
77
  .pos-0-0 .myco-player{left:14%;bottom:62%;} .pos-1-0 .myco-player{left:41%;bottom:62%;} .pos-2-0 .myco-player{left:67%;bottom:62%;}
78
  .pos-0-1 .myco-player{left:14%;bottom:36%;} .pos-1-1 .myco-player{left:41%;bottom:36%;} .pos-2-1 .myco-player{left:67%;bottom:36%;}
@@ -133,13 +139,12 @@ def forest_scene(
133
  pos_cls = f"pos-{pos_x}-{pos_y}"
134
 
135
  # ── Read LLM-driven signals ──────────────────────────────────────────
136
- scene_mood = (current or {}).get("scene_mood", "normal")
137
  myco_emotion = (current or {}).get("myco_emotion", "curious")
138
 
139
  if scene_mood not in MOOD_BG: scene_mood = "normal"
140
  if myco_emotion not in EMOTION_ANIMATION: myco_emotion = "curious"
141
 
142
- stage_bg = "linear-gradient(transparent 62%, rgba(106,79,33,.55) 63%), " + MOOD_BG[scene_mood]
143
  myco_anim = EMOTION_ANIMATION[myco_emotion]
144
  myco_filt = MYCO_FILTER[myco_emotion]
145
  outer_bg = SCENE_BG.get(scene_mood, SCENE_BG["normal"])
@@ -150,32 +155,57 @@ def forest_scene(
150
  rarity = "common"
151
  sprite = "✨"
152
  status = "The moss is listening. Click Search Clearing to wake the forest."
153
- omen = "🍄 Myco twitches excitedly. It senses spores nearby."
154
- event_label = "🌌 Dreaming Forest"
155
- encounter = "The next clearing may reveal a memory, creature, or poison omen."
156
- mystery_label = "The Wrong Memory"
157
- score_total = "0"
158
- health = "3"
159
  reward_text = "Search a clearing to score spores."
160
  state_cls = "state-waiting"
 
 
161
  else:
162
  mushroom_name = current.get("name", "Mystery mushroom")
163
  rarity = current.get("rarity", "Common").lower()
164
- # Sprite logic ...
165
- sprite = "🍄‍🟫" # Simplified for brevity; logic remains as provided
166
- # ... [remaining render logic]
167
  state_cls = _scene_state_class(current)
168
- # ... status/omen text generation
 
 
 
169
 
170
  import time
171
  anim_key = str(int(time.time() * 1000))[-6:]
172
 
173
- # Returning the final HTML template string
174
- return f'''{SCENE_STYLES}<div class="forest-scene rarity-{escape(rarity)} {pos_cls} {state_cls}"
175
- data-key="{anim_key}" data-mood="{escape(scene_mood)}" data-emotion="{escape(myco_emotion)}"
176
- style="background: radial-gradient(circle at 50% 72%,rgba(255,239,173,.18),transparent 8rem), radial-gradient(circle at 18% 24%,rgba(134,225,153,.15),transparent 10rem), {escape(outer_bg)}; border: 1px solid rgba(225,255,209,.25);">
177
- ... [Include full template structure here] ...
178
- </div>'''
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
  # ── Helper Functions ───────────────────────────────────────────────────
181
 
@@ -202,17 +232,12 @@ def _scene_state_class(current):
202
  def _safe_collection(collection):
203
  return list(collection or [])
204
 
205
- # ... [Remaining functions: game_status_markdown, mushroom_card, etc.]
206
- def game_status_markdown(
207
- current: MushroomState | None,
208
- collection: CollectionState | None,
209
- position: tuple[int, int] | None = None,
210
- ) -> str:
211
  count = len(_safe_collection(collection))
212
  pos_x, pos_y = _safe_position(position)
213
  active_mystery = next_mystery(count)
214
  mystery_line = _mystery_goal_line(active_mystery, count)
215
-
216
  if current is None:
217
  objective = "Goal: solve **one mystery** — why does Myco remember a vanished forest?"
218
  discovery = "No active discovery yet. Move Myco, then Search Clearing."
@@ -261,14 +286,8 @@ def _mystery_bloom_meter(count):
261
  return f"{petals} Impossible Bloom visible"
262
  return f"{petals} {5-filled} clue-whispers remain"
263
 
264
- def mushroom_card(
265
- mushroom: Mushroom | None,
266
- clue: str | None = None,
267
- secret: str | None = None,
268
- state: MushroomState | None = None,
269
- ) -> str:
270
- if mushroom is None:
271
- return "### 🌲 Forest Clearing\nClick **Search Clearing** — watch Myco run toward the mushroom!"
272
  s = state or {}
273
  poison = mushroom.name in POISONOUS_MUSHROOM_NAMES
274
  poison_warning = "\n> ⚠️ **Myco senses danger — do NOT pick this without studying first!**" if poison else ""
@@ -291,7 +310,7 @@ def mushroom_card(
291
 
292
  def _mushroom_value_label(rarity):
293
  if rarity == "Legendary": return "100 spores"
294
- if rarity == "Rare": return "35 spores + special discovery"
295
  return "10 spores"
296
 
297
  def _poison_risk_label(name):
@@ -313,8 +332,7 @@ def world_map_markdown(collection: CollectionState | None) -> str:
313
  '</div>',
314
  )))
315
  secrets = tuple(_secret_badge(t.name, count >= t.required_discoveries) for t in revealed_mysteries(count))
316
- if not secrets:
317
- secrets = (_secret_badge("First Spore", False),)
318
  return "\n".join((
319
  '<div class="world-map-panel">',
320
  ' <div class="world-map-header"><p class="eyebrow">World Map</p>',
@@ -332,24 +350,17 @@ def _secret_badge(label, unlocked):
332
 
333
  def dex_markdown(collection: CollectionState | None) -> str:
334
  current_collection = _safe_collection(collection)
335
- if not current_collection:
336
- return EMPTY_DEX
337
  rows = ["### 📖 MycoDex", ""]
338
  for entry in current_collection:
339
- rows.append(
340
- f"* 🍄 **{entry['name']}** — {entry['rarity']} — {entry['habitat']} "
341
- f"— clue: {entry.get('clue','not studied yet')}"
342
- )
343
  return "\n".join(rows)
344
 
345
- def _safe_collection(collection):
346
- return list(collection or [])
347
-
348
  def progress_markdown(collection: CollectionState | None) -> str:
349
- count = len(_safe_collection(collection))
350
- active_area = current_area(count)
351
- upcoming_area = next_area(count)
352
- unlocked_names= ", ".join(a.name for a in unlocked_areas(count))
353
  rows = [
354
  "### ✨ Forest Progress",
355
  f"**MycoDex discoveries:** {count}",
@@ -374,138 +385,5 @@ def _mystery_goal_line(active_mystery, count):
374
  return f"**Mystery:** {remaining} clue discoveries until **{active_mystery.name}** — {active_mystery.clue}"
375
 
376
  def game_intro_markdown() -> str:
377
- return """
378
- <div class="myco-hero-banner">
379
-
380
- <div class="floating-spores">
381
- <span>✨</span>
382
- <span>✦</span>
383
- <span>✨</span>
384
- <span>✧</span>
385
- <span>✨</span>
386
- </div>
387
-
388
- <div class="myco-avatar">🍄</div>
389
-
390
- <h1>🌲 The Deep Forest 🌲</h1>
391
-
392
- <div class="highlight-line">
393
- 🍄 Myco wants to investigate <span>EVERY SINGLE ONE</span>.
394
- </div>
395
-
396
- <div class="myco-thought">
397
- 💭 “I know this forest... but I don't remember why.”
398
- </div>
399
-
400
- </div>
401
-
402
- <style>
403
- .myco-hero-banner{
404
- position:relative;
405
- overflow:hidden;
406
- text-align:center;
407
- padding:2rem;
408
- margin-bottom:1rem;
409
- border-radius:24px;
410
-
411
- background:
412
- radial-gradient(circle at 20% 20%, rgba(255,240,160,.18), transparent 30%),
413
- radial-gradient(circle at 80% 30%, rgba(120,255,180,.15), transparent 35%),
414
- linear-gradient(
415
- 135deg,
416
- #0f2027 0%,
417
- #203a43 35%,
418
- #2c5530 70%,
419
- #0f2027 100%
420
- );
421
-
422
- border:1px solid rgba(255,255,255,.12);
423
-
424
- box-shadow:
425
- 0 0 30px rgba(0,0,0,.35),
426
- inset 0 0 60px rgba(255,255,255,.03);
427
- }
428
-
429
- .myco-avatar{
430
- font-size:4.5rem;
431
- animation:bounce 2.2s ease-in-out infinite;
432
- filter:drop-shadow(0 0 18px rgba(255,210,100,.7));
433
- }
434
-
435
- .myco-hero-banner h1{
436
- color:#f8ffd7 !important;
437
- margin:.4rem 0 .8rem;
438
- font-size:2.2rem;
439
- text-shadow:0 0 16px rgba(255,255,180,.3);
440
- }
441
-
442
- .highlight-line{
443
- margin:1.2rem auto;
444
- padding:.9rem 1.2rem;
445
- max-width:700px;
446
- border-radius:999px;
447
-
448
- background:linear-gradient(
449
- 90deg,
450
- rgba(255,210,80,.18),
451
- rgba(255,255,255,.06),
452
- rgba(255,210,80,.18)
453
- );
454
-
455
- color:#ffffff !important;
456
- font-weight:700;
457
- font-size:1.05rem;
458
- }
459
-
460
- .highlight-line span{
461
- color:#ffd54f !important;
462
- font-weight:900;
463
- letter-spacing:.08em;
464
- text-shadow:0 0 12px rgba(255,215,0,.7);
465
- }
466
-
467
- .myco-thought{
468
- margin-top:1rem;
469
- color:#fff6c2 !important;
470
- font-style:italic;
471
- animation:pulse 3s ease-in-out infinite;
472
- }
473
-
474
- .floating-spores span{
475
- position:absolute;
476
- font-size:1.2rem;
477
- animation:float 6s linear infinite;
478
- }
479
-
480
- .floating-spores span:nth-child(1){left:10%;top:15%;}
481
- .floating-spores span:nth-child(2){left:25%;top:60%;animation-delay:1s;}
482
- .floating-spores span:nth-child(3){left:50%;top:10%;animation-delay:2s;}
483
- .floating-spores span:nth-child(4){left:75%;top:55%;animation-delay:3s;}
484
- .floating-spores span:nth-child(5){left:90%;top:20%;animation-delay:4s;}
485
-
486
- @keyframes bounce{
487
- 0%,100%{transform:translateY(0);}
488
- 50%{transform:translateY(-10px);}
489
- }
490
-
491
- @keyframes pulse{
492
- 0%,100%{opacity:.75;}
493
- 50%{opacity:1;}
494
- }
495
-
496
- @keyframes float{
497
- 0%{
498
- transform:translateY(15px);
499
- opacity:.2;
500
- }
501
- 50%{
502
- opacity:1;
503
- }
504
- 100%{
505
- transform:translateY(-20px);
506
- opacity:.2;
507
- }
508
- }
509
- </style>
510
- """
511
 
 
33
  "wonder": "myco-bob 3.2s ease-in-out infinite",
34
  "proud": "myco-bob 2s ease-in-out infinite",
35
  "sad": "myco-fall 0.6s ease both",
36
+ "scanning": "myco-bob 2.4s ease-in-out infinite, myco-scan 3s ease-in-out infinite",
37
  }
38
 
39
  MYCO_FILTER: dict[str, str] = {
 
44
  "wonder": "drop-shadow(0 6px 18px rgba(140,100,255,.5))",
45
  "proud": "drop-shadow(0 6px 14px rgba(100,255,160,.4))",
46
  "sad": "drop-shadow(0 6px 14px rgba(100,100,180,.3))",
47
+ "scanning": "drop-shadow(0 6px 18px rgba(255,255,255,.5))",
48
  }
49
 
50
  SCENE_BG: dict[str, str] = {
 
70
  .sparkle-one { left:34%; top:18%; font-size:1.8rem; animation:twinkle 1.8s ease-in-out infinite; }
71
  .sparkle-two { right:33%; top:24%; font-size:1.6rem; animation:twinkle 2.2s ease-in-out infinite reverse; }
72
  @keyframes twinkle { 0%,100%{opacity:.3;transform:scale(.85);} 50%{opacity:1;transform:scale(1.18);} }
73
+
74
+ /* Myco Animation Logic */
75
+ .myco-player { font-size:4rem; z-index:2; display: inline-block; transition: left .32s cubic-bezier(.34,1.56,.64,1), bottom .32s cubic-bezier(.34,1.56,.64,1), filter .6s ease; }
76
  @keyframes myco-bob { 0%,100%{transform:translateY(0);} 50%{transform:translateY(-.45rem);} }
77
  @keyframes myco-recoil { 0%{transform:translateX(0);} 30%{transform:translateX(-1.2rem);} 60%{transform:translateX(.4rem);} 100%{transform:translateX(0);} }
78
  @keyframes myco-run-in { 0%{transform:translateX(-3rem) scaleX(-1);} 100%{transform:translateX(0) scaleX(1);} }
79
  @keyframes myco-fall { 0%{transform:rotate(0) translateY(0); opacity:1;} 100%{transform:rotate(-90deg) translateY(2rem); opacity:.4;} }
80
+ @keyframes myco-scan { 0%,100%{transform:rotateY(0deg) scaleX(1);} 30%{transform:rotateY(-25deg) scaleX(1);} 70%{transform:rotateY(25deg) scaleX(1);} }
81
+
82
  /* Position Grid */
83
  .pos-0-0 .myco-player{left:14%;bottom:62%;} .pos-1-0 .myco-player{left:41%;bottom:62%;} .pos-2-0 .myco-player{left:67%;bottom:62%;}
84
  .pos-0-1 .myco-player{left:14%;bottom:36%;} .pos-1-1 .myco-player{left:41%;bottom:36%;} .pos-2-1 .myco-player{left:67%;bottom:36%;}
 
139
  pos_cls = f"pos-{pos_x}-{pos_y}"
140
 
141
  # ── Read LLM-driven signals ──────────────────────────────────────────
142
+ scene_mood = (current or {}).get("scene_mood", "normal")
143
  myco_emotion = (current or {}).get("myco_emotion", "curious")
144
 
145
  if scene_mood not in MOOD_BG: scene_mood = "normal"
146
  if myco_emotion not in EMOTION_ANIMATION: myco_emotion = "curious"
147
 
 
148
  myco_anim = EMOTION_ANIMATION[myco_emotion]
149
  myco_filt = MYCO_FILTER[myco_emotion]
150
  outer_bg = SCENE_BG.get(scene_mood, SCENE_BG["normal"])
 
155
  rarity = "common"
156
  sprite = "✨"
157
  status = "The moss is listening. Click Search Clearing to wake the forest."
 
 
 
 
 
 
158
  reward_text = "Search a clearing to score spores."
159
  state_cls = "state-waiting"
160
+ event_title = "Dreaming Forest"
161
+ encounter = "The next clearing may reveal a memory, creature, or poison omen."
162
  else:
163
  mushroom_name = current.get("name", "Mystery mushroom")
164
  rarity = current.get("rarity", "Common").lower()
165
+ sprite = "🍄‍🟫"
 
 
166
  state_cls = _scene_state_class(current)
167
+ status = f"A {rarity} {mushroom_name} breaks through the moss!"
168
+ reward_text = current.get("reward_text", "Decide whether to risk the pickup.")
169
+ event_title = current.get('event_title', 'Strange Omen')
170
+ encounter = current.get("encounter", "The forest waits.")
171
 
172
  import time
173
  anim_key = str(int(time.time() * 1000))[-6:]
174
 
175
+ return f'''{SCENE_STYLES}
176
+ <div class="forest-scene rarity-{escape(rarity)} {pos_cls} {state_cls}"
177
+ data-key="{anim_key}" data-mood="{escape(scene_mood)}" data-emotion="{escape(myco_emotion)}"
178
+ style="background: radial-gradient(circle at 50% 72%,rgba(255,239,173,.18),transparent 8rem), radial-gradient(circle at 18% 24%,rgba(134,225,153,.15),transparent 10rem), {escape(outer_bg)}; border: 1px solid rgba(225,255,209,.25);">
179
+
180
+ <div class="scene-hud">
181
+ <span>🧭 {escape(area.name)}</span>
182
+ <span>🎮 ({pos_x+1},{pos_y+1})</span>
183
+ <span>⭐ {current.get('score_total', '0') if current else '0'}</span>
184
+ <span>❤️ {current.get('health', '3') if current else '3'}/3</span>
185
+ </div>
186
+
187
+ <div class="scene-stage">
188
+ <span class="tree-left">🌲</span>
189
+ <span class="tree-right">🌳</span>
190
+ <span class="grass-one">🌿</span>
191
+ <span class="grass-two">🍃</span>
192
+ <span class="myco-player" style="animation: {myco_anim}; filter: {myco_filt};" title="Myco">🍄</span>
193
+ <span class="mushroom-sprite" title="{escape(mushroom_name)}">{sprite}</span>
194
+ </div>
195
+
196
+ <div class="forest-map">
197
+ {_movement_tiles(pos_x, pos_y)}
198
+ </div>
199
+
200
+ <div class="scene-caption">
201
+ <strong>{escape(status)}</strong>
202
+ </div>
203
+ <div class="score-burst">{escape(reward_text)}</div>
204
+ <div class="story-moment">
205
+ <strong>{escape(event_title)}</strong>
206
+ <span>{escape(encounter)}</span>
207
+ </div>
208
+ </div>'''
209
 
210
  # ── Helper Functions ───────────────────────────────────────────────────
211
 
 
232
  def _safe_collection(collection):
233
  return list(collection or [])
234
 
235
+ def game_status_markdown(current: MushroomState | None, collection: CollectionState | None, position: tuple[int, int] | None = None) -> str:
 
 
 
 
 
236
  count = len(_safe_collection(collection))
237
  pos_x, pos_y = _safe_position(position)
238
  active_mystery = next_mystery(count)
239
  mystery_line = _mystery_goal_line(active_mystery, count)
240
+
241
  if current is None:
242
  objective = "Goal: solve **one mystery** — why does Myco remember a vanished forest?"
243
  discovery = "No active discovery yet. Move Myco, then Search Clearing."
 
286
  return f"{petals} Impossible Bloom visible"
287
  return f"{petals} {5-filled} clue-whispers remain"
288
 
289
+ def mushroom_card(mushroom: Mushroom | None, clue: str | None = None, secret: str | None = None, state: MushroomState | None = None) -> str:
290
+ if mushroom is None: return "### 🌲 Forest Clearing\nClick **Search Clearing** — watch Myco run toward the mushroom!"
 
 
 
 
 
 
291
  s = state or {}
292
  poison = mushroom.name in POISONOUS_MUSHROOM_NAMES
293
  poison_warning = "\n> ⚠️ **Myco senses danger — do NOT pick this without studying first!**" if poison else ""
 
310
 
311
  def _mushroom_value_label(rarity):
312
  if rarity == "Legendary": return "100 spores"
313
+ if rarity == "Rare": return "35 spores + special discovery"
314
  return "10 spores"
315
 
316
  def _poison_risk_label(name):
 
332
  '</div>',
333
  )))
334
  secrets = tuple(_secret_badge(t.name, count >= t.required_discoveries) for t in revealed_mysteries(count))
335
+ if not secrets: secrets = (_secret_badge("First Spore", False),)
 
336
  return "\n".join((
337
  '<div class="world-map-panel">',
338
  ' <div class="world-map-header"><p class="eyebrow">World Map</p>',
 
350
 
351
  def dex_markdown(collection: CollectionState | None) -> str:
352
  current_collection = _safe_collection(collection)
353
+ if not current_collection: return EMPTY_DEX
 
354
  rows = ["### 📖 MycoDex", ""]
355
  for entry in current_collection:
356
+ rows.append(f"* 🍄 **{entry['name']}** — {entry['rarity']} — {entry['habitat']} — clue: {entry.get('clue','not studied yet')}")
 
 
 
357
  return "\n".join(rows)
358
 
 
 
 
359
  def progress_markdown(collection: CollectionState | None) -> str:
360
+ count = len(_safe_collection(collection))
361
+ active_area = current_area(count)
362
+ upcoming_area = next_area(count)
363
+ unlocked_names = ", ".join(a.name for a in unlocked_areas(count))
364
  rows = [
365
  "### ✨ Forest Progress",
366
  f"**MycoDex discoveries:** {count}",
 
385
  return f"**Mystery:** {remaining} clue discoveries until **{active_mystery.name}** — {active_mystery.clue}"
386
 
387
  def game_intro_markdown() -> str:
388
+ return """<div class="myco-hero-banner"><div class="floating-spores"><span>✨</span><span>✦</span><span>✨</span><span>✧</span><span>✨</span></div><div class="myco-avatar">🍄</div><h1>🌲 The Deep Forest 🌲</h1><div class="highlight-line">🍄 Myco wants to investigate <span>EVERY SINGLE ONE</span>.</div><div class="myco-thought">💭 “I know this forest... but I don't remember why.”</div></div>"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389