Download backend/render.py from build-small-hackathon/mumbai-local: direct link, hf CLI and curl.
- Browser
- Download file 5.34 kB
-
https://huggingface.co/spaces/build-small-hackathon/mumbai-local/resolve/main/backend/render.py
- Command line
-
hf download hf://spaces/build-small-hackathon/mumbai-local/backend/render.py
-
curl -L -o render.py https://huggingface.co/spaces/build-small-hackathon/mumbai-local/resolve/main/backend/render.py
5.34 kB
| """ | |
| render.py β turn engine state into the terse prompt strings the templates expect. Pure view | |
| layer: reads state + the turn's legal actions, returns strings. No model calls, no rules | |
| decisions. Keeps the LLM contract in one place so prompt tweaks don't leak into the engine. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from engine import STATIONS, MAJORS, INTERCHANGES, CARDS, B | |
| from rules import legal_cards, card_available | |
| PROMPT_DIR = os.path.join(os.path.dirname(__file__), "prompts") | |
| def load_prompts(prompt_dir=PROMPT_DIR): | |
| # MLP_DISPATCHER_SYSTEM lets a run swap in a variant system prompt without editing the file. | |
| disp_sys = os.environ.get("MLP_DISPATCHER_SYSTEM", "dispatcher_system.txt") | |
| def rd(name): | |
| path = name if os.path.isabs(name) else os.path.join(prompt_dir, name) | |
| with open(path, encoding="utf-8") as f: | |
| return f.read() | |
| return { | |
| "dispatcher_system": rd(disp_sys), | |
| "dispatcher_user": rd("dispatcher_user_template.txt"), | |
| "chaos_system": rd("chaos_system.txt"), | |
| "chaos_user": rd("chaos_user_template.txt"), | |
| } | |
| def _dir(d): | |
| return "DOWN" if d == +1 else "UP" # +1 increases index toward Virar = DOWN | |
| def _fill(template, mapping): | |
| out = template | |
| for k, v in mapping.items(): | |
| out = out.replace("{{" + k + "}}", str(v)) | |
| return "\n".join(l for l in out.splitlines() if not l.lstrip().startswith("#")) | |
| # ---------------------------------------------------------------- action description | |
| def describe_action(a): | |
| t = a["type"] | |
| if t.startswith("deploy_"): | |
| return f"{t} {a['unit']} -> {a['to']} ({a['target']}, resolves in {a['resolves_in']}t)" | |
| if t == "move_train": | |
| return f"move_train {a['train']} -> {a['to']}" | |
| if t == "hold_train": | |
| return f"hold_train {a['train']} @{a['at']}" | |
| if t in ("switch_to_fast", "switch_to_slow"): | |
| return f"{t} {a['train']} @{a['at']}" | |
| if t == "prioritize_train": | |
| return f"prioritize_train {a['train']}" | |
| if t == "clear_platform_priority": | |
| return f"clear_platform_priority @{a['at']}" | |
| if t == "issue_announcement": | |
| return f"issue_announcement {a.get('scope', 'line')}-wide" | |
| extra = " ".join(f"{k}={v}" for k, v in a.items() if k not in ("action_id", "type")) | |
| return f"{t} {extra}".rstrip() | |
| # ---------------------------------------------------------------- blocks | |
| def _stations_block(g): | |
| out = [] | |
| for s in g.stations: | |
| pct = s.pct() | |
| flag = "!!" if pct > 100 else "!" if pct > 85 else "" | |
| tag = " [MAJOR]" if s.name in MAJORS else "" | |
| out.append(f"- {s.name}{tag} {int(s.crowd)}/{s.cap} ({pct:.0f}%{flag})") | |
| return "\n".join(out) | |
| def _incidents_block(g): | |
| if not g.incidents: | |
| return "- none" | |
| return "\n".join(f"- [{i.itype}] {i.location} {i.track} sev{i.severity} age{i.age}t" | |
| + (" (unit en route)" if i.assigned else "") for i in g.incidents) | |
| def _trains_block(g): | |
| return "\n".join(f'- {t.id} "{t.name}" [{t.ttype}] @{STATIONS[t.pos]} {_dir(t.direction)} {t.track} ' | |
| f'delay+{t.delay} ' + ("held" if t.held else (f"stuck{t.stuck_turns}t" if t.stuck_turns else "running")) | |
| for t in g.trains) | |
| def _personnel_block(g): | |
| return "\n".join(f"- {u.id} {u.utype} @{STATIONS[u.loc]} {'busy' if u.busy else 'available'} " | |
| f"used {u.used}/{B['personnel_max']}" for u in g.units) | |
| def _legal_actions_block(A): | |
| return "\n".join(f"{a['action_id']} {describe_action(a)}" for a in A) | |
| def _legal_cards_block(g): | |
| cards = legal_cards(g) | |
| if not cards: | |
| return "- (no playable cards β hoard energy)" | |
| return "\n".join(f"{c['card']} cost {c['cost']}" for c in cards) | |
| def _peak_banner(g): | |
| if g.phase != "peak": | |
| return "" | |
| return (">>> PEAK RUSH (the escalator): inflow +40%, anger growth +30%, a SIGNAL FAILURE now " | |
| "blocks ALL tracks, TRAIN TROUBLE card unlocks (+2 trains), monsoon flood drops to 8, " | |
| "rain won't quit <<<") | |
| # ---------------------------------------------------------------- public renderers | |
| def render_dispatcher_user(template, g, A): | |
| return _fill(template, { | |
| "turn": g.turn + 1, "total_turns": B["turns_to_win"], "phase": g.phase, | |
| "peak_rush_banner": _peak_banner(g), | |
| "score": f"{g.score:.0f}", "delay": f"{g.delay:.0f}", "safety": f"{g.safety:.0f}", | |
| "anger": f"{g.anger:.0f}", "pressure": f"{g.pressure:.0f}", "patience": f"{g.patience:.0f}", | |
| "incidents_block": _incidents_block(g), "stations_block": _stations_block(g), | |
| "trains_block": _trains_block(g), "personnel_block": _personnel_block(g), | |
| "legal_actions_block": _legal_actions_block(A), | |
| }) | |
| def render_chaos_user(template, g): | |
| return _fill(template, { | |
| "turn": g.turn + 1, "total_turns": B["turns_to_win"], "phase": g.phase, | |
| "peak_rush_banner": _peak_banner(g), | |
| "safety": f"{g.safety:.0f}", "anger": f"{g.anger:.0f}", "pressure": f"{g.pressure:.0f}", | |
| "delay": f"{g.delay:.0f}", "stations_block": _stations_block(g), | |
| "incidents_block": _incidents_block(g), "energy": g.energy, | |
| "regen": B["chaos_energy_regen"], "legal_cards_block": _legal_cards_block(g), | |
| }) | |