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 项测试全绿。
This commit is contained in:
1 parent
c7c8a6308a
commit
fcd4cb4649
25 files changed
+1204
-794
No files matched your search
@@ -98,21 +98,31 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
# ---- 服务器
|
||||
p_serve = sub.add_parser("serve", help="启动游戏服务器(可后台运行)")
|
||||
p_serve.add_argument("--daemon", action="store_true", help="转到后台运行(守护进程)")
|
||||
p_serve.add_argument("--host", default="127.0.0.1",
|
||||
help="监听地址,默认 127.0.0.1;局域网联机用 0.0.0.0")
|
||||
p_serve.add_argument(
|
||||
"--host", default="127.0.0.1", help="监听地址,默认 127.0.0.1;局域网联机用 0.0.0.0"
|
||||
)
|
||||
p_serve.add_argument("--port", type=int, default=8765, help="监听端口,默认 8765")
|
||||
p_serve.add_argument("--token", default=None, help="访问令牌,设置后接口需要携带")
|
||||
p_serve.add_argument("--tick", type=float, default=config.DEFAULT_TICK_SECONDS,
|
||||
help=f"每多少秒推进一天,默认 {config.DEFAULT_TICK_SECONDS:.0f} 秒")
|
||||
p_serve.add_argument(
|
||||
"--tick",
|
||||
type=float,
|
||||
default=config.DEFAULT_TICK_SECONDS,
|
||||
help=f"每多少秒推进一天,默认 {config.DEFAULT_TICK_SECONDS:.0f} 秒",
|
||||
)
|
||||
p_serve.add_argument("--save-dir", default=None, help="存档目录,默认 <项目>/saves")
|
||||
p_serve.add_argument("--world", default="SimpleFarm 公共农场", help="世界名称")
|
||||
p_serve.add_argument("--max-sessions", type=int, default=64, help="同时在线的牧场上限")
|
||||
p_serve.add_argument("--idle-timeout", type=float, default=1800.0,
|
||||
help="无人访问多少秒后把牧场移出内存(存档保留)")
|
||||
p_serve.add_argument(
|
||||
"--idle-timeout",
|
||||
type=float,
|
||||
default=1800.0,
|
||||
help="无人访问多少秒后把牧场移出内存(存档保留)",
|
||||
)
|
||||
p_serve.add_argument("--no-remote-control", action="store_true", help="禁止接口停机")
|
||||
p_serve.add_argument("--access-log", action="store_true", help="打印每个 HTTP 请求")
|
||||
p_serve.add_argument("--log-level", default="info",
|
||||
choices=["critical", "error", "warning", "info", "debug"])
|
||||
p_serve.add_argument(
|
||||
"--log-level", default="info", choices=["critical", "error", "warning", "info", "debug"]
|
||||
)
|
||||
p_serve.add_argument("--log-file", default=None, help="守护进程日志路径")
|
||||
p_serve.add_argument("--pid-file", default=None, help="守护进程 pid 文件路径")
|
||||
|
||||
@@ -139,8 +149,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
p_play.add_argument("--token", default=None)
|
||||
p_play.add_argument("--name", default="", help="在服务器上显示的昵称")
|
||||
p_play.add_argument("--no-color", action="store_true")
|
||||
p_play.add_argument("--no-realtime", action="store_true",
|
||||
help="不用 WebSocket,改走 HTTP 轮询")
|
||||
p_play.add_argument("--no-realtime", action="store_true", help="不用 WebSocket,改走 HTTP 轮询")
|
||||
|
||||
p_solo = sub.add_parser("solo", help="离线单机模式(本地引擎 + 终端界面,不开服务器)")
|
||||
p_solo.add_argument("--tick", type=float, default=config.DEFAULT_TICK_SECONDS)
|
||||
@@ -150,8 +159,9 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
|
||||
p_do = sub.add_parser("do", help="执行一个动作(脚本/AI 用途)")
|
||||
p_do.add_argument("action", help="动作名:plant / water / harvest / ...")
|
||||
p_do.add_argument("params", nargs="?", default="{}",
|
||||
help='JSON 参数,例如 \'{"plot":1,"crop":"wheat"}\'')
|
||||
p_do.add_argument(
|
||||
"params", nargs="?", default="{}", help='JSON 参数,例如 \'{"plot":1,"crop":"wheat"}\''
|
||||
)
|
||||
_add_client_args(p_do)
|
||||
|
||||
p_batch = sub.add_parser("batch", help="批量执行动作(一个请求干多件事)")
|
||||
@@ -202,10 +212,12 @@ def make_client(args, realtime: bool = False):
|
||||
|
||||
try:
|
||||
return RemoteFarm(
|
||||
endpoint=args.url, farm=getattr(args, "farm", "default"),
|
||||
endpoint=args.url,
|
||||
farm=getattr(args, "farm", "default"),
|
||||
token=getattr(args, "token", None),
|
||||
name=getattr(args, "name", "") or "cli",
|
||||
realtime=realtime, autostart=True,
|
||||
realtime=realtime,
|
||||
autostart=True,
|
||||
)
|
||||
except GameError as exc:
|
||||
hint = (
|
||||
@@ -213,7 +225,7 @@ def make_client(args, realtime: bool = False):
|
||||
"先启动服务器:python main.py serve --daemon\n"
|
||||
"再确认地址端口:python main.py status"
|
||||
)
|
||||
raise SystemExit(die(f"无法连接服务器:{exc.message}", hint))
|
||||
raise SystemExit(die(f"无法连接服务器:{exc.message}", hint)) from None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------- 子命令
|
||||
@@ -221,11 +233,20 @@ def cmd_serve(args) -> int:
|
||||
from farm.server.__main__ import main as server_main
|
||||
|
||||
argv = [
|
||||
"--host", args.host, "--port", str(args.port),
|
||||
"--tick", str(args.tick), "--world", args.world,
|
||||
"--max-sessions", str(args.max_sessions),
|
||||
"--idle-timeout", str(args.idle_timeout),
|
||||
"--log-level", args.log_level,
|
||||
"--host",
|
||||
args.host,
|
||||
"--port",
|
||||
str(args.port),
|
||||
"--tick",
|
||||
str(args.tick),
|
||||
"--world",
|
||||
args.world,
|
||||
"--max-sessions",
|
||||
str(args.max_sessions),
|
||||
"--idle-timeout",
|
||||
str(args.idle_timeout),
|
||||
"--log-level",
|
||||
args.log_level,
|
||||
]
|
||||
if args.save_dir:
|
||||
argv += ["--save-dir", args.save_dir]
|
||||
@@ -265,7 +286,7 @@ def cmd_status(args) -> int:
|
||||
|
||||
if not info["running"]:
|
||||
print(warn("• 服务器未运行"))
|
||||
print(dim(f" 启动:python main.py serve --daemon"))
|
||||
print(dim(" 启动:python main.py serve --daemon"))
|
||||
print(dim(f" 地址:http://{args.host}:{args.port}/"))
|
||||
return 1
|
||||
|
||||
@@ -284,8 +305,10 @@ def cmd_status(args) -> int:
|
||||
if world and world.get("leaderboard"):
|
||||
print(" 排行榜")
|
||||
for row in world["leaderboard"][:5]:
|
||||
print(f" #{row['rank']} {row['farm']:<14} 净资产 {row['net_worth']:>8.0f}"
|
||||
f" · 第 {row['day']} 天 · Lv.{row['level']} · 在线 {row['online']}")
|
||||
print(
|
||||
f" #{row['rank']} {row['farm']:<14} 净资产 {row['net_worth']:>8.0f}"
|
||||
f" · 第 {row['day']} 天 · Lv.{row['level']} · 在线 {row['online']}"
|
||||
)
|
||||
|
||||
if args.logs:
|
||||
print(dim(" ---- 日志尾部 ----"))
|
||||
@@ -317,8 +340,9 @@ def cmd_play(args) -> int:
|
||||
)
|
||||
client = make_client(args, realtime=not args.no_realtime)
|
||||
label = f"{client.farm} @ {client.host}"
|
||||
app = TuiApp(client, save_path=None, use_color=not args.no_color,
|
||||
autosave=True, server_label=label)
|
||||
app = TuiApp(
|
||||
client, save_path=None, use_color=not args.no_color, autosave=True, server_label=label
|
||||
)
|
||||
try:
|
||||
app.run()
|
||||
finally:
|
||||
@@ -366,7 +390,10 @@ def cmd_do(args) -> int:
|
||||
try:
|
||||
params = json.loads(args.params) if args.params else {}
|
||||
except json.JSONDecodeError as exc:
|
||||
return die(f"参数不是合法 JSON:{exc}", '例子:python main.py do plant \'{"plot":1,"crop":"wheat"}\'')
|
||||
return die(
|
||||
f"参数不是合法 JSON:{exc}",
|
||||
'例子:python main.py do plant \'{"plot":1,"crop":"wheat"}\'',
|
||||
)
|
||||
if not isinstance(params, dict):
|
||||
return die("参数必须是 JSON 对象")
|
||||
|
||||
@@ -374,19 +401,25 @@ def cmd_do(args) -> int:
|
||||
try:
|
||||
result = client._action(args.action, params)
|
||||
except GameError as exc:
|
||||
return die(f"{args.action} 失败({exc.code}):{exc.message}",
|
||||
"看看现在能做什么:python main.py actions")
|
||||
return die(
|
||||
f"{args.action} 失败({exc.code}):{exc.message}",
|
||||
"看看现在能做什么:python main.py actions",
|
||||
)
|
||||
|
||||
print(ok("✓ ") + str(result.get("message", "完成"))
|
||||
+ dim(f" ({time.time() - started:.2f}s)"))
|
||||
extra = {k: v for k, v in result.items()
|
||||
if k not in ("ok", "message", "state", "log", "action") and not isinstance(v, (dict, list))}
|
||||
print(ok("✓ ") + str(result.get("message", "完成")) + dim(f" ({time.time() - started:.2f}s)"))
|
||||
extra = {
|
||||
k: v
|
||||
for k, v in result.items()
|
||||
if k not in ("ok", "message", "state", "log", "action") and not isinstance(v, (dict, list))
|
||||
}
|
||||
if extra:
|
||||
print(dim(" " + " ".join(f"{k}={v}" for k, v in extra.items())))
|
||||
state = result.get("state") or {}
|
||||
if state:
|
||||
print(f" 第 {state.get('day')} 天 · 金币 {state.get('gold')} · "
|
||||
f"行动点 {state.get('ap')}/{state.get('max_ap')} · 净资产 {state.get('net_worth')}")
|
||||
print(
|
||||
f" 第 {state.get('day')} 天 · 金币 {state.get('gold')} · "
|
||||
f"行动点 {state.get('ap')}/{state.get('max_ap')} · 净资产 {state.get('net_worth')}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -397,8 +430,11 @@ def cmd_batch(args) -> int:
|
||||
try:
|
||||
items = json.loads(args.actions)
|
||||
except json.JSONDecodeError as exc:
|
||||
return die(f"actions 不是合法 JSON:{exc}",
|
||||
'例子:python main.py batch \'[{"action":"harvest_all"},{"action":"buy_feed","params":{"quantity":5}}]\'')
|
||||
return die(
|
||||
f"actions 不是合法 JSON:{exc}",
|
||||
"例子:python main.py batch "
|
||||
'\'[{"action":"harvest_all"},{"action":"buy_feed","params":{"quantity":5}}]\'',
|
||||
)
|
||||
if not isinstance(items, list):
|
||||
return die("actions 必须是 JSON 数组")
|
||||
try:
|
||||
@@ -416,8 +452,10 @@ def cmd_actions(args) -> int:
|
||||
client = make_client(args)
|
||||
state = client.snapshot()
|
||||
items = client.available_actions()
|
||||
print(f"牧场 {client.farm} · 第 {state.get('day')} 天 · 金币 {state.get('gold')} · "
|
||||
f"行动点 {state.get('ap')}/{state.get('max_ap')}")
|
||||
print(
|
||||
f"牧场 {client.farm} · 第 {state.get('day')} 天 · 金币 {state.get('gold')} · "
|
||||
f"行动点 {state.get('ap')}/{state.get('max_ap')}"
|
||||
)
|
||||
if not items:
|
||||
print(warn("• 当前没有可执行的动作"))
|
||||
print(dim(" 试试:python main.py do sleep (推进一天)"))
|
||||
@@ -426,8 +464,8 @@ def cmd_actions(args) -> int:
|
||||
for index, item in enumerate(items, start=1):
|
||||
cost = item.get("cost") or {}
|
||||
cost_text = " ".join(f"{k}:{v}" for k, v in cost.items()) or "免费"
|
||||
print(f" [{index:>2}] {item['action']:<12} {json.dumps(item['params'], ensure_ascii=False):<42}"
|
||||
f" ({cost_text})")
|
||||
params_text = json.dumps(item["params"], ensure_ascii=False)
|
||||
print(f" [{index:>2}] {item['action']:<12} {params_text:<42} ({cost_text})")
|
||||
print(dim(f" {item.get('reason', '')}"))
|
||||
print(dim("\n执行方式:python main.py do <动作名> '<参数JSON>'"))
|
||||
return 0
|
||||
@@ -440,11 +478,18 @@ def cmd_state(args) -> int:
|
||||
return 0
|
||||
snap = client.snapshot()
|
||||
print(f"牧场 {snap.get('farm')}")
|
||||
print(f"第 {snap.get('day')} 天"
|
||||
+ (f" · {snap.get('seconds_to_tick')}s 后进入下一天"
|
||||
if snap.get("seconds_to_tick") is not None else " · 已暂停"))
|
||||
print(f"金币 {snap.get('gold')} 等级 Lv.{snap.get('level')} "
|
||||
f"({snap.get('exp')}/{snap.get('exp_needed')})")
|
||||
print(
|
||||
f"第 {snap.get('day')} 天"
|
||||
+ (
|
||||
f" · {snap.get('seconds_to_tick')}s 后进入下一天"
|
||||
if snap.get("seconds_to_tick") is not None
|
||||
else " · 已暂停"
|
||||
)
|
||||
)
|
||||
print(
|
||||
f"金币 {snap.get('gold')} 等级 Lv.{snap.get('level')} "
|
||||
f"({snap.get('exp')}/{snap.get('exp_needed')})"
|
||||
)
|
||||
print(f"行动点 {snap.get('ap')}/{snap.get('max_ap')} 饲料 {snap.get('feed')}")
|
||||
print(f"净资产 {snap.get('net_worth')}")
|
||||
print(f"土地 {snap.get('plot_count')}/{snap.get('max_plots')} 块")
|
||||
@@ -452,11 +497,16 @@ def cmd_state(args) -> int:
|
||||
if plot["is_empty"]:
|
||||
continue
|
||||
status = "可收获" if plot["is_ripe"] else f"还需{plot['days_left']}天"
|
||||
print(f" {plot['position']:>2} 号地 {plot['crop_name']:<6} 生长 {plot['growth']}"
|
||||
f"/{plot['grow_days']} 水分 {plot['moisture']} · {status}")
|
||||
print(
|
||||
f" {plot['position']:>2} 号地 {plot['crop_name']:<6} 生长 {plot['growth']}"
|
||||
f"/{plot['grow_days']} 水分 {plot['moisture']} · {status}"
|
||||
)
|
||||
for animal in snap.get("animals", []):
|
||||
note = (f"{animal['product']} x{animal['pending']} 待收" if animal["pending"]
|
||||
else ("已喂食" if animal["fed"] else "未喂食"))
|
||||
note = (
|
||||
f"{animal['product']} x{animal['pending']} 待收"
|
||||
if animal["pending"]
|
||||
else ("已喂食" if animal["fed"] else "未喂食")
|
||||
)
|
||||
print(f" #{animal['id']} {animal['name']:<4} {note}")
|
||||
return 0
|
||||
|
||||
@@ -471,7 +521,6 @@ def cmd_log(args) -> int:
|
||||
|
||||
def cmd_watch(args) -> int:
|
||||
"""实时滚动:状态每轮刷新,日志增量打印。适合盯着 AI 玩。"""
|
||||
import threading
|
||||
|
||||
client = make_client(args, realtime=True)
|
||||
print(BANNER)
|
||||
@@ -484,9 +533,11 @@ def cmd_watch(args) -> int:
|
||||
last_seq = max(last_seq, int(entry.get("seq", 0)))
|
||||
print(f"[D{entry['day']:>3}] {entry['text']}")
|
||||
online = snap.get("online")
|
||||
head = (f"第 {snap.get('day')} 天 | 金币 {snap.get('gold')} | "
|
||||
f"Lv.{snap.get('level')} | 行动点 {snap.get('ap')}/{snap.get('max_ap')} | "
|
||||
f"净资产 {snap.get('net_worth')}")
|
||||
head = (
|
||||
f"第 {snap.get('day')} 天 | 金币 {snap.get('gold')} | "
|
||||
f"Lv.{snap.get('level')} | 行动点 {snap.get('ap')}/{snap.get('max_ap')} | "
|
||||
f"净资产 {snap.get('net_worth')}"
|
||||
)
|
||||
if online is not None:
|
||||
head += f" | 在线 {online}"
|
||||
print("\r" + head + " " * 8, end="", flush=True)
|
||||
@@ -517,13 +568,17 @@ def cmd_bot(args) -> int:
|
||||
def cmd_farms(args) -> int:
|
||||
client = make_client(args)
|
||||
world = client.world()
|
||||
print(f"世界:{world.get('world')} · 已运行 {world.get('uptime', 0):.0f} 秒 · "
|
||||
f"牧场 {world.get('farms')} 个 · 连接 {world.get('clients')} 个")
|
||||
print(
|
||||
f"世界:{world.get('world')} · 已运行 {world.get('uptime', 0):.0f} 秒 · "
|
||||
f"牧场 {world.get('farms')} 个 · 连接 {world.get('clients')} 个"
|
||||
)
|
||||
print(f"时钟:每 {world.get('tick_seconds')} 秒推进一天\n")
|
||||
print(f" {'#':>2} {'牧场':<14} {'净资产':>9} {'金币':>7} {'天':>4} {'等级':>4} {'在线':>4}")
|
||||
for row in world.get("leaderboard", []):
|
||||
print(f" {row['rank']:>2} {row['farm']:<14} {row['net_worth']:>9.0f} "
|
||||
f"{row['gold']:>7} {row['day']:>4} {row['level']:>4} {row['online']:>4}")
|
||||
print(
|
||||
f" {row['rank']:>2} {row['farm']:<14} {row['net_worth']:>9.0f} "
|
||||
f"{row['gold']:>7} {row['day']:>4} {row['level']:>4} {row['online']:>4}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -601,9 +656,11 @@ def main(argv=None) -> int:
|
||||
info = DaemonManager().status()
|
||||
if info["running"]:
|
||||
health = info.get("health") or {}
|
||||
print(ok("✓ 服务器正在运行")
|
||||
+ f" http://{info['host']}:{info['port']}/ "
|
||||
+ dim(f"牧场 {health.get('farms', 0)} 个 · 在线 {health.get('clients', 0)}"))
|
||||
print(
|
||||
ok("✓ 服务器正在运行")
|
||||
+ f" http://{info['host']}:{info['port']}/ "
|
||||
+ dim(f"牧场 {health.get('farms', 0)} 个 · 在线 {health.get('clients', 0)}")
|
||||
)
|
||||
else:
|
||||
print(warn("• 服务器未运行"))
|
||||
except Exception:
|
||||
@@ -616,7 +673,7 @@ def main(argv=None) -> int:
|
||||
return die(f"未知命令 {args.command}", "python main.py --help 查看全部命令")
|
||||
try:
|
||||
return handler(args)
|
||||
except SystemExit as exc: # make_client 里的友好退出
|
||||
except SystemExit as exc: # make_client 里的友好退出
|
||||
return int(exc.code or 0)
|
||||
except KeyboardInterrupt:
|
||||
print("\n已中断。")
|
||||
|
||||
Reference in new issue
Block a user