Add new PNG images for clean and preview outputs
- Added `image_clean.png` to the output directory for the clean image representation. - Added `image_clean_preview.png` for the preview of the clean image. - Introduced `image_svg_clean.png` for the SVG clean image representation.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import argparse
|
||||
import random
|
||||
import os
|
||||
import json
|
||||
@@ -8,6 +9,7 @@ from engine import maze, sdl2 as engine, controls, graphics, unit_manager, scori
|
||||
from engine.collision_system import CollisionSystem
|
||||
from units import points
|
||||
from engine.user_profile_integration import UserProfileIntegration
|
||||
from runtime_paths import bundle_path
|
||||
|
||||
|
||||
class MiceMaze(
|
||||
@@ -19,11 +21,13 @@ class MiceMaze(
|
||||
|
||||
# ==================== INITIALIZATION ====================
|
||||
|
||||
def __init__(self, maze_file):
|
||||
def __init__(self, maze_file, level_index=0):
|
||||
# Initialize user profile integration
|
||||
self.profile_integration = UserProfileIntegration()
|
||||
self.map_source = maze_file
|
||||
self.current_level = level_index
|
||||
|
||||
self.map = maze.Map(maze_file)
|
||||
self.map = maze.Map(maze_file, level_index=level_index)
|
||||
|
||||
# Load profile-specific settings
|
||||
self.audio = self.profile_integration.get_setting('sound_enabled', True)
|
||||
@@ -31,6 +35,7 @@ class MiceMaze(
|
||||
|
||||
self.cell_size = 40
|
||||
self.full_screen = False
|
||||
self.loaded_theme_index = None
|
||||
|
||||
# Initialize render engine with profile-aware title
|
||||
player_name = self.profile_integration.get_profile_name()
|
||||
@@ -46,6 +51,7 @@ class MiceMaze(
|
||||
|
||||
self.load_assets()
|
||||
self.render_engine.window.show()
|
||||
self.render_engine.show_intro(bundle_path("assets", "Rat", "intro.png"))
|
||||
self.pointer = (random.randint(1, self.map.width-2), random.randint(1, self.map.height-2))
|
||||
self.scroll_cursor()
|
||||
self.points = 0
|
||||
@@ -64,7 +70,9 @@ class MiceMaze(
|
||||
|
||||
self.scrolling_direction = None
|
||||
self.game_status = "start_menu"
|
||||
self.menu_screen = "start"
|
||||
self.game_end = (False, None)
|
||||
self.run_recorded = False
|
||||
self.scrolling = False
|
||||
self.sounds = {}
|
||||
self.start_game()
|
||||
@@ -75,14 +83,18 @@ class MiceMaze(
|
||||
|
||||
def get_config(self):
|
||||
configs = {}
|
||||
for file in os.listdir("conf"):
|
||||
conf_dir = bundle_path("conf")
|
||||
for file in os.listdir(conf_dir):
|
||||
if file.endswith(".json"):
|
||||
with open(os.path.join("conf", file)) as f:
|
||||
with open(os.path.join(conf_dir, file), encoding="utf-8") as f:
|
||||
configs[file[:-5]] = json.load(f)
|
||||
return configs
|
||||
|
||||
def start_game(self):
|
||||
self.combined_scores = False
|
||||
print(f"[flow] start_game: level={self.current_level + 1}")
|
||||
self._valid_positions = None
|
||||
self.combined_scores = None
|
||||
self.run_recorded = False
|
||||
self.ammo = {
|
||||
"bomb": {
|
||||
"count": 2,
|
||||
@@ -106,17 +118,116 @@ class MiceMaze(
|
||||
|
||||
# Clear blood layer on game start/restart
|
||||
self.blood_layer_sprites.clear()
|
||||
self.cave_foreground_tiles.clear()
|
||||
self.game_end = (False, None)
|
||||
self.game_status = "start_menu"
|
||||
self.menu_screen = "start"
|
||||
self.units.clear()
|
||||
self.unit_positions.clear()
|
||||
self.unit_positions_before.clear()
|
||||
self.points = 0
|
||||
self.pointer = (random.randint(1, self.map.width-2), random.randint(1, self.map.height-2))
|
||||
self.scroll_cursor()
|
||||
|
||||
for _ in range(5):
|
||||
self.spawn_rat()
|
||||
|
||||
def load_level(self, level_index, preserve_points=True, show_menu=False, menu_screen=None):
|
||||
next_theme_index = level_index % maze.LEVELS_PER_DAT_FILE // 8 + 1
|
||||
print(
|
||||
f"[flow] load_level requested: target_level={level_index + 1} "
|
||||
f"preserve_points={preserve_points} show_menu={show_menu} menu_screen={menu_screen} "
|
||||
f"current_points={self.points} next_theme={next_theme_index}"
|
||||
)
|
||||
self.current_level = level_index
|
||||
self.map = maze.Map(self.map_source, level_index=level_index)
|
||||
self._valid_positions = None
|
||||
self.collision_system = CollisionSystem(
|
||||
self.cell_size,
|
||||
self.map.width,
|
||||
self.map.height
|
||||
)
|
||||
|
||||
if getattr(self, "loaded_theme_index", None) != next_theme_index:
|
||||
print(
|
||||
f"[flow] theme switch needed: loaded_theme={getattr(self, 'loaded_theme_index', None)} "
|
||||
f"-> next_theme={next_theme_index}"
|
||||
)
|
||||
self.load_assets()
|
||||
else:
|
||||
print(f"[flow] theme unchanged: reusing theme {next_theme_index}")
|
||||
self.units.clear()
|
||||
self.unit_positions.clear()
|
||||
self.unit_positions_before.clear()
|
||||
self.blood_stains = {}
|
||||
self.blood_layer_sprites.clear()
|
||||
self.cave_foreground_tiles.clear()
|
||||
self.background_texture = None
|
||||
self.ammo = {
|
||||
"bomb": {"count": 2, "max": 8},
|
||||
"nuclear": {"count": 1, "max": 1},
|
||||
"mine": {"count": 2, "max": 4},
|
||||
"gas": {"count": 2, "max": 4},
|
||||
}
|
||||
self.combined_scores = None
|
||||
self.game_end = (False, None)
|
||||
self.pointer = (random.randint(1, self.map.width-2), random.randint(1, self.map.height-2))
|
||||
self.scroll_cursor()
|
||||
if not preserve_points:
|
||||
self.points = 0
|
||||
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)
|
||||
self.spawn_rat()
|
||||
|
||||
if show_menu:
|
||||
self.game_status = "start_menu"
|
||||
self.menu_screen = menu_screen or "level_intro"
|
||||
print(f"[flow] level loaded into menu state: menu_screen={self.menu_screen} points={self.points}")
|
||||
else:
|
||||
self.game_status = "game"
|
||||
self.menu_screen = None
|
||||
print(f"[flow] level loaded directly into gameplay: level={self.current_level + 1} points={self.points}")
|
||||
|
||||
def advance_level(self):
|
||||
print(f"[flow] advance_level called from level={self.current_level + 1} points={self.points}")
|
||||
if self.map.source_path.suffix.lower() != ".dat":
|
||||
self.load_level(self.current_level, preserve_points=True, show_menu=True, menu_screen="level_intro")
|
||||
return
|
||||
|
||||
next_level = (self.current_level + 1) % maze.LEVELS_PER_DAT_FILE
|
||||
print(f"[flow] advancing to level={next_level + 1}")
|
||||
self.load_level(next_level, preserve_points=True, show_menu=True, menu_screen="level_intro")
|
||||
|
||||
def reset_game(self):
|
||||
self.pause = False
|
||||
self.game_status = "game"
|
||||
self.game_end = (False, None)
|
||||
self.units.clear()
|
||||
self.points = 0
|
||||
self.start_game()
|
||||
print(
|
||||
f"[flow] reset_game called: game_end={self.game_end} game_status={self.game_status} "
|
||||
f"menu_screen={self.menu_screen} points={self.points} level={self.current_level + 1}"
|
||||
)
|
||||
if self.game_end[0]:
|
||||
if self.game_end[1]:
|
||||
print("[flow] reset_game -> post-victory path")
|
||||
self.advance_level()
|
||||
else:
|
||||
print("[flow] reset_game -> restart from level 1 after defeat")
|
||||
self.load_level(0, preserve_points=False, show_menu=False)
|
||||
return
|
||||
|
||||
if self.game_status == "paused":
|
||||
print("[flow] reset_game -> unpausing current level")
|
||||
self.game_status = "game"
|
||||
return
|
||||
|
||||
if self.game_status == "start_menu":
|
||||
print(f"[flow] reset_game -> leaving menu_screen={self.menu_screen} and entering gameplay")
|
||||
self.game_status = "game"
|
||||
self.menu_screen = None
|
||||
return
|
||||
|
||||
print("[flow] reset_game -> hard reload current level")
|
||||
self.load_level(self.current_level, preserve_points=False, show_menu=False)
|
||||
|
||||
|
||||
# ==================== GAME LOGIC ====================
|
||||
@@ -140,30 +251,36 @@ class MiceMaze(
|
||||
self.render_engine.dialog("Pause")
|
||||
return
|
||||
if self.game_status == "start_menu":
|
||||
# Create personalized greeting
|
||||
player_name = self.profile_integration.get_profile_name()
|
||||
device_id = self.profile_integration.get_device_id()
|
||||
|
||||
greeting_title = f"Welcome to Mice, {player_name}!"
|
||||
|
||||
# Build subtitle with proper formatting
|
||||
subtitle = "A game by Matteo, because he was bored."
|
||||
device_line = f"Device: {device_id}"
|
||||
|
||||
# Show profile stats if available
|
||||
if self.profile_integration.current_profile:
|
||||
profile = self.profile_integration.current_profile
|
||||
stats_line = f"Best Score: {profile['best_score']} | Games: {profile['games_played']}"
|
||||
full_subtitle = f"{subtitle}\n{device_line}\n{stats_line}"
|
||||
if self.menu_screen == "level_intro":
|
||||
self.render_engine.dialog(
|
||||
f"Level {self.current_level + 1}",
|
||||
subtitle=f"Points: {self.points}\nPress Return to begin",
|
||||
image=self.assets["BMP_WEWIN"],
|
||||
)
|
||||
else:
|
||||
full_subtitle = f"{device_line}\nNo profile loaded - playing as guest"
|
||||
|
||||
self.render_engine.dialog(greeting_title,
|
||||
subtitle=full_subtitle,
|
||||
image=self.assets["BMP_WEWIN"])
|
||||
player_name = self.profile_integration.get_profile_name()
|
||||
device_id = self.profile_integration.get_device_id()
|
||||
|
||||
greeting_title = f"Welcome to Mice, {player_name}!"
|
||||
subtitle = "A game by Matteo, because he was bored."
|
||||
device_line = f"Device: {device_id}"
|
||||
|
||||
if self.profile_integration.current_profile:
|
||||
profile = self.profile_integration.current_profile
|
||||
stats_line = f"Best Score: {profile['best_score']} | Games: {profile['games_played']}"
|
||||
full_subtitle = f"{subtitle}\n{device_line}\n{stats_line}\nPress Return to start"
|
||||
else:
|
||||
full_subtitle = f"{device_line}\nNo profile loaded - playing as guest\nPress Return to start"
|
||||
|
||||
self.render_engine.dialog(
|
||||
greeting_title,
|
||||
subtitle=full_subtitle,
|
||||
image=self.assets["BMP_WEWIN"],
|
||||
)
|
||||
return
|
||||
self.render_engine.delete_tag("unit")
|
||||
self.render_engine.delete_tag("effect")
|
||||
self.render_engine.delete_tag("cave")
|
||||
self.render_engine.draw_pointer(self.pointer[0] * self.cell_size, self.pointer[1] * self.cell_size)
|
||||
|
||||
# Clear collision system for new frame
|
||||
@@ -220,6 +337,8 @@ class MiceMaze(
|
||||
for unit in self.units.copy().values():
|
||||
unit.collisions()
|
||||
unit.draw()
|
||||
|
||||
self.draw_cave_foreground()
|
||||
|
||||
self.render_engine.update_status(f"Mice: {self.count_rats()} - Points: {self.points}")
|
||||
self.refill_ammo()
|
||||
@@ -234,30 +353,22 @@ class MiceMaze(
|
||||
|
||||
def game_over(self):
|
||||
if self.game_end[0]:
|
||||
if not self.combined_scores:
|
||||
self.combined_scores = self.profile_integration.get_global_leaderboard(4)
|
||||
global_scores = []
|
||||
|
||||
for entry in self.combined_scores:
|
||||
# Convert to format expected by dialog: [date, score, name, device]
|
||||
global_scores.append([
|
||||
entry.get('last_play', ''),
|
||||
entry.get('best_score', 0),
|
||||
entry.get('user_id', 'Unknown'),
|
||||
|
||||
])
|
||||
if self.combined_scores is None:
|
||||
self.combined_scores = self.profile_integration.get_device_leaderboard(5)
|
||||
|
||||
if not self.game_end[1]:
|
||||
self.render_engine.dialog(
|
||||
"Game Over: Mice are too many!",
|
||||
image=self.assets["BMP_WEWIN"],
|
||||
scores=global_scores
|
||||
subtitle=f"Reached level: {self.current_level + 1}\nPress Return to restart from level 1",
|
||||
scores=self.combined_scores
|
||||
)
|
||||
else:
|
||||
self.render_engine.dialog(
|
||||
f"You Win! Points: {self.points}",
|
||||
f"Level {self.current_level + 1} Clear! Points: {self.points}",
|
||||
image=self.assets["BMP_WEWIN"],
|
||||
scores=global_scores
|
||||
subtitle="Press Return for the next level",
|
||||
scores=self.combined_scores
|
||||
)
|
||||
|
||||
|
||||
@@ -268,9 +379,12 @@ class MiceMaze(
|
||||
self.render_engine.play_sound("WEWIN.WAV")
|
||||
self.game_end = (True, False)
|
||||
self.game_status = "paused"
|
||||
print(f"[flow] defeat reached: rats={count_rats} points={self.points} level={self.current_level + 1}")
|
||||
|
||||
# Track incomplete game in profile
|
||||
self.profile_integration.update_game_stats(self.points, completed=False)
|
||||
if not self.run_recorded:
|
||||
self.save_score()
|
||||
self.profile_integration.update_game_stats(self.points, completed=False)
|
||||
self.run_recorded = True
|
||||
|
||||
return True
|
||||
if not count_rats and not any(isinstance(unit, points.Point) for unit in self.units.values()):
|
||||
@@ -279,19 +393,26 @@ class MiceMaze(
|
||||
self.render_engine.play_sound("WELLDONE.WAV", tag="effects")
|
||||
self.game_end = (True, True)
|
||||
self.game_status = "paused"
|
||||
|
||||
# Save score to both traditional file and user profile
|
||||
self.save_score()
|
||||
self.profile_integration.update_game_stats(self.points, completed=True)
|
||||
self.combined_scores = self.profile_integration.get_device_leaderboard(5)
|
||||
print(f"[flow] victory reached: points={self.points} level={self.current_level + 1}")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Run Mice! with DAT or JSON map loading")
|
||||
parser.add_argument("--level", type=int, default=0, help="Level index to load from level.dat (default: 0)")
|
||||
parser.add_argument("--map", dest="map_path", default=None, help="Optional map path override (.dat or .json)")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parse_args()
|
||||
print("Game starting...")
|
||||
solver = MiceMaze('maze.json')
|
||||
map_source = args.map_path or maze.get_default_map_source()
|
||||
print(f"Loading map from {map_source} (level {args.level})")
|
||||
solver = MiceMaze(map_source, level_index=args.level)
|
||||
solver.run()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user