移除行动点系统,倒计时本地走秒,字体优化

为什么:游戏的时间(40 分钟一天、只能自然流动)本身就是唯一的行动节奏
阀门,再叠一层"每天 10 点行动点"纯属重复惩罚——浇水还要体力既不拟真
也不好玩。删掉之后,稀缺的只剩时间本身,放置玩法的定位反而更纯粹。

改动范围(引擎到界面连根拔):
- config 删 START_AP/AP_COST;models 删 ap 字段与 max_ap,存档 payload
  里的旧 ap 键被 from_dict 自然忽略;
- engine 删 _spend_ap 与全部 8 个调用点、每日恢复逻辑与日志;
  available_actions 不再按行动点筛,cost 只剩金币;
- storage 建表去掉 ap 列,老库在 connect() 里 ALTER TABLE DROP COLUMN
  无痛迁移(实测 saves/farm.db 已自动迁完);
- bot 删 AP 分支(无 AP 后 available_actions 恒可用);client/TUI/CLI/
  README/帮助文本同步清理。

网页端顺带两件事:
- "下一天"倒计时改为客户端本地走秒——SSE 只在有变化时推送,以前倒计时
  只在推送瞬间跳变,现在每秒都在动(记基点 + 1s 定时器补齐);
- 字体优化:页面字体栈补齐 MiSans/HarmonyOS Sans/PingFang,正文字号
  14→15,统计数字 tabular-nums;canvas 小字统一字体栈并加粗提对比度。

测试适配:删 2 个 AP 专属用例(无 AP 可测)、改名/收敛 5 个,
532 项全绿(skip 39),ruff/pyright 干净。真机验证:老库迁移、
统计条无行动点、倒计时逐秒递减(39:04→39:02)、动作不再报 no_ap。
This commit is contained in:
snowy-yang committed 2026-09-25 22:44:08 +08:00
1 parent 92b1a628b1
commit 15d290a60a
18 files changed
+82 -179

No files matched your search

+17 -43
View File
@@ -129,8 +129,6 @@ class FarmEngine:
"level": state.level,
"exp": state.exp,
"exp_needed": state.exp_needed,
"ap": state.ap,
"max_ap": state.max_ap,
"feed": state.feed,
"plot_count": len(state.plots),
"max_plots": state.max_plots,
@@ -216,7 +214,6 @@ class FarmEngine:
},
"feed_price": config.FEED_UNIT_PRICE,
"water_capacity": config.WATER_CAPACITY,
"ap_cost": config.AP_COST,
}
# ---- catalog 的三个子表。拆开是因为它们的"变化频率"各不相同:
@@ -299,7 +296,7 @@ class FarmEngine:
"""列出当前**合法**的动作,供 AI 直接决策使用。
每个动作包含:``action``(接口名)、``params``(参数模板)、
``cost``(金币/行动点开销)、``reason``(为什么值得做)。
``cost``(金币开销)、``reason``(为什么值得做)。
"""
with self._lock:
state = self.state
@@ -314,15 +311,12 @@ class FarmEngine:
# ---- available_actions 的三个分区;拆开是为了各自能独立阅读/测试
def _plot_actions(self, state: GameState) -> list[dict[str, Any]]:
"""每块地当前能做的事:播种 / 收获 / 浇水。"""
# 能种什么只取决于等级、金币和行动点,与**具体是哪块地**无关,
# 能种什么只取决于等级和金币,与**具体是哪块地**无关,
# 所以整趟只筛一次,不要放进地块循环里做 O(地块 × 作物) 次判断。
plant_ap = config.AP_COST["plant"]
plantable = [
(key, info)
for key, info in config.CROPS.items()
if not self._is_locked(info)
and state.gold >= info["seed_cost"]
and state.ap >= plant_ap
if not self._is_locked(info) and state.gold >= info["seed_cost"]
]
actions: list[dict[str, Any]] = []
for plot in state.plots:
@@ -333,7 +327,7 @@ class FarmEngine:
{
"action": "plant",
"params": {"plot": position, "crop": key},
"cost": {"gold": info["seed_cost"], "ap": plant_ap},
"cost": {"gold": info["seed_cost"]},
"reason": f"{position} 号地空闲,可种{info['name']}",
}
)
@@ -342,11 +336,11 @@ class FarmEngine:
{
"action": "harvest",
"params": {"plot": position},
"cost": {"gold": 0, "ap": config.AP_COST["harvest"]},
"cost": {"gold": 0},
"reason": f"{position} 号地的{config.CROPS[plot.crop]['name']}已成熟",
}
)
elif self._needs_water(plot) and state.ap >= config.AP_COST["water"]:
elif self._needs_water(plot):
reason = (
f"{position} 号地缺水,作物已停止生长"
if plot.moisture <= 0
@@ -356,7 +350,7 @@ class FarmEngine:
{
"action": "water",
"params": {"plot": position},
"cost": {"gold": 0, "ap": config.AP_COST["water"]},
"cost": {"gold": 0},
"reason": reason,
}
)
@@ -371,19 +365,19 @@ class FarmEngine:
{
"action": "collect",
"params": {"animal_id": animal.aid},
"cost": {"gold": 0, "ap": config.AP_COST["collect"]},
"cost": {"gold": 0},
"reason": f"{animal.name}有 {animal.pending} 份{animal.product}待收取",
}
)
elif not animal.fed:
need = animal.info["feed_per_day"]
cost = 0 if state.feed >= need else need * config.FEED_EMERGENCY_PRICE
if state.gold >= cost and state.ap >= config.AP_COST["feed"]:
if state.gold >= cost:
actions.append(
{
"action": "feed",
"params": {"animal_id": animal.aid},
"cost": {"gold": cost, "ap": config.AP_COST["feed"]},
"cost": {"gold": cost},
"reason": f"{animal.name}(#{animal.aid})今天还没喂食",
}
)
@@ -398,7 +392,7 @@ class FarmEngine:
{
"action": "buy_plot",
"params": {},
"cost": {"gold": price, "ap": config.AP_COST["buy_plot"]},
"cost": {"gold": price},
"reason": f"可花 {price} 金币开垦第 {len(state.plots) + 1} 块地",
}
)
@@ -408,7 +402,7 @@ class FarmEngine:
{
"action": "buy_feed",
"params": {"quantity": config.FEED_BULK},
"cost": {"gold": bulk_cost, "ap": config.AP_COST["buy_feed"]},
"cost": {"gold": bulk_cost},
"reason": "饲料库存偏低,建议补货",
}
)
@@ -421,7 +415,7 @@ class FarmEngine:
{
"action": "buy_animal",
"params": {"kind": key},
"cost": {"gold": info["cost"], "ap": config.AP_COST["buy_animal"]},
"cost": {"gold": info["cost"]},
"reason": f"可购买{info['name']}({info['cost']} 金币)",
}
)
@@ -457,7 +451,7 @@ class FarmEngine:
{
"action": "sell",
"params": {"key": key, "quantity": count},
"cost": {"gold": 0, "ap": config.AP_COST["sell"]},
"cost": {"gold": 0},
"reason": ";".join(reasons),
}
)
@@ -474,7 +468,7 @@ class FarmEngine:
{
"action": "build",
"params": {"key": key},
"cost": {"gold": entry["next_cost"], "ap": config.AP_COST["build"]},
"cost": {"gold": entry["next_cost"]},
"reason": f"可造第 {entry['owned'] + 1} 座{entry['name']}:{entry['effect']}",
}
)
@@ -531,17 +525,6 @@ class FarmEngine:
return animal
raise GameError("no_such_animal", f"没有编号为 {aid} 的动物")
def _spend_ap(self, action: str) -> None:
cost = config.AP_COST.get(action, 0)
if cost and self.state.ap < cost:
raise GameError(
"no_ap",
f"行动点不足:{action} 需要 {cost} 点,当前只剩 {self.state.ap} 点。"
f"等一天的时间自然走完就会恢复。",
)
if cost:
self.state.ap -= cost
def _spend_gold(self, amount: int, what: str) -> None:
amount = int(amount)
if amount < 0:
@@ -639,7 +622,6 @@ class FarmEngine:
)
self._spend_gold(info["seed_cost"], f"购买{info['name']}种子")
self._spend_ap("plant")
plot.crop = crop
plot.growth = 0.0
@@ -666,7 +648,6 @@ class FarmEngine:
"already_watered",
f"{plot.index + 1} 号地水分已满({plot.moisture}/{config.WATER_CAPACITY})",
)
self._spend_ap("water")
plot.moisture = config.WATER_CAPACITY
self.add_log(f"给 {plot.index + 1} 号地浇水(水分 {config.WATER_CAPACITY})", "action")
self._notify()
@@ -684,7 +665,6 @@ class FarmEngine:
)
crop_key = plot.crop
info = config.CROPS[crop_key]
self._spend_ap("harvest")
quantity = self._roll_yield()
# 先入库再清地:谷仓满了会在这里抛 granary_full,作物原地不动,
@@ -729,7 +709,6 @@ class FarmEngine:
raise GameError("pasture_full", f"牧场已满(上限 {config.MAX_ANIMALS} 只)")
self._spend_gold(info["cost"], f"购买{info['name']}")
self._spend_ap("buy_animal")
animal = Animal(kind)
self.state.animals.append(animal)
@@ -766,7 +745,6 @@ class FarmEngine:
self.state.feed += shortfall
self.add_log(f"饲料不足,临时高价补货 {shortfall} 份(-{cost} 金币)", "warn")
self._spend_ap("feed")
self.state.feed -= quantity
animal.fed = True
animal.starve = 0
@@ -788,7 +766,6 @@ class FarmEngine:
f"{animal.name} #{animal.aid} 暂时没有可收取的{animal.product}"
f"(还需 {animal.days_to_product} 天)",
)
self._spend_ap("collect")
quantity = animal.pending
key = f"animal:{animal.kind}"
# 与收获同理:先确认装得下,再清空待收,免得产出凭空消失
@@ -850,7 +827,6 @@ class FarmEngine:
f"土地已达当前等级上限({self.state.max_plots} 块),升级后可继续开垦",
)
self._spend_gold(price, "开垦新土地")
self._spend_ap("buy_plot")
plot = Plot(len(self.state.plots))
self.state.plots.append(plot)
self.state.plots_bought += 1
@@ -971,7 +947,6 @@ class FarmEngine:
raise GameError("build_limit", f"{entry['name']}已建满({entry['max_level']} 座)")
self._spend_gold(entry["next_cost"], f"建造{entry['name']}")
self._spend_ap("build")
self.state.buildings[key] = entry["owned"] + 1
built = self._building_entry(self.state, key)
self.add_log(
@@ -1070,7 +1045,7 @@ class FarmEngine:
顺序是一条流水线:浇水(水井/喷灌)保住作物 → 收割机把熟了的收进
谷仓 → 磨坊把陈粮磨成饲料 → 喂食器喂牲口 → 售货机趁高价清库存。
共同的设计底线:**只替代玩家本来要花行动点做的事,绝不自动花钱**
共同的设计底线:**只替代玩家本来要花工夫做的事,绝不自动花钱**
——喂食器缺饲料停摆、收割机遇满仓停摆,都宁可停也不硬来。
"""
return (
@@ -1246,7 +1221,6 @@ class FarmEngine:
state = self.state
info = self._resolve_weather(state, weather)
state.day += 1
state.ap = state.max_ap
events: list[str] = []
# ---- 建筑自动化。必须排在生长结算之前:水井救的正是"今天就断水"的
@@ -1303,7 +1277,7 @@ class FarmEngine:
self.add_log(text, "warn")
today = f"第 {state.day} 天开始({reason}),今日{info['name']}"
if events:
self.add_log(f"{today},行动点已恢复", "info")
self.add_log(f"{today},新的一天开始了", "info")
else:
self.add_log(f"{today},一切安好", "info")