Add non-regression test states and Bluetooth diagnostic script
- Introduced a new JSON file containing non-regression test states with detailed unit information, including positions, ages, and movement directions across multiple frames. - Added a shell script for Bluetooth diagnostics that checks system information, Bluetooth binaries, running processes, D-Bus status, Bluetooth controller details, and audio stack status, providing a comprehensive overview for troubleshooting.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# Game constants and configuration
|
||||
|
||||
LEVEL_MUSIC_CONFIG = "level_music"
|
||||
START_MENU_MUSIC = "High_Score_Garden.mp3"
|
||||
RUN_COMPLETE_MUSIC = "Sunset_At_Pixel_Gardens.mp3"
|
||||
START_MENU_ANIMATION = "anim/start_mice.gif"
|
||||
SUPPORTED_MUSIC_EXTENSIONS = {".mp3", ".ogg", ".wav"}
|
||||
BASE_INITIAL_RATS = 5
|
||||
DEFAULT_DIFFICULTY = "easy"
|
||||
DIFFICULTY_ALIASES = {
|
||||
"medium": "normal",
|
||||
"normale": "normal",
|
||||
}
|
||||
START_MENU_AUDIO_OPTIONS = (
|
||||
("sound_volume", "Suono"),
|
||||
("music_volume", "Musica"),
|
||||
)
|
||||
VOLUME_STEP = 5
|
||||
|
||||
GAME_END_LEVEL_CLEAR = "level_clear"
|
||||
GAME_END_DEFEAT = "defeat"
|
||||
GAME_END_RUN_COMPLETE = "run_complete"
|
||||
|
||||
DIFFICULTY_OPTIONS = (
|
||||
{
|
||||
"key": "easy",
|
||||
"label": "Easy",
|
||||
"starting_rats_multiplier": 1,
|
||||
"speed_multiplier": 1.0,
|
||||
"fill": (235, 246, 234),
|
||||
"accent": (88, 148, 82),
|
||||
},
|
||||
{
|
||||
"key": "normal",
|
||||
"label": "Normal",
|
||||
"starting_rats_multiplier": 2,
|
||||
"speed_multiplier": 1.5,
|
||||
"fill": (252, 242, 223),
|
||||
"accent": (204, 146, 44),
|
||||
},
|
||||
{
|
||||
"key": "hard",
|
||||
"label": "Hard",
|
||||
"starting_rats_multiplier": 3,
|
||||
"speed_multiplier": 2.0,
|
||||
"fill": (251, 229, 229),
|
||||
"accent": (188, 68, 68),
|
||||
},
|
||||
)
|
||||
|
||||
DIFFICULTY_OPTIONS_BY_KEY = {option["key"]: option for option in DIFFICULTY_OPTIONS}
|
||||
|
||||
START_MENU_COLORS = {
|
||||
"panel_fill": (255, 255, 255),
|
||||
"panel_border": (52, 52, 52),
|
||||
"header_fill": (255, 255, 255),
|
||||
"text": (24, 24, 24),
|
||||
"muted": (82, 82, 82),
|
||||
"hint_fill": (255, 255, 255),
|
||||
"track_fill": (212, 215, 216),
|
||||
"card_fill": (255, 255, 255),
|
||||
}
|
||||
|
||||
START_MENU_AUDIO_STYLES = {
|
||||
"sound_volume": {
|
||||
"accent": (214, 146, 62),
|
||||
"fill": (251, 241, 225),
|
||||
},
|
||||
"music_volume": {
|
||||
"accent": (91, 122, 208),
|
||||
"fill": (230, 235, 248),
|
||||
},
|
||||
}
|
||||
+59
-50
@@ -196,8 +196,12 @@ def resolve_keybindings(preferred_profile=None, preferred_file=None, render_engi
|
||||
|
||||
|
||||
class KeyBindings:
|
||||
def __init__(self, game):
|
||||
self.game = game
|
||||
self.bindings = {}
|
||||
|
||||
def _binding_sections_for_action(self):
|
||||
game_end_active, game_end_reason = getattr(self, "game_end", (False, None))
|
||||
game_end_active, game_end_reason = getattr(self.game, "game_end", (False, None))
|
||||
if game_end_active:
|
||||
if game_end_reason == "level_clear":
|
||||
return ["keybinding_level_clear", "keybinding_paused"]
|
||||
@@ -207,12 +211,12 @@ class KeyBindings:
|
||||
return ["keybinding_run_complete", "keybinding_paused"]
|
||||
return ["keybinding_paused"]
|
||||
|
||||
if getattr(self, "game_status", None) == "start_menu":
|
||||
if getattr(self, "menu_screen", None) == "level_intro":
|
||||
if getattr(self.game, "game_status", None) == "start_menu":
|
||||
if getattr(self.game, "menu_screen", None) == "level_intro":
|
||||
return ["keybinding_level_intro", "keybinding_start_menu"]
|
||||
return ["keybinding_start_menu"]
|
||||
|
||||
status = getattr(self, "game_status", None)
|
||||
status = getattr(self.game, "game_status", None)
|
||||
if status:
|
||||
return [f"keybinding_{status}"]
|
||||
|
||||
@@ -222,14 +226,14 @@ class KeyBindings:
|
||||
preferred_profile = None
|
||||
preferred_file = None
|
||||
|
||||
if hasattr(self, "profile_integration") and self.profile_integration:
|
||||
preferred_profile = self.profile_integration.get_setting("keybindings_profile")
|
||||
preferred_file = self.profile_integration.get_setting("keybindings_file")
|
||||
if hasattr(self.game, "profile_integration") and self.game.profile_integration:
|
||||
preferred_profile = self.game.profile_integration.get_setting("keybindings_profile")
|
||||
preferred_file = self.game.profile_integration.get_setting("keybindings_file")
|
||||
|
||||
bindings, source_path, profile_name, reason = resolve_keybindings(
|
||||
preferred_profile=preferred_profile,
|
||||
preferred_file=preferred_file,
|
||||
render_engine=getattr(self, "render_engine", None),
|
||||
render_engine=getattr(self.game, "render_engine", None),
|
||||
)
|
||||
|
||||
self.bindings = self._validate_bindings(bindings, source_path)
|
||||
@@ -255,7 +259,8 @@ class KeyBindings:
|
||||
continue
|
||||
|
||||
method_name = value.split("|", 1)[0]
|
||||
method = getattr(self, method_name, None)
|
||||
# Check both self (KeyBindings) and self.game (MiceMaze)
|
||||
method = getattr(self, method_name, getattr(self.game, method_name, None))
|
||||
if callable(method):
|
||||
validated[section_name][action] = value
|
||||
continue
|
||||
@@ -272,7 +277,7 @@ class KeyBindings:
|
||||
return validated
|
||||
|
||||
def trigger(self, action):
|
||||
if not hasattr(self, "bindings"):
|
||||
if not self.bindings:
|
||||
self.initialize_keybindings()
|
||||
|
||||
value = None
|
||||
@@ -286,77 +291,81 @@ class KeyBindings:
|
||||
|
||||
if "|" in value:
|
||||
method_name, *args = value.split("|")
|
||||
method = getattr(self, method_name, None)
|
||||
method = getattr(self, method_name, getattr(self.game, method_name, None))
|
||||
if callable(method):
|
||||
method(*args)
|
||||
return None
|
||||
|
||||
method = getattr(self, value, None)
|
||||
method = getattr(self, value, getattr(self.game, value, None))
|
||||
if callable(method):
|
||||
method()
|
||||
return None
|
||||
|
||||
|
||||
def spawn_rat(self):
|
||||
self.game.unit_manager.spawn_rat()
|
||||
|
||||
def spawn_new_bomb(self):
|
||||
self.spawn_bomb(self.pointer)
|
||||
self.game.unit_manager.spawn_bomb(self.game.pointer)
|
||||
|
||||
def spawn_new_mine(self):
|
||||
self.spawn_mine(self.pointer)
|
||||
self.game.unit_manager.spawn_mine(self.game.pointer)
|
||||
|
||||
def spawn_new_nuclear_bomb(self):
|
||||
self.spawn_nuclear_bomb(self.pointer)
|
||||
self.game.unit_manager.spawn_nuclear_bomb(self.game.pointer)
|
||||
|
||||
def spawn_new_gas(self):
|
||||
self.spawn_gas()
|
||||
self.game.unit_manager.spawn_gas()
|
||||
|
||||
def toggle_audio(self):
|
||||
self.render_engine.audio = not self.render_engine.audio
|
||||
self.audio = self.render_engine.audio
|
||||
if hasattr(self, "profile_integration") and self.profile_integration:
|
||||
self.profile_integration.set_setting("sound_enabled", self.audio)
|
||||
if not self.render_engine.audio:
|
||||
self.render_engine.stop_sound()
|
||||
self.game.render_engine.audio = not self.game.render_engine.audio
|
||||
self.game.audio = self.game.render_engine.audio
|
||||
if hasattr(self.game, "profile_integration") and self.game.profile_integration:
|
||||
self.game.profile_integration.set_setting("sound_enabled", self.game.audio)
|
||||
if not self.game.render_engine.audio:
|
||||
self.game.render_engine.stop_sound()
|
||||
|
||||
def toggle_pause(self):
|
||||
if getattr(self, "game_end", (False, None))[0]:
|
||||
if getattr(self.game, "game_end", (False, None))[0]:
|
||||
return
|
||||
|
||||
if self.game_status == "game":
|
||||
self.game_status = "paused"
|
||||
if self.game.state_machine.current_state == state_machine.GameState.PLAYING:
|
||||
self.game.state_machine.transition_to(state_machine.GameState.PAUSED)
|
||||
return
|
||||
|
||||
if self.game_status == "paused":
|
||||
self.game_status = "game"
|
||||
if self.game.state_machine.current_state == state_machine.GameState.PAUSED:
|
||||
self.game.state_machine.transition_to(state_machine.GameState.PLAYING)
|
||||
return
|
||||
|
||||
if self.game_status == "start_menu" and getattr(self, "menu_screen", None) == "start":
|
||||
self.reset_game()
|
||||
if self.game.state_machine.current_state == state_machine.GameState.START_MENU:
|
||||
self.game.reset_game()
|
||||
|
||||
def toggle_full_screen(self):
|
||||
self.full_screen = not self.full_screen
|
||||
self.render_engine.full_screen(self.full_screen)
|
||||
self.game.full_screen = not self.game.full_screen
|
||||
self.game.render_engine.full_screen(self.game.full_screen)
|
||||
|
||||
def quit_game(self):
|
||||
self.render_engine.close()
|
||||
self.game.render_engine.close()
|
||||
|
||||
def start_scrolling(self, direction):
|
||||
self.scrolling_direction = direction
|
||||
if not self.scrolling:
|
||||
self.scrolling = 1
|
||||
self.game.scrolling_direction = direction
|
||||
if not self.game.scrolling:
|
||||
self.game.scrolling = 1
|
||||
|
||||
def stop_scrolling(self):
|
||||
self.scrolling = 0
|
||||
self.game.scrolling = 0
|
||||
|
||||
def scroll(self):
|
||||
if self.scrolling:
|
||||
if not self.scrolling % 5:
|
||||
if self.scrolling_direction == "Up":
|
||||
self.scroll_cursor(y=-1)
|
||||
elif self.scrolling_direction == "Down":
|
||||
self.scroll_cursor(y=1)
|
||||
elif self.scrolling_direction == "Left":
|
||||
self.scroll_cursor(x=-1)
|
||||
elif self.scrolling_direction == "Right":
|
||||
self.scroll_cursor(x=1)
|
||||
self.scrolling += 1
|
||||
|
||||
if self.game.scrolling:
|
||||
if not self.game.scrolling % 5:
|
||||
if self.game.scrolling_direction == "Up":
|
||||
self.game.graphics.scroll_cursor(y=-1)
|
||||
elif self.game.scrolling_direction == "Down":
|
||||
self.game.graphics.scroll_cursor(y=1)
|
||||
elif self.game.scrolling_direction == "Left":
|
||||
self.game.graphics.scroll_cursor(x=-1)
|
||||
elif self.game.scrolling_direction == "Right":
|
||||
self.game.graphics.scroll_cursor(x=1)
|
||||
self.game.scrolling += 1
|
||||
|
||||
def axis_scroll(self, x, y):
|
||||
self.scroll_cursor(1 if x > 0 else -1, 1 if y > 0 else -1)
|
||||
self.game.graphics.scroll_cursor(1 if x > 0 else -1, 1 if y > 0 else -1)
|
||||
+381
-65
@@ -1,16 +1,21 @@
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
|
||||
from engine import maze
|
||||
from engine import maze, config
|
||||
from engine.collision_system import CollisionLayer
|
||||
from runtime_paths import bundle_path
|
||||
|
||||
class Graphics():
|
||||
class Graphics:
|
||||
def __init__(self, game):
|
||||
self.game = game
|
||||
self.loaded_theme_index = None
|
||||
|
||||
def load_assets(self):
|
||||
theme_index = self.get_theme_index()
|
||||
print(f"[gfx] load_assets requested: level={self.current_level + 1} theme={theme_index}")
|
||||
if getattr(self, "startup_loading_active", False):
|
||||
self._update_startup_loading(
|
||||
print(f"[gfx] load_assets requested: level={self.game.current_level + 1} theme={theme_index}")
|
||||
if getattr(self.game, "startup_loading_active", False):
|
||||
self.game._update_startup_loading(
|
||||
"Loading graphics",
|
||||
detail=f"Preparing theme {theme_index}",
|
||||
progress=0.3,
|
||||
@@ -24,8 +29,8 @@ class Graphics():
|
||||
|
||||
if not getattr(self, "common_assets_loaded", False):
|
||||
print("Loading graphics assets...")
|
||||
if getattr(self, "startup_loading_active", False):
|
||||
self._update_startup_loading(
|
||||
if getattr(self.game, "startup_loading_active", False):
|
||||
self.game._update_startup_loading(
|
||||
"Loading graphics",
|
||||
detail="Decoding sprites and tiles",
|
||||
progress=0.4,
|
||||
@@ -41,11 +46,11 @@ class Graphics():
|
||||
self.rat_assets_textures[sex] = {}
|
||||
self.rat_image_sizes[sex] = {}
|
||||
for direction in ["UP", "DOWN", "LEFT", "RIGHT"]:
|
||||
self.rat_assets[sex][direction] = self.render_engine.load_image(
|
||||
self.rat_assets[sex][direction] = self.game.render_engine.load_image(
|
||||
f"Rat/BMP_{sex}_{direction}.png",
|
||||
transparent_color=((125, 125, 125), (128, 128, 128)),
|
||||
)
|
||||
texture = self.render_engine.load_image(
|
||||
texture = self.game.render_engine.load_image(
|
||||
f"Rat/BMP_{sex}_{direction}.png",
|
||||
transparent_color=((125, 125, 125), (128, 128, 128)),
|
||||
surface=False,
|
||||
@@ -54,30 +59,36 @@ class Graphics():
|
||||
self.rat_image_sizes[sex][direction] = texture.size
|
||||
|
||||
for n in range(5):
|
||||
self.bomb_assets[n] = self.render_engine.load_image(
|
||||
self.bomb_assets[n] = self.game.render_engine.load_image(
|
||||
f"Rat/BMP_BOMB{n}.png",
|
||||
transparent_color=((125, 125, 125), (128, 128, 128)),
|
||||
)
|
||||
|
||||
rat_asset_dir = bundle_path("assets", "Rat")
|
||||
for file in os.listdir(rat_asset_dir):
|
||||
if file.endswith(".png"):
|
||||
self.assets[file[:-4]] = self.render_engine.load_image(
|
||||
f"Rat/{file}",
|
||||
transparent_color=((125, 125, 125), (128, 128, 128)),
|
||||
)
|
||||
for file in sorted(os.listdir(rat_asset_dir)):
|
||||
if file.endswith(".png") and not file.startswith("."):
|
||||
# Check if it's one of our expected BMP files or other known assets
|
||||
# to avoid loading temporary or irrelevant PNGs
|
||||
file_key = file[:-4]
|
||||
try:
|
||||
self.assets[file_key] = self.game.render_engine.load_image(
|
||||
f"Rat/{file}",
|
||||
transparent_color=((125, 125, 125), (128, 128, 128)),
|
||||
)
|
||||
except (FileNotFoundError, IOError) as e:
|
||||
print(f"Warning: Could not load asset {file}: {e}")
|
||||
|
||||
print("Pre-generating blood stain pool...")
|
||||
if getattr(self, "startup_loading_active", False):
|
||||
self._update_startup_loading(
|
||||
if getattr(self.game, "startup_loading_active", False):
|
||||
self.game._update_startup_loading(
|
||||
"Loading graphics",
|
||||
detail="Generating blood pool",
|
||||
progress=0.58,
|
||||
)
|
||||
self.blood_stain_textures = []
|
||||
for _ in range(10):
|
||||
blood_surface = self.render_engine.generate_blood_surface()
|
||||
blood_texture = self.render_engine.draw_blood_surface(blood_surface, (0, 0))
|
||||
blood_surface = self.game.render_engine.generate_blood_surface()
|
||||
blood_texture = self.game.render_engine.draw_blood_surface(blood_surface, (0, 0))
|
||||
if blood_texture:
|
||||
self.blood_stain_textures.append(blood_texture)
|
||||
|
||||
@@ -88,33 +99,33 @@ class Graphics():
|
||||
|
||||
if theme_index not in self.theme_assets_cache:
|
||||
print(f"Loading theme assets {theme_index}...")
|
||||
if getattr(self, "startup_loading_active", False):
|
||||
self._update_startup_loading(
|
||||
if getattr(self.game, "startup_loading_active", False):
|
||||
self.game._update_startup_loading(
|
||||
"Loading graphics",
|
||||
detail=f"Loading theme {theme_index} art",
|
||||
progress=0.74,
|
||||
)
|
||||
self.theme_assets_cache[theme_index] = {
|
||||
"floor_tile": self.render_engine.create_color_surface((128, 128, 128)),
|
||||
"tunnel": self.render_engine.load_image("Rat/BMP_TUNNEL.png"),
|
||||
"floor_tile": self.game.render_engine.create_color_surface((128, 128, 128)),
|
||||
"tunnel": self.game.render_engine.load_image("Rat/BMP_TUNNEL.png"),
|
||||
"grasses": [
|
||||
self.render_engine.load_image(f"Rat/BMP_{theme_index}_GRASS_{i+1}.png", surface=True)
|
||||
self.game.render_engine.load_image(f"Rat/BMP_{theme_index}_GRASS_{i+1}.png", surface=True)
|
||||
for i in range(4)
|
||||
],
|
||||
"grass_textures": [
|
||||
self.render_engine.load_image(f"Rat/BMP_{theme_index}_GRASS_{i+1}.png")
|
||||
self.game.render_engine.load_image(f"Rat/BMP_{theme_index}_GRASS_{i+1}.png")
|
||||
for i in range(4)
|
||||
],
|
||||
"flowers": [
|
||||
self.render_engine.load_image(f"Rat/BMP_{theme_index}_FLOWER_{i+1}.png", surface=True)
|
||||
self.game.render_engine.load_image(f"Rat/BMP_{theme_index}_FLOWER_{i+1}.png", surface=True)
|
||||
for i in range(4)
|
||||
],
|
||||
"flower_textures": [
|
||||
self.render_engine.load_image(f"Rat/BMP_{theme_index}_FLOWER_{i+1}.png")
|
||||
self.game.render_engine.load_image(f"Rat/BMP_{theme_index}_FLOWER_{i+1}.png")
|
||||
for i in range(4)
|
||||
],
|
||||
"caves": {
|
||||
direction: self.render_engine.load_image(
|
||||
direction: self.game.render_engine.load_image(
|
||||
f"Rat/BMP_{theme_index}_CAVE_{direction}.png",
|
||||
transparent_color=((125, 125, 125), (128, 128, 128)),
|
||||
surface=False,
|
||||
@@ -122,7 +133,7 @@ class Graphics():
|
||||
for direction in ["UP", "DOWN", "LEFT", "RIGHT"]
|
||||
},
|
||||
"explosions": {
|
||||
direction: self.render_engine.load_image(
|
||||
direction: self.game.render_engine.load_image(
|
||||
f"Rat/BMP_{theme_index}_EXPLOSION_{direction}.png",
|
||||
transparent_color=((125, 125, 125), (128, 128, 128)),
|
||||
surface=False,
|
||||
@@ -130,15 +141,15 @@ class Graphics():
|
||||
for direction in ["UP", "DOWN", "LEFT", "RIGHT"]
|
||||
},
|
||||
"edges": {
|
||||
direction: self.render_engine.load_image(f"Rat/BMP_{theme_index}_{direction}.png", surface=True)
|
||||
direction: self.game.render_engine.load_image(f"Rat/BMP_{theme_index}_{direction}.png", surface=True)
|
||||
for direction in ["N", "S", "E", "W"]
|
||||
},
|
||||
"corners": {
|
||||
direction: self.render_engine.load_image(f"Rat/BMP_{theme_index}_{direction}.png", surface=True)
|
||||
direction: self.game.render_engine.load_image(f"Rat/BMP_{theme_index}_{direction}.png", surface=True)
|
||||
for direction in ["NE", "NW", "SE", "SW"]
|
||||
},
|
||||
"inner_corners": {
|
||||
direction: self.render_engine.load_image(f"Rat/BMP_{theme_index}_{direction}.png", surface=True)
|
||||
direction: self.game.render_engine.load_image(f"Rat/BMP_{theme_index}_{direction}.png", surface=True)
|
||||
for direction in ["EN", "ES", "WN", "WS"]
|
||||
},
|
||||
}
|
||||
@@ -146,8 +157,8 @@ class Graphics():
|
||||
else:
|
||||
print(f"[gfx] theme cache hit -> reusing theme {theme_index}")
|
||||
|
||||
if getattr(self, "startup_loading_active", False):
|
||||
self._update_startup_loading(
|
||||
if getattr(self.game, "startup_loading_active", False):
|
||||
self.game._update_startup_loading(
|
||||
"Loading graphics",
|
||||
detail="Finishing render setup",
|
||||
progress=0.84,
|
||||
@@ -168,27 +179,27 @@ class Graphics():
|
||||
self.inner_corners = theme_assets["inner_corners"]
|
||||
|
||||
def get_theme_index(self):
|
||||
return self.current_level % 32 // 8 + 1
|
||||
return self.game.current_level % 32 // 8 + 1
|
||||
|
||||
|
||||
|
||||
# ==================== RENDERING ====================
|
||||
|
||||
def draw_maze(self):
|
||||
if self.background_texture is None:
|
||||
print(f"[gfx] generating background texture for level={self.current_level + 1} theme={self.loaded_theme_index}")
|
||||
if self.game.background_texture is None:
|
||||
print(f"[gfx] generating background texture for level={self.game.current_level + 1} theme={self.loaded_theme_index}")
|
||||
self.regenerate_background()
|
||||
self.render_engine.draw_background(self.background_texture)
|
||||
self.game.render_engine.draw_background(self.game.background_texture)
|
||||
|
||||
# Draw blood layer as sprites (optimized - no background regeneration)
|
||||
self.draw_blood_layer()
|
||||
|
||||
def draw_cave_foreground(self):
|
||||
active_cave_explosions = {}
|
||||
for unit in self.units.values():
|
||||
for unit in self.game.units.values():
|
||||
if unit.collision_layer != CollisionLayer.EXPLOSION:
|
||||
continue
|
||||
if not self.map.is_tunnel(*unit.position):
|
||||
if not self.game.map.is_tunnel(*unit.position):
|
||||
continue
|
||||
active_cave_explosions[unit.position] = getattr(unit, "cave_direction", None)
|
||||
|
||||
@@ -196,30 +207,30 @@ class Graphics():
|
||||
if (cell_x, cell_y) in active_cave_explosions:
|
||||
explosion_direction = active_cave_explosions[(cell_x, cell_y)] or direction
|
||||
surface = self.explosions.get(explosion_direction, surface)
|
||||
self.render_engine.draw_image(x, y, surface, anchor="nw", tag="cave")
|
||||
self.game.render_engine.draw_image(x, y, surface, anchor="nw", tag="cave")
|
||||
|
||||
def draw_blood_layer(self):
|
||||
"""Draw all blood stains as sprites overlay (optimized)"""
|
||||
for blood_texture, x, y in self.blood_layer_sprites:
|
||||
self.render_engine.draw_image(x, y, blood_texture, tag="blood")
|
||||
self.game.render_engine.draw_image(x, y, blood_texture, tag="blood")
|
||||
|
||||
def regenerate_background(self):
|
||||
"""Generate or regenerate the background texture (static - no blood stains)"""
|
||||
texture_tiles = []
|
||||
self.cave_foreground_tiles = []
|
||||
half_cell = self.cell_size // 2
|
||||
half_cell = self.game.cell_size // 2
|
||||
|
||||
def draw(surface, x, y):
|
||||
texture_tiles.append((surface, x, y))
|
||||
|
||||
def draw_cave(surface, x, y, direction):
|
||||
self.cave_foreground_tiles.append((x // self.cell_size, y // self.cell_size, direction, surface, x, y))
|
||||
self.cave_foreground_tiles.append((x // self.game.cell_size, y // self.game.cell_size, direction, surface, x, y))
|
||||
|
||||
def occupied(x, y):
|
||||
return self.map.in_bounds(x, y) and self.map.get_cell(x, y) != maze.MAP_EMPTY
|
||||
return self.game.map.in_bounds(x, y) and self.game.map.get_cell(x, y) != maze.MAP_EMPTY
|
||||
|
||||
def is_tunnel(x, y):
|
||||
return self.map.in_bounds(x, y) and self.map.get_cell(x, y) == maze.MAP_TUNNEL
|
||||
return self.game.map.in_bounds(x, y) and self.game.map.get_cell(x, y) == maze.MAP_TUNNEL
|
||||
|
||||
def random_wall():
|
||||
return random.choice(self.grasses)
|
||||
@@ -232,16 +243,16 @@ class Graphics():
|
||||
|
||||
def random_flower_texture():
|
||||
return random.choice(self.flower_textures)
|
||||
for y, row in enumerate(self.map.tiles):
|
||||
for y, row in enumerate(self.game.map.tiles):
|
||||
for x, cell in enumerate(row):
|
||||
px = x * self.cell_size
|
||||
py = y * self.cell_size
|
||||
px = x * self.game.cell_size
|
||||
py = y * self.game.cell_size
|
||||
|
||||
if cell == maze.MAP_EMPTY:
|
||||
continue
|
||||
|
||||
if cell == maze.MAP_WALL:
|
||||
if x == 0 or y == 0 or x == self.map.width - 1 or y == self.map.height - 1:
|
||||
if x == 0 or y == 0 or x == self.game.map.width - 1 or y == self.game.map.height - 1:
|
||||
draw(random_wall(), px, py)
|
||||
|
||||
if x > 0 and y > 0 and (not occupied(x - 1, y - 1) or not occupied(x, y - 1) or not occupied(x - 1, y)):
|
||||
@@ -257,7 +268,7 @@ class Graphics():
|
||||
else:
|
||||
draw(self.corners["NW"], px, py)
|
||||
|
||||
if y < self.map.height - 1 and x < self.map.width - 1:
|
||||
if y < self.game.map.height - 1 and x < self.game.map.width - 1:
|
||||
south = occupied(x, y + 1)
|
||||
east = occupied(x + 1, y)
|
||||
southeast = occupied(x + 1, y + 1)
|
||||
@@ -266,8 +277,8 @@ class Graphics():
|
||||
random.randrange(10) != 0
|
||||
or x == 0
|
||||
or y == 0
|
||||
or x == self.map.width - 2
|
||||
or y == self.map.height - 2
|
||||
or x == self.game.map.width - 2
|
||||
or y == self.game.map.height - 2
|
||||
or is_tunnel(x + 1, y)
|
||||
or is_tunnel(x, y + 1)
|
||||
or is_tunnel(x + 1, y + 1)
|
||||
@@ -285,7 +296,7 @@ class Graphics():
|
||||
else:
|
||||
draw(self.corners["SE"], px + half_cell, py + half_cell)
|
||||
|
||||
if y > 0 and x < self.map.width - 1 and (not occupied(x + 1, y - 1) or not occupied(x, y - 1) or not occupied(x + 1, y)):
|
||||
if y > 0 and x < self.game.map.width - 1 and (not occupied(x + 1, y - 1) or not occupied(x, y - 1) or not occupied(x + 1, y)):
|
||||
north = occupied(x, y - 1)
|
||||
east = occupied(x + 1, y)
|
||||
if north or east:
|
||||
@@ -298,7 +309,7 @@ class Graphics():
|
||||
else:
|
||||
draw(self.corners["NE"], px + half_cell, py)
|
||||
|
||||
if y < self.map.height - 1 and x > 0 and (not occupied(x - 1, y + 1) or not occupied(x, y + 1) or not occupied(x - 1, y)):
|
||||
if y < self.game.map.height - 1 and x > 0 and (not occupied(x - 1, y + 1) or not occupied(x, y + 1) or not occupied(x - 1, y)):
|
||||
south = occupied(x, y + 1)
|
||||
west = occupied(x - 1, y)
|
||||
if south or west:
|
||||
@@ -337,27 +348,332 @@ class Graphics():
|
||||
draw_cave(self.caves["UP"], px, py, "UP")
|
||||
|
||||
# Blood stains now handled separately as overlay layer
|
||||
self.background_texture = self.render_engine.create_texture(texture_tiles, fill_color=(128, 128, 128))
|
||||
self.game.background_texture = self.game.render_engine.create_texture(texture_tiles, fill_color=(128, 128, 128))
|
||||
|
||||
def add_blood_stain(self, position):
|
||||
"""Add a blood stain as sprite overlay (opti mized - no background regeneration)"""
|
||||
"""Add a blood stain as sprite overlay (optimized - no background regeneration)"""
|
||||
# Pick random blood texture from pre-generated pool
|
||||
if not self.blood_stain_textures:
|
||||
return
|
||||
|
||||
blood_texture = random.choice(self.blood_stain_textures)
|
||||
x = position[0] * self.cell_size
|
||||
y = position[1] * self.cell_size
|
||||
x = position[0] * self.game.cell_size
|
||||
y = position[1] * self.game.cell_size
|
||||
|
||||
# Add to blood layer sprites instead of regenerating background
|
||||
self.blood_layer_sprites.append((blood_texture, x, y))
|
||||
|
||||
def scroll_cursor(self, x=0, y=0):
|
||||
if self.pointer[0] + x > self.map.width or self.pointer[1] + y > self.map.height:
|
||||
if self.game.pointer[0] + x > self.game.map.width or self.game.pointer[1] + y > self.game.map.height:
|
||||
return
|
||||
|
||||
self.pointer = (
|
||||
max(1, min(self.map.width-2, self.pointer[0] + x)),
|
||||
max(1, min(self.map.height-2, self.pointer[1] + y))
|
||||
self.game.pointer = (
|
||||
max(1, min(self.game.map.width-2, self.game.pointer[0] + x)),
|
||||
max(1, min(self.game.map.height-2, self.game.pointer[1] + y))
|
||||
)
|
||||
self.game.render_engine.scroll_view(self.game.pointer)
|
||||
|
||||
# ==================== MENU RENDERING ====================
|
||||
|
||||
def _menu_font(self, size):
|
||||
clamped = max(10, min(69, int(size)))
|
||||
return self.game.render_engine.fonts[clamped]
|
||||
|
||||
def _draw_start_menu_difficulty_selector(self, x, y, width, height):
|
||||
colors = config.START_MENU_COLORS
|
||||
difficulty_config = self.game._difficulty_config()
|
||||
accent = difficulty_config["accent"]
|
||||
render_engine = self.game.render_engine
|
||||
center_x = x + width // 2
|
||||
compact_selector = height <= 72
|
||||
title_font = self._menu_font(render_engine.target_size[1] // 38)
|
||||
value_font = self._menu_font(render_engine.target_size[1] // 23)
|
||||
arrow_font = value_font
|
||||
section_gap = 4 if compact_selector else 6
|
||||
title_line_height = max(14, render_engine.target_size[1] // 32)
|
||||
value_line_height = max(18, render_engine.target_size[1] // 24)
|
||||
current_y = y + 2
|
||||
|
||||
render_engine.draw_text("Difficulty", title_font, ("center", current_y), colors["muted"])
|
||||
current_y += title_line_height + section_gap
|
||||
|
||||
arrow_offset = max(34, min(52, width // 7))
|
||||
arrow_y = current_y - (1 if compact_selector else 0)
|
||||
render_engine.draw_text("<", arrow_font, (center_x - arrow_offset, arrow_y), colors["muted"])
|
||||
render_engine.draw_text(">", arrow_font, (center_x + arrow_offset, arrow_y), colors["muted"])
|
||||
|
||||
render_engine.draw_text(
|
||||
difficulty_config["label"],
|
||||
value_font,
|
||||
("center", current_y),
|
||||
accent,
|
||||
)
|
||||
current_y += value_line_height
|
||||
|
||||
def _draw_start_menu_slider(self, x, y, width, height, setting_name, label, value, selected):
|
||||
colors = config.START_MENU_COLORS
|
||||
style = config.START_MENU_AUDIO_STYLES[setting_name]
|
||||
accent = style["accent"]
|
||||
fill_color = style["fill"] if selected else colors["card_fill"]
|
||||
border_color = accent if selected else (156, 156, 156)
|
||||
text_color = colors["text"]
|
||||
render_engine = self.game.render_engine
|
||||
|
||||
render_engine.draw_rectangle(x, y, width, height, "start_menu_slider", filling=fill_color)
|
||||
render_engine.draw_rectangle(x, y, width, height, "start_menu_slider", outline=border_color)
|
||||
|
||||
if selected:
|
||||
render_engine.draw_rectangle(x + 12, y + 10, 8, height - 20, "start_menu_slider", filling=accent)
|
||||
|
||||
title_y = y + 10
|
||||
render_engine.draw_text(label, self._menu_font(render_engine.target_size[1] // 32), (x + 34, title_y), text_color)
|
||||
render_engine.draw_text(f"{value}%", self._menu_font(render_engine.target_size[1] // 34), (x + width - 84, title_y + 2), text_color)
|
||||
|
||||
track_x = x + 34
|
||||
track_y = y + height - 26
|
||||
track_width = width - 68
|
||||
track_height = 14
|
||||
filled_width = int(track_width * value / 100)
|
||||
knob_width = 14
|
||||
knob_x = track_x + int((track_width - knob_width) * value / 100)
|
||||
|
||||
render_engine.draw_rectangle(track_x, track_y, track_width, track_height, "start_menu_slider", filling=colors["track_fill"])
|
||||
render_engine.draw_rectangle(track_x, track_y, track_width, track_height, "start_menu_slider", outline=(128, 128, 128))
|
||||
if filled_width > 0:
|
||||
render_engine.draw_rectangle(track_x, track_y, filled_width, track_height, "start_menu_slider", filling=accent)
|
||||
render_engine.draw_rectangle(knob_x, track_y - 4, knob_width, track_height + 8, "start_menu_slider", filling=(255, 255, 255))
|
||||
render_engine.draw_rectangle(knob_x, track_y - 4, knob_width, track_height + 8, "start_menu_slider", outline=accent)
|
||||
|
||||
def _current_start_menu_animation_frame(self):
|
||||
animation = getattr(self.game, "start_menu_animation", None)
|
||||
if not animation or not animation["frames"]:
|
||||
return None, (0, 0)
|
||||
|
||||
if len(animation["frames"]) == 1 or animation["total_duration"] <= 0:
|
||||
return animation["frames"][0], animation["size"]
|
||||
|
||||
elapsed_ms = int((time.monotonic() - self.game.start_menu_animation_started_at) * 1000)
|
||||
current_offset = elapsed_ms % animation["total_duration"]
|
||||
accumulated = 0
|
||||
for index, duration in enumerate(animation["durations"]):
|
||||
accumulated += duration
|
||||
if current_offset < accumulated:
|
||||
return animation["frames"][index], animation["size"]
|
||||
|
||||
return animation["frames"][-1], animation["size"]
|
||||
|
||||
def _render_audio_menu(self, title, subtitle_lines, primary_action_text, hint_text, image_name="BMP_WEWIN"):
|
||||
colors = config.START_MENU_COLORS
|
||||
render_engine = self.game.render_engine
|
||||
target_width, target_height = render_engine.target_size
|
||||
compact_menu = target_height <= 540
|
||||
panel_x = max(48, target_width // 12)
|
||||
panel_y = max(34, target_height // 18)
|
||||
panel_width = target_width - panel_x * 2
|
||||
panel_height = target_height - panel_y * 2
|
||||
header_height = max(44, target_height // 13)
|
||||
|
||||
render_engine.draw_rectangle(panel_x, panel_y, panel_width, panel_height, "start_menu", filling=colors["panel_fill"])
|
||||
render_engine.draw_rectangle(panel_x, panel_y, panel_width, panel_height, "start_menu", outline=colors["panel_border"])
|
||||
render_engine.draw_rectangle(panel_x, panel_y, panel_width, header_height, "start_menu", filling=colors["header_fill"])
|
||||
render_engine.draw_text(
|
||||
title,
|
||||
self._menu_font(target_height // 20),
|
||||
("center", panel_y + 16),
|
||||
colors["text"],
|
||||
)
|
||||
|
||||
image = self.assets[image_name]
|
||||
image_width, image_height = render_engine.get_image_size(image)
|
||||
image_y = panel_y + header_height + (14 if compact_menu else 18)
|
||||
render_engine.draw_image(
|
||||
target_width // 2 - image_width // 2 - render_engine.w_offset,
|
||||
image_y - render_engine.h_offset,
|
||||
image,
|
||||
"start_menu",
|
||||
)
|
||||
|
||||
info_y = image_y + image_height + 18
|
||||
line_gap = 18 if compact_menu else max(20, target_height // 34)
|
||||
for index, line in enumerate(subtitle_lines):
|
||||
render_engine.draw_text(
|
||||
line,
|
||||
self._menu_font(target_height // (34 if index == 0 else 36)),
|
||||
("center", info_y + line_gap * index),
|
||||
colors["text"] if index == 0 else colors["muted"],
|
||||
)
|
||||
|
||||
cta_y = info_y + line_gap * len(subtitle_lines) + 8
|
||||
render_engine.draw_text(
|
||||
primary_action_text,
|
||||
self._menu_font(target_height // 31),
|
||||
("center", cta_y),
|
||||
colors["text"],
|
||||
)
|
||||
|
||||
card_width = max(320, min(panel_width - 160, 720))
|
||||
card_height = 60 if compact_menu else max(68, target_height // 10)
|
||||
card_x = target_width // 2 - card_width // 2
|
||||
cards_y = cta_y + (16 if compact_menu else 26)
|
||||
card_gap = 12 if compact_menu else 16
|
||||
for index, (setting_name, label) in enumerate(config.START_MENU_AUDIO_OPTIONS):
|
||||
card_y = cards_y + index * (card_height + card_gap)
|
||||
self._draw_start_menu_slider(
|
||||
card_x,
|
||||
card_y,
|
||||
card_width,
|
||||
card_height,
|
||||
setting_name,
|
||||
label,
|
||||
getattr(self.game, setting_name),
|
||||
index == self.game.start_menu_selection,
|
||||
)
|
||||
|
||||
cards_bottom = cards_y + len(config.START_MENU_AUDIO_OPTIONS) * card_height + (len(config.START_MENU_AUDIO_OPTIONS) - 1) * card_gap
|
||||
if compact_menu:
|
||||
hint_y = min(cards_bottom + 8, panel_y + panel_height - 28)
|
||||
render_engine.draw_text(
|
||||
hint_text,
|
||||
self._menu_font(target_height // 42),
|
||||
("center", hint_y),
|
||||
colors["muted"],
|
||||
)
|
||||
else:
|
||||
hint_y = cards_bottom + 10
|
||||
hint_height = max(34, target_height // 18)
|
||||
hint_width = card_width
|
||||
hint_x = card_x
|
||||
render_engine.draw_rectangle(hint_x, hint_y, hint_width, hint_height, "start_menu", filling=colors["hint_fill"])
|
||||
render_engine.draw_rectangle(hint_x, hint_y, hint_width, hint_height, "start_menu", outline=(170, 170, 170))
|
||||
render_engine.draw_text(
|
||||
hint_text,
|
||||
self._menu_font(target_height // 40),
|
||||
("center", hint_y + 10),
|
||||
colors["muted"],
|
||||
)
|
||||
|
||||
def render_start_menu(self):
|
||||
colors = config.START_MENU_COLORS
|
||||
render_engine = self.game.render_engine
|
||||
target_width, target_height = render_engine.target_size
|
||||
compact_menu = target_height <= 540
|
||||
panel_x = max(48, target_width // 12)
|
||||
panel_y = max(34, target_height // 18)
|
||||
panel_width = target_width - panel_x * 2
|
||||
panel_height = target_height - panel_y * 2
|
||||
header_height = max(58, target_height // 10)
|
||||
title_font = self._menu_font(target_height // 15)
|
||||
body_font = self._menu_font(target_height // 28)
|
||||
meta_font = self._menu_font(target_height // 30)
|
||||
cta_font = self._menu_font(target_height // 22)
|
||||
hint_font = self._menu_font(target_height // 32)
|
||||
line_gap = 22 if compact_menu else max(26, target_height // 26)
|
||||
|
||||
render_engine.draw_rectangle(panel_x, panel_y, panel_width, panel_height, "start_menu", filling=colors["panel_fill"])
|
||||
render_engine.draw_rectangle(panel_x, panel_y, panel_width, panel_height, "start_menu", outline=colors["panel_border"])
|
||||
render_engine.draw_rectangle(panel_x, panel_y, panel_width, header_height, "start_menu", filling=colors["header_fill"])
|
||||
render_engine.draw_text(
|
||||
f"Welcome to Mice, {self.game.profile_integration.get_profile_name()}!",
|
||||
title_font,
|
||||
("center", panel_y + max(12, header_height // 5)),
|
||||
colors["text"],
|
||||
)
|
||||
|
||||
animation_frame, animation_size = self._current_start_menu_animation_frame()
|
||||
animation_bottom = panel_y + header_height + 18
|
||||
if animation_frame is not None:
|
||||
max_animation_width = panel_width - (70 if compact_menu else 110)
|
||||
display_width = min(animation_size[0], max_animation_width)
|
||||
scale_factor = display_width / animation_size[0]
|
||||
display_height = max(1, int(animation_size[1] * scale_factor))
|
||||
animation_x = target_width // 2 - display_width // 2
|
||||
animation_y = panel_y + header_height + (18 if compact_menu else 26)
|
||||
render_engine.draw_image(
|
||||
animation_x - render_engine.w_offset,
|
||||
animation_y - render_engine.h_offset,
|
||||
animation_frame,
|
||||
"start_menu",
|
||||
source_rect=(0, 0, animation_size[0], animation_size[1]),
|
||||
dest_size=(display_width, display_height),
|
||||
)
|
||||
animation_bottom = animation_y + display_height
|
||||
|
||||
player_profile = self.game.profile_integration.current_profile
|
||||
device_id = self.game.profile_integration.get_device_id()
|
||||
subtitle_lines = ["A game by Matteo, because he was bored."]
|
||||
if player_profile:
|
||||
if compact_menu:
|
||||
subtitle_lines.append(
|
||||
f"Best: {player_profile['best_score']} | Games: {player_profile['games_played']}"
|
||||
)
|
||||
else:
|
||||
subtitle_lines.append(f"Device: {device_id}")
|
||||
subtitle_lines.append(
|
||||
f"Best Score: {player_profile['best_score']} | Games: {player_profile['games_played']}"
|
||||
)
|
||||
elif compact_menu:
|
||||
subtitle_lines.append(f"Guest profile | {device_id}")
|
||||
else:
|
||||
subtitle_lines.append(f"Device: {device_id}")
|
||||
subtitle_lines.append("No profile loaded - playing as guest")
|
||||
|
||||
info_y = animation_bottom + (18 if compact_menu else 22)
|
||||
for index, line in enumerate(subtitle_lines):
|
||||
render_engine.draw_text(
|
||||
line,
|
||||
body_font if index == 0 else meta_font,
|
||||
("center", info_y + line_gap * index),
|
||||
colors["text"] if index == 0 else colors["muted"],
|
||||
)
|
||||
|
||||
difficulty_width = max(360, min(panel_width - 160, 760))
|
||||
difficulty_height = 54 if compact_menu else max(72, target_height // 9)
|
||||
difficulty_x = target_width // 2 - difficulty_width // 2
|
||||
difficulty_y = info_y + line_gap * len(subtitle_lines) + (12 if compact_menu else 18)
|
||||
self._draw_start_menu_difficulty_selector(
|
||||
difficulty_x,
|
||||
difficulty_y,
|
||||
difficulty_width,
|
||||
difficulty_height,
|
||||
)
|
||||
|
||||
cta_y = difficulty_y + difficulty_height + (12 if compact_menu else 24)
|
||||
render_engine.draw_text(
|
||||
"Press Return to start",
|
||||
cta_font,
|
||||
("center", cta_y),
|
||||
colors["text"],
|
||||
)
|
||||
if compact_menu:
|
||||
render_engine.draw_text(
|
||||
"Arrows change difficulty",
|
||||
hint_font,
|
||||
("center", cta_y + line_gap),
|
||||
colors["muted"],
|
||||
)
|
||||
render_engine.draw_text(
|
||||
"Esc quits M toggles audio",
|
||||
hint_font,
|
||||
("center", cta_y + line_gap + 18),
|
||||
colors["muted"],
|
||||
)
|
||||
else:
|
||||
render_engine.draw_text(
|
||||
"Arrows change difficulty Esc quits M toggles audio",
|
||||
hint_font,
|
||||
("center", cta_y + line_gap + 8),
|
||||
colors["muted"],
|
||||
)
|
||||
|
||||
def render_pause_menu(self):
|
||||
subtitle_lines = [
|
||||
f"Level {self.game.current_level + 1} | Points: {self.game.points}",
|
||||
f"Rats in maze: {self.game.unit_manager.count_rats()}",
|
||||
]
|
||||
self._render_audio_menu(
|
||||
title="Pause",
|
||||
subtitle_lines=subtitle_lines,
|
||||
primary_action_text="Press Return to resume",
|
||||
hint_text="Up/Down select Left/Right adjust Esc quits",
|
||||
image_name="BMP_PAUSE" if "BMP_PAUSE" in self.assets else "BMP_WEWIN",
|
||||
)
|
||||
self.render_engine.scroll_view(self.pointer)
|
||||
+13
-10
@@ -8,19 +8,22 @@ SCORES_FILE = persistent_data_path("scores.txt", default_text="")
|
||||
|
||||
|
||||
class Scoring:
|
||||
def __init__(self, game):
|
||||
self.game = game
|
||||
|
||||
# ==================== SCORING ====================
|
||||
|
||||
|
||||
def save_score(self):
|
||||
# Save to traditional scores.txt file
|
||||
with SCORES_FILE.open("a", encoding="utf-8") as f:
|
||||
player_name = getattr(self, 'profile_integration', None)
|
||||
if player_name and hasattr(player_name, 'get_profile_name'):
|
||||
name = player_name.get_profile_name()
|
||||
device_id = player_name.get_device_id()
|
||||
f.write(f"{datetime.datetime.now()} - {self.points} - {name} - {device_id}\n")
|
||||
profile_integration = getattr(self.game, 'profile_integration', None)
|
||||
if profile_integration and hasattr(profile_integration, 'get_profile_name'):
|
||||
name = profile_integration.get_profile_name()
|
||||
device_id = profile_integration.get_device_id()
|
||||
f.write(f"{datetime.datetime.now()} - {self.game.points} - {name} - {device_id}\n")
|
||||
else:
|
||||
f.write(f"{datetime.datetime.now()} - {self.points} - Guest\n")
|
||||
|
||||
f.write(f"{datetime.datetime.now()} - {self.game.points} - Guest\n")
|
||||
|
||||
def read_score(self):
|
||||
table = []
|
||||
try:
|
||||
@@ -40,6 +43,6 @@ class Scoring:
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return table[:5] # Return top 5 scores instead of 3
|
||||
|
||||
|
||||
def add_point(self, value):
|
||||
self.points += value
|
||||
self.game.points += value
|
||||
@@ -0,0 +1,42 @@
|
||||
from enum import Enum, auto
|
||||
|
||||
class GameState(Enum):
|
||||
START_MENU = auto()
|
||||
PLAYING = auto()
|
||||
PAUSED = auto()
|
||||
GAME_OVER = auto()
|
||||
VICTORY = auto()
|
||||
|
||||
class StateMachine:
|
||||
def __init__(self, game):
|
||||
self.game = game
|
||||
self.current_state = GameState.START_MENU
|
||||
|
||||
def transition_to(self, new_state):
|
||||
print(f"[state] Transitioning from {self.current_state} to {new_state}")
|
||||
self.current_state = new_state
|
||||
|
||||
# Sincronizzazione per compatibilità con KeyBindings e logica esistente
|
||||
if new_state == GameState.PLAYING:
|
||||
self.game.game_status = "game"
|
||||
self.game.menu_screen = None
|
||||
elif new_state == GameState.PAUSED:
|
||||
self.game.game_status = "paused"
|
||||
self.game.menu_screen = None
|
||||
elif new_state == GameState.START_MENU:
|
||||
self.game.game_status = "start_menu"
|
||||
self.game.menu_screen = "start"
|
||||
elif new_state in [GameState.GAME_OVER, GameState.VICTORY]:
|
||||
self.game.game_status = "paused" # Legacy status used for key handling in end screens
|
||||
self.game.menu_screen = None
|
||||
|
||||
def update(self):
|
||||
# Dispatch alla logica originale ripristinata in MiceMaze/Graphics
|
||||
if self.current_state == GameState.START_MENU:
|
||||
self.game.graphics.render_start_menu()
|
||||
elif self.current_state == GameState.PAUSED:
|
||||
self.game.graphics.render_pause_menu()
|
||||
elif self.current_state in [GameState.GAME_OVER, GameState.VICTORY]:
|
||||
# Delega al metodo game_over originale che gestisce i dialoghi specifici
|
||||
self.game.game_over()
|
||||
|
||||
+35
-32
@@ -5,23 +5,26 @@ from units import gas, rat, bomb, mine
|
||||
|
||||
|
||||
class UnitManager:
|
||||
def __init__(self, game):
|
||||
self.game = game
|
||||
|
||||
def _spawnable_rat_positions(self):
|
||||
positions = []
|
||||
for y in range(1, self.map.height - 1):
|
||||
for x in range(1, self.map.width - 1):
|
||||
if not self.map.is_empty(x, y):
|
||||
for y in range(1, self.game.map.height - 1):
|
||||
for x in range(1, self.game.map.width - 1):
|
||||
if not self.game.map.is_empty(x, y):
|
||||
continue
|
||||
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
||||
nx = x + dx
|
||||
ny = y + dy
|
||||
if self.map.in_bounds(nx, ny) and self.map.is_empty(nx, ny):
|
||||
if self.game.map.in_bounds(nx, ny) and self.game.map.is_empty(nx, ny):
|
||||
positions.append((x, y))
|
||||
break
|
||||
return positions
|
||||
|
||||
def has_weapon_at(self, position):
|
||||
"""Check if there's a weapon (bomb, gas, mine) at the given position"""
|
||||
for unit in self.units.values():
|
||||
for unit in self.game.units.values():
|
||||
if unit.position == position:
|
||||
# Check if it's a weapon type (not a rat or points)
|
||||
if isinstance(unit, (bomb.Timer, bomb.NuclearBomb, gas.Gas, mine.Mine)):
|
||||
@@ -30,9 +33,9 @@ class UnitManager:
|
||||
|
||||
def can_place_weapon_at(self, position):
|
||||
x, y = position
|
||||
if not self.map.in_bounds(x, y):
|
||||
if not self.game.map.in_bounds(x, y):
|
||||
return False
|
||||
if not self.map.is_empty(x, y):
|
||||
if not self.game.map.is_empty(x, y):
|
||||
return False
|
||||
if self.has_weapon_at(position):
|
||||
return False
|
||||
@@ -40,19 +43,19 @@ class UnitManager:
|
||||
|
||||
def count_rats(self):
|
||||
count = 0
|
||||
for unit in self.units.values():
|
||||
for unit in self.game.units.values():
|
||||
if isinstance(unit, rat.Rat):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def spawn_gas(self, parent_id=None):
|
||||
if not self.can_place_weapon_at(self.pointer):
|
||||
if not self.can_place_weapon_at(self.game.pointer):
|
||||
return
|
||||
if self.ammo["gas"]["count"] <= 0:
|
||||
if self.game.ammo["gas"]["count"] <= 0:
|
||||
return
|
||||
self.ammo["gas"]["count"] -= 1
|
||||
self.render_engine.play_sound("GAS.WAV")
|
||||
self.spawn_unit(gas.Gas, self.pointer, parent_id=parent_id)
|
||||
self.game.ammo["gas"]["count"] -= 1
|
||||
self.game.render_engine.play_sound("GAS.WAV")
|
||||
self.spawn_unit(gas.Gas, self.game.pointer, parent_id=parent_id)
|
||||
|
||||
def spawn_rat(self, position=None):
|
||||
if position is None:
|
||||
@@ -68,9 +71,9 @@ class UnitManager:
|
||||
# Try nearby positions
|
||||
for dx, dy in [(0,1), (1,0), (0,-1), (-1,0), (1,1), (-1,-1), (1,-1), (-1,1)]:
|
||||
alt_pos = (position[0] + dx, position[1] + dy)
|
||||
if not self.map.in_bounds(alt_pos[0], alt_pos[1]):
|
||||
if not self.game.map.in_bounds(alt_pos[0], alt_pos[1]):
|
||||
continue
|
||||
if self.map.is_empty(alt_pos[0], alt_pos[1]) and not self.has_weapon_at(alt_pos):
|
||||
if self.game.map.is_empty(alt_pos[0], alt_pos[1]) and not self.has_weapon_at(alt_pos):
|
||||
position = alt_pos
|
||||
break
|
||||
else:
|
||||
@@ -84,45 +87,45 @@ class UnitManager:
|
||||
def spawn_bomb(self, position):
|
||||
if not self.can_place_weapon_at(position):
|
||||
return
|
||||
if self.ammo["bomb"]["count"] <= 0:
|
||||
if self.game.ammo["bomb"]["count"] <= 0:
|
||||
return
|
||||
self.render_engine.play_sound("PUTDOWN.WAV")
|
||||
self.game.render_engine.play_sound("PUTDOWN.WAV")
|
||||
self.spawn_unit(bomb.Timer, position)
|
||||
self.ammo["bomb"]["count"] -= 1
|
||||
self.game.ammo["bomb"]["count"] -= 1
|
||||
|
||||
def spawn_nuclear_bomb(self, position):
|
||||
"""Spawn a nuclear bomb at the specified position"""
|
||||
if self.ammo["nuclear"]["count"] <= 0:
|
||||
if self.game.ammo["nuclear"]["count"] <= 0:
|
||||
return
|
||||
if not self.can_place_weapon_at(position):
|
||||
return
|
||||
self.render_engine.play_sound("NUCLEAR.WAV")
|
||||
self.ammo["nuclear"]["count"] -= 1
|
||||
self.game.render_engine.play_sound("NUCLEAR.WAV")
|
||||
self.game.ammo["nuclear"]["count"] -= 1
|
||||
self.spawn_unit(bomb.NuclearBomb, position)
|
||||
|
||||
def spawn_mine(self, position):
|
||||
if self.ammo["mine"]["count"] <= 0:
|
||||
if self.game.ammo["mine"]["count"] <= 0:
|
||||
return
|
||||
if not self.can_place_weapon_at(position):
|
||||
return
|
||||
self.render_engine.play_sound("PUTDOWN.WAV")
|
||||
self.ammo["mine"]["count"] -= 1
|
||||
self.game.render_engine.play_sound("PUTDOWN.WAV")
|
||||
self.game.ammo["mine"]["count"] -= 1
|
||||
self.spawn_unit(mine.Mine, position, on_bottom=True)
|
||||
|
||||
def spawn_unit(self, unit, position, on_bottom=False, **kwargs):
|
||||
id = uuid.uuid4()
|
||||
if on_bottom:
|
||||
self.units = {id: unit(self, position, id, **kwargs), **self.units}
|
||||
self.game.units = {id: unit(self.game, position, id, **kwargs), **self.game.units}
|
||||
else:
|
||||
self.units[id] = unit(self, position, id, **kwargs)
|
||||
self.game.units[id] = unit(self.game, position, id, **kwargs)
|
||||
|
||||
def choose_start(self):
|
||||
if not hasattr(self, '_valid_positions') or self._valid_positions is None:
|
||||
self._valid_positions = self._spawnable_rat_positions()
|
||||
print(f"[flow] choose_start computed {len(self._valid_positions)} spawnable cells", flush=True)
|
||||
if not self._valid_positions:
|
||||
if not hasattr(self.game, '_valid_positions') or self.game._valid_positions is None:
|
||||
self.game._valid_positions = self._spawnable_rat_positions()
|
||||
print(f"[flow] choose_start computed {len(self.game._valid_positions)} spawnable cells", flush=True)
|
||||
if not self.game._valid_positions:
|
||||
return None
|
||||
return random.choice(self._valid_positions)
|
||||
return random.choice(self.game._valid_positions)
|
||||
|
||||
def get_unit_by_id(self, id):
|
||||
return self.units.get(id) or None
|
||||
return self.game.units.get(id) or None
|
||||
|
||||
Reference in New Issue
Block a user