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
+56
-52
@@ -16,10 +16,12 @@ TUI 与 HTTP/gRPC 接口都调用同一组方法,因此三方共享完全一
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import random
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from . import config
|
||||
from .models import Animal, GameState, Plot, clamp
|
||||
@@ -34,7 +36,7 @@ class GameError(Exception):
|
||||
self.message = message
|
||||
self.status = status
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {"ok": False, "error": self.code, "message": self.message}
|
||||
|
||||
|
||||
@@ -52,9 +54,9 @@ class FarmEngine:
|
||||
self._last_sleep = 0.0
|
||||
self.paused = False
|
||||
# 由 Scheduler 维护:下一次自动推进的绝对时间戳(无调度器时为 None)
|
||||
self.next_tick_at: Optional[float] = None
|
||||
self.next_tick_at: float | None = None
|
||||
# 状态变化回调(TUI 用它触发重绘)
|
||||
self._listeners: List[Callable[[], None]] = []
|
||||
self._listeners: list[Callable[[], None]] = []
|
||||
|
||||
# ================================================================ 订阅
|
||||
def subscribe(self, callback: Callable[[], None]) -> None:
|
||||
@@ -63,11 +65,9 @@ class FarmEngine:
|
||||
|
||||
def _notify(self) -> None:
|
||||
for callback in list(self._listeners):
|
||||
try:
|
||||
# 监听器异常不能影响游戏主流程
|
||||
with contextlib.suppress(Exception):
|
||||
callback()
|
||||
except Exception:
|
||||
# 监听器异常不能影响游戏主流程
|
||||
pass
|
||||
|
||||
# ================================================================ 日志
|
||||
def add_log(self, text: str, kind: str = "info") -> None:
|
||||
@@ -77,20 +77,20 @@ class FarmEngine:
|
||||
{
|
||||
"seq": self._log_seq,
|
||||
"day": self.state.day,
|
||||
"kind": kind, # info / good / bad / warn / action
|
||||
"kind": kind, # info / good / bad / warn / action
|
||||
"text": text,
|
||||
}
|
||||
)
|
||||
if len(self.state.log) > config.MAX_LOG_ENTRIES:
|
||||
del self.state.log[: len(self.state.log) - config.MAX_LOG_ENTRIES]
|
||||
|
||||
def recent_log(self, limit: int = 12, since: int = 0) -> List[Dict[str, Any]]:
|
||||
def recent_log(self, limit: int = 12, since: int = 0) -> list[dict[str, Any]]:
|
||||
with self._lock:
|
||||
entries = [e for e in self.state.log if e["seq"] > since]
|
||||
return entries[-limit:] if limit > 0 else entries
|
||||
|
||||
# ================================================================ 查询
|
||||
def snapshot(self) -> Dict[str, Any]:
|
||||
def snapshot(self) -> dict[str, Any]:
|
||||
"""返回完整状态快照(供 TUI 渲染与 API 响应)。"""
|
||||
with self._lock:
|
||||
data = self.state.to_dict()
|
||||
@@ -101,7 +101,7 @@ class FarmEngine:
|
||||
return data
|
||||
|
||||
@property
|
||||
def seconds_to_tick(self) -> Optional[float]:
|
||||
def seconds_to_tick(self) -> float | None:
|
||||
if self.paused or self.next_tick_at is None:
|
||||
return None
|
||||
return round(max(0.0, self.next_tick_at - time.time()), 1)
|
||||
@@ -110,7 +110,7 @@ class FarmEngine:
|
||||
with self._lock:
|
||||
return round(self.state.market.get(key, 0.0), 2)
|
||||
|
||||
def catalog(self) -> Dict[str, Any]:
|
||||
def catalog(self) -> dict[str, Any]:
|
||||
"""返回所有可种植/可养殖对象及其在市场上的实时价格。"""
|
||||
with self._lock:
|
||||
crops = []
|
||||
@@ -148,9 +148,7 @@ class FarmEngine:
|
||||
"exp": info["exp"],
|
||||
"level_req": info["level_req"],
|
||||
"locked": self.state.level < info["level_req"],
|
||||
"profit_per_day": round(
|
||||
(price - feed_cost) / max(1, info["cycle"]), 2
|
||||
),
|
||||
"profit_per_day": round((price - feed_cost) / max(1, info["cycle"]), 2),
|
||||
}
|
||||
)
|
||||
return {
|
||||
@@ -161,14 +159,14 @@ class FarmEngine:
|
||||
"ap_cost": config.AP_COST,
|
||||
}
|
||||
|
||||
def available_actions(self) -> List[Dict[str, Any]]:
|
||||
def available_actions(self) -> list[dict[str, Any]]:
|
||||
"""列出当前**合法**的动作,供 AI 直接决策使用。
|
||||
|
||||
每个动作包含:``action``(接口名)、``params``(参数模板)、
|
||||
``cost``(金币/行动点开销)、``reason``(为什么值得做)。
|
||||
"""
|
||||
with self._lock:
|
||||
actions: List[Dict[str, Any]] = []
|
||||
actions: list[dict[str, Any]] = []
|
||||
state = self.state
|
||||
|
||||
# ---- 收获 / 浇水 / 播种
|
||||
@@ -193,7 +191,9 @@ class FarmEngine:
|
||||
"action": "harvest",
|
||||
"params": {"plot": plot.index + 1},
|
||||
"cost": {"gold": 0, "ap": config.AP_COST["harvest"]},
|
||||
"reason": f"{plot.index + 1} 号地的{config.CROPS[plot.crop]['name']}已成熟",
|
||||
"reason": (
|
||||
f"{plot.index + 1} 号地的{config.CROPS[plot.crop]['name']}已成熟"
|
||||
),
|
||||
}
|
||||
)
|
||||
elif plot.moisture == 0 and state.ap >= config.AP_COST["water"]:
|
||||
@@ -274,7 +274,8 @@ class FarmEngine:
|
||||
try:
|
||||
number = int(plot_no)
|
||||
except (TypeError, ValueError):
|
||||
raise GameError("bad_param", f"地块编号必须是整数,收到 {plot_no!r}")
|
||||
# 底层 ValueError 的细节对调用者没意义,只保留友好提示
|
||||
raise GameError("bad_param", f"地块编号必须是整数,收到 {plot_no!r}") from None
|
||||
if number < 1 or number > len(self.state.plots):
|
||||
raise GameError(
|
||||
"no_such_plot",
|
||||
@@ -286,7 +287,7 @@ class FarmEngine:
|
||||
try:
|
||||
aid = int(animal_id)
|
||||
except (TypeError, ValueError):
|
||||
raise GameError("bad_param", f"动物编号必须是整数,收到 {animal_id!r}")
|
||||
raise GameError("bad_param", f"动物编号必须是整数,收到 {animal_id!r}") from None
|
||||
for animal in self.state.animals:
|
||||
if animal.aid == aid:
|
||||
return animal
|
||||
@@ -316,14 +317,14 @@ class FarmEngine:
|
||||
self.state.total_spent += amount
|
||||
|
||||
def _earn(self, amount: float, reason: str) -> int:
|
||||
earned = max(0, int(round(amount)))
|
||||
earned = max(0, round(amount))
|
||||
self.state.gold += earned
|
||||
self.state.total_earned += earned
|
||||
return earned
|
||||
|
||||
def _gain_exp(self, amount: int) -> List[str]:
|
||||
def _gain_exp(self, amount: int) -> list[str]:
|
||||
"""增加经验并处理连续升级,返回升级提示文本。"""
|
||||
messages: List[str] = []
|
||||
messages: list[str] = []
|
||||
self.state.exp += int(amount)
|
||||
guard = 0
|
||||
while self.state.exp >= self.state.exp_needed and guard < 50:
|
||||
@@ -353,7 +354,7 @@ class FarmEngine:
|
||||
return 2 if random.random() < config.LUCKY_CHANCE else 1
|
||||
|
||||
# ================================================================ 动作
|
||||
def plant(self, plot_no: Any, crop: Any) -> Dict[str, Any]:
|
||||
def plant(self, plot_no: Any, crop: Any) -> dict[str, Any]:
|
||||
with self._lock:
|
||||
if not isinstance(crop, str) or crop not in config.CROPS:
|
||||
raise GameError(
|
||||
@@ -368,7 +369,9 @@ class FarmEngine:
|
||||
)
|
||||
plot = self._get_plot(plot_no)
|
||||
if plot.crop is not None:
|
||||
raise GameError("plot_busy", f"{plot.index + 1} 号地已经种了{config.CROPS[plot.crop]['name']}")
|
||||
raise GameError(
|
||||
"plot_busy", f"{plot.index + 1} 号地已经种了{config.CROPS[plot.crop]['name']}"
|
||||
)
|
||||
|
||||
self._spend_gold(info["seed_cost"], f"购买{info['name']}种子")
|
||||
self._spend_ap("plant")
|
||||
@@ -382,11 +385,13 @@ class FarmEngine:
|
||||
self._notify()
|
||||
return {
|
||||
"ok": True,
|
||||
"message": f"{plot.index + 1} 号地已种下{info['name']},{info['grow_days']} 天后成熟",
|
||||
"message": (
|
||||
f"{plot.index + 1} 号地已种下{info['name']},{info['grow_days']} 天后成熟"
|
||||
),
|
||||
"plot": plot.to_dict(),
|
||||
}
|
||||
|
||||
def water(self, plot_no: Any) -> Dict[str, Any]:
|
||||
def water(self, plot_no: Any) -> dict[str, Any]:
|
||||
with self._lock:
|
||||
plot = self._get_plot(plot_no)
|
||||
if plot.crop is None:
|
||||
@@ -402,7 +407,7 @@ class FarmEngine:
|
||||
self._notify()
|
||||
return {"ok": True, "message": f"{plot.index + 1} 号地已浇水", "plot": plot.to_dict()}
|
||||
|
||||
def harvest(self, plot_no: Any) -> Dict[str, Any]:
|
||||
def harvest(self, plot_no: Any) -> dict[str, Any]:
|
||||
with self._lock:
|
||||
plot = self._get_plot(plot_no)
|
||||
if plot.crop is None:
|
||||
@@ -426,9 +431,7 @@ class FarmEngine:
|
||||
self.state.total_harvested += quantity
|
||||
|
||||
lucky = "(丰收!)" if quantity > 1 else ""
|
||||
self.add_log(
|
||||
f"收获 {quantity} 份{info['name']}{lucky},+{earned} 金币", "good"
|
||||
)
|
||||
self.add_log(f"收获 {quantity} 份{info['name']}{lucky},+{earned} 金币", "good")
|
||||
levelups = self._gain_exp(info["exp"])
|
||||
self._notify()
|
||||
return {
|
||||
@@ -440,7 +443,7 @@ class FarmEngine:
|
||||
"level_ups": levelups,
|
||||
}
|
||||
|
||||
def buy_animal(self, kind: Any) -> Dict[str, Any]:
|
||||
def buy_animal(self, kind: Any) -> dict[str, Any]:
|
||||
with self._lock:
|
||||
if not isinstance(kind, str) or kind not in config.ANIMALS:
|
||||
raise GameError(
|
||||
@@ -469,7 +472,7 @@ class FarmEngine:
|
||||
"animal": animal.to_dict(),
|
||||
}
|
||||
|
||||
def feed(self, animal_id: Any, quantity: Any = None) -> Dict[str, Any]:
|
||||
def feed(self, animal_id: Any, quantity: Any = None) -> dict[str, Any]:
|
||||
with self._lock:
|
||||
animal = self._get_animal(animal_id)
|
||||
if animal.fed:
|
||||
@@ -481,7 +484,7 @@ class FarmEngine:
|
||||
try:
|
||||
quantity = int(quantity)
|
||||
except (TypeError, ValueError):
|
||||
raise GameError("bad_param", "喂食数量必须是整数")
|
||||
raise GameError("bad_param", "喂食数量必须是整数") from None
|
||||
if quantity <= 0:
|
||||
raise GameError("bad_param", "喂食数量必须大于 0")
|
||||
if quantity > need:
|
||||
@@ -507,7 +510,7 @@ class FarmEngine:
|
||||
"animal": animal.to_dict(),
|
||||
}
|
||||
|
||||
def collect(self, animal_id: Any) -> Dict[str, Any]:
|
||||
def collect(self, animal_id: Any) -> dict[str, Any]:
|
||||
with self._lock:
|
||||
animal = self._get_animal(animal_id)
|
||||
if animal.pending <= 0:
|
||||
@@ -524,7 +527,8 @@ class FarmEngine:
|
||||
self.state.total_collected += quantity
|
||||
|
||||
self.add_log(
|
||||
f"收取 {quantity} 份{animal.product}({animal.name} #{animal.aid}),+{earned} 金币",
|
||||
f"收取 {quantity} 份{animal.product}({animal.name} #{animal.aid}),"
|
||||
f"+{earned} 金币",
|
||||
"good",
|
||||
)
|
||||
levelups = self._gain_exp(animal.info["exp"] * quantity)
|
||||
@@ -537,12 +541,12 @@ class FarmEngine:
|
||||
"level_ups": levelups,
|
||||
}
|
||||
|
||||
def buy_feed(self, quantity: Any = config.FEED_BULK) -> Dict[str, Any]:
|
||||
def buy_feed(self, quantity: Any = config.FEED_BULK) -> dict[str, Any]:
|
||||
with self._lock:
|
||||
try:
|
||||
quantity = int(quantity)
|
||||
except (TypeError, ValueError):
|
||||
raise GameError("bad_param", "饲料数量必须是整数")
|
||||
raise GameError("bad_param", "饲料数量必须是整数") from None
|
||||
if quantity <= 0:
|
||||
raise GameError("bad_param", "饲料数量必须大于 0")
|
||||
if quantity > config.MAX_FEED_PURCHASE:
|
||||
@@ -561,7 +565,7 @@ class FarmEngine:
|
||||
"feed": self.state.feed,
|
||||
}
|
||||
|
||||
def buy_plot(self) -> Dict[str, Any]:
|
||||
def buy_plot(self) -> dict[str, Any]:
|
||||
with self._lock:
|
||||
price = self.state.next_plot_price
|
||||
if price < 0:
|
||||
@@ -583,7 +587,7 @@ class FarmEngine:
|
||||
"next_plot_price": self.state.next_plot_price,
|
||||
}
|
||||
|
||||
def sleep(self) -> Dict[str, Any]:
|
||||
def sleep(self) -> dict[str, Any]:
|
||||
"""手动推进一天,带冷却避免刷时间。"""
|
||||
with self._lock:
|
||||
elapsed = time.time() - self._last_sleep
|
||||
@@ -594,14 +598,14 @@ class FarmEngine:
|
||||
events = self.advance_day(reason="休息")
|
||||
return {"ok": True, "message": f"进入第 {self.state.day} 天", "events": events}
|
||||
|
||||
def pause(self, paused: Optional[bool] = None) -> Dict[str, Any]:
|
||||
def pause(self, paused: bool | None = None) -> dict[str, Any]:
|
||||
with self._lock:
|
||||
self.paused = bool(paused) if paused is not None else not self.paused
|
||||
self.add_log("时间已暂停" if self.paused else "时间继续流动", "warn")
|
||||
self._notify()
|
||||
return {"ok": True, "paused": self.paused}
|
||||
|
||||
def reset(self, keep_tick: bool = True) -> Dict[str, Any]:
|
||||
def reset(self, keep_tick: bool = True) -> dict[str, Any]:
|
||||
with self._lock:
|
||||
tick = self.tick_seconds
|
||||
self.state = GameState()
|
||||
@@ -614,7 +618,7 @@ class FarmEngine:
|
||||
self._notify()
|
||||
return {"ok": True, "message": "农场已重置"}
|
||||
|
||||
def save(self, path: Optional[str] = None) -> Dict[str, Any]:
|
||||
def save(self, path: str | None = None) -> dict[str, Any]:
|
||||
"""把当前状态写入磁盘。
|
||||
|
||||
单机模式下由 TUI 直接调用;服务器模式里存档位置由牧场会话决定,
|
||||
@@ -627,13 +631,13 @@ class FarmEngine:
|
||||
return {"ok": True, "message": f"已存档到 {target}", "path": target}
|
||||
|
||||
# ================================================================ 时间推进
|
||||
def advance_day(self, reason: str = "自动") -> List[str]:
|
||||
def advance_day(self, reason: str = "自动") -> list[str]:
|
||||
"""推进游戏内一天,返回本日发生的事件文本。"""
|
||||
with self._lock:
|
||||
state = self.state
|
||||
state.day += 1
|
||||
state.ap = state.max_ap
|
||||
events: List[str] = []
|
||||
events: list[str] = []
|
||||
|
||||
# ---- 作物生长
|
||||
for plot in state.plots:
|
||||
@@ -643,12 +647,14 @@ class FarmEngine:
|
||||
plot.moisture -= 1
|
||||
plot.growth = min(plot.grow_days, plot.growth + 1)
|
||||
if plot.is_ripe:
|
||||
events.append(f"{plot.index + 1} 号地的{config.CROPS[plot.crop]['name']}已成熟")
|
||||
events.append(
|
||||
f"{plot.index + 1} 号地的{config.CROPS[plot.crop]['name']}已成熟"
|
||||
)
|
||||
else:
|
||||
events.append(f"{plot.index + 1} 号地缺水,作物停止生长")
|
||||
|
||||
# ---- 牲畜
|
||||
dead: List[Animal] = []
|
||||
dead: list[Animal] = []
|
||||
for animal in state.animals:
|
||||
if animal.fed:
|
||||
animal.fed = False
|
||||
@@ -710,7 +716,7 @@ class Scheduler:
|
||||
self.engine = engine
|
||||
self.enabled = enabled
|
||||
self._stop = threading.Event()
|
||||
self._thread: Optional[threading.Thread] = None
|
||||
self._thread: threading.Thread | None = None
|
||||
|
||||
def start(self) -> None:
|
||||
if not self.enabled or self._thread is not None:
|
||||
@@ -734,10 +740,8 @@ class Scheduler:
|
||||
if deadline is None:
|
||||
deadline = time.time() + engine.tick_seconds
|
||||
if time.time() >= deadline:
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
engine.advance_day(reason="时间流逝")
|
||||
except Exception:
|
||||
pass
|
||||
engine.next_tick_at = time.time() + engine.tick_seconds
|
||||
else:
|
||||
engine.next_tick_at = deadline
|
||||
Reference in new issue
Block a user