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 项全部通过
148 lines
5.0 KiB
Python
148 lines
5.0 KiB
Python
#!/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())
|