Files
SimpleFarm/tools/preview_tui.py
T
snowy-yang d7a7107b0a chore: 引入 pyright 类型检查清零 + README 工程化章节
pyright(basic 模式)从 15 errors / 10 warnings 降到 0/0:

- pyproject: pythonPlatform="All"(termios/msvcrt 双平台分支不再误报),
  reportUnsupportedDunderAll=false(__init__ 惰性导出属正常写法)
- keys.py: 捕获 sys.stdin 局部变量 + 提前返回,消除 fileno() 的 None 收窄告警
- server/app.py: lifespan 改用 @asynccontextmanager 并标注 AsyncIterator[None]
- server/actions.py: 显式标注 result: dict[str, Any],消除 isinstance 冗余报错
- server/__main__.py: 用 importlib.util.find_spec 探测 websockets
- tests/test_server.py: 同上,uvicorn 探测改为 find_spec
- tools/preview_tui.py: TYPE_CHECKING 下引入 RemoteFarm,修正返回类型标注
- farm/tui.py: 取消未使用变量,统一 round/取整写法

其他:
- README: 新增第 11 节「工程化:uv + ruff + pyright」,快速开始全部改用
  uv sync / uv run;原第 11 节起顺延为 12
- requirements.txt: 由 uv export --no-dev --no-hashes 重新生成(锁定版本)
- 清理临时日志文件

验收:ruff check 全通过 / ruff format 26 文件无改动 / pyright 0 错误 /
unittest 97 项全部通过
2026-09-18 21:24:30 +08:00

148 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""把 TUI 渲染结果输出到终端,用于检查排版与配色。
不进入交互循环,也不读取键盘——只是把某一时刻的完整画面画出来。
用法:
python tools/preview_tui.py # 使用默认的中等规模农场(本地引擎)
python tools/preview_tui.py --width 100 # 指定终端宽度
python tools/preview_tui.py --color # 输出带 ANSI 颜色
python tools/preview_tui.py --scene empty # 初始状态
python tools/preview_tui.py --scene rich # 后期大农场
python tools/preview_tui.py --scene menu # 播种菜单
python tools/preview_tui.py --url http://127.0.0.1:8765 --farm alice
# 渲染**真实服务器**上的牧场
"""
from __future__ import annotations
import argparse
import contextlib
import io
import os
import random
import re
import sys
from typing import TYPE_CHECKING
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from farm.engine import FarmEngine
from farm.tui import TuiApp
if TYPE_CHECKING: # 只在类型检查时导入,运行时不引入依赖
from farm.client import RemoteFarm
SCENES = ("empty", "mixed", "rich", "menu", "help")
def build_remote(url: str, farm: str, token: str | None) -> tuple[RemoteFarm, TuiApp]:
"""连到真实服务器,取一份现状来渲染。"""
from farm.client import RemoteFarm
client = RemoteFarm(endpoint=url, farm=farm, token=token, realtime=False)
app = TuiApp(
client, use_color=True, autosave=False, server_label=f"{client.farm} @ {client.host}"
)
return client, app
def build_engine(scene: str) -> tuple[FarmEngine, TuiApp]:
random.seed(42)
engine = FarmEngine(tick_seconds=15)
app = TuiApp(engine, use_color=True, autosave=False)
if scene == "empty":
return engine, app
# ---- 制造一个"进行中"的农场
engine.state.level = 2
engine.state.gold = 340
engine.state.feed = 4
engine.plant(1, "wheat")
engine.water(1)
engine.plant(2, "carrot")
engine.plant(3, "wheat")
engine.plant(4, "carrot")
engine.advance_day()
engine.advance_day()
engine.buy_animal("chicken")
engine.feed(1)
if scene == "rich":
engine.state.level = 4
engine.state.gold = 2400
engine.state.feed = 22
for _ in range(2):
engine.state.gold = 99999
engine.buy_plot()
engine.plant(5, "corn")
engine.plant(6, "tomato")
engine.water(5)
engine.state.gold = 2400
engine.buy_animal("sheep")
engine.buy_animal("cow")
engine.advance_day()
engine.advance_day()
engine.add_log("收获 2 份小麦(丰收!),+31 金币", "good")
engine.add_log("升级到 Lv.4!奖励 240 金币,解锁:南瓜", "good")
if scene == "menu":
# 找第一块空地,把播种菜单打开
empty = next((p.index for p in engine.state.plots if p.is_empty), 0)
app.plot_cursor = empty
app._open_crop_menu()
if scene == "help":
app.mode = "help"
return engine, app
def main() -> int:
parser = argparse.ArgumentParser(description="渲染一帧 TUI 画面")
parser.add_argument("--width", type=int, default=112, help="终端宽度")
parser.add_argument("--height", type=int, default=32, help="终端高度")
parser.add_argument("--scene", choices=SCENES, default="mixed", help="场景")
parser.add_argument("--color", action="store_true", help="保留 ANSI 颜色")
parser.add_argument(
"--url", default=None, help="连到真实服务器渲染(例如 http://127.0.0.1:8765)"
)
parser.add_argument("--farm", default="default", help="--url 模式下要看的牧场")
parser.add_argument("--token", default=None, help="--url 模式下的访问令牌")
args = parser.parse_args()
os.environ["COLUMNS"] = str(args.width)
os.environ["LINES"] = str(args.height)
if args.url:
engine, app = build_remote(args.url, args.farm, args.token)
state = engine.snapshot()
print(
f"# 来自真实服务器 {args.url} · 牧场 {args.farm} · 第 {state.get('day')} 天 · "
f"金币 {state.get('gold')} · 在线 {state.get('online')}\n"
)
else:
engine, app = build_engine(args.scene)
# 预览脚本没有跑 Scheduler,这里手动给个倒计时,让画面更接近真实运行状态
if engine.next_tick_at is None and not engine.paused:
import time as _time
engine.next_tick_at = _time.time() + 8.4
app.use_color = args.color
buffer = io.StringIO()
with contextlib.redirect_stdout(buffer):
app._render()
output = buffer.getvalue()
# 去掉清屏、光标控制与行尾清理符,预览里只保留可见内容
output = re.sub(r"\x1b\[[0-9;?]*[A-Za-z]", "", output)
print(output)
return 0
if __name__ == "__main__":
sys.exit(main())