docker000 / app.py
seachen's picture
Deploy Quiet Town Corner Shop Docker demo (Gradio + Python sim)
c18529b verified
Raw
History Blame Contribute Delete
6.35 kB
"""Hugging Face Docker Space demo for Quiet Town Corner Shop.
Interactive Gradio shell over the pure-Python shop simulation.
"""
from __future__ import annotations
import json
import os
from pathlib import Path
from typing import Any
import gradio as gr
# Project root is the Space root (Dockerfile WORKDIR)
ROOT = Path(__file__).resolve().parent
os.chdir(ROOT)
from game.sim.day_cycle import Pace # noqa: E402
from game.sim.session import GameSession # noqa: E402
PACE_MAP = {
"Relaxed": Pace.RELAXED,
"Normal": Pace.NORMAL,
"Brisk": Pace.BRISK,
}
HERO_CANDIDATES = [
ROOT / "assets/shop/interior_alley_flower_shop_01.jpg",
ROOT / "assets/shop/interior_grad_v1.jpg",
ROOT / "assets/shop/interior_morning_grad_v1.jpg",
ROOT / "assets/shop/exterior_dusk_grad_v1.jpg",
]
CHARACTER_CANDIDATES = [
ROOT / "assets/characters/regular_lan_portrait_01.jpg",
ROOT / "assets/characters/regular_mori_portrait_01.jpg",
ROOT / "assets/characters/regular_lan_grad_v1.jpg",
]
def _pick_images(paths: list[Path], limit: int = 4) -> list[str]:
out: list[str] = []
for p in paths:
if p.is_file():
out.append(str(p))
if len(out) >= limit:
break
if len(out) < limit:
shop = ROOT / "assets" / "shop"
if shop.is_dir():
for p in sorted(shop.glob("*.jpg"))[: limit - len(out)]:
out.append(str(p))
return out
def _regular_visits_nonzero(raw: dict[str, Any] | None) -> dict[str, int]:
if not raw:
return {}
return {k: int(v) for k, v in raw.items() if v}
def run_one_day(seed: int, pace_name: str) -> tuple[str, str, list[str]]:
pace = PACE_MAP.get(pace_name, Pace.NORMAL)
session = GameSession(rng_seed=int(seed), pace=pace)
report = session.run_until_night()
summary = session.night_summary()
payload = {
"title": "Quiet Town Corner Shop · one-day sim",
"seed": int(seed),
"pace": pace_name,
"day_report": {
"day_index": report.day_index,
"enters": report.enters,
"sales": report.sales,
"revenue": report.revenue,
"wallet_end": report.wallet_end,
"mood_line": report.mood_line,
"phase_log": list(report.phase_log or []),
"regular_visits": _regular_visits_nonzero(getattr(report, "regular_visits", None)),
},
"night_summary": summary,
}
pretty = json.dumps(payload, ensure_ascii=False, indent=2)
card = (
f"### Day result\n"
f"- **Wallet**: {report.wallet_end}\n"
f"- **Enters / sales / revenue**: {report.enters} / {report.sales} / {report.revenue}\n"
f"- **Mood**: {report.mood_line}\n"
f"- **Diary**: {summary.get('diary', '')}\n"
f"- **Top atmosphere**: {', '.join(summary.get('top_dims') or [])}\n"
)
gallery = _pick_images(HERO_CANDIDATES + CHARACTER_CANDIDATES, limit=6)
return card, pretty, gallery
def run_multiday(seed: int, pace_name: str, days: int) -> tuple[str, str, list[str]]:
pace = PACE_MAP.get(pace_name, Pace.BRISK)
days = max(1, min(int(days), 7))
session = GameSession(rng_seed=int(seed), pace=pace)
rows: list[dict[str, Any]] = []
for _ in range(days):
if hasattr(session, "restock"):
try:
session.restock("tulip_pink", 2)
except Exception:
pass
report = session.run_until_night()
summary = session.night_summary()
rows.append(
{
"day": report.day_index,
"enters": report.enters,
"sales": report.sales,
"revenue": report.revenue,
"wallet_end": report.wallet_end,
"mood": report.mood_line,
"diary": summary.get("diary"),
}
)
if hasattr(session, "sleep"):
session.sleep()
pretty = json.dumps(
{"title": "Multi-day wallet curve", "seed": int(seed), "pace": pace_name, "days": rows},
ensure_ascii=False,
indent=2,
)
lines = ["### Multi-day summary"]
for r in rows:
lines.append(
f"- Day {r['day']}: wallet **{r['wallet_end']}**, "
f"sales {r['sales']}, revenue {r['revenue']}{r['mood']}"
)
gallery = _pick_images(HERO_CANDIDATES + CHARACTER_CANDIDATES, limit=6)
return "\n".join(lines), pretty, gallery
INTRO = """
# 小镇角落小店 · Quiet Town Corner Shop
Graduation-project **cozy flower-shop sim** (Python pure logic + content JSON + Imagine art).
This Space is a **portfolio demo**: run the day-cycle simulation in the browser.
Full source: [github.com/xiaoqianran/web005](https://github.com/xiaoqianran/web005)
| Layer | Notes |
|-------|--------|
| Sim | open → tidy → guests → atmosphere / stock → regulars |
| Content | JSON SKUs, NPCs, dialogue, tuning |
| Art | shop / product / furniture / character stills |
| Runtime | stdlib-first Python; Gradio shell for HF Docker Space |
"""
def build_ui() -> gr.Blocks:
with gr.Blocks(title="Quiet Town Corner Shop") as demo:
gr.Markdown(INTRO)
with gr.Row():
seed = gr.Number(value=7, label="RNG seed", precision=0)
pace = gr.Dropdown(choices=list(PACE_MAP.keys()), value="Brisk", label="Pace")
days = gr.Slider(1, 7, value=3, step=1, label="Days (multi-day)")
with gr.Row():
btn_day = gr.Button("Run one day", variant="primary")
btn_multi = gr.Button("Run multi-day curve")
card = gr.Markdown("Results will appear here.")
raw = gr.Code(language="json", label="JSON report")
gallery = gr.Gallery(label="Art samples", columns=3, height=360)
btn_day.click(run_one_day, inputs=[seed, pace], outputs=[card, raw, gallery])
btn_multi.click(run_multiday, inputs=[seed, pace, days], outputs=[card, raw, gallery])
# Warm first paint
demo.load(
fn=lambda: run_one_day(7, "Brisk"),
inputs=None,
outputs=[card, raw, gallery],
)
return demo
if __name__ == "__main__":
ui = build_ui()
# HF Docker Spaces expect the app on 7860
ui.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", "7860")))