Browse Source

Aggiungi gestione dei controlli da tastiera e migliora la logica di spawn delle unità

master
Matteo Benedetto 1 year ago
parent
commit
3f513f1f19
  1. BIN
      assets/decterm.ttf
  2. BIN
      assets/terminal.ttf
  3. 38
      engine/controls.py
  4. 1
      engine/maze.py
  5. 82
      engine/sdl2.py
  6. 64
      rats.py
  7. 3
      requirements.txt
  8. BIN
      units/__pycache__/rat.cpython-313.pyc
  9. 5
      units/bomb.py
  10. 2
      units/rat.py

BIN
assets/decterm.ttf

Binary file not shown.

BIN
assets/terminal.ttf

Binary file not shown.

38
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()

1
engine/maze.py

@ -9,4 +9,5 @@ class Map:
self.width = len(self.matrix[0]) self.width = len(self.matrix[0])
def is_wall(self, x, y): def is_wall(self, x, y):
"""Restituisce True se la cella è un muro, False altrimenti."""
return self.matrix[y][x] return self.matrix[y][x]

82
engine/sdl2.py

@ -1,17 +1,16 @@
import sys
import os import os
import time
import sdl2 import sdl2
import sdl2.ext import sdl2.ext
import sdl2.sdlmixer
from sdl2.ext.compat import byteify
from PIL import Image from PIL import Image
class GameWindow: class GameWindow:
"""Classe che gestisce la finestra di gioco e il rendering grafico.""" def __init__(self, width, height, cell_size, title="", key_callback=None):
def __init__(self, width, height, cell_size, title="Mice!", key_callback=None):
self.cell_size = cell_size self.cell_size = cell_size
self.width = width * cell_size self.width = width * cell_size
self.height = height * 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]: if self.width > self.actual_screen_size[0] or self.height > self.actual_screen_size[1]:
self.target_size = self.actual_screen_size self.target_size = self.actual_screen_size
else: else:
@ -24,15 +23,18 @@ class GameWindow:
self.max_h_offset = self.target_size[1] - self.height self.max_h_offset = self.target_size[1] - self.height
print(f"Screen size: {self.width}x{self.height}") print(f"Screen size: {self.width}x{self.height}")
self.delay = 30 self.delay = 30
sdl2.ext.init(joystick=True) sdl2.ext.init(joystick=True, audio=True)
self.load_joystick() 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 = sdl2.ext.Window(title=title, size=self.target_size,)# flags=sdl2.SDL_WINDOW_FULLSCREEN)
self.window.show() self.window.show()
self.renderer = sdl2.ext.Renderer(self.window, flags=sdl2.SDL_RENDERER_ACCELERATED) self.renderer = sdl2.ext.Renderer(self.window, flags=sdl2.SDL_RENDERER_ACCELERATED)
self.factory = sdl2.ext.SpriteFactory(renderer=self.renderer) 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.running = True
self.key_callback = key_callback self.key_callback = key_callback
self.performance = 0
def load_joystick(self): def load_joystick(self):
sdl2.SDL_Init(sdl2.SDL_INIT_JOYSTICK) sdl2.SDL_Init(sdl2.SDL_INIT_JOYSTICK)
@ -57,7 +59,6 @@ class GameWindow:
else: else:
new_data.append(item) new_data.append(item)
image.putdata(new_data) image.putdata(new_data)
# Ridimensiona l'immagine in base a cell_size
scale = self.cell_size // 20 scale = self.cell_size // 20
image = image.resize((image.width * scale, image.height * scale), Image.NEAREST) image = image.resize((image.width * scale, image.height * scale), Image.NEAREST)
return self.factory.from_surface(sdl2.ext.pillow_to_surface(image)) 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) sprite.position = (x+self.w_offset, y+self.h_offset)
self.renderer.copy(sprite, dstrect=sprite.position) 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 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): def draw_pointer(self, x, y):
x=x+self.w_offset x=x+self.w_offset
@ -91,9 +95,14 @@ class GameWindow:
return image.size return image.size
def update_status(self, text): def update_status(self, text):
self.renderer.fill((10, 3, 100, 40), sdl2.ext.Color(255, 255, 255)) fps = int(1000 / self.performance) if self.performance != 0 else 0
self.draw_text(text, self.fonts[30], (20, 5), sdl2.ext.Color(0, 0, 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): def new_cycle(self, delay, callback):
pass pass
@ -102,6 +111,7 @@ class GameWindow:
def mainloop(self, **kwargs): def mainloop(self, **kwargs):
while self.running: while self.running:
performance_start = sdl2.SDL_GetPerformanceCounter()
self.renderer.clear() self.renderer.clear()
if "bg_update" in kwargs: if "bg_update" in kwargs:
kwargs["bg_update"]() kwargs["bg_update"]()
@ -113,32 +123,54 @@ class GameWindow:
elif event.type == sdl2.SDL_KEYDOWN and self.key_callback: elif event.type == sdl2.SDL_KEYDOWN and self.key_callback:
key = sdl2.SDL_GetKeyName(event.key.keysym.sym).decode('utf-8') key = sdl2.SDL_GetKeyName(event.key.keysym.sym).decode('utf-8')
self.key_callback(key) 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: 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: elif event.type == sdl2.SDL_JOYBUTTONDOWN:
key = event.jbutton.button key = event.jbutton.button
self.key_callback(key) self.key_callback(key)
# Disegna qui gli sprite # Disegna qui gli sprite
self.renderer.present() 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): def close(self):
self.running = False self.running = False
sdl2.ext.quit() sdl2.ext.quit()
def scroll_view(self, pointer): 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, 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: # Scale down and invert
self.w_offset = x x = -(x // 2) * self.cell_size
else: y = -(y // 2) * self.cell_size
self.w_offset = self.max_w_offset
# 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: def play_sound(self, sound_file):
self.h_offset = y 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()}")

64
rats.py

@ -5,35 +5,36 @@ import random
from units import rat, bomb from units import rat, bomb
import uuid import uuid
import subprocess import subprocess
from engine import maze, sdl2 as engine from engine import maze, sdl2 as engine, controls
import os import os
class MiceMaze: class MiceMaze(controls.KeyBindings):
def __init__(self, maze_file): def __init__(self, maze_file):
self.map = maze.Map(maze_file) self.map = maze.Map(maze_file)
self.pointer = (2,2)
self.audio = True self.audio = True
self.cell_size = 40 self.cell_size = 40
self.full_screen = False self.full_screen = False
self.engine = engine.GameWindow(self.map.width, self.map.height, self.cell_size, "Mice!", key_callback=self.key_pressed) 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.points = 0
self.graphics_load() self.graphics_load()
self.units = {} self.units = {}
self.unit_positions = {} self.unit_positions = {}
self.unit_positions_before = {} self.unit_positions_before = {}
self.scrolling_direction = None
self.scrolling = False
for _ in range(5): for _ in range(5):
self.new_rat() self.new_rat()
def new_rat(self, position=None): def new_rat(self, position=None):
if position is None: if position is None:
position = self.choose_start() position = self.choose_start()
id = uuid.uuid4()
rat_class = rat.Male if random.random() < 0.5 else rat.Female 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): def spawn_bomb(self, position):
id = uuid.uuid4() self.spawn_unit(bomb.Timer, position)
self.units[id] = bomb.Timer(self, position, id)
def spawn_unit(self, unit, position, **kwargs): def spawn_unit(self, unit, position, **kwargs):
id = uuid.uuid4() id = uuid.uuid4()
@ -75,32 +76,6 @@ class MiceMaze:
def run(self): def run(self):
self.draw_maze() self.draw_maze()
self.engine.mainloop(update=self.update_maze, bg_update=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): def scroll_cursor(self, x=0, y=0):
if self.pointer[0] + x > self.map.width or self.pointer[1] + y > self.map.height: 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): def play_sound(self, sound_file):
if self.audio: 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): def graphics_load(self):
self.tunnel = self.engine.load_image("Rat/BMP_TUNNEL.png") self.tunnel = self.engine.load_image("Rat/BMP_TUNNEL.png")
@ -134,6 +110,26 @@ class MiceMaze:
def add_point(self, value): def add_point(self, value):
self.points += 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__": if __name__ == "__main__":
profiler = cProfile.Profile() profiler = cProfile.Profile()

3
requirements.txt

@ -1 +1,2 @@
pyaudioop pysdl2
pysdl2-dll

BIN
units/__pycache__/rat.cpython-313.pyc

Binary file not shown.

5
units/bomb.py

@ -37,7 +37,6 @@ class Bomb(Unit):
n= 3 -n +1 n= 3 -n +1
if n < 0: if n < 0:
n = 0 n = 0
print(self.age)
image = self.game.bomb_assets[n] image = self.game.bomb_assets[n]
image_size = self.game.engine.get_image_size(image) image_size = self.game.engine.get_image_size(image)
self.rat_image = image self.rat_image = image
@ -69,13 +68,11 @@ class Timer(Bomb):
for victim in self.game.unit_positions.get((x, y), []): for victim in self.game.unit_positions.get((x, y), []):
if victim.id in self.game.units: if victim.id in self.game.units:
if victim.partial_move >= 0.5: if victim.partial_move >= 0.5:
victim.die() victim.die()
self.game.spawn_unit(Point, (x, y), value=10)
for victim in self.game.unit_positions_before.get((x, y), []): for victim in self.game.unit_positions_before.get((x, y), []):
if victim.id in self.game.units: if victim.id in self.game.units:
if victim.partial_move < 0.5: if victim.partial_move < 0.5:
victim.die() victim.die()
self.game.spawn_unit(Point, (x, y), value=10)
else: else:
break break
if direction == "N": if direction == "N":

2
units/rat.py

@ -1,4 +1,5 @@
from .unit import Unit from .unit import Unit
from .points import Point
import random import random
import uuid import uuid
@ -99,6 +100,7 @@ class Rat(Unit):
if not unit: if not unit:
unit = self unit = self
self.game.units.pop(unit.id) self.game.units.pop(unit.id)
self.game.spawn_unit(Point, unit.position_before)
def draw(self): def draw(self):
direction = self.calculate_rat_direction() direction = self.calculate_rat_direction()

Loading…
Cancel
Save