This commit is contained in:
2025-08-17 18:49:07 +02:00
parent a910c8f74a
commit 8a32aad877
13 changed files with 259 additions and 30 deletions
+1
View File
@@ -10,5 +10,6 @@
"scroll_right": ["Right", 11], "scroll_right": ["Right", 11],
"spawn_bomb": ["Space", 1], "spawn_bomb": ["Space", 1],
"spawn_mine": ["Left Ctrl", 2, 17], "spawn_mine": ["Left Ctrl", 2, 17],
"spawn_nuclear_bomb": ["N", 3],
"pause": ["P", 16] "pause": ["P", 16]
} }
+20 -7
View File
@@ -1,17 +1,20 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Audio conversion script to convert mine.wav to u8 22100 Hz format Audio conversion script to convert audio files to u8 22100 Hz format
""" """
from pydub import AudioSegment from pydub import AudioSegment
import os import os
import sys
import argparse
def convert_mine_wav(): def convert_audio(input_path, output_path=None):
"""Convert mine.wav to u8 format at 22100 Hz""" """Convert audio file to u8 format at 22100 Hz"""
# Input and output paths # Generate output path if not provided
input_path = "sound/mine.wav" if output_path is None:
output_path = "sound/mine_converted.wav" base_name = os.path.splitext(input_path)[0]
output_path = f"{base_name}_converted.wav"
if not os.path.exists(input_path): if not os.path.exists(input_path):
print(f"Error: {input_path} not found!") print(f"Error: {input_path} not found!")
@@ -73,5 +76,15 @@ def convert_mine_wav():
except Exception as e: except Exception as e:
print(f"Error during conversion: {e}") print(f"Error during conversion: {e}")
def main():
"""Main function to handle command line arguments"""
parser = argparse.ArgumentParser(description='Convert audio files to u8 22100 Hz format')
parser.add_argument('input_file', help='Input audio file path')
parser.add_argument('-o', '--output', help='Output file path (optional)')
args = parser.parse_args()
convert_audio(args.input_file, args.output)
if __name__ == "__main__": if __name__ == "__main__":
convert_mine_wav() main()
+2
View File
@@ -30,6 +30,8 @@ class KeyBindings:
self.spawn_bomb(self.pointer) self.spawn_bomb(self.pointer)
elif key in keybindings.get("spawn_mine", []): elif key in keybindings.get("spawn_mine", []):
self.spawn_mine(self.pointer) self.spawn_mine(self.pointer)
elif key in keybindings.get("spawn_nuclear_bomb", []):
self.spawn_nuclear_bomb(self.pointer)
elif key in keybindings.get("pause", []): elif key in keybindings.get("pause", []):
self.game_status = "paused" if self.game_status == "game" else "game" self.game_status = "paused" if self.game_status == "game" else "game"
elif key in keybindings.get("start_game", []): elif key in keybindings.get("start_game", []):
+5
View File
@@ -6,11 +6,16 @@ class Graphics():
self.tunnel = self.render_engine.load_image("Rat/BMP_TUNNEL.png", surface=True) self.tunnel = self.render_engine.load_image("Rat/BMP_TUNNEL.png", surface=True)
self.grasses = [self.render_engine.load_image(f"Rat/BMP_1_GRASS_{i+1}.png", surface=True) for i in range(4)] self.grasses = [self.render_engine.load_image(f"Rat/BMP_1_GRASS_{i+1}.png", surface=True) for i in range(4)]
self.rat_assets = {} self.rat_assets = {}
self.rat_assets_textures = {}
self.bomb_assets = {} self.bomb_assets = {}
for sex in ["MALE", "FEMALE", "BABY"]: for sex in ["MALE", "FEMALE", "BABY"]:
self.rat_assets[sex] = {} self.rat_assets[sex] = {}
for direction in ["UP", "DOWN", "LEFT", "RIGHT"]: for direction in ["UP", "DOWN", "LEFT", "RIGHT"]:
self.rat_assets[sex][direction] = self.render_engine.load_image(f"Rat/BMP_{sex}_{direction}.png", transparent_color=(128, 128, 128)) self.rat_assets[sex][direction] = self.render_engine.load_image(f"Rat/BMP_{sex}_{direction}.png", transparent_color=(128, 128, 128))
for sex in ["MALE", "FEMALE", "BABY"]:
self.rat_assets_textures[sex] = {}
for direction in ["UP", "DOWN", "LEFT", "RIGHT"]:
self.rat_assets_textures[sex][direction] = self.render_engine.load_image(f"Rat/BMP_{sex}_{direction}.png", transparent_color=(128, 128, 128), surface=False)
for n in range(5): for n in range(5):
self.bomb_assets[n] = self.render_engine.load_image(f"Rat/BMP_BOMB{n}.png", transparent_color=(128, 128, 128)) self.bomb_assets[n] = self.render_engine.load_image(f"Rat/BMP_BOMB{n}.png", transparent_color=(128, 128, 128))
self.assets = {} self.assets = {}
+1 -1
View File
@@ -16,7 +16,7 @@ class Scoring:
for row in rows: for row in rows:
table.append(row.split(" - ")) table.append(row.split(" - "))
table.sort(key=lambda x: int(x[1]), reverse=True) table.sort(key=lambda x: int(x[1]), reverse=True)
return table return table[:3]
def add_point(self, value): def add_point(self, value):
self.points += value self.points += value
+119 -11
View File
@@ -54,6 +54,18 @@ class GameWindow:
self.running = True self.running = True
self.delay = 30 self.delay = 30
self.performance = 0 self.performance = 0
self.last_status_text = ""
self.stats_sprite = None
self.mean_fps = 0
self.fpss = []
self.text_width = 0
self.text_height = 0
self.ammo_text = ""
# White flash effect state
self.white_flash_active = False
self.white_flash_start_time = 0
self.white_flash_opacity = 255
# Input handling # Input handling
self.key_down, self.key_up, self.axis_scroll = key_callback self.key_down, self.key_up, self.axis_scroll = key_callback
@@ -151,7 +163,7 @@ class GameWindow:
"""Draw background texture with current view offset""" """Draw background texture with current view offset"""
self.renderer.copy(bg_texture, dstrect=sdl2.SDL_Rect(self.w_offset, self.h_offset, self.width, self.height)) self.renderer.copy(bg_texture, dstrect=sdl2.SDL_Rect(self.w_offset, self.h_offset, self.width, self.height))
def draw_image(self, x, y, sprite, tag, anchor="nw"): def draw_image(self, x, y, sprite, tag=None, anchor="nw"):
"""Draw an image sprite at specified coordinates""" """Draw an image sprite at specified coordinates"""
if not self.is_in_visible_area(x, y): if not self.is_in_visible_area(x, y):
return return
@@ -225,20 +237,50 @@ class GameWindow:
# TODO: Fix outline parameter usage # TODO: Fix outline parameter usage
color = (0, 0, 255) if self.button_cursor == list(coords) else (0, 0, 0) color = (0, 0, 255) if self.button_cursor == list(coords) else (0, 0, 0)
self.draw_rectangle(x, y, width, height, "button", outline=color) self.draw_rectangle(x, y, width, height, "button", outline=color)
self.draw_text(text, self.fonts[20], (x + 10, y + 10), (0, 0, 0)) #self.draw_text(text, self.fonts[20], (x + 10, y + 10), (0, 0, 0))
def update_status(self, text): def update_status(self, text):
"""Update and display the status bar with FPS information""" """Update and display the status bar with FPS information"""
fps = int(1000 / self.performance) if self.performance != 0 else 0 fps = int(1000 / self.performance) if self.performance != 0 else 0
status_text = f"FPS: {fps} - {text}" # at 10% of probability print fps
font = self.fonts[20] if len(self.fpss) > 20:
sprite = self.factory.from_text(status_text, color=sdl2.ext.Color(0, 0, 0), fontmanager=font) self.mean_fps = round(sum(self.fpss) / len(self.fpss)) if self.fpss else fps
text_width, text_height = sprite.size #print(f"FPS: {self.mean_fps}")
self.fpss.clear()
# Draw background for status text else:
self.renderer.fill((3, 3, text_width + 10, text_height + 4), sdl2.ext.Color(255, 255, 255)) self.fpss.append(fps)
self.draw_text(status_text, font, (8, 5), sdl2.ext.Color(0, 0, 0))
status_text = f"FPS: {self.mean_fps} - {text}"
if status_text != self.last_status_text:
self.last_status_text = status_text
font = self.fonts[20]
self.stats_sprite = self.factory.from_text(status_text, color=sdl2.ext.Color(0, 0, 0), fontmanager=font)
if self.text_width != self.stats_sprite.size[0] or self.text_height != self.stats_sprite.size[1]:
self.text_width, self.text_height = self.stats_sprite.size
# create a background for the status text using texture
self.stats_background = self.factory.from_color(sdl2.ext.Color(255, 255, 255), (self.text_width + 10, self.text_height + 4))
# self.renderer.fill((3, 3, self.text_width + 10, self.text_height + 4), sdl2.ext.Color(255, 255, 255))
self.renderer.copy(self.stats_background, dstrect=sdl2.SDL_Rect(3, 3, self.text_width + 10, self.text_height + 4))
self.renderer.copy(self.stats_sprite, dstrect=sdl2.SDL_Rect(8, 5, self.text_width, self.text_height))
def update_ammo(self, ammo, assets):
"""Update and display the ammo count"""
ammo_text = f"{ammo['bomb']['count']}/{ammo['bomb']['max']} {ammo['mine']['count']}/{ammo['mine']['max']} {ammo['nuclear']['count']}/{ammo['nuclear']['max']} "
if self.ammo_text != ammo_text:
self.ammo_text = ammo_text
font = self.fonts[20]
self.ammo_sprite = self.factory.from_text(ammo_text, color=sdl2.ext.Color(0, 0, 0), fontmanager=font)
text_width, text_height = self.ammo_sprite.size
self.ammo_background = self.factory.from_color(sdl2.ext.Color(255, 255, 255), (text_width + 10, text_height + 4))
text_width, text_height = self.ammo_sprite.size
position = (self.target_size[0] - text_width - 10, self.target_size[1] - text_height - 5)
#self.renderer.fill((position[0] - 5, position[1] - 2, text_width + 10, text_height + 4), sdl2.ext.Color(255, 255, 255))
self.renderer.copy(self.ammo_background, dstrect=sdl2.SDL_Rect(position[0] - 5, position[1] - 2, text_width + 10, text_height + 4))
self.renderer.copy(self.ammo_sprite, dstrect=sdl2.SDL_Rect(position[0], position[1], text_width, text_height))
self.renderer.copy(assets["BMP_BOMB0"], dstrect=sdl2.SDL_Rect(position[0]+25, position[1], 20, 20))
self.renderer.copy(assets["mine"], dstrect=sdl2.SDL_Rect(position[0]+80, position[1], 20, 20))
self.renderer.copy(assets["BMP_NUCLEAR"], dstrect=sdl2.SDL_Rect(position[0]+130, position[1], 20, 20))
# ====================== # ======================
# VIEW & NAVIGATION # VIEW & NAVIGATION
# ====================== # ======================
@@ -328,6 +370,10 @@ class GameWindow:
# Execute main update # Execute main update
kwargs["update"]() kwargs["update"]()
# Update and draw white flash effect
if self.update_white_flash():
self.draw_white_flash()
# Handle SDL events # Handle SDL events
events = sdl2.ext.get_events() events = sdl2.ext.get_events()
for event in events: for event in events:
@@ -335,7 +381,11 @@ class GameWindow:
self.running = False self.running = False
elif event.type == sdl2.SDL_KEYDOWN and self.key_down: elif event.type == sdl2.SDL_KEYDOWN and self.key_down:
key = sdl2.SDL_GetKeyName(event.key.keysym.sym).decode('utf-8') key = sdl2.SDL_GetKeyName(event.key.keysym.sym).decode('utf-8')
self.key_down(key) # Check for Right Ctrl key to trigger white flash
if event.key.keysym.sym == sdl2.SDLK_RCTRL:
self.trigger_white_flash()
else:
self.key_down(key)
elif event.type == sdl2.SDL_KEYUP and self.key_up: elif event.type == sdl2.SDL_KEYUP and self.key_up:
key = sdl2.SDL_GetKeyName(event.key.keysym.sym).decode('utf-8') key = sdl2.SDL_GetKeyName(event.key.keysym.sym).decode('utf-8')
self.key_up(key) self.key_up(key)
@@ -362,6 +412,64 @@ class GameWindow:
# SPECIAL EFFECTS # SPECIAL EFFECTS
# ====================== # ======================
def trigger_white_flash(self):
"""Trigger the white flash effect"""
self.white_flash_active = True
self.white_flash_start_time = sdl2.SDL_GetTicks()
self.white_flash_opacity = 255
def update_white_flash(self):
"""Update the white flash effect and return True if it should be drawn"""
if not self.white_flash_active:
return False
current_time = sdl2.SDL_GetTicks()
elapsed_time = current_time - self.white_flash_start_time
if elapsed_time < 1000: # First 1 second : full white
self.white_flash_opacity = 255
return True
elif elapsed_time < 3000: # Next 2 seconds: fade out
# Calculate fade based on remaining time (2000ms fade duration)
fade_progress = (elapsed_time - 1000) / 2000.0 # 0.0 to 1.0
self.white_flash_opacity = int(255 * (1.0 - fade_progress))
return True
else: # Effect is complete
self.white_flash_active = False
self.white_flash_opacity = 0
return False
def draw_white_flash(self):
"""Draw the white flash overlay"""
if self.white_flash_opacity > 0:
# Create a white surface with the current opacity
white_surface = sdl2.SDL_CreateRGBSurface(
0, self.target_size[0], self.target_size[1], 32,
0x000000FF, # R mask
0x0000FF00, # G mask
0x00FF0000, # B mask
0xFF000000 # A mask
)
if white_surface:
# Fill surface with white
sdl2.SDL_FillRect(white_surface, None,
sdl2.SDL_MapRGBA(white_surface.contents.format,
255, 255, 255, self.white_flash_opacity))
# Convert to texture and draw
white_texture = self.factory.from_surface(white_surface)
white_texture.position = (0, 0)
# Enable alpha blending for the texture
sdl2.SDL_SetTextureBlendMode(white_texture.texture, sdl2.SDL_BLENDMODE_BLEND)
# Draw the white overlay
self.renderer.copy(white_texture, dstrect=sdl2.SDL_Rect(0, 0, self.target_size[0], self.target_size[1]))
# Clean up
sdl2.SDL_FreeSurface(white_surface)
# ====================== # ======================
# UTILITY METHODS # UTILITY METHODS
# ====================== # ======================
+23 -4
View File
@@ -17,19 +17,38 @@ class UnitManager:
self.spawn_unit(rat_class, position) self.spawn_unit(rat_class, position)
def spawn_bomb(self, position): def spawn_bomb(self, position):
if self.ammo["bomb"]["count"] <= 0:
return
self.render_engine.play_sound("PUTDOWN.WAV") self.render_engine.play_sound("PUTDOWN.WAV")
self.spawn_unit(bomb.Timer, position) self.spawn_unit(bomb.Timer, position)
self.ammo["bomb"]["count"] -= 1
def spawn_nuclear_bomb(self, position):
"""Spawn a nuclear bomb at the specified position"""
if self.ammo["nuclear"]["count"] <= 0:
return
if self.map.is_wall(position[0], position[1]):
return
self.render_engine.play_sound("NUCLEAR.WAV")
self.ammo["nuclear"]["count"] -= 1
self.spawn_unit(bomb.NuclearBomb, position)
def spawn_mine(self, position): def spawn_mine(self, position):
if self.ammo["mine"]["count"] <= 0:
return
if self.map.is_wall(position[0], position[1]): if self.map.is_wall(position[0], position[1]):
return return
self.render_engine.play_sound("PUTDOWN.WAV") self.render_engine.play_sound("PUTDOWN.WAV")
self.spawn_unit(mine.Mine, position) self.ammo["mine"]["count"] -= 1
self.spawn_unit(mine.Mine, position, on_bottom=True)
def spawn_unit(self, unit, position, **kwargs): def spawn_unit(self, unit, position, on_bottom=False, **kwargs):
id = uuid.uuid4() id = uuid.uuid4()
self.units[id] = unit(self, position, id, **kwargs) if on_bottom:
self.units = {id: unit(self, position, id, **kwargs), **self.units}
else:
self.units[id] = unit(self, position, id, **kwargs)
def choose_start(self): def choose_start(self):
if not hasattr(self, '_valid_positions'): if not hasattr(self, '_valid_positions'):
self._valid_positions = [ self._valid_positions = [
+27 -4
View File
@@ -1,13 +1,11 @@
#!/usr/bin/python3 #!/usr/bin/python3
import random import random
import uuid
from engine import maze, sdl2 as engine, controls, graphics, unit_manager, scoring
import os import os
import datetime
import json import json
from engine import maze, sdl2 as engine, controls, graphics, unit_manager, scoring
class MiceMaze( class MiceMaze(
controls.KeyBindings, controls.KeyBindings,
unit_manager.UnitManager, unit_manager.UnitManager,
@@ -40,6 +38,20 @@ class MiceMaze(
self.start_game() self.start_game()
self.background_texture = None self.background_texture = None
self.configs = self.get_config() self.configs = self.get_config()
self.ammo = {
"bomb": {
"count": 2,
"max": 5
},
"nuclear": {
"count": 1,
"max": 1
},
"mine": {
"count": 5,
"max": 5
}
}
def get_config(self): def get_config(self):
configs = {} configs = {}
@@ -57,6 +69,15 @@ class MiceMaze(
# ==================== GAME LOGIC ==================== # ==================== GAME LOGIC ====================
def refill_ammo(self):
for ammo_type, data in self.ammo.items():
if ammo_type == "bomb":
if random.random() < 0.01:
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"])
def update_maze(self): def update_maze(self):
if self.game_over(): if self.game_over():
return return
@@ -79,6 +100,8 @@ class MiceMaze(
unit.collisions() unit.collisions()
unit.draw() unit.draw()
self.render_engine.update_status(f"Mice: {self.count_rats()} - Points: {self.points}") self.render_engine.update_status(f"Mice: {self.count_rats()} - Points: {self.points}")
self.refill_ammo()
self.render_engine.update_ammo(self.ammo, self.assets)
self.scroll() self.scroll()
self.render_engine.new_cycle(50, self.update_maze) self.render_engine.new_cycle(50, self.update_maze)
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+57
View File
@@ -1,9 +1,12 @@
from .unit import Unit from .unit import Unit
from . import rat
from .points import Point from .points import Point
import uuid import uuid
import random
# Costanti # Costanti
AGE_THRESHOLD = 200 AGE_THRESHOLD = 200
NUCLEAR_TIMER = 50 # 1 second at ~50 FPS
class Bomb(Unit): class Bomb(Unit):
@@ -106,4 +109,58 @@ class Explosion(Bomb):
x_pos = self.position_before[0] * self.game.cell_size + (self.game.cell_size - image_size[0]) // 2 + partial_x x_pos = self.position_before[0] * self.game.cell_size + (self.game.cell_size - image_size[0]) // 2 + partial_x
y_pos = self.position_before[1] * self.game.cell_size + (self.game.cell_size - image_size[1]) // 2 + partial_y y_pos = self.position_before[1] * self.game.cell_size + (self.game.cell_size - image_size[1]) // 2 + partial_y
self.game.render_engine.draw_image(x_pos, y_pos, image, anchor="nw", tag="unit")
class NuclearBomb(Unit):
def __init__(self, game, position=(0,0), id=None):
super().__init__(game, position, id)
self.speed = 1 # Slow countdown
self.fight = False
self.timer = NUCLEAR_TIMER # 1 second timer
def move(self):
"""Count down the nuclear timer"""
self.timer -= 1
if self.timer <= 0:
self.explode()
def collisions(self):
pass
def explode(self):
"""Nuclear explosion that affects all rats on the map"""
print("NUCLEAR EXPLOSION!")
# Play nuclear explosion sound
self.game.render_engine.play_sound("nuke.wav")
# Trigger white screen effect
self.game.render_engine.trigger_white_flash()
# Remove the nuclear bomb from the game
if self.id in self.game.units:
self.game.units.pop(self.id)
# Kill 90% of all rats on the map
rats_to_kill = []
# If unit is a child class of Rat
for unit in self.game.units.values():
if isinstance(unit, rat.Rat):
if random.random() < 0.7: # 70% chance to kill each rat
rats_to_kill.append(unit)
for unit in rats_to_kill:
unit.die(score=None)
print(f"Nuclear explosion killed {len(rats_to_kill)} rats!")
def draw(self):
"""Draw nuclear bomb on position"""
image = self.game.assets["BMP_NUCLEAR"]
image_size = self.game.render_engine.get_image_size(image)
x_pos = self.position[0] * self.game.cell_size + (self.game.cell_size - image_size[0]) // 2
y_pos = self.position[1] * self.game.cell_size + (self.game.cell_size - image_size[1]) // 2
self.game.render_engine.draw_image(x_pos, y_pos, image, anchor="nw", tag="unit") self.game.render_engine.draw_image(x_pos, y_pos, image, anchor="nw", tag="unit")
+4 -3
View File
@@ -98,8 +98,9 @@ class Rat(Unit):
self.game.units.pop(target_unit.id) self.game.units.pop(target_unit.id)
# Rat-specific behavior: spawn points # Rat-specific behavior: spawn points
self.game.spawn_unit(Point, death_position, value=score) if score not in [None, 0]:
self.game.spawn_unit(Point, death_position, value=score)
# Add blood stain directly to background # Add blood stain directly to background
self.game.add_blood_stain(death_position) self.game.add_blood_stain(death_position)
@@ -108,7 +109,7 @@ class Rat(Unit):
direction = self.calculate_rat_direction() direction = self.calculate_rat_direction()
sex = self.sex if self.age > AGE_THRESHOLD else "BABY" sex = self.sex if self.age > AGE_THRESHOLD else "BABY"
image = self.game.rat_assets[sex][direction] image = self.game.rat_assets_textures[sex][direction]
image_size = self.game.render_engine.get_image_size(image) image_size = self.game.render_engine.get_image_size(image)
self.rat_image = image self.rat_image = image
partial_x, partial_y = 0, 0 partial_x, partial_y = 0, 0