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
+19
-14
@@ -370,15 +370,17 @@ class ActionTestCase(unittest.TestCase):
|
||||
self.assert_code("already_watered", self.engine.water, 2)
|
||||
|
||||
# ------------------------------------------------------------------ 收获
|
||||
def test_harvest_pays_the_market_price(self):
|
||||
def test_harvest_lands_in_the_granary_at_market_price(self):
|
||||
self.engine.plant(1, "wheat")
|
||||
self.engine.state.plots[0].growth = config.CROPS["wheat"]["grow_days"]
|
||||
gold = self.engine.state.gold
|
||||
with mock.patch("farm.engine.random.random", return_value=0.99):
|
||||
result = self.engine.harvest(1)
|
||||
self.assertEqual(result["quantity"], 1)
|
||||
self.assertEqual(result["earned"], config.CROPS["wheat"]["base_price"])
|
||||
self.assertEqual(self.engine.state.gold, gold + result["earned"])
|
||||
self.assertEqual(result["price"], self.engine.market_price("wheat"))
|
||||
self.assertEqual(result["value"], result["price"] * result["quantity"])
|
||||
self.assertEqual(self.engine.state.inventory["wheat"], 1, "收成先进谷仓")
|
||||
self.assertEqual(self.engine.state.gold, gold, "收获本身不给钱,卖了才给")
|
||||
self.assertTrue(self.engine.state.plots[0].is_empty)
|
||||
|
||||
def test_lucky_harvest_doubles_the_yield(self):
|
||||
@@ -387,7 +389,7 @@ class ActionTestCase(unittest.TestCase):
|
||||
with mock.patch("farm.engine.random.random", return_value=0.0):
|
||||
result = self.engine.harvest(1)
|
||||
self.assertEqual(result["quantity"], 2)
|
||||
self.assertEqual(result["earned"], config.CROPS["wheat"]["base_price"] * 2)
|
||||
self.assertEqual(self.engine.state.inventory["wheat"], 2)
|
||||
|
||||
def test_harvest_before_ripe_is_refused(self):
|
||||
self.engine.plant(1, "carrot")
|
||||
@@ -458,15 +460,17 @@ class ActionTestCase(unittest.TestCase):
|
||||
self.assert_code("bad_param", self.engine.feed, aid, 0)
|
||||
self.assert_code("bad_param", self.engine.feed, aid, "many")
|
||||
|
||||
def test_collect_pays_for_every_pending_product(self):
|
||||
def test_collect_lands_every_pending_product_in_the_granary(self):
|
||||
self.engine.buy_animal("chicken")
|
||||
animal = self.engine.state.animals[0]
|
||||
animal.pending = 3
|
||||
gold = self.engine.state.gold
|
||||
result = self.engine.collect(animal.aid)
|
||||
self.assertEqual(result["quantity"], 3)
|
||||
self.assertEqual(result["earned"], config.ANIMALS["chicken"]["base_price"] * 3)
|
||||
self.assertEqual(self.engine.state.gold, gold + result["earned"])
|
||||
self.assertEqual(result["price"], self.engine.market_price("animal:chicken"))
|
||||
self.assertEqual(result["value"], result["price"] * 3)
|
||||
self.assertEqual(self.engine.state.inventory["animal:chicken"], 3, "产物先入库")
|
||||
self.assertEqual(self.engine.state.gold, gold, "收取本身不给钱,卖了才给")
|
||||
self.assertEqual(animal.pending, 0)
|
||||
|
||||
def test_collect_without_output_is_refused(self):
|
||||
@@ -563,25 +567,26 @@ class GrowthTestCase(unittest.TestCase):
|
||||
self.engine.plant(1, "carrot") # 3 天,刚好够观察缺水停摆
|
||||
self.assertEqual(self.engine.state.plots[0].moisture, config.PLANT_INITIAL_MOISTURE)
|
||||
|
||||
self.engine.advance_day()
|
||||
# 天气按天随机,这里钉死成晴天(每天蒸发 1 点),才观察得到"断水停摆"。
|
||||
self.engine.advance_day(weather="sunny")
|
||||
self.assertEqual(self.engine.state.plots[0].growth, 1)
|
||||
|
||||
self.engine.advance_day()
|
||||
self.engine.advance_day(weather="sunny")
|
||||
self.assertEqual(self.engine.state.plots[0].moisture, 0)
|
||||
self.assertEqual(self.engine.state.plots[0].growth, 2)
|
||||
|
||||
events = self.engine.advance_day()
|
||||
events = self.engine.advance_day(weather="sunny")
|
||||
self.assertEqual(self.engine.state.plots[0].growth, 2, "没水就不该再长")
|
||||
self.assertTrue(any("缺水" in text for text in events))
|
||||
|
||||
def test_watering_resumes_growth(self):
|
||||
self.engine.plant(1, "carrot")
|
||||
self.engine.advance_day()
|
||||
self.engine.advance_day()
|
||||
self.engine.advance_day() # 此刻缺水停摆
|
||||
self.engine.advance_day(weather="sunny")
|
||||
self.engine.advance_day(weather="sunny")
|
||||
self.engine.advance_day(weather="sunny") # 此刻缺水停摆
|
||||
self.assertEqual(self.engine.state.plots[0].growth, 2, "断水期间不该生长")
|
||||
self.engine.water(1) # 浇到 3 点水
|
||||
self.engine.advance_day()
|
||||
self.engine.advance_day(weather="sunny")
|
||||
self.assertEqual(self.engine.state.plots[0].growth, 3, "浇水后又开始长了")
|
||||
self.assertTrue(self.engine.state.plots[0].is_ripe)
|
||||
|
||||
|
||||
Reference in new issue
Block a user