#!/usr/bin/env python3 import unittest from pathlib import Path from types import SimpleNamespace from engine import maze from rats import ( GAME_END_LEVEL_CLEAR, GAME_END_RUN_COMPLETE, MiceMaze, RUN_COMPLETE_MUSIC, ) def build_dummy(level_index): dummy = MiceMaze.__new__(MiceMaze) dummy.map = SimpleNamespace(source_path=Path("/tmp/level.dat")) dummy.current_level = level_index dummy.total_levels = maze.DEFAULT_LEVELS_PER_DAT_FILE dummy.points = 1234 dummy.run_recorded = False dummy.combined_scores = None dummy.game_status = "game" dummy.game_end = (False, None) dummy.menu_screen = None dummy.units = {} dummy.assets = {"BMP_WEWIN": object(), "end": object(), "clear": object(), "lose": object()} dummy.explosions = {"LEFT": object(), "RIGHT": object(), "UP": object(), "DOWN": object()} dummy.count_rats = lambda: 0 dummy.start_menu_animation_started_at = 0 dummy._difficulty_config = lambda: {"label": "Hard"} sounds = [] dialogs = [] stats = [] saves = [] load_calls = [] dummy.render_engine = SimpleNamespace( stop_sound=lambda: sounds.append(("stop",)), play_sound=lambda *args, **kwargs: sounds.append((args, kwargs)), dialog=lambda *args, **kwargs: dialogs.append((args, kwargs)), ) dummy.profile_integration = SimpleNamespace( update_game_stats=lambda score, completed=True: stats.append((score, completed)) or True, get_device_leaderboard=lambda limit: [{"user_id": "Player1", "best_score": 1234}][:limit], get_profile_name=lambda: "Player1", ) dummy.save_score = lambda: saves.append(dummy.points) dummy.load_level = lambda *args, **kwargs: load_calls.append((args, kwargs)) return dummy, sounds, dialogs, stats, saves, load_calls class FinalLevelFlowTests(unittest.TestCase): def test_debug_flag_helper_opens_final_dialog_without_recording_score(self): dummy, _, _, stats, saves, _ = build_dummy(3) dummy.activate_debug_run_complete_dialog(score=777) self.assertEqual(dummy.current_level, dummy.total_levels - 1) self.assertEqual(dummy.points, 777) self.assertEqual(dummy.game_end, (True, GAME_END_RUN_COMPLETE)) self.assertTrue(dummy.run_recorded) self.assertEqual(dummy.game_status, "paused") self.assertEqual(stats, []) self.assertEqual(saves, []) def test_run_complete_screen_uses_dedicated_music(self): dummy, _, _, _, _, _ = build_dummy(3) music_calls = [] dummy.render_engine.play_music = lambda track, loop=True: music_calls.append((track, loop)) or True dummy.render_engine.pause_music = lambda: music_calls.append(("pause", False)) dummy.game_end = (True, GAME_END_RUN_COMPLETE) dummy.update_background_music() self.assertEqual(music_calls, [(RUN_COMPLETE_MUSIC, True)]) def test_advance_level_does_not_wrap_after_last_dat_level(self): dummy, _, _, stats, saves, load_calls = build_dummy(maze.DEFAULT_LEVELS_PER_DAT_FILE - 1) dummy.advance_level() self.assertEqual(dummy.game_end, (True, GAME_END_RUN_COMPLETE)) self.assertTrue(dummy.run_recorded) self.assertEqual(stats, [(1234, True)]) self.assertEqual(saves, [1234]) self.assertEqual(load_calls, []) def test_advance_level_starts_next_dat_level_immediately(self): dummy, _, _, _, _, load_calls = build_dummy(4) dummy.advance_level() self.assertEqual( load_calls, [((5,), {"preserve_points": True, "show_menu": False})], ) def test_advance_level_starts_non_dat_level_immediately(self): dummy, _, _, _, _, load_calls = build_dummy(0) dummy.map = SimpleNamespace(source_path=Path("/tmp/level.json")) dummy.advance_level() self.assertEqual( load_calls, [((0,), {"preserve_points": True, "show_menu": False})], ) def test_regular_level_clear_keeps_run_open(self): dummy, sounds, dialogs, stats, saves, _ = build_dummy(4) result = dummy.game_over() self.assertTrue(result) self.assertEqual(dummy.game_end, (True, GAME_END_LEVEL_CLEAR)) self.assertFalse(dummy.run_recorded) self.assertEqual(dummy.combined_scores, [{"user_id": "Player1", "best_score": 1234}]) self.assertEqual(stats, []) self.assertEqual(saves, []) self.assertEqual( sounds, [ ("stop",), (("VICTORY.WAV",), {}), ], ) dummy.game_over() self.assertEqual(dialogs[-1][0][0], "Level 5 Clear! Points: 1234") self.assertIs(dialogs[-1][1]["image"], dummy.assets["clear"]) def test_defeat_dialog_uses_lose_art(self): dummy, sounds, dialogs, stats, saves, _ = build_dummy(4) dummy.count_rats = lambda: 201 result = dummy.game_over() self.assertTrue(result) self.assertEqual(dummy.game_end, (True, "defeat")) self.assertEqual(stats, [(1234, False)]) self.assertEqual(saves, [1234]) self.assertEqual( sounds, [ ("stop",), (("WEWIN.WAV",), {}), ], ) dummy.game_over() self.assertEqual(dialogs[-1][0][0], "Game Over: Mice are too many!") self.assertIs(dialogs[-1][1]["image"], dummy.assets["lose"]) self.assertEqual(dialogs[-1][1]["image_scale"], 0.48) self.assertEqual(dialogs[-1][1]["scores"], dummy.combined_scores) self.assertEqual( dialogs[-1][1]["subtitle"], "Reached level: 5\nPress Return to go back to the start menu", ) def test_final_dat_level_records_score_and_shows_dedicated_dialog(self): dummy, sounds, dialogs, stats, saves, _ = build_dummy(maze.DEFAULT_LEVELS_PER_DAT_FILE - 1) result = dummy.game_over() self.assertTrue(result) self.assertEqual(dummy.game_end, (True, GAME_END_RUN_COMPLETE)) self.assertTrue(dummy.run_recorded) self.assertEqual(stats, [(1234, True)]) self.assertEqual(saves, [1234]) self.assertEqual(dummy.combined_scores, [{"user_id": "Player1", "best_score": 1234}]) self.assertEqual( sounds, [ ("stop",), (("VICTORY.WAV",), {}), (("WELLDONE.WAV",), {"tag": "effects"}), ], ) dummy.game_over() self.assertEqual(dialogs[-1][0][0], "THE END") self.assertEqual(dialogs[-1][1]["style"], "run_complete") self.assertEqual(dialogs[-1][1]["current_score"], 1234) self.assertIs(dialogs[-1][1]["image"], dummy.assets["end"]) def test_return_after_final_dialog_goes_back_to_start_menu(self): dummy, _, _, _, _, load_calls = build_dummy(maze.DEFAULT_LEVELS_PER_DAT_FILE - 1) dummy.game_end = (True, GAME_END_RUN_COMPLETE) dummy.game_status = "paused" dummy.reset_game() self.assertEqual( load_calls, [((0,), {"preserve_points": False, "show_menu": True, "menu_screen": "start"})], ) def test_return_after_defeat_goes_back_to_start_menu(self): dummy, _, _, _, _, load_calls = build_dummy(4) dummy.game_end = (True, "defeat") dummy.game_status = "paused" dummy.reset_game() self.assertEqual( load_calls, [((0,), {"preserve_points": False, "show_menu": True, "menu_screen": "start"})], ) def test_toggle_pause_ignores_game_end_dialogs(self): dummy, _, _, _, _, _ = build_dummy(2) dummy.game_status = "paused" dummy.game_end = (True, GAME_END_LEVEL_CLEAR) dummy.toggle_pause() self.assertEqual(dummy.game_status, "paused") self.assertEqual(dummy.game_end, (True, GAME_END_LEVEL_CLEAR)) def test_toggle_pause_still_toggles_real_pause(self): dummy, _, _, _, _, _ = build_dummy(2) dummy.toggle_pause() self.assertEqual(dummy.game_status, "paused") dummy.toggle_pause() self.assertEqual(dummy.game_status, "game") if __name__ == "__main__": unittest.main()