feat: 加天气 / 谷仓 / 建筑三套系统,收获改为入库 + 择时卖出
动手前先修掉了两处「死机制」,新增的三个系统各自激活一个: * 天气:小麦 2 天成熟、初始水分 2、每天只掉 1 点,等于永远不需要浇水。 现在水分消耗由天气决定(晴 1 / 多云 0 / 干旱 2,雨天反补 3),而 `state.forecast` 把明天的天气提前告诉你——「今晚浇不浇」成了能算的决策。 5 种天气、权重合计 100、同种不连出;暴风雨会毁未熟作物,温室免疫。 * 谷仓:市场波动原本纯装饰(收获当场按市价变现,没有择时空间)。现在收获与 收取只进 `state.inventory`,靠新增的 `sell` 换金币,价格波动才真正参与决策。 仓满时 `_store()` 抛 `granary_full` 且不改状态,作物原样留在地里。 * 建筑:新增 `build`,买的是「以后不用天天手动做」——水井自动浇干地、 喂食器自动喂食(只吃库存饲料,绝不自动花钱)、温室、谷仓扩容。 每天在 `advance_day` 里先于生长执行,同类越造越贵(×1.6)。 存档升到 `SAVE_VERSION=2`,配 `MIN_SAVE_VERSION=1` 向后兼容,旧档缺的新字段 一律走默认值,不作废玩家手里的档。`advance_day(weather=...)` 是留给测试的 确定性入口。 顺带修两个真 bug: * `daemon.py` 三处 `makedirs(os.path.dirname(path))`:`--pid-file pid.txt` 这种只给文件名的用法会让 dirname 变成空串,启动阶段直接崩。抽出 `ensure_parent_dir()` 兜底。 * `RemoteFarm.snapshot()` 是本地缓存,非实时客户端轮询会一直读到旧数据, 看起来像服务器卡死。补 docstring 指明要自己调 `refresh()`。 质量门禁:ruff format/check 全绿、pyright 0 错误、503 项测试 OK(skip 35)、 bench 无回归(brief 19.5µs / snapshot 48.5µs / POST full=1 400µs)。 另起真实服务器(--tick 3 + 预置存档)跑了 38 项端到端验收,全过。
This commit is contained in:
1 parent
a92c29ead2
commit
cf24cb43f1
21 files changed
+1349
-112
No files matched your search
+35
-5
@@ -24,7 +24,7 @@ import urllib.request
|
||||
|
||||
from farm import config, net
|
||||
from farm.engine import GameError
|
||||
from farm.server.daemon import DaemonManager, pid_alive, port_busy
|
||||
from farm.server.daemon import DaemonManager, ensure_parent_dir, pid_alive, port_busy
|
||||
from farm.server.hub import (
|
||||
CLOSE_MESSAGE,
|
||||
Client,
|
||||
@@ -657,11 +657,16 @@ class BriefAndRulesTestCase(unittest.TestCase):
|
||||
self.hub = ConnectionHub()
|
||||
self.session = self.manager.get("alice")
|
||||
|
||||
def test_brief_keys_exist_in_the_snapshot(self):
|
||||
"""回归:snapshot 改字段名时,brief 必须跟着改,否则动作回执直接 500。"""
|
||||
snap = self.session.snapshot()
|
||||
def test_brief_keys_exist_in_the_brief_payload(self):
|
||||
"""回归:brief_source 改字段名时,BRIEF_KEYS 必须跟着改,否则动作回执直接 500。
|
||||
|
||||
BRIEF_KEYS 的数据源是 ``FarmEngine.brief_source()``(精简状态),
|
||||
**不是**完整快照 ``session.snapshot()``——后者包含 market/plots 等大块数据,
|
||||
拿它来断言会把"brief 只读精简字段"这个设计意图测丢。
|
||||
"""
|
||||
data = brief(self.session, self.hub)
|
||||
for key in BRIEF_KEYS:
|
||||
self.assertIn(key, snap, f"快照里没有 {key}")
|
||||
self.assertIn(key, data, f"回执里没有 {key}")
|
||||
|
||||
def test_brief_exposes_the_to_do_lists(self):
|
||||
self.session.engine.plant(1, "wheat")
|
||||
@@ -805,6 +810,31 @@ class DaemonTestCase(unittest.TestCase):
|
||||
self.assertEqual(len(lines), 5)
|
||||
self.assertEqual(lines[-1], "第49行")
|
||||
|
||||
def test_ensure_parent_dir_tolerates_a_bare_file_name(self):
|
||||
"""回归:路径没有目录部分时 dirname 返回空串,makedirs("") 直接崩。"""
|
||||
ensure_parent_dir("server.pid") # 不该抛
|
||||
ensure_parent_dir(os.path.join(self.dir, "a", "b", "server.pid"))
|
||||
self.assertTrue(os.path.isdir(os.path.join(self.dir, "a", "b")))
|
||||
|
||||
def test_pid_file_without_a_directory_still_works(self):
|
||||
"""``--pid-file server.pid`` 这种只给文件名的用法必须能起来。"""
|
||||
manager = DaemonManager(
|
||||
host="127.0.0.1",
|
||||
port=free_port(),
|
||||
save_dir=self.dir,
|
||||
pid_file="server.pid",
|
||||
log_file="server.log",
|
||||
)
|
||||
cwd = os.getcwd()
|
||||
os.chdir(self.dir)
|
||||
try:
|
||||
manager.write_pid(4321)
|
||||
self.assertEqual(manager.read_pid(), 4321)
|
||||
manager.clear_pid()
|
||||
self.assertIsNone(manager.read_pid())
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
|
||||
|
||||
@unittest.skipUnless(HAS_TESTCLIENT, "未安装 httpx(TestClient 依赖它)")
|
||||
class RestApiTestCase(unittest.TestCase):
|
||||
|
||||
Reference in new issue
Block a user