Files
SimpleFarm/tools/preview_tui.py
T
snowy-yang fcd4cb4649 style: 引入 ruff,474 项告警清零
配置见 pyproject.toml:
- line-length=100:贴着现有代码定。若用默认的 88 会重排 122 行,
  噪音淹没真正有价值的修复。
- 启用 E/W/F/I/UP/B/C4/SIM/RET/PERF/RUF;**不**启用 PTH。因为本项目的路径
  多是"字符串身份"——要塞进 subprocess 命令行、写进 JSON 存档、打印给用户,
  全换 pathlib 只是包一层再拆一层,换来 70 处纯风格改动。
- 忽略 RUF001/002/003:中文全角标点是正确写法,不是"歧义字符"。
- 忽略 PERF203:剩下两处都是"逐项容错"语义(逐个牧场存档要立刻上报失败、
  收获全部要跳过失败地块)。把 try 提到循环外会毁掉"一项失败不影响其它项"
  这个行为,是错的优化。

自动修复 358 项:Dict/List/Optional 换成 PEP 585/604 写法、导入排序、
失效的 noqa、空占位 f-string 等。

手工修复的(这些是真问题,不是风格偏好):
- B904 x9:except 中 raise 补异常链。刻意区分——网络/IO 失败用 from exc 保留
  根因;参数解析失败用 from None,因为底层 ValueError 对玩家只是噪音。
- RUF012 x2:StateView 的 plots/animals 原本是**类级共享可变列表**,
  改成在 __init__ 里初始化(apply({}) 会提前返回,所以必须显式摆好)。
- F841:TUI 里一个赋值后再没用过的死变量。
- SIM105 x11:try/except/pass 统一改成 contextlib.suppress。
- SIM115:日志句柄改用 with。Popen 已把 fd 复制给子进程,父进程可安全关闭。
- E501 x7:拆超长行;main.py 那处顺手提取变量,避免超长嵌套表达式。
- E741:把 l/r 改成 left_line/right_line。
- 其余:RUF046 / PERF401 / RET504 / RUF005 / UP031 / B007。

ruff format 重排 24 个文件。

验证:97 项测试全绿。
2026-09-18 21:18:22 +08:00

144 lines
4.8 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
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from farm.engine import FarmEngine
from farm.tui import TuiApp
SCENES = ("empty", "mixed", "rich", "menu", "help")
def build_remote(url: str, farm: str, token: str | None) -> tuple[object, 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())