Initial commit: Plants turn-based battle game

This commit is contained in:
2026-08-28 14:26:07 +02:00
commit 6840b715de
158 changed files with 13352 additions and 0 deletions
+313
View File
@@ -0,0 +1,313 @@
import atexit
import os
import pathlib
import tempfile
import time
import unittest
# Point the engine at a fresh empty save to keep these tests hermetic.
import engine.collection as collection
_fd, _tmp_path = tempfile.mkstemp(suffix=".json")
os.close(_fd)
_TMP = pathlib.Path(_tmp_path)
_ORIG_DATA_FILE = collection._DATA_FILE
collection._DATA_FILE = _TMP
@atexit.register
def _restore_collection():
collection._DATA_FILE = _ORIG_DATA_FILE
if _TMP.exists():
_TMP.unlink()
class SoilTest(unittest.TestCase):
def setUp(self):
data = collection.load()
data["owned"] = ["Rose", "Lavender", "Zinnia"]
data["soil"] = {"seed_cd_until": 0.0, "seeds": [], "plots": []}
data["water"] = 10
data["fertilizer"] = 10
collection.save(data)
def test_retrieve_seed(self):
self.assertEqual(collection.retrieve_seed("Rose"), "")
state = collection.soil_state()
self.assertEqual(len(state["seeds"]), 1)
self.assertEqual(state["seeds"][0]["plant"], "Rose")
self.assertGreater(state["cd_left"], 0) # cooldown set
def test_retrieve_respects_cooldown(self):
collection.retrieve_seed("Rose")
self.assertNotEqual(collection.retrieve_seed("Lavender"), "")
state = collection.soil_state()
self.assertEqual(len(state["seeds"]), 1)
def test_retrieve_requires_owned(self):
self.assertNotEqual(collection.retrieve_seed("NotAPlant"), "")
data = collection.load()
data["discovered"] = ["Marigold"]
collection.save(data)
self.assertNotEqual(collection.retrieve_seed("Marigold"), "")
def test_plant_creates_stage_zero_plot(self):
collection.retrieve_seed("Rose")
self.assertEqual(collection.plant_seed(0), "")
state = collection.soil_state()
self.assertEqual(len(state["plots"]), 1)
self.assertEqual(state["seeds"], [])
plot = state["plots"][0]
self.assertEqual(plot["stage"], 0)
self.assertFalse(plot["grown"])
self.assertFalse(plot["stalled"]) # stage not yet full
def test_stage_fills_over_time(self):
data = collection.load()
data["soil"]["plots"] = [{"plant": "Rose", "stage_started_at": 0, "stage": 0, "water": 0}]
collection.save(data)
state = collection.soil_state()
plot = state["plots"][0]
self.assertEqual(plot["stage_progress"], 1.0)
self.assertTrue(plot["stalled"]) # stage full -> needs water
self.assertFalse(plot["grown"]) # but not the last stage
def test_water_requires_stalled(self):
collection.retrieve_seed("Rose")
collection.plant_seed(0)
self.assertNotEqual(collection.water_plot(0), "") # not stalled yet
def test_water_advances_stage(self):
data = collection.load()
data["soil"]["plots"] = [{"plant": "Rose", "stage_started_at": 0, "stage": 0, "water": 0}]
collection.save(data)
self.assertEqual(collection.water_plot(0), "")
self.assertEqual(collection.available_fertilizer(), 9)
state = collection.soil_state()
self.assertEqual(state["plots"][0]["stage"], 1)
self.assertEqual(state["plots"][0]["water"], 1)
self.assertFalse(state["plots"][0]["stalled"]) # new stage just started
def test_water_requires_fertilizer_pool(self):
data = collection.load()
data["fertilizer"] = 0
data["soil"]["plots"] = [{"plant": "Rose", "stage_started_at": 0, "stage": 0, "water": 0}]
collection.save(data)
self.assertNotEqual(collection.water_plot(0), "")
def test_harvest_requires_grown(self):
collection.retrieve_seed("Rose")
collection.plant_seed(0)
self.assertNotEqual(collection.harvest_plot(0), "") # not grown
def test_grow_all_stages_then_harvest(self):
# Rose is super-rare -> 4 stages. Plant with an old stage_started_at so each
# stage is instantly full, then water through stages 0..2.
data = collection.load()
data["soil"]["plots"] = [{"plant": "Rose", "stage_started_at": 0, "stage": 0, "water": 0}]
collection.save(data)
total = collection.GROWTH_STAGES["super_rare"]
for expected in range(total - 1):
self.assertNotEqual(collection.harvest_plot(0), "") # not grown mid-way
self.assertEqual(collection.water_plot(0), "", f"water to advance to {expected + 1}")
# each watered stage is instantly full again (stage_started_at=now)
data = collection.load()
data["soil"]["plots"][0]["stage_started_at"] = 0
collection.save(data)
state = collection.soil_state()
self.assertTrue(state["plots"][0]["grown"])
dup_before = collection.get_progress("Rose")["duplicates"]
self.assertEqual(collection.harvest_plot(0), "")
state = collection.soil_state()
self.assertEqual(state["plots"], [])
self.assertEqual(collection.get_progress("Rose")["duplicates"], dup_before + 1)
def test_higher_rarity_more_stages(self):
self.assertGreater(collection.GROWTH_STAGES["super_super_rare"],
collection.GROWTH_STAGES["super_rare"])
self.assertGreater(collection.GROWTH_STAGES["super_rare"],
collection.GROWTH_STAGES["rare"])
self.assertGreater(collection.GROWTH_STAGES["rare"],
collection.GROWTH_STAGES["common"])
def test_default_one_plot_unlocked(self):
state = collection.soil_state()
self.assertEqual(state["plots_unlocked"], 1)
self.assertEqual(state["plots_max"], collection.SOIL_PLOTS_MAX)
def test_soil_gated_by_owned_plants(self):
data = collection.load()
data["owned"] = ["Rose", "Lavender"]
collection.save(data)
self.assertFalse(collection.soil_unlocked())
data = collection.load()
data["owned"] = [f"Plant{i}" for i in range(collection.SOIL_GATE_PLANTS)]
for n in data["owned"]:
data["progress"].setdefault(n, {"level": 1, "stage": 1, "xp": 0,
"duplicates": 0, "watered_at": 0})
collection.save(data)
self.assertTrue(collection.soil_unlocked())
def test_plot_unlock_costs_escalate(self):
self.assertEqual(collection.plot_unlock_cost(2), (10, 5))
self.assertEqual(collection.plot_unlock_cost(3), (50, 20))
self.assertEqual(collection.plot_unlock_cost(4), (100, 50))
self.assertEqual(collection.plot_unlock_cost(5), (0, 0))
def test_plant_limited_to_unlocked_plots(self):
data = collection.load()
data["soil"]["plots"] = [{"plant": "Rose", "stage_started_at": 0, "stage": 0, "water": 0}]
collection.save(data)
# 1 unlocked, 1 planted -> planting another seed fails
collection.retrieve_seed("Lavender")
self.assertEqual(len(collection.soil_state()["seeds"]), 1)
self.assertNotEqual(collection.plant_seed(0), "")
def test_unlock_plot_spends_coins_and_fertilizer(self):
data = collection.load()
data["coins"] = 10
data["fertilizer"] = 5
collection.save(data)
self.assertEqual(collection.unlock_plot(), "")
self.assertEqual(collection.soil_state()["plots_unlocked"], 2)
self.assertEqual(collection.available_coins(), 0)
self.assertEqual(collection.available_fertilizer(), 0)
# too little fertilizer now
self.assertNotEqual(collection.unlock_plot(), "")
def test_unlock_plot_requires_coins(self):
data = collection.load()
data["coins"] = 0
data["fertilizer"] = 50
collection.save(data)
self.assertNotEqual(collection.unlock_plot(), "")
def test_unlock_plot_requires_fertilizer(self):
data = collection.load()
data["coins"] = 50
data["fertilizer"] = 4
collection.save(data)
self.assertNotEqual(collection.unlock_plot(), "")
def test_unlock_plot_max_four(self):
data = collection.load()
data["coins"] = 500
data["fertilizer"] = 500
collection.save(data)
for _ in range(3):
self.assertEqual(collection.unlock_plot(), "")
self.assertEqual(collection.soil_state()["plots_unlocked"], collection.SOIL_PLOTS_MAX)
self.assertNotEqual(collection.unlock_plot(), "") # capped
def test_after_unlock_extra_plot_usable(self):
data = collection.load()
data["coins"] = 10
data["fertilizer"] = 5
data["soil"]["plots"] = [{"plant": "Rose", "stage_started_at": 0, "stage": 0, "water": 0}]
collection.save(data)
collection.unlock_plot() # now 2 unlocked, 1 used
collection.retrieve_seed("Lavender")
self.assertEqual(collection.plant_seed(0), "")
self.assertEqual(len(collection.soil_state()["plots"]), 2)
class AutoFarmerTest(unittest.TestCase):
def setUp(self):
data = collection.load()
data["owned"] = ["Rose", "Lavender", "Zinnia"]
data["soil"] = {"seed_cd_until": 0.0, "seeds": [], "plots": []}
data["fertilizer"] = 10
data["coins"] = 100
data["auto_farmer"] = False
collection.save(data)
def _stalled_plot(self):
data = collection.load()
data["soil"]["plots"] = [{"plant": "Rose", "stage_started_at": 0, "stage": 0, "water": 0}]
collection.save(data)
def test_accrue_fertilizer_grants_one_per_hour(self):
data = collection.load()
data["fertilizer_accrued_at"] = time.time() - 7200 # 2 hours ago
data["fertilizer"] = 0
collection.save(data)
self.assertEqual(collection.accrue_fertilizer(), 2)
self.assertEqual(collection.available_fertilizer(), 2)
def test_accrue_fertilizer_caps(self):
data = collection.load()
data["fertilizer_accrued_at"] = time.time() - 7200
data["fertilizer"] = 99
collection.save(data)
self.assertEqual(collection.accrue_fertilizer(), 1)
self.assertEqual(collection.available_fertilizer(), 100)
def test_add_fertilizer_caps(self):
data = collection.load()
data["fertilizer"] = 99
collection.save(data)
collection.add_fertilizer(10)
self.assertEqual(collection.available_fertilizer(), 100)
def test_buy_auto_farmer(self):
self.assertEqual(collection.buy_auto_farmer(), "")
self.assertTrue(collection.auto_farmer_owned())
self.assertEqual(collection.available_coins(), 0)
self.assertNotEqual(collection.buy_auto_farmer(), "") # already owned
def test_buy_auto_farmer_requires_coins(self):
data = collection.load()
data["coins"] = 50
collection.save(data)
self.assertNotEqual(collection.buy_auto_farmer(), "")
self.assertFalse(collection.auto_farmer_owned())
def test_buy_activates_farmer(self):
self.assertEqual(collection.buy_auto_farmer(), "")
self.assertTrue(collection.auto_farmer_active())
def test_toggle_auto_farmer(self):
collection.buy_auto_farmer()
self.assertTrue(collection.auto_farmer_active())
self.assertFalse(collection.toggle_auto_farmer()) # turned off
self.assertFalse(collection.auto_farmer_active())
self.assertTrue(collection.toggle_auto_farmer()) # back on
self.assertTrue(collection.auto_farmer_owned()) # still owns it
def test_auto_farm_respects_toggle(self):
self._stalled_plot()
collection.buy_auto_farmer()
collection.toggle_auto_farmer() # turn it off
self.assertEqual(collection.auto_farm(), "Auto farmer off")
self.assertEqual(collection.available_fertilizer(), 10) # nothing spent
self.assertEqual(collection.soil_state()["plots"][0]["stage"], 0)
def test_auto_farm_fertilizes_stalled_plot(self):
self._stalled_plot()
self.assertEqual(collection.buy_auto_farmer(), "")
self.assertEqual(collection.auto_farm(), "")
self.assertEqual(collection.available_fertilizer(), 9)
self.assertEqual(collection.soil_state()["plots"][0]["stage"], 1)
def test_auto_farm_requires_farmer(self):
self._stalled_plot()
self.assertEqual(collection.auto_farm(), "No auto farmer")
def test_auto_farm_does_not_plant_or_harvest(self):
data = collection.load()
data["soil"]["plots"] = [
{"plant": "Rose", "stage_started_at": 0, "stage": 0, "water": 0}, # stalled
{"plant": "Rose", "stage_started_at": 0, "stage": 3, "water": 0}, # grown (epic)
]
data["soil"]["seeds"] = [{"plant": "Lavender"}]
collection.save(data)
self.assertEqual(collection.buy_auto_farmer(), "")
self.assertEqual(collection.auto_farm(), "")
state = collection.soil_state()
self.assertEqual(len(state["plots"]), 2) # grown plot not harvested
self.assertEqual(len(state["seeds"]), 1) # seed not planted
self.assertEqual(state["plots"][0]["stage"], 1) # stalled plot advanced
if __name__ == "__main__":
unittest.main()