diff --git a/assets/decterm.ttf b/assets/decterm.ttf new file mode 100644 index 0000000..ee4d6f7 Binary files /dev/null and b/assets/decterm.ttf differ diff --git a/assets/terminal.ttf b/assets/terminal.ttf new file mode 100644 index 0000000..b880c3f Binary files /dev/null and b/assets/terminal.ttf differ diff --git a/engine/controls.py b/engine/controls.py new file mode 100644 index 0000000..dd73ea4 --- /dev/null +++ b/engine/controls.py @@ -0,0 +1,38 @@ +# This file contains the Controls class, which is responsible for handling user input. +# The key_pressed method is called when a key is pressed, and it contains the logic for handling different key presses. + +import random + +class KeyBindings: + def key_pressed(self, key, coords=None): + + #print(key) + if key == "Q" or key == 12: + self.engine.close() + elif key == "Return" or key == 13: + self.new_rat() + elif key == "D": + if self.units: + self.units[random.choice(list(self.units.keys()))].die() + elif key == "M": + self.audio = not self.audio + elif key == "F": + self.full_screen = not self.full_screen + self.engine.full_screen(self.full_screen) + elif key == "Up" or key == 8: + self.start_scrolling("Up") + elif key == "Down" or key == 9: + self.start_scrolling("Down") + elif key == "Left" or key == 10: + self.start_scrolling("Left") + elif key == "Right" or key == 11: + self.start_scrolling("Right") + elif key == "Space" or key == 1: + self.play_sound("PUTDOWN.WAV") + self.spawn_bomb(self.pointer) + elif key == "UpRelease" or key == "DownRelease" or key == "LeftRelease" or key == "RightRelease": + self.stop_scrolling() + # elif key == "mouse": + # adjusted_coords = (coords[0]//self.cell_size*2, coords[1]//self.cell_size*2) + # self.pointer = adjusted_coords + # self.scroll_cursor() \ No newline at end of file diff --git a/engine/maze.py b/engine/maze.py index 10a3e21..aa299c7 100644 --- a/engine/maze.py +++ b/engine/maze.py @@ -9,4 +9,5 @@ class Map: self.width = len(self.matrix[0]) def is_wall(self, x, y): + """Restituisce True se la cella รจ un muro, False altrimenti.""" return self.matrix[y][x] \ No newline at end of file diff --git a/engine/sdl2.py b/engine/sdl2.py index 74f39d1..1551917 100644 --- a/engine/sdl2.py +++ b/engine/sdl2.py @@ -1,17 +1,16 @@ -import sys import os -import time import sdl2 import sdl2.ext +import sdl2.sdlmixer +from sdl2.ext.compat import byteify from PIL import Image class GameWindow: - """Classe che gestisce la finestra di gioco e il rendering grafico.""" - def __init__(self, width, height, cell_size, title="Mice!", key_callback=None): + def __init__(self, width, height, cell_size, title="", key_callback=None): self.cell_size = cell_size self.width = width * cell_size self.height = height * cell_size - self.actual_screen_size = (640, 480) + self.actual_screen_size = (640, 480) if self.width > self.actual_screen_size[0] or self.height > self.actual_screen_size[1]: self.target_size = self.actual_screen_size else: @@ -24,15 +23,18 @@ class GameWindow: self.max_h_offset = self.target_size[1] - self.height print(f"Screen size: {self.width}x{self.height}") self.delay = 30 - sdl2.ext.init(joystick=True) + sdl2.ext.init(joystick=True, audio=True) self.load_joystick() + sdl2.SDL_Init(sdl2.SDL_INIT_AUDIO) + sdl2.sdlmixer.Mix_OpenAudio(44100, sdl2.sdlmixer.MIX_DEFAULT_FORMAT, 2, 2048) self.window = sdl2.ext.Window(title=title, size=self.target_size,)# flags=sdl2.SDL_WINDOW_FULLSCREEN) self.window.show() self.renderer = sdl2.ext.Renderer(self.window, flags=sdl2.SDL_RENDERER_ACCELERATED) self.factory = sdl2.ext.SpriteFactory(renderer=self.renderer) - self.fonts = self.generate_fonts("assets/AmaticSC-Regular.ttf") + self.fonts = self.generate_fonts("assets/decterm.ttf") self.running = True self.key_callback = key_callback + self.performance = 0 def load_joystick(self): sdl2.SDL_Init(sdl2.SDL_INIT_JOYSTICK) @@ -57,7 +59,6 @@ class GameWindow: else: new_data.append(item) image.putdata(new_data) - # Ridimensiona l'immagine in base a cell_size scale = self.cell_size // 20 image = image.resize((image.width * scale, image.height * scale), Image.NEAREST) return self.factory.from_surface(sdl2.ext.pillow_to_surface(image)) @@ -74,9 +75,12 @@ class GameWindow: sprite.position = (x+self.w_offset, y+self.h_offset) self.renderer.copy(sprite, dstrect=sprite.position) - def draw_rectangle(self, x, y, width, height, tag, outline="red"): + def draw_rectangle(self, x, y, width, height, tag, outline="red", filling=None): x, y = x + self.w_offset, y + self.h_offset - self.renderer.draw_rect((x, y, width, height), color=sdl2.ext.Color(255, 0, 0)) + if filling: + self.renderer.fill((x, y, width, height), sdl2.ext.Color(*filling)) + else: + self.renderer.draw_rect((x, y, width, height), sdl2.ext.Color(*outline)) def draw_pointer(self, x, y): x=x+self.w_offset @@ -91,9 +95,14 @@ class GameWindow: return image.size def update_status(self, text): - self.renderer.fill((10, 3, 100, 40), sdl2.ext.Color(255, 255, 255)) - self.draw_text(text, self.fonts[30], (20, 5), sdl2.ext.Color(0, 0, 0)) - + fps = int(1000 / self.performance) if self.performance != 0 else 0 + text = f"FPS: {fps} - {text}" + font = self.fonts[20] + sprite = self.factory.from_text(text, color=sdl2.ext.Color(0, 0, 0), fontmanager=font) + text_width, text_height = sprite.size + self.renderer.fill((3, 3, text_width + 10, text_height + 4), sdl2.ext.Color(255, 255, 255)) + self.draw_text(text, font, (8, 5), sdl2.ext.Color(0, 0, 0)) + def new_cycle(self, delay, callback): pass @@ -102,6 +111,7 @@ class GameWindow: def mainloop(self, **kwargs): while self.running: + performance_start = sdl2.SDL_GetPerformanceCounter() self.renderer.clear() if "bg_update" in kwargs: kwargs["bg_update"]() @@ -113,32 +123,54 @@ class GameWindow: elif event.type == sdl2.SDL_KEYDOWN and self.key_callback: key = sdl2.SDL_GetKeyName(event.key.keysym.sym).decode('utf-8') self.key_callback(key) + elif event.type == sdl2.SDL_KEYUP and self.key_callback: + key = sdl2.SDL_GetKeyName(event.key.keysym.sym).decode('utf-8') + "Release" + self.key_callback(key) elif event.type == sdl2.SDL_MOUSEMOTION: - self.scroll_view((event.motion.x//self.cell_size, event.motion.y//self.cell_size)) + self.key_callback("mouse", coords=(event.motion.x, event.motion.y)) elif event.type == sdl2.SDL_JOYBUTTONDOWN: key = event.jbutton.button self.key_callback(key) # Disegna qui gli sprite self.renderer.present() - sdl2.SDL_Delay(self.delay) + self.performance = (sdl2.SDL_GetPerformanceCounter() - performance_start) / sdl2.SDL_GetPerformanceFrequency() * 1000 + if self.performance < self.delay: + delay = self.delay - round(self.performance) + sdl2.SDL_Delay(delay) def close(self): self.running = False sdl2.ext.quit() def scroll_view(self, pointer): + """ + Adjusts the view offset based on the given pointer coordinates. + Scales them down by half, then adjusts offsets, ensuring they don't + exceed maximum allowed values. + """ x, y = pointer - x = x//2 - y = y//2 - x = -x * self.cell_size - y = -y * self.cell_size - if not x <= self.max_w_offset + self.cell_size: - self.w_offset = x - else: - self.w_offset = self.max_w_offset + # Scale down and invert + x = -(x // 2) * self.cell_size + y = -(y // 2) * self.cell_size + + # Clamp horizontal offset + if x <= self.max_w_offset + self.cell_size: + x = self.max_w_offset + + # Clamp vertical offset + if y < self.max_h_offset: + y = self.max_h_offset + + self.w_offset = x + self.h_offset = y - if not y < self.max_h_offset: - self.h_offset = y \ No newline at end of file + def play_sound(self, sound_file): + sample = sdl2.sdlmixer.Mix_LoadWAV(byteify(sound_file, "utf-8")) + if sample is None: + raise RuntimeError("Cannot open audio file: {}".format(sdl2.sdlmixer.Mix_GetError())) + channel = sdl2.sdlmixer.Mix_PlayChannel(-1, sample, 0) + if channel == -1: + print(f"Cannot play sound: {sdl2.sdlmixer.Mix_GetError()}") \ No newline at end of file diff --git a/rats.py b/rats.py index 9480f84..f91cca8 100644 --- a/rats.py +++ b/rats.py @@ -5,35 +5,36 @@ import random from units import rat, bomb import uuid import subprocess -from engine import maze, sdl2 as engine +from engine import maze, sdl2 as engine, controls import os -class MiceMaze: +class MiceMaze(controls.KeyBindings): def __init__(self, maze_file): self.map = maze.Map(maze_file) - self.pointer = (2,2) self.audio = True self.cell_size = 40 self.full_screen = False self.engine = engine.GameWindow(self.map.width, self.map.height, self.cell_size, "Mice!", key_callback=self.key_pressed) + self.pointer = (random.randint(1, self.map.width-2), random.randint(1, self.map.height-2)) + self.scroll_cursor() self.points = 0 self.graphics_load() self.units = {} self.unit_positions = {} self.unit_positions_before = {} + self.scrolling_direction = None + self.scrolling = False for _ in range(5): self.new_rat() def new_rat(self, position=None): if position is None: position = self.choose_start() - id = uuid.uuid4() rat_class = rat.Male if random.random() < 0.5 else rat.Female - self.units[id] = rat_class(self, position, id) + self.spawn_unit(rat_class, position) def spawn_bomb(self, position): - id = uuid.uuid4() - self.units[id] = bomb.Timer(self, position, id) + self.spawn_unit(bomb.Timer, position) def spawn_unit(self, unit, position, **kwargs): id = uuid.uuid4() @@ -75,32 +76,6 @@ class MiceMaze: def run(self): self.draw_maze() self.engine.mainloop(update=self.update_maze, bg_update=self.draw_maze) - - def key_pressed(self, key): - print(key) - if key == "Q" or key == 12: - self.engine.close() - elif key == "Return" or key == 13: - self.new_rat() - elif key == "D": - if self.units: - self.units[random.choice(list(self.units.keys()))].die() - elif key == "M": - self.audio = not self.audio - elif key == "F": - self.full_screen = not self.full_screen - self.engine.full_screen(self.full_screen) - elif key == "Up" or key == 8: - self.scroll_cursor(y=-1) - elif key == "Down" or key == 9: - self.scroll_cursor(y=1) - elif key == "Left" or key == 10: - self.scroll_cursor(x=-1) - elif key == "Right" or key == 11: - self.scroll_cursor(x=1) - elif key == "Space" or key == 1: - self.play_sound("PUTDOWN.WAV") - self.spawn_bomb(self.pointer) def scroll_cursor(self, x=0, y=0): if self.pointer[0] + x > self.map.width or self.pointer[1] + y > self.map.height: @@ -114,7 +89,8 @@ class MiceMaze: def play_sound(self, sound_file): if self.audio: - subprocess.Popen(["aplay", f"sound/{sound_file}"]) + #subprocess.Popen(["aplay", f"sound/{sound_file}"]) + self.engine.play_sound(f"sound/{sound_file}") def graphics_load(self): self.tunnel = self.engine.load_image("Rat/BMP_TUNNEL.png") @@ -134,6 +110,26 @@ class MiceMaze: def add_point(self, value): self.points += value + + def start_scrolling(self, direction): + self.scrolling_direction = direction + self.scrolling = True + self.scroll() + + def stop_scrolling(self): + self.scrolling = False + + def scroll(self): + if self.scrolling: + 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.engine.new_cycle(100, self.scroll) if __name__ == "__main__": profiler = cProfile.Profile() diff --git a/requirements.txt b/requirements.txt index c76bf47..5a0ddaf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -pyaudioop \ No newline at end of file +pysdl2 +pysdl2-dll \ No newline at end of file diff --git a/units/__pycache__/rat.cpython-313.pyc b/units/__pycache__/rat.cpython-313.pyc index a721d1d..b794d63 100644 Binary files a/units/__pycache__/rat.cpython-313.pyc and b/units/__pycache__/rat.cpython-313.pyc differ diff --git a/units/bomb.py b/units/bomb.py index fea34c0..4523772 100644 --- a/units/bomb.py +++ b/units/bomb.py @@ -37,7 +37,6 @@ class Bomb(Unit): n= 3 -n +1 if n < 0: n = 0 - print(self.age) image = self.game.bomb_assets[n] image_size = self.game.engine.get_image_size(image) self.rat_image = image @@ -69,13 +68,11 @@ class Timer(Bomb): for victim in self.game.unit_positions.get((x, y), []): if victim.id in self.game.units: if victim.partial_move >= 0.5: - victim.die() - self.game.spawn_unit(Point, (x, y), value=10) + victim.die() for victim in self.game.unit_positions_before.get((x, y), []): if victim.id in self.game.units: if victim.partial_move < 0.5: victim.die() - self.game.spawn_unit(Point, (x, y), value=10) else: break if direction == "N": diff --git a/units/rat.py b/units/rat.py index 4a8e190..162c291 100644 --- a/units/rat.py +++ b/units/rat.py @@ -1,4 +1,5 @@ from .unit import Unit +from .points import Point import random import uuid @@ -99,6 +100,7 @@ class Rat(Unit): if not unit: unit = self self.game.units.pop(unit.id) + self.game.spawn_unit(Point, unit.position_before) def draw(self): direction = self.calculate_rat_direction()