Browse Source

mine implementation

master
Matteo Benedetto 4 months ago
parent
commit
c35fc9f1f7
  1. BIN
      assets/Rat/mine.png
  2. 0
      colorize_assets.py
  3. 1
      conf/keybinding_game.json
  4. 77
      convert_audio.py
  5. 3
      engine/controls.py
  6. 4
      engine/graphics.py
  7. 9
      engine/unit_manager.py
  8. 6
      rats.py
  9. 123
      resize_assets.py
  10. BIN
      sound/mine.wav
  11. BIN
      sound/mine_original.wav
  12. 1
      units/__init__.py
  13. 59
      units/mine.py

BIN
assets/Rat/mine.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

0
colorize_assets.py

1
conf/keybinding_game.json

@ -9,5 +9,6 @@
"scroll_left": ["Left", 10],
"scroll_right": ["Right", 11],
"spawn_bomb": ["Space", 1],
"spawn_mine": ["Left Ctrl", "Control_R", 17],
"pause": ["P", 16]
}

77
convert_audio.py

@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""
Audio conversion script to convert mine.wav to u8 22100 Hz format
"""
from pydub import AudioSegment
import os
def convert_mine_wav():
"""Convert mine.wav to u8 format at 22100 Hz"""
# Input and output paths
input_path = "sound/mine.wav"
output_path = "sound/mine_converted.wav"
if not os.path.exists(input_path):
print(f"Error: {input_path} not found!")
return
try:
# Load the audio file
print(f"Loading {input_path}...")
audio = AudioSegment.from_wav(input_path)
# Print current format info
print(f"Original format:")
print(f" Sample rate: {audio.frame_rate} Hz")
print(f" Channels: {audio.channels}")
print(f" Sample width: {audio.sample_width} bytes ({audio.sample_width * 8} bits)")
print(f" Duration: {len(audio)} ms")
# Convert to mono if stereo
if audio.channels > 1:
print("Converting to mono...")
audio = audio.set_channels(1)
# Convert to 22100 Hz sample rate
print("Converting sample rate to 22100 Hz...")
audio = audio.set_frame_rate(22100)
# Convert to 8-bit unsigned (u8)
print("Converting to 8-bit unsigned format...")
audio = audio.set_sample_width(1) # 1 byte = 8 bits
# Export the converted audio
print(f"Saving to {output_path}...")
audio.export(output_path, format="wav")
# Print new format info
converted_audio = AudioSegment.from_wav(output_path)
print(f"\nConverted format:")
print(f" Sample rate: {converted_audio.frame_rate} Hz")
print(f" Channels: {converted_audio.channels}")
print(f" Sample width: {converted_audio.sample_width} bytes ({converted_audio.sample_width * 8} bits)")
print(f" Duration: {len(converted_audio)} ms")
print(f"\nConversion complete! Output saved as: {output_path}")
# Optionally replace the original file
replace = input("\nReplace original mine.wav with converted version? (y/n): ").lower()
if replace == 'y':
import shutil
# Backup original
backup_path = "sound/mine_original.wav"
shutil.copy2(input_path, backup_path)
print(f"Original file backed up as: {backup_path}")
# Replace original
shutil.copy2(output_path, input_path)
os.remove(output_path)
print(f"Original file replaced with converted version.")
except Exception as e:
print(f"Error during conversion: {e}")
if __name__ == "__main__":
convert_mine_wav()

3
engine/controls.py

@ -27,8 +27,9 @@ class KeyBindings:
elif key in keybindings.get("scroll_right", []):
self.start_scrolling("Right")
elif key in keybindings.get("spawn_bomb", []):
self.render_engine.play_sound("PUTDOWN.WAV")
self.spawn_bomb(self.pointer)
elif key in keybindings.get("spawn_mine", []):
self.spawn_mine(self.pointer)
elif key in keybindings.get("pause", []):
self.game_status = "paused" if self.game_status == "game" else "game"
elif key in keybindings.get("start_game", []):

4
engine/graphics.py

@ -2,6 +2,7 @@ import os
class Graphics():
def load_assets(self):
print("Loading graphics assets...")
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.rat_assets = {}
@ -18,6 +19,7 @@ class Graphics():
self.assets[file[:-4]] = self.render_engine.load_image(f"Rat/{file}")
# ==================== RENDERING ====================
def draw_maze(self):
@ -65,7 +67,7 @@ class Graphics():
self.blood_stains[position] = new_blood_surface
# Regenerate background to include the updated blood stain
self.regenerate_background()
self.background_texture = None
def scroll_cursor(self, x=0, y=0):
if self.pointer[0] + x > self.map.width or self.pointer[1] + y > self.map.height:

9
engine/unit_manager.py

@ -1,6 +1,6 @@
import random
import uuid
from units import rat, bomb
from units import rat, bomb, mine
class UnitManager:
def count_rats(self):
@ -17,8 +17,15 @@ class UnitManager:
self.spawn_unit(rat_class, position)
def spawn_bomb(self, position):
self.render_engine.play_sound("PUTDOWN.WAV")
self.spawn_unit(bomb.Timer, position)
def spawn_mine(self, position):
if self.map.is_wall(position[0], position[1]):
return
self.render_engine.play_sound("PUTDOWN.WAV")
self.spawn_unit(mine.Mine, position)
def spawn_unit(self, unit, position, **kwargs):
id = uuid.uuid4()
self.units[id] = unit(self, position, id, **kwargs)

6
rats.py

@ -96,14 +96,14 @@ class MiceMaze(
return True
if self.count_rats() > 200:
count_rats = self.count_rats()
if count_rats > 200:
self.render_engine.stop_sound()
self.render_engine.play_sound("WEWIN.WAV")
self.game_end = (True, False)
self.game_status = "paused"
return True
if not len(self.units):
if not count_rats:
self.render_engine.stop_sound()
self.render_engine.play_sound("VICTORY.WAV")
self.render_engine.play_sound("WELLDONE.WAV", tag="effects")

123
resize_assets.py

@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""
Script to resize PNG asset files to 18x18 pixels and center them on a 20x20 canvas.
Saves the result back to the same file.
"""
import os
import glob
from PIL import Image, ImageOps
import argparse
def resize_and_center_image(image_path, target_size=(18, 18), canvas_size=(20, 20)):
"""
Resize an image to target_size and center it on a canvas of canvas_size.
Args:
image_path (str): Path to the image file
target_size (tuple): Size to resize the image to (width, height)
canvas_size (tuple): Size of the final canvas (width, height)
"""
try:
# Open the image
with Image.open(image_path) as img:
# Convert to RGBA to handle transparency
img = img.convert("RGBA")
# Resize the image to target size using high-quality resampling
resized_img = img.resize(target_size, Image.Resampling.LANCZOS)
# Create a new transparent canvas
canvas = Image.new("RGBA", canvas_size, (0, 0, 0, 0))
# Calculate position to center the resized image
x_offset = (canvas_size[0] - target_size[0]) // 2
y_offset = (canvas_size[1] - target_size[1]) // 2
# Paste the resized image onto the canvas
canvas.paste(resized_img, (x_offset, y_offset), resized_img)
# Save back to the same file
canvas.save(image_path, "PNG", optimize=True)
print(f"✓ Processed: {os.path.basename(image_path)}")
except Exception as e:
print(f"✗ Error processing {image_path}: {str(e)}")
def process_directory(directory_path, file_pattern="*.png"):
"""
Process all PNG files in a directory.
Args:
directory_path (str): Path to the directory containing PNG files
file_pattern (str): Pattern to match files (default: "*.png")
"""
if not os.path.exists(directory_path):
print(f"Error: Directory '{directory_path}' does not exist.")
return
# Find all PNG files matching the pattern
search_pattern = os.path.join(directory_path, file_pattern)
png_files = glob.glob(search_pattern)
if not png_files:
print(f"No PNG files found in '{directory_path}' matching pattern '{file_pattern}'")
return
print(f"Found {len(png_files)} PNG files to process...")
# Process each file
for png_file in png_files:
resize_and_center_image(png_file)
print(f"\nCompleted processing {len(png_files)} files.")
def process_single_file(file_path):
"""
Process a single PNG file.
Args:
file_path (str): Path to the PNG file
"""
if not os.path.exists(file_path):
print(f"Error: File '{file_path}' does not exist.")
return
if not file_path.lower().endswith('.png'):
print(f"Error: File '{file_path}' is not a PNG file.")
return
print(f"Processing single file: {os.path.basename(file_path)}")
resize_and_center_image(file_path)
print("Processing complete.")
def main():
parser = argparse.ArgumentParser(description="Resize PNG assets to 18x18px and center on 20x20px canvas")
parser.add_argument("path", help="Path to PNG file or directory containing PNG files")
parser.add_argument("--pattern", default="*.png", help="File pattern to match (default: *.png)")
args = parser.parse_args()
if os.path.isfile(args.path):
process_single_file(args.path)
elif os.path.isdir(args.path):
process_directory(args.path, args.pattern)
else:
print(f"Error: '{args.path}' is not a valid file or directory.")
if __name__ == "__main__":
# If run without arguments, process the assets/Rat directory by default
import sys
if len(sys.argv) == 1:
# Default to processing the assets/Rat directory
script_dir = os.path.dirname(os.path.abspath(__file__))
assets_dir = os.path.join(script_dir, "assets", "Rat")
if os.path.exists(assets_dir):
print("No arguments provided. Processing assets/Rat directory by default...")
process_directory(assets_dir)
else:
print("assets/Rat directory not found. Please provide a path as argument.")
print("Usage: python resize_assets.py <path_to_file_or_directory>")
else:
main()

BIN
sound/mine.wav

Binary file not shown.

BIN
sound/mine_original.wav

Binary file not shown.

1
units/__init__.py

@ -6,3 +6,4 @@ from .unit import Unit
from .rat import Rat, Male, Female
from .bomb import Bomb, Timer, Explosion
from .points import Point
from .mine import Mine

59
units/mine.py

@ -0,0 +1,59 @@
from .unit import Unit
from .bomb import Explosion
class Mine(Unit):
def __init__(self, game, position=(0,0), id=None):
super().__init__(game, position, id)
self.speed = 1.0 # Mine doesn't move but needs speed for consistency
self.armed = True # Mine is active and ready to explode
def move(self):
"""Mines don't move, but we need to check for collision with rats each frame."""
pass
def collisions(self):
"""Check if a rat steps on the mine (has position_before on mine's position)."""
if not self.armed:
return
# Check for rats that have position_before on this mine's position
for rat_unit in self.game.unit_positions_before.get(self.position, []):
if hasattr(rat_unit, 'sex'): # Check if it's a rat (rats have sex attribute)
# Mine explodes and kills the rat
self.explode(rat_unit)
break
def explode(self, victim_rat):
"""Mine explodes, killing the rat and destroying itself."""
if not self.armed:
return
print("MINE EXPLOSION!")
self.game.render_engine.play_sound("BOMB.WAV")
# Kill the rat that stepped on the mine
if victim_rat.id in self.game.units:
victim_rat.die(score=5)
# Remove the mine from the game
self.die()
def draw(self):
"""Draw the mine using the mine asset."""
if not self.armed:
return
# Use mine asset
image = self.game.assets["mine"]
image_size = self.game.render_engine.get_image_size(image)
# Center the mine in the cell
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")
def die(self, score=None):
"""Remove mine from game and disarm it."""
self.armed = False
Explosion(self.game, self.position).draw()
super().die(score)
Loading…
Cancel
Save