import atexit import os import pathlib import tempfile import unittest # Point the engine at a fresh empty save to keep collection-dependent tests hermetic. import engine.collection as collection from engine import bosses from engine.battle import Battle from engine.plants import make_leveled from engine.unit import TEAM_ENEMY, TEAM_PLAYER _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 BossSchedulingTest(unittest.TestCase): def test_boss_appears_on_multiples_of_20(self): self.assertEqual(bosses.boss_for_floor(20), "Stones") self.assertEqual(bosses.boss_for_floor(40), "Stones") self.assertEqual(bosses.boss_for_floor(100), "Stones") self.assertIsNone(bosses.boss_for_floor(19)) self.assertIsNone(bosses.boss_for_floor(1)) def test_boss_stats_are_extreme(self): u = bosses.make_boss("Stones", 20) # very low speed, medium defense, high health, medium attack self.assertLessEqual(u.speed, 40) self.assertGreaterEqual(u.max_hp, 900) self.assertGreaterEqual(u.defense, 80) self.assertTrue(60 <= u.attack <= 200) class BossMechanicTest(unittest.TestCase): def _battle(self): a = make_leveled("Rose", TEAM_PLAYER, 20, 1) a.max_hp = 5000 a.hp = 5000 u = bosses.make_boss("Stones", 20) b = Battle([a], [u]) b.start() return b, a, u def test_landslide_blocked_until_three_stacks(self): b, a, u = self._battle() b.current = u pebble, landslide = u.skills u.acted = False self.assertFalse(b.use_skill(u, landslide, a)) # 0 stacks: blocked self.assertEqual(b.valid_skill_targets(u, landslide), []) for _ in range(3): u.acted = False self.assertTrue(b.use_skill(u, pebble, a)) self.assertEqual(u.status["boss_stacks"], 3) u.acted = False self.assertTrue(b.use_skill(u, landslide, a)) # now available self.assertEqual(u.status["boss_stacks"], 0) # released def test_boss_stacks_cap_at_three(self): b, a, u = self._battle() b.current = u pebble, _ = u.skills for _ in range(6): u.acted = False self.assertTrue(b.use_skill(u, pebble, a)) self.assertEqual(u.status["boss_stacks"], 3) # never exceeds 3 def test_boss_ai_pebbles_then_landslides_then_repeats(self): b, a, u = self._battle() moves = [] guard = 0 while len(moves) < 7 and guard < 120 and b.winner is None: guard += 1 if b.current is u and not u.acted: before = u.status.get("boss_stacks", 0) used = b.enemy_ai(u) after = u.status.get("boss_stacks", 0) if used: moves.append("Pebble" if after > before else ("Landslide" if before >= 3 and after == 0 else "?")) b.next_turn() self.assertEqual(moves[:4], ["Pebble", "Pebble", "Pebble", "Landslide"]) self.assertEqual(moves[4:], ["Pebble", "Pebble", "Pebble"]) def test_landslide_hits_harder_than_pebble(self): b, a, u = self._battle() b.current = u pebble, landslide = u.skills for _ in range(3): u.acted = False b.use_skill(u, pebble, a) u.acted = False pebble_dmg = b._hit_damage.get(id(a), 0) a.hp = a.max_hp # reset before landslide u.status["boss_stacks"] = 3 b.use_skill(u, landslide, a) landslide_dmg = b._hit_damage.get(id(a), 0) self.assertGreater(landslide_dmg, pebble_dmg * 2) class BossRewardTest(unittest.TestCase): def setUp(self): data = collection.load() data["owned"] = ["Rose"] data["bosses_fought"] = [] data["runes"] = [] collection.save(data) def test_mark_boss_fought(self): self.assertEqual(collection.bosses_fought(), set()) collection.mark_boss_fought("Stones") self.assertEqual(collection.bosses_fought(), {"Stones"}) def test_boss_rune_drop_is_high_chance_and_select(self): colors = set() total = 0 for _ in range(300): r = collection.boss_rune_drop("Stones") if r: total += 1 self.assertIn(r["color"], ("ruby", "amethyst")) colors.add(r["color"]) self.assertGreaterEqual(total, 150) # ~80% chance self.assertEqual(colors, {"ruby", "amethyst"}) if __name__ == "__main__": unittest.main()