Add comprehensive test suite for game mechanics and level handling
- Introduced `test_final_level_flow.py` to validate final level transitions and game end scenarios. - Created `test_game_over_flow.py` to ensure game over conditions trigger correctly based on rat counts. - Implemented `test_keybindings.py` to verify keybinding configurations and their context-specific actions. - Developed `test_level_editor.py` to assess level editor functionalities and layout computations. - Added `test_level_io.py` for testing level data serialization and deserialization. - Established `test_loop_logic_parity.py` to ensure consistent game state across multiple simulation runs. - Created `test_non_regression.py` to simulate game behavior and capture states for future verification. - Implemented `test_verify.py` to compare current game states against a golden master for regression detection.
This commit is contained in:
@@ -10,6 +10,7 @@ from engine import maze, sdl2 as engine, controls, graphics, unit_manager, scori
|
||||
from engine.state_machine import GameState
|
||||
from engine.collision_system import CollisionSystem
|
||||
from units import points
|
||||
from units.unit import UnitType
|
||||
from engine.user_profile_integration import UserProfileIntegration
|
||||
from runtime_paths import bundle_path
|
||||
|
||||
@@ -394,18 +395,6 @@ class MiceMaze:
|
||||
|
||||
# ==================== GAME LOGIC ====================
|
||||
|
||||
def refill_ammo(self):
|
||||
for ammo_type, data in self.ammo.items():
|
||||
if ammo_type == "bomb":
|
||||
if random.random() < 0.02:
|
||||
data["count"] = min(data["count"] + 1, data["max"])
|
||||
elif ammo_type == "mine":
|
||||
if random.random() < 0.05:
|
||||
data["count"] = min(data["count"] + 1, data["max"])
|
||||
elif ammo_type == "gas":
|
||||
if random.random() < 0.01:
|
||||
data["count"] = min(data["count"] + 1, data["max"])
|
||||
|
||||
def _can_adjust_audio_menu(self):
|
||||
if self.game_end[0]:
|
||||
return False
|
||||
@@ -493,22 +482,25 @@ class MiceMaze:
|
||||
self.render_engine.delete_tag("effect")
|
||||
self.render_engine.delete_tag("cave")
|
||||
|
||||
# Clear collision system for new frame
|
||||
# Clear collision system and legacy dictionaries
|
||||
self.collision_system.clear()
|
||||
self.unit_positions.clear()
|
||||
self.unit_positions_before.clear()
|
||||
|
||||
# First pass: Register all units in collision system BEFORE move
|
||||
# This allows bombs/gas to find victims during their move()
|
||||
for unit in sorted(self.units.values(), key=lambda u: int(u.id)):
|
||||
# Calculate bbox if not yet set (first frame)
|
||||
if not hasattr(unit, 'bbox') or unit.bbox == (0, 0, 0, 0):
|
||||
# Temporary bbox based on position
|
||||
# Sort units by ID for deterministic execution
|
||||
sorted_units = sorted(self.units.values(), key=lambda u: int(u.id))
|
||||
|
||||
# Pass 1: MOVE and REGISTER
|
||||
for unit in sorted_units:
|
||||
# 1a. Move unit (logic)
|
||||
unit.move()
|
||||
|
||||
# 1b. Register final position in collision system
|
||||
if unit.bbox == (0.0, 0.0, 0.0, 0.0):
|
||||
x_pos = unit.position[0] * self.cell_size
|
||||
y_pos = unit.position[1] * self.cell_size
|
||||
unit.bbox = (x_pos, y_pos, x_pos + self.cell_size, y_pos + self.cell_size)
|
||||
unit.bbox = (float(x_pos), float(y_pos), float(x_pos + self.cell_size), float(y_pos + self.cell_size))
|
||||
|
||||
# Register unit in optimized collision system
|
||||
self.collision_system.register_unit(
|
||||
unit.id,
|
||||
unit.bbox,
|
||||
@@ -520,40 +512,17 @@ class MiceMaze:
|
||||
# Maintain backward compatibility dictionaries
|
||||
self.unit_positions.setdefault(unit.position, []).append(unit)
|
||||
self.unit_positions_before.setdefault(unit.position_before, []).append(unit)
|
||||
|
||||
# Second pass: move all units (can now access collision system)
|
||||
for unit in sorted(self.units.values(), key=lambda u: int(u.id)):
|
||||
unit.move()
|
||||
|
||||
# Third pass: Update collision system with new positions after move
|
||||
self.collision_system.clear()
|
||||
self.unit_positions.clear()
|
||||
self.unit_positions_before.clear()
|
||||
|
||||
for unit in sorted(self.units.values(), key=lambda u: int(u.id)):
|
||||
# Register with updated positions/bbox from move()
|
||||
self.collision_system.register_unit(
|
||||
unit.id,
|
||||
unit.bbox,
|
||||
unit.position,
|
||||
unit.position_before,
|
||||
unit.collision_layer
|
||||
)
|
||||
|
||||
self.unit_positions.setdefault(unit.position, []).append(unit)
|
||||
self.unit_positions_before.setdefault(unit.position_before, []).append(unit)
|
||||
# Fourth pass: check collisions and draw
|
||||
for unit in sorted(self.units.values(), key=lambda u: int(u.id)):
|
||||
|
||||
# Pass 2: RESOLVE and DRAW
|
||||
for unit in sorted_units:
|
||||
unit.collisions()
|
||||
unit.draw()
|
||||
|
||||
self.graphics.draw_cave_foreground()
|
||||
self.render_engine.draw_pointer(self.pointer[0] * self.cell_size, self.pointer[1] * self.cell_size)
|
||||
|
||||
|
||||
|
||||
self.render_engine.update_status(f"Mice: {self.unit_manager.count_rats()} - Points: {self.points}")
|
||||
self.refill_ammo()
|
||||
self.unit_manager.refill_ammo()
|
||||
self.render_engine.update_ammo(self.ammo, self.graphics.assets)
|
||||
self.controls.scroll()
|
||||
self.render_engine.new_cycle(50, self.update_maze)
|
||||
@@ -603,7 +572,7 @@ class MiceMaze:
|
||||
self._record_run_result(completed=False)
|
||||
|
||||
return True
|
||||
if not count_rats and not any(isinstance(unit, points.Point) for unit in self.units.values()):
|
||||
if not count_rats and not any(unit.type == UnitType.POINT for unit in self.units.values()):
|
||||
self.render_engine.stop_sound()
|
||||
self.render_engine.play_sound("VICTORY.WAV")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user