Refine start menu difficulty and preview workflow

This commit is contained in:
John Doe
2026-05-08 18:26:55 +02:00
parent f1770b218c
commit d32d2cd79c
12 changed files with 401 additions and 14 deletions
+166 -11
View File
@@ -17,11 +17,44 @@ LEVEL_MUSIC_CONFIG = "level_music"
START_MENU_MUSIC = "High_Score_Garden.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
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),
@@ -65,6 +98,10 @@ class MiceMaze(
self.audio = self.profile_integration.get_setting('sound_enabled', True)
self.sound_volume = self.profile_integration.get_setting('sound_volume', 50)
self.music_volume = self.profile_integration.get_setting('music_volume', self.sound_volume)
self.difficulty = DEFAULT_DIFFICULTY
self.initial_rat_count = BASE_INITIAL_RATS
self.rat_speed_multiplier = 1.0
self._apply_difficulty(self.profile_integration.get_setting('difficulty', DEFAULT_DIFFICULTY), persist=False)
self.cell_size = 40
self.full_screen = False
@@ -166,9 +203,52 @@ class MiceMaze(
fallback_track = random.choice(self.available_music_tracks)
print(f"[audio] level {level_number} music={fallback_track} source=random")
return fallback_track
def _normalize_difficulty(self, difficulty_key):
difficulty_key = DIFFICULTY_ALIASES.get(difficulty_key, difficulty_key)
if difficulty_key in DIFFICULTY_OPTIONS_BY_KEY:
return difficulty_key
return DEFAULT_DIFFICULTY
def _difficulty_config(self):
return DIFFICULTY_OPTIONS_BY_KEY[self.difficulty]
def _apply_difficulty(self, difficulty_key, persist=True):
normalized_difficulty = self._normalize_difficulty(difficulty_key)
difficulty_config = DIFFICULTY_OPTIONS_BY_KEY[normalized_difficulty]
self.difficulty = normalized_difficulty
self.initial_rat_count = max(
1,
int(round(BASE_INITIAL_RATS * difficulty_config["starting_rats_multiplier"])),
)
self.rat_speed_multiplier = difficulty_config["speed_multiplier"]
if persist:
self.profile_integration.set_setting("difficulty", normalized_difficulty)
def _can_adjust_start_difficulty(self):
if self.game_end[0]:
return False
return self.game_status == "start_menu" and self.menu_screen == "start"
def _cycle_difficulty(self, delta):
if not self._can_adjust_start_difficulty():
return
current_index = 0
for index, option in enumerate(DIFFICULTY_OPTIONS):
if option["key"] == self.difficulty:
current_index = index
break
next_index = (current_index + delta) % len(DIFFICULTY_OPTIONS)
self._apply_difficulty(DIFFICULTY_OPTIONS[next_index]["key"])
def start_game(self):
print(f"[flow] start_game: level={self.current_level + 1}")
print(
f"[flow] start_game: level={self.current_level + 1} "
f"difficulty={self.difficulty} starting_rats={self.initial_rat_count} "
f"rat_speed={int(self.rat_speed_multiplier * 100)}%"
)
self.start_menu_animation_started_at = time.monotonic()
self.current_level_music = self._resolve_level_music(self.current_level)
self._valid_positions = None
@@ -208,7 +288,7 @@ class MiceMaze(
self.pointer = (random.randint(1, self.map.width-2), random.randint(1, self.map.height-2))
self.scroll_cursor()
for _ in range(5):
for _ in range(self.initial_rat_count):
self.spawn_rat()
def load_level(self, level_index, preserve_points=True, show_menu=False, menu_screen=None):
@@ -258,8 +338,12 @@ class MiceMaze(
self.run_recorded = False
print("[flow] points reset for new run")
for spawn_index in range(5):
print(f"[flow] spawning rat {spawn_index + 1}/5 for level={self.current_level + 1}", flush=True)
print(
f"[flow] spawning {self.initial_rat_count} rats for level={self.current_level + 1} "
f"difficulty={self.difficulty}",
flush=True,
)
for _ in range(self.initial_rat_count):
self.spawn_rat()
if show_menu:
@@ -302,6 +386,9 @@ class MiceMaze(
if self.game_status == "start_menu":
print(f"[flow] reset_game -> leaving menu_screen={self.menu_screen} and entering gameplay")
if self.menu_screen == "start":
self.load_level(0, preserve_points=False, show_menu=False)
return
self.game_status = "game"
self.menu_screen = None
return
@@ -329,6 +416,37 @@ class MiceMaze(
return False
return self.game_status == "paused"
def _draw_start_menu_difficulty_selector(self, x, y, width, height):
colors = START_MENU_COLORS
difficulty_config = self._difficulty_config()
accent = difficulty_config["accent"]
render_engine = self.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 _menu_font(self, size):
clamped = max(10, min(69, int(size)))
return self.render_engine.fonts[clamped]
@@ -549,19 +667,44 @@ class MiceMaze(
colors["text"] if index == 0 else colors["muted"],
)
cta_y = info_y + line_gap * len(subtitle_lines) + 18
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"],
)
render_engine.draw_text(
"Esc quits M toggles audio",
hint_font,
("center", cta_y + line_gap + 8),
colors["muted"],
)
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 = [
@@ -588,11 +731,17 @@ class MiceMaze(
self.profile_integration.set_setting(setting_name, clamped)
def menu_up(self):
if self._can_adjust_start_difficulty():
self._cycle_difficulty(-1)
return
if not self._can_adjust_audio_menu():
return
self.start_menu_selection = (self.start_menu_selection - 1) % len(START_MENU_AUDIO_OPTIONS)
def menu_down(self):
if self._can_adjust_start_difficulty():
self._cycle_difficulty(1)
return
if not self._can_adjust_audio_menu():
return
self.start_menu_selection = (self.start_menu_selection + 1) % len(START_MENU_AUDIO_OPTIONS)
@@ -605,9 +754,15 @@ class MiceMaze(
self._apply_volume_setting(setting_name, current_value + delta)
def menu_left(self):
if self._can_adjust_start_difficulty():
self._cycle_difficulty(-1)
return
self._adjust_selected_volume(-VOLUME_STEP)
def menu_right(self):
if self._can_adjust_start_difficulty():
self._cycle_difficulty(1)
return
self._adjust_selected_volume(VOLUME_STEP)
def update_background_music(self):