Spaces:
Sleeping
Sleeping
Delete ui/old--renderers.py
Browse files- ui/old--renderers.py +0 -686
ui/old--renderers.py
DELETED
|
@@ -1,686 +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 |
-
POISONOUS_MUSHROOM_NAMES = {
|
| 15 |
-
"Ghost Gill", "Pepper Pixie", "Ruby Knuckle", "Clockwork Chanterelle",
|
| 16 |
-
}
|
| 17 |
-
|
| 18 |
-
SCENE_STYLES = """
|
| 19 |
-
<style>
|
| 20 |
-
.forest-scene {
|
| 21 |
-
position: relative; isolation: isolate;
|
| 22 |
-
overflow: hidden; min-height: 20rem;
|
| 23 |
-
padding: 0.75rem; border-radius: 28px;
|
| 24 |
-
background:
|
| 25 |
-
radial-gradient(circle at 50% 72%, rgba(255,239,173,.26), transparent 8rem),
|
| 26 |
-
radial-gradient(circle at 18% 24%, rgba(134,225,153,.22), transparent 10rem),
|
| 27 |
-
linear-gradient(180deg,#123626 0%,#245c3c 54%,#523b20 100%);
|
| 28 |
-
border: 1px solid rgba(225,255,209,.25);
|
| 29 |
-
box-shadow: 0 18px 48px rgba(8,30,20,.26);
|
| 30 |
-
color: #f9ffe9;
|
| 31 |
-
}
|
| 32 |
-
.scene-hud {
|
| 33 |
-
position: relative; z-index: 3;
|
| 34 |
-
display: flex; flex-wrap: wrap; gap: .4rem;
|
| 35 |
-
padding: .5rem .75rem; border-radius: 999px;
|
| 36 |
-
background: rgba(13,38,28,.72); backdrop-filter: blur(6px);
|
| 37 |
-
font-size: .8rem; font-weight: 800;
|
| 38 |
-
}
|
| 39 |
-
.scene-stage {
|
| 40 |
-
position: relative; height: 13rem;
|
| 41 |
-
margin: .6rem 0;
|
| 42 |
-
background:
|
| 43 |
-
linear-gradient(transparent 62%, rgba(106,79,33,.55) 63%),
|
| 44 |
-
radial-gradient(ellipse at bottom, rgba(190,255,180,.2), transparent 60%);
|
| 45 |
-
border-radius: 22px; overflow: hidden;
|
| 46 |
-
}
|
| 47 |
-
.scene-stage span { position: absolute; line-height:1; }
|
| 48 |
-
.tree-left { left:6%; bottom:18%; font-size:5rem; filter:drop-shadow(0 8px 12px rgba(0,0,0,.28)); }
|
| 49 |
-
.tree-right { right:7%; bottom:20%; font-size:4.75rem; filter:drop-shadow(0 8px 12px rgba(0,0,0,.28)); }
|
| 50 |
-
.grass-one { left:26%; bottom:14%; font-size:2.4rem; }
|
| 51 |
-
.grass-two { right:28%;bottom:16%; font-size:2.2rem; }
|
| 52 |
-
.sparkle-one { left:34%; top:18%; font-size:1.8rem; animation:twinkle 1.8s ease-in-out infinite; }
|
| 53 |
-
.sparkle-two { right:33%; top:24%; font-size:1.6rem; animation:twinkle 2.2s ease-in-out infinite reverse; }
|
| 54 |
-
@keyframes twinkle {
|
| 55 |
-
0%,100%{opacity:.3;transform:scale(.85);}
|
| 56 |
-
50%{opacity:1;transform:scale(1.18);}
|
| 57 |
-
}
|
| 58 |
-
.myco-player {
|
| 59 |
-
font-size:4rem; z-index:2;
|
| 60 |
-
transition: left .32s cubic-bezier(.34,1.56,.64,1), bottom .32s cubic-bezier(.34,1.56,.64,1);
|
| 61 |
-
animation: myco-bob 2.4s ease-in-out infinite;
|
| 62 |
-
filter:drop-shadow(0 6px 14px rgba(255,92,92,.28));
|
| 63 |
-
}
|
| 64 |
-
@keyframes myco-bob {
|
| 65 |
-
0%,100%{transform:translateY(0);}
|
| 66 |
-
50%{transform:translateY(-.45rem);}
|
| 67 |
-
}
|
| 68 |
-
.pos-0-0 .myco-player{left:14%;bottom:62%;}
|
| 69 |
-
.pos-1-0 .myco-player{left:41%;bottom:62%;}
|
| 70 |
-
.pos-2-0 .myco-player{left:67%;bottom:62%;}
|
| 71 |
-
.pos-0-1 .myco-player{left:14%;bottom:36%;}
|
| 72 |
-
.pos-1-1 .myco-player{left:41%;bottom:36%;}
|
| 73 |
-
.pos-2-1 .myco-player{left:67%;bottom:36%;}
|
| 74 |
-
.pos-0-2 .myco-player{left:14%;bottom:13%;}
|
| 75 |
-
.pos-1-2 .myco-player{left:41%;bottom:13%;}
|
| 76 |
-
.pos-2-2 .myco-player{left:67%;bottom:13%;}
|
| 77 |
-
.mushroom-sprite {
|
| 78 |
-
right:33%; bottom:22%; font-size:3.6rem; z-index:2;
|
| 79 |
-
filter:drop-shadow(0 6px 14px rgba(0,0,0,.3));
|
| 80 |
-
}
|
| 81 |
-
.state-waiting .mushroom-sprite { opacity:0; }
|
| 82 |
-
.state-discovered .mushroom-sprite {
|
| 83 |
-
animation: shroom-popup 0.7s cubic-bezier(.34,1.56,.64,1) both;
|
| 84 |
-
}
|
| 85 |
-
.state-discovered .myco-player {
|
| 86 |
-
animation: myco-bob 2.4s ease-in-out infinite,
|
| 87 |
-
myco-run-in 0.6s cubic-bezier(.34,1.56,.64,1) both;
|
| 88 |
-
}
|
| 89 |
-
@keyframes shroom-popup {
|
| 90 |
-
0% { opacity:0; transform:translateY(3rem) scale(.2); }
|
| 91 |
-
60% { opacity:1; transform:translateY(-.4rem) scale(1.18); }
|
| 92 |
-
100% { opacity:1; transform:translateY(0) scale(1); }
|
| 93 |
-
}
|
| 94 |
-
@keyframes myco-run-in {
|
| 95 |
-
0% { transform:translateX(-3rem) scaleX(-1); }
|
| 96 |
-
100% { transform:translateX(0) scaleX(1); }
|
| 97 |
-
}
|
| 98 |
-
.state-picked .mushroom-sprite {
|
| 99 |
-
animation: shroom-catch 0.55s cubic-bezier(.34,1.56,.64,1) both;
|
| 100 |
-
}
|
| 101 |
-
.state-picked .myco-player {
|
| 102 |
-
animation: myco-bob 2.4s ease-in-out infinite, myco-catch-jump 0.55s ease both;
|
| 103 |
-
}
|
| 104 |
-
@keyframes shroom-catch {
|
| 105 |
-
0% { opacity:1; transform:translateY(0) scale(1); }
|
| 106 |
-
40% { opacity:1; transform:translateY(-2.5rem) scale(1.3) rotate(15deg); }
|
| 107 |
-
100% { opacity:0; transform:translateY(-5rem) scale(.2) rotate(40deg); }
|
| 108 |
-
}
|
| 109 |
-
@keyframes myco-catch-jump {
|
| 110 |
-
0%,100%{ transform:translateY(0); }
|
| 111 |
-
40% { transform:translateY(-1.4rem) scale(1.1); }
|
| 112 |
-
}
|
| 113 |
-
.state-danger .mushroom-sprite {
|
| 114 |
-
animation: shroom-popup 0.7s cubic-bezier(.34,1.56,.64,1) both,
|
| 115 |
-
poison-pulse 1.2s ease-in-out infinite 0.7s;
|
| 116 |
-
filter: drop-shadow(0 0 18px rgba(255,62,91,.9));
|
| 117 |
-
}
|
| 118 |
-
.state-danger .myco-player {
|
| 119 |
-
animation: myco-bob 2.4s ease-in-out infinite, myco-recoil 0.5s ease both;
|
| 120 |
-
}
|
| 121 |
-
@keyframes poison-pulse {
|
| 122 |
-
0%,100%{ filter:drop-shadow(0 0 10px rgba(255,62,91,.6)); }
|
| 123 |
-
50% { filter:drop-shadow(0 0 28px rgba(255,62,91,1)); }
|
| 124 |
-
}
|
| 125 |
-
@keyframes myco-recoil {
|
| 126 |
-
0% { transform:translateX(0); }
|
| 127 |
-
30% { transform:translateX(-1.2rem); }
|
| 128 |
-
60% { transform:translateX(.4rem); }
|
| 129 |
-
100%{ transform:translateX(0); }
|
| 130 |
-
}
|
| 131 |
-
.state-game-over .forest-scene,
|
| 132 |
-
.state-game-over {
|
| 133 |
-
animation: screen-shake .45s ease both;
|
| 134 |
-
background:
|
| 135 |
-
radial-gradient(circle at 50% 72%, rgba(255,62,91,.35), transparent 8rem),
|
| 136 |
-
linear-gradient(180deg,#3b0a0a 0%,#6b1a1a 54%,#2a0a0a 100%) !important;
|
| 137 |
-
}
|
| 138 |
-
.state-game-over .mushroom-sprite {
|
| 139 |
-
animation: shroom-explode .6s ease both;
|
| 140 |
-
filter: drop-shadow(0 0 24px rgba(255,62,91,1));
|
| 141 |
-
}
|
| 142 |
-
.state-game-over .myco-player { animation: myco-fall .6s ease both; }
|
| 143 |
-
@keyframes screen-shake {
|
| 144 |
-
0%,100%{ transform:translateX(0); }
|
| 145 |
-
20% { transform:translateX(-8px); }
|
| 146 |
-
40% { transform:translateX(8px); }
|
| 147 |
-
60% { transform:translateX(-5px); }
|
| 148 |
-
80% { transform:translateX(5px); }
|
| 149 |
-
}
|
| 150 |
-
@keyframes shroom-explode {
|
| 151 |
-
0% { transform:scale(1) rotate(0); opacity:1; }
|
| 152 |
-
50% { transform:scale(1.8) rotate(20deg); opacity:.8; }
|
| 153 |
-
100%{ transform:scale(0) rotate(90deg); opacity:0; }
|
| 154 |
-
}
|
| 155 |
-
@keyframes myco-fall {
|
| 156 |
-
0% { transform:rotate(0) translateY(0); opacity:1; }
|
| 157 |
-
100%{ transform:rotate(-90deg) translateY(2rem); opacity:.4; }
|
| 158 |
-
}
|
| 159 |
-
.rarity-rare .mushroom-sprite { filter:drop-shadow(0 0 18px rgba(138,220,255,.9)); }
|
| 160 |
-
.rarity-legendary .mushroom-sprite { filter:drop-shadow(0 0 28px rgba(255,215,0,1)); animation:legendary-pulse 1s ease-in-out infinite 0.7s; }
|
| 161 |
-
@keyframes legendary-pulse {
|
| 162 |
-
0%,100%{ filter:drop-shadow(0 0 18px rgba(255,215,0,.7)); transform:scale(1); }
|
| 163 |
-
50% { filter:drop-shadow(0 0 36px rgba(255,215,0,1)); transform:scale(1.1); }
|
| 164 |
-
}
|
| 165 |
-
.forest-map {
|
| 166 |
-
display:grid; grid-template-columns:repeat(3,1fr);
|
| 167 |
-
gap:.3rem; margin:.4rem auto .6rem;
|
| 168 |
-
max-width:9rem; padding:.4rem;
|
| 169 |
-
border:1px solid rgba(255,255,255,.18); border-radius:14px;
|
| 170 |
-
background:rgba(13,38,28,.58);
|
| 171 |
-
}
|
| 172 |
-
.map-tile {
|
| 173 |
-
display:grid; min-height:1.7rem; place-items:center;
|
| 174 |
-
border-radius:8px; background:rgba(255,255,255,.1); font-weight:900;
|
| 175 |
-
}
|
| 176 |
-
.map-tile.active { background:rgba(255,224,138,.92); color:#3d2c00!important; }
|
| 177 |
-
.scene-caption {
|
| 178 |
-
display:flex; justify-content:space-between; gap:.6rem;
|
| 179 |
-
padding:.55rem .8rem; border-radius:14px;
|
| 180 |
-
background:rgba(13,38,28,.68); backdrop-filter:blur(6px);
|
| 181 |
-
align-items:center;
|
| 182 |
-
}
|
| 183 |
-
.scene-caption span { color:#d9f4d4; }
|
| 184 |
-
.score-burst {
|
| 185 |
-
margin:.65rem auto 0; max-width:520px;
|
| 186 |
-
padding:.5rem .85rem; border-radius:999px;
|
| 187 |
-
background:rgba(255,224,112,.18); border:1px solid rgba(255,244,190,.36);
|
| 188 |
-
color:#fff7d6; font-weight:900; text-align:center;
|
| 189 |
-
animation:score-pop .56s ease both;
|
| 190 |
-
}
|
| 191 |
-
@keyframes score-pop {
|
| 192 |
-
0%{transform:scale(.92);opacity:0;}
|
| 193 |
-
45%{transform:scale(1.06);opacity:1;}
|
| 194 |
-
100%{transform:scale(1);opacity:1;}
|
| 195 |
-
}
|
| 196 |
-
.state-game-over .score-burst {
|
| 197 |
-
background:rgba(130,0,30,.45); border-color:rgba(255,120,140,.55);
|
| 198 |
-
}
|
| 199 |
-
.story-moment {
|
| 200 |
-
margin:.75rem auto 0; max-width:760px;
|
| 201 |
-
padding:.8rem 1rem; border:1px solid rgba(255,234,166,.3);
|
| 202 |
-
border-radius:18px; background:rgba(25,18,45,.72);
|
| 203 |
-
color:#fff7d6; box-shadow:0 0 24px rgba(165,111,255,.22);
|
| 204 |
-
}
|
| 205 |
-
.story-moment strong,.story-moment span { display:block; }
|
| 206 |
-
.story-moment span { margin-top:.2rem; color:#f6e7b2; }
|
| 207 |
-
.generating .forest-scene, .pending .forest-scene { opacity:.88!important; }
|
| 208 |
-
.progress-level,.progress-level-inner { display:none!important; }
|
| 209 |
-
div.gradio-html { background:transparent!important; }
|
| 210 |
-
.wrap.default.pending,.wrap.default.generating { background:transparent!important; }
|
| 211 |
-
|
| 212 |
-
/* AI activity indicator */
|
| 213 |
-
.ai-tick-badge {
|
| 214 |
-
display:inline-block; padding:.15rem .55rem; border-radius:999px;
|
| 215 |
-
background:rgba(80,220,120,.22); border:1px solid rgba(80,220,120,.4);
|
| 216 |
-
color:#90ffb8; font-size:.72rem; font-weight:800;
|
| 217 |
-
animation: ai-pulse 2s ease-in-out infinite;
|
| 218 |
-
}
|
| 219 |
-
@keyframes ai-pulse {
|
| 220 |
-
0%,100%{ opacity:.6; } 50%{ opacity:1; }
|
| 221 |
-
}
|
| 222 |
-
</style>
|
| 223 |
-
"""
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
# ---------------------------------------------------------------------------
|
| 227 |
-
# Static UI fragments
|
| 228 |
-
# ---------------------------------------------------------------------------
|
| 229 |
-
|
| 230 |
-
def hero_markdown() -> str:
|
| 231 |
-
return """
|
| 232 |
-
<div class="myco-hero">
|
| 233 |
-
<div class="myco-mascot">🍄</div>
|
| 234 |
-
<div>
|
| 235 |
-
<p class="eyebrow">Tiny AI forest game</p>
|
| 236 |
-
<h1>Myco</h1>
|
| 237 |
-
<p>Follow an AI mushroom companion through a glowing forest. Every discovery starts with Myco's intuition.</p>
|
| 238 |
-
</div>
|
| 239 |
-
</div>
|
| 240 |
-
"""
|
| 241 |
-
|
| 242 |
-
def home_forest_markdown() -> str:
|
| 243 |
-
return """
|
| 244 |
-
<div class="home-forest-postcard" aria-label="Animated Myco forest postcard">
|
| 245 |
-
<style>
|
| 246 |
-
.home-myco-wrapper, .home-brown-wrapper { position:relative; display:inline-block; }
|
| 247 |
-
.home-spore {
|
| 248 |
-
position:absolute; left:50%; top:20%; width:4px; height:4px;
|
| 249 |
-
background:#a8ffb2; border-radius:50%; filter:blur(0.5px);
|
| 250 |
-
box-shadow:0 0 6px #52ff6b,0 0 10px #fff;
|
| 251 |
-
opacity:0; pointer-events:none;
|
| 252 |
-
animation:floatHomeSpore var(--duration) ease-out infinite;
|
| 253 |
-
animation-delay:var(--delay);
|
| 254 |
-
}
|
| 255 |
-
@keyframes floatHomeSpore {
|
| 256 |
-
0% { transform:translate(0,0) scale(1); opacity:0; }
|
| 257 |
-
15% { opacity:1; }
|
| 258 |
-
80% { opacity:0.7; }
|
| 259 |
-
100% { transform:translate(var(--x),var(--y)) scale(0.2); opacity:0; }
|
| 260 |
-
}
|
| 261 |
-
</style>
|
| 262 |
-
<div class="home-sky">
|
| 263 |
-
<span class="home-milky-way">🌌</span>
|
| 264 |
-
<span class="home-star star-one">✨</span>
|
| 265 |
-
<span class="home-star star-two">✦</span>
|
| 266 |
-
<span class="home-star star-three">✧</span>
|
| 267 |
-
</div>
|
| 268 |
-
<div class="home-forest-floor">
|
| 269 |
-
<span class="home-tree left">🌲</span>
|
| 270 |
-
<span class="home-grass grass-left">🌿</span>
|
| 271 |
-
<span class="home-myco-wrapper">
|
| 272 |
-
<span class="home-myco">🍄</span>
|
| 273 |
-
<span class="home-spore" style="--x:-25px;--y:-45px;--duration:3.5s;--delay:0s;"></span>
|
| 274 |
-
<span class="home-spore" style="--x:20px;--y:-35px;--duration:4.2s;--delay:0.8s;"></span>
|
| 275 |
-
<span class="home-spore" style="--x:-5px;--y:-60px;--duration:3s;--delay:1.5s;"></span>
|
| 276 |
-
</span>
|
| 277 |
-
<span class="home-brown-wrapper">
|
| 278 |
-
<span class="home-brown-mushroom">🍄🟫</span>
|
| 279 |
-
<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>
|
| 280 |
-
<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>
|
| 281 |
-
</span>
|
| 282 |
-
<span class="home-grass grass-right">🍃</span>
|
| 283 |
-
<span class="home-tree right">🌳</span>
|
| 284 |
-
<span class="home-moon">🌖</span>
|
| 285 |
-
</div>
|
| 286 |
-
<p><strong>The Milky Way is awake.</strong> Myco hears a brown mushroom whispering your first clue.</p>
|
| 287 |
-
<span class="home-whisper">"Tap Play. Let's find what the forest is hiding."</span>
|
| 288 |
-
</div>
|
| 289 |
-
"""
|
| 290 |
-
|
| 291 |
-
def play_hook_markdown() -> str:
|
| 292 |
-
return """
|
| 293 |
-
<div class="play-hook">
|
| 294 |
-
<p class="eyebrow">Mission</p>
|
| 295 |
-
<h2>Goal: follow clues until Myco remembers the Impossible Mushroom.</h2>
|
| 296 |
-
<div class="hook-steps">
|
| 297 |
-
<span>1 · Move through the forest</span>
|
| 298 |
-
<span>2 · Search Clearing — mushroom pops up!</span>
|
| 299 |
-
<span>3 · Myco reacts · Pick, Study, or Follow Whisper</span>
|
| 300 |
-
</div>
|
| 301 |
-
<p>Every clue advances one mystery. Follow whispers to reveal why the MycoDex is regrowing a forest that vanished.</p>
|
| 302 |
-
</div>
|
| 303 |
-
"""
|
| 304 |
-
|
| 305 |
-
def game_intro_markdown() -> str:
|
| 306 |
-
return """
|
| 307 |
-
<div style="text-align:center;margin-bottom:.75rem;">
|
| 308 |
-
<h3>🌲 The Deep Forest</h3>
|
| 309 |
-
<p style="color:#359635;font-size:.9em;">
|
| 310 |
-
You step into the glowing undergrowth. Myco hops off your shoulder, its cap faintly pulsing.<br>
|
| 311 |
-
Click <strong>Search Clearing</strong> — watch Myco run toward whatever pops up.
|
| 312 |
-
<span class="ai-tick-badge">🤖 Myco AI roaming</span>
|
| 313 |
-
</p>
|
| 314 |
-
</div>
|
| 315 |
-
"""
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
# ---------------------------------------------------------------------------
|
| 319 |
-
# Forest scene — main animated panel
|
| 320 |
-
# ---------------------------------------------------------------------------
|
| 321 |
-
|
| 322 |
-
def forest_scene(
|
| 323 |
-
current: "MushroomState | None",
|
| 324 |
-
collection: "CollectionState | None",
|
| 325 |
-
position: "tuple[int,int] | None" = None,
|
| 326 |
-
) -> str:
|
| 327 |
-
import time
|
| 328 |
-
count = len(_safe_collection(collection))
|
| 329 |
-
area = current_area(count)
|
| 330 |
-
pos_x, pos_y = _safe_position(position)
|
| 331 |
-
pos_cls = f"pos-{pos_x}-{pos_y}"
|
| 332 |
-
|
| 333 |
-
# Safe defaults
|
| 334 |
-
mushroom_name = "Hidden mushroom"
|
| 335 |
-
rarity = "common"
|
| 336 |
-
sprite = "✨"
|
| 337 |
-
status = "The moss is listening. Click Search Clearing to wake the forest."
|
| 338 |
-
omen = "Myco is waiting to sense the first spore."
|
| 339 |
-
event_label = "🌌 Dreaming Forest"
|
| 340 |
-
encounter = "The next clearing may reveal a memory, creature, or poison omen."
|
| 341 |
-
mystery_label = "The Wrong Memory"
|
| 342 |
-
score_total = "0"
|
| 343 |
-
health = "3"
|
| 344 |
-
reward_text = "Search a clearing to score spores."
|
| 345 |
-
state_cls = "state-waiting"
|
| 346 |
-
|
| 347 |
-
if current is not None:
|
| 348 |
-
mushroom_name = current.get("name", "Mystery cluster")
|
| 349 |
-
rarity = current.get("rarity", "Common").lower()
|
| 350 |
-
game_over = current.get("game_over") == "Yes"
|
| 351 |
-
|
| 352 |
-
event_label = f"{current.get('event_emoji','✨')} {current.get('event_title','Strange Omen')}"
|
| 353 |
-
encounter = current.get("encounter", "Go pick them up!")
|
| 354 |
-
mystery_label = current.get("mystery_title", "The Wrong Memory")
|
| 355 |
-
score_total = current.get("score_total", "0")
|
| 356 |
-
health = current.get("health", "3")
|
| 357 |
-
reward_text = current.get("reward_text", "Dodge the poison, collect the rest!")
|
| 358 |
-
state_cls = _scene_state_class(current)
|
| 359 |
-
|
| 360 |
-
if game_over:
|
| 361 |
-
status = "💀 GAME OVER: You stepped on a poisonous mushroom!"
|
| 362 |
-
omen = "Press Search Clearing to start a new run."
|
| 363 |
-
elif current.get("picked") == "Yes":
|
| 364 |
-
status = "✅ Area clear! All safe mushrooms collected."
|
| 365 |
-
omen = "Search the next clearing!"
|
| 366 |
-
else:
|
| 367 |
-
status = "Mushrooms sprouted! Move Myco over them to collect."
|
| 368 |
-
omen = "🍄 Watch your step!"
|
| 369 |
-
|
| 370 |
-
anim_key = str(int(time.time() * 1000))[-6:]
|
| 371 |
-
|
| 372 |
-
return f'''{SCENE_STYLES}
|
| 373 |
-
<div class="forest-scene rarity-{escape(rarity)} {pos_cls} {state_cls}" data-key="{anim_key}">
|
| 374 |
-
<div class="scene-hud">
|
| 375 |
-
<span>🧭 {escape(area.name)}</span>
|
| 376 |
-
<span>🎮 ({pos_x+1},{pos_y+1})</span>
|
| 377 |
-
<span>📖 {count} found</span>
|
| 378 |
-
<span>⭐ {escape(str(score_total))}</span>
|
| 379 |
-
<span>❤️ {escape(str(health))}/3</span>
|
| 380 |
-
<span>{escape(event_label)}</span>
|
| 381 |
-
<span>🧩 {escape(mystery_label)}</span>
|
| 382 |
-
</div>
|
| 383 |
-
|
| 384 |
-
<div class="scene-stage" aria-label="Magical forest scene">
|
| 385 |
-
<span class="tree tree-left">🌲</span>
|
| 386 |
-
<span class="tree tree-right">🌲</span>
|
| 387 |
-
<span class="grass grass-one">🌿</span>
|
| 388 |
-
<span class="grass grass-two">🍃</span>
|
| 389 |
-
<span class="myco-player" title="Myco">🍄</span>
|
| 390 |
-
<span class="mushroom-sprite" title="{escape(mushroom_name)}">{escape(sprite)}</span>
|
| 391 |
-
<span class="sparkle sparkle-one">✨</span>
|
| 392 |
-
<span class="sparkle sparkle-two">✦</span>
|
| 393 |
-
<span class="wood-log">🪵</span>
|
| 394 |
-
</div>
|
| 395 |
-
|
| 396 |
-
<div class="forest-map" aria-label="Forest grid">
|
| 397 |
-
{_movement_tiles(pos_x, pos_y, current)}
|
| 398 |
-
</div>
|
| 399 |
-
|
| 400 |
-
<div class="scene-caption">
|
| 401 |
-
<strong>{escape(str(status))}</strong>
|
| 402 |
-
<span>{escape(str(omen))}</span>
|
| 403 |
-
</div>
|
| 404 |
-
<div class="score-burst" aria-live="polite">{escape(str(reward_text))}</div>
|
| 405 |
-
<div class="story-moment">
|
| 406 |
-
<strong>{escape(str(event_label))}</strong>
|
| 407 |
-
<span>{escape(str(encounter))}</span>
|
| 408 |
-
</div>
|
| 409 |
-
</div>'''
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
# ---------------------------------------------------------------------------
|
| 413 |
-
# Helpers
|
| 414 |
-
# ---------------------------------------------------------------------------
|
| 415 |
-
|
| 416 |
-
def _safe_position(position):
|
| 417 |
-
if position is None:
|
| 418 |
-
return (1, 1)
|
| 419 |
-
try:
|
| 420 |
-
x, y = position
|
| 421 |
-
return (max(0, min(2, int(x))), max(0, min(2, int(y))))
|
| 422 |
-
except Exception:
|
| 423 |
-
return (1, 1)
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
def _movement_tiles(active_x, active_y, current=None):
|
| 427 |
-
"""Draws the grid, safely decoding strings to ensure mushrooms always show up!"""
|
| 428 |
-
mush_coords = {}
|
| 429 |
-
|
| 430 |
-
# --- DEFENSIVE PARSING ---
|
| 431 |
-
# If Gradio passed the state as a string, turn it back into a dictionary
|
| 432 |
-
if isinstance(current, str):
|
| 433 |
-
try:
|
| 434 |
-
current = json.loads(current)
|
| 435 |
-
except:
|
| 436 |
-
try:
|
| 437 |
-
current = ast.literal_eval(current)
|
| 438 |
-
except:
|
| 439 |
-
current = {} # Default to empty if parsing fails
|
| 440 |
-
|
| 441 |
-
# Map out where the mushrooms are
|
| 442 |
-
if current and isinstance(current, dict) and "active_mushrooms" in current:
|
| 443 |
-
for m in current["active_mushrooms"]:
|
| 444 |
-
# Ensure we are using ints for coordinates, just in case
|
| 445 |
-
if m.get("picked") == "No" or m.get("picked") is False:
|
| 446 |
-
mx, my = int(m["mush_x"]), int(m["mush_y"])
|
| 447 |
-
mush_coords[(mx, my)] = m
|
| 448 |
-
|
| 449 |
-
tiles = []
|
| 450 |
-
for y in range(3):
|
| 451 |
-
for x in range(3):
|
| 452 |
-
# Ensure player coordinates are ints
|
| 453 |
-
is_player = (int(x), int(y)) == (int(active_x), int(active_y))
|
| 454 |
-
is_mush = (int(x), int(y)) in mush_coords
|
| 455 |
-
|
| 456 |
-
active_class = " active" if is_player else ""
|
| 457 |
-
|
| 458 |
-
if is_player:
|
| 459 |
-
icon = "🍄" # Myco
|
| 460 |
-
elif is_mush:
|
| 461 |
-
# If it's a mushroom, show poison skull or magic sparkles
|
| 462 |
-
m_data = mush_coords[(int(x), int(y))]
|
| 463 |
-
icon = "☠️" if m_data.get("poison") == "Yes" else "✨"
|
| 464 |
-
else:
|
| 465 |
-
icon = "·" # Empty grass
|
| 466 |
-
|
| 467 |
-
tiles.append(f'<span class="map-tile{active_class}">{icon}</span>')
|
| 468 |
-
|
| 469 |
-
return "".join(tiles)
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
def _scene_state_class(current):
|
| 473 |
-
if not current:
|
| 474 |
-
return "state-waiting"
|
| 475 |
-
if current.get("game_over") == "Yes":
|
| 476 |
-
return "state-game-over"
|
| 477 |
-
if current.get("picked") == "Yes":
|
| 478 |
-
return "state-picked"
|
| 479 |
-
if current.get("poison") == "Yes":
|
| 480 |
-
return "state-danger"
|
| 481 |
-
return "state-discovered"
|
| 482 |
-
|
| 483 |
-
def _safe_collection(collection):
|
| 484 |
-
return list(collection or [])
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
# ---------------------------------------------------------------------------
|
| 488 |
-
# Game status panel
|
| 489 |
-
# ---------------------------------------------------------------------------
|
| 490 |
-
|
| 491 |
-
def game_status_markdown(
|
| 492 |
-
current: "MushroomState | None",
|
| 493 |
-
collection: "CollectionState | None",
|
| 494 |
-
position: "tuple[int,int] | None" = None,
|
| 495 |
-
) -> str:
|
| 496 |
-
count = len(_safe_collection(collection))
|
| 497 |
-
pos_x, pos_y = _safe_position(position)
|
| 498 |
-
active_mystery = next_mystery(count)
|
| 499 |
-
mystery_line = _mystery_goal_line(active_mystery, count)
|
| 500 |
-
|
| 501 |
-
if current is None:
|
| 502 |
-
objective = "Goal: solve **one mystery** — why does Myco remember a vanished forest?"
|
| 503 |
-
discovery = "No active discovery yet. Move Myco, then Search Clearing."
|
| 504 |
-
ai_job = "Myco AI is roaming — watch it search, study, and collect automatically."
|
| 505 |
-
event = "**Event:** waiting for the first omen"
|
| 506 |
-
clue_line = mystery_line
|
| 507 |
-
thread_line = "**Mystery thread:** The Wrong Memory — search the first clearing."
|
| 508 |
-
bloom_line = f"**Impossible bloom:** {_mystery_bloom_meter(count)}"
|
| 509 |
-
score_line = "**Score:** 0 spores · **Health:** 3/3"
|
| 510 |
-
else:
|
| 511 |
-
name = current.get("name", "Mystery mushroom")
|
| 512 |
-
studied = current.get("studied") == "Yes"
|
| 513 |
-
game_over = current.get("game_over") == "Yes"
|
| 514 |
-
picked = current.get("picked") == "Yes"
|
| 515 |
-
|
| 516 |
-
if game_over:
|
| 517 |
-
objective = "**💀 GAME OVER** — press **Search Clearing** to start a new run."
|
| 518 |
-
elif picked:
|
| 519 |
-
objective = "Current objective: **move to the next clearing and search again**."
|
| 520 |
-
else:
|
| 521 |
-
next_step = "Collect it to preserve the clue" if studied else "Study it, then Collect or Pick"
|
| 522 |
-
objective = f"Current objective: **{next_step}**."
|
| 523 |
-
|
| 524 |
-
bloom_line = f"**Impossible bloom:** {_mystery_bloom_meter(count)}"
|
| 525 |
-
score_line = (
|
| 526 |
-
f"**Score:** {current.get('score_total','0')} spores · "
|
| 527 |
-
f"**Health:** {current.get('health','3')}/3 · "
|
| 528 |
-
f"**Last:** {current.get('reward_text','—')}"
|
| 529 |
-
)
|
| 530 |
-
discovery = f"Active discovery: **{name}** · Rarity: **{current.get('rarity','Unknown')}**"
|
| 531 |
-
event = f"**Event:** {current.get('event_emoji','✨')} {current.get('event_title','Strange Omen')}"
|
| 532 |
-
clue_line = f"**Clue:** {current.get('clue','Myco is still listening.')}"
|
| 533 |
-
thread_line = (
|
| 534 |
-
f"**Mystery thread:** {current.get('mystery_title','The Wrong Memory')} — "
|
| 535 |
-
f"Next: {current.get('mystery_next','follow the whisper.')}"
|
| 536 |
-
)
|
| 537 |
-
ai_job = "Myco AI is watching — it will study and collect automatically while you explore."
|
| 538 |
-
|
| 539 |
-
return "\n".join((
|
| 540 |
-
"### 🎮 Quest Log",
|
| 541 |
-
f"**MycoDex:** {count} discoveries · **Map:** ({pos_x+1},{pos_y+1})",
|
| 542 |
-
discovery, objective, score_line, bloom_line,
|
| 543 |
-
event, clue_line, thread_line, mystery_line, ai_job,
|
| 544 |
-
))
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
# ---------------------------------------------------------------------------
|
| 548 |
-
# Mushroom card
|
| 549 |
-
# ---------------------------------------------------------------------------
|
| 550 |
-
|
| 551 |
-
def mushroom_card(
|
| 552 |
-
mushroom: "Mushroom | None",
|
| 553 |
-
clue: "str | None" = None,
|
| 554 |
-
secret: "str | None" = None,
|
| 555 |
-
state: "MushroomState | None" = None,
|
| 556 |
-
) -> str:
|
| 557 |
-
if mushroom is None:
|
| 558 |
-
return "### 🌲 Forest Clearing\nClick **Search Clearing** — watch Myco run toward the mushroom!"
|
| 559 |
-
s = state or {}
|
| 560 |
-
poison = mushroom.name in POISONOUS_MUSHROOM_NAMES
|
| 561 |
-
warning = "\n> ⚠️ **Myco senses danger — do NOT pick this without studying first!**" if poison else ""
|
| 562 |
-
return "\n".join((
|
| 563 |
-
f"### 🍄🟫 {mushroom.name}",
|
| 564 |
-
f"**{mushroom.rarity} discovery** · found near **{mushroom.habitat}**",
|
| 565 |
-
f"> {mushroom.lore}",
|
| 566 |
-
warning,
|
| 567 |
-
"",
|
| 568 |
-
f"* **Edible:** {mushroom.edible}",
|
| 569 |
-
f"* **Magic:** {mushroom.magic}",
|
| 570 |
-
f"* **Danger:** {mushroom.danger}",
|
| 571 |
-
f"* **Value:** {_mushroom_value_label(mushroom.rarity)}",
|
| 572 |
-
f"* **Poison risk:** {_poison_risk_label(mushroom.name)}",
|
| 573 |
-
f"* **Event:** {s.get('event_emoji','✨')} {s.get('event_title','Strange Omen')}",
|
| 574 |
-
f"* **Mystery:** {s.get('mystery_title','The Wrong Memory')}",
|
| 575 |
-
f"* **Clue:** {clue or 'Study with Myco to reveal the clue.'}",
|
| 576 |
-
f"* **Secret:** {secret or 'Collect it to see how the MycoDex reacts.'}",
|
| 577 |
-
f"* **Status:** {'⚠️ POISONOUS' if poison else '✅ Appears safe'}",
|
| 578 |
-
))
|
| 579 |
-
|
| 580 |
-
def _mushroom_value_label(rarity):
|
| 581 |
-
return {"Legendary": "100 spores", "Rare": "35 spores"}.get(rarity, "10 spores")
|
| 582 |
-
|
| 583 |
-
def _poison_risk_label(name):
|
| 584 |
-
return "⚠️ POISONOUS — Game Over if picked!" if name in POISONOUS_MUSHROOM_NAMES else "No obvious poison"
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
# ---------------------------------------------------------------------------
|
| 588 |
-
# World map
|
| 589 |
-
# ---------------------------------------------------------------------------
|
| 590 |
-
|
| 591 |
-
def world_map_markdown(collection: "CollectionState | None") -> str:
|
| 592 |
-
count = len(_safe_collection(collection))
|
| 593 |
-
cards = []
|
| 594 |
-
for area in AREA_UNLOCKS:
|
| 595 |
-
unlocked = count >= area.required_discoveries
|
| 596 |
-
state = "unlocked" if unlocked else "locked"
|
| 597 |
-
badge = "✅ Unlocked" if unlocked else f"🔒 {area.required_discoveries - count} away"
|
| 598 |
-
cards.append("\n".join((
|
| 599 |
-
f'<div class="map-card {state}">',
|
| 600 |
-
' <div class="map-card-top">',
|
| 601 |
-
f' <strong>{escape(area.name)}</strong><span>{badge}</span>',
|
| 602 |
-
' </div>',
|
| 603 |
-
f' <p>{escape(area.description)}</p>',
|
| 604 |
-
'</div>',
|
| 605 |
-
)))
|
| 606 |
-
secrets = tuple(_secret_badge(t.name, count >= t.required_discoveries) for t in revealed_mysteries(count))
|
| 607 |
-
if not secrets:
|
| 608 |
-
secrets = (_secret_badge("First Spore", False),)
|
| 609 |
-
return "\n".join((
|
| 610 |
-
'<div class="world-map-panel">',
|
| 611 |
-
' <div class="world-map-header"><p class="eyebrow">World Map</p>',
|
| 612 |
-
' <h2>Forest Atlas</h2>',
|
| 613 |
-
f' <p>{count} discoveries logged.</p></div>',
|
| 614 |
-
f' <div class="achievement-row">{"".join(secrets)}</div>',
|
| 615 |
-
f' <div class="map-grid">{"".join(cards)}</div>',
|
| 616 |
-
'</div>',
|
| 617 |
-
))
|
| 618 |
-
|
| 619 |
-
def _secret_badge(label, unlocked):
|
| 620 |
-
state = "earned" if unlocked else "pending"
|
| 621 |
-
icon = "🔎" if unlocked else "◇"
|
| 622 |
-
return f'<span class="achievement {state}">{icon} {escape(label)}</span>'
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
# ---------------------------------------------------------------------------
|
| 626 |
-
# MycoDex
|
| 627 |
-
# ---------------------------------------------------------------------------
|
| 628 |
-
|
| 629 |
-
def dex_markdown(collection: "CollectionState | None") -> str:
|
| 630 |
-
current_collection = _safe_collection(collection)
|
| 631 |
-
if not current_collection:
|
| 632 |
-
return EMPTY_DEX
|
| 633 |
-
rows = ["### 📖 MycoDex", ""]
|
| 634 |
-
for entry in current_collection:
|
| 635 |
-
poison_flag = " ☠️" if entry.get("poison") == "Yes" else ""
|
| 636 |
-
rows.append(
|
| 637 |
-
f"* 🍄 **{entry['name']}**{poison_flag} — {entry['rarity']} — "
|
| 638 |
-
f"{entry['habitat']} — clue: {entry.get('clue','not studied yet')}"
|
| 639 |
-
)
|
| 640 |
-
return "\n".join(rows)
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
# ---------------------------------------------------------------------------
|
| 644 |
-
# Progress
|
| 645 |
-
# ---------------------------------------------------------------------------
|
| 646 |
-
|
| 647 |
-
def progress_markdown(collection: "CollectionState | None") -> str:
|
| 648 |
-
count = len(_safe_collection(collection))
|
| 649 |
-
active_area = current_area(count)
|
| 650 |
-
upcoming_area = next_area(count)
|
| 651 |
-
unlocked_names = ", ".join(a.name for a in unlocked_areas(count))
|
| 652 |
-
rows = [
|
| 653 |
-
"### ✨ Forest Progress",
|
| 654 |
-
f"**MycoDex discoveries:** {count}",
|
| 655 |
-
f"**Current area:** {active_area.name} — {active_area.description}",
|
| 656 |
-
f"**Unlocked:** {unlocked_names}",
|
| 657 |
-
]
|
| 658 |
-
upcoming_mystery = next_mystery(count)
|
| 659 |
-
if upcoming_area:
|
| 660 |
-
rows.append(f"**Next area:** {upcoming_area.required_discoveries - count} more to unlock **{upcoming_area.name}**.")
|
| 661 |
-
else:
|
| 662 |
-
rows.append(f"**All areas visible.** You reached **{AREA_UNLOCKS[-1].name}**.")
|
| 663 |
-
if upcoming_mystery:
|
| 664 |
-
rows.append(f"**Next secret:** {upcoming_mystery.required_discoveries - count} more to reveal **{upcoming_mystery.name}**.")
|
| 665 |
-
else:
|
| 666 |
-
rows.append("**All current secrets found.** Myco thinks the Elder is watching kindly.")
|
| 667 |
-
return "\n\n".join(rows)
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
# ---------------------------------------------------------------------------
|
| 671 |
-
# Helpers
|
| 672 |
-
# ---------------------------------------------------------------------------
|
| 673 |
-
|
| 674 |
-
def _mystery_goal_line(active_mystery, count):
|
| 675 |
-
if active_mystery is None:
|
| 676 |
-
return "**Mystery:** Every known clue is awake. Keep exploring for rare surprises."
|
| 677 |
-
remaining = active_mystery.required_discoveries - count
|
| 678 |
-
return f"**Mystery:** {remaining} clue discoveries until **{active_mystery.name}** — {active_mystery.clue}"
|
| 679 |
-
|
| 680 |
-
def _mystery_bloom_meter(count):
|
| 681 |
-
filled = max(0, min(5, count))
|
| 682 |
-
petals = "".join("🌕" if i < filled else "🌑" for i in range(5))
|
| 683 |
-
if filled >= 5:
|
| 684 |
-
return f"{petals} Impossible Bloom visible"
|
| 685 |
-
return f"{petals} {5 - filled} clue-whispers remain"
|
| 686 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|