From 243bb6d9bddc756ca7fc42ab96e6d914776bb327 Mon Sep 17 00:00:00 2001 From: Matteo Benedetto Date: Wed, 13 Aug 2025 23:30:43 +0200 Subject: [PATCH] Enhance blood stain mechanics: add dynamic blood surface generation and integrate blood stains into game rendering --- engine/graphics.py | 38 +++++++++++---- engine/sdl2.py | 114 ++++++++++++++++++++++++++++++++++++++++++++- rats.py | 4 +- units/bomb.py | 2 - units/rat.py | 8 +++- units/unit.py | 2 +- 6 files changed, 154 insertions(+), 14 deletions(-) diff --git a/engine/graphics.py b/engine/graphics.py index c508c09..dc12dcd 100644 --- a/engine/graphics.py +++ b/engine/graphics.py @@ -22,15 +22,37 @@ class Graphics(): def draw_maze(self): if self.background_texture is None: - - texture_tiles = [] - for y, row in enumerate(self.map.matrix): - for x, cell in enumerate(row): - variant = x*y % 4 - tile = self.grasses[variant] if cell else self.tunnel - texture_tiles.append((tile, x*self.cell_size, y*self.cell_size)) - self.background_texture = self.render_engine.create_texture(texture_tiles) + print("Generating background texture") + self.regenerate_background() self.render_engine.draw_background(self.background_texture) + + def regenerate_background(self): + """Generate or regenerate the background texture with all permanent elements""" + texture_tiles = [] + for y, row in enumerate(self.map.matrix): + for x, cell in enumerate(row): + variant = x*y % 4 + tile = self.grasses[variant] if cell else self.tunnel + texture_tiles.append((tile, x*self.cell_size, y*self.cell_size)) + + # Add blood stains if any exist + if hasattr(self, 'blood_stains'): + for position, blood_surface in self.blood_stains.items(): + texture_tiles.append((blood_surface, position[0]*self.cell_size, position[1]*self.cell_size)) + + self.background_texture = self.render_engine.create_texture(texture_tiles) + + def add_blood_stain(self, position): + """Add a blood stain to the background at the specified position""" + if not hasattr(self, 'blood_stains'): + self.blood_stains = {} + + # Generate blood surface + blood_surface = self.render_engine.generate_blood_surface() + self.blood_stains[position] = blood_surface + + # Regenerate background to include the new blood stain + self.regenerate_background() def scroll_cursor(self, x=0, y=0): if self.pointer[0] + x > self.map.width or self.pointer[1] + y > self.map.height: diff --git a/engine/sdl2.py b/engine/sdl2.py index 2c031f4..cc4ac0f 100644 --- a/engine/sdl2.py +++ b/engine/sdl2.py @@ -1,8 +1,10 @@ import os +import random import sdl2 import sdl2.ext from sdl2.ext.compat import byteify from ctypes import * +import ctypes from PIL import Image from sdl2 import SDL_AudioSpec @@ -47,7 +49,9 @@ class GameWindow: self.audio_devs["base"] = sdl2.SDL_OpenAudioDevice(None, 0, SDL_AudioSpec(freq=22050, aformat=sdl2.AUDIO_U8, channels=1, samples=2048), None, 0) self.audio_devs["effects"] = sdl2.SDL_OpenAudioDevice(None, 0, SDL_AudioSpec(freq=22050, aformat=sdl2.AUDIO_U8, channels=1, samples=2048), None, 0) self.audio_devs["music"] = sdl2.SDL_OpenAudioDevice(None, 0, SDL_AudioSpec(freq=22050, aformat=sdl2.AUDIO_U8, channels=1, samples=2048), None, 0) + def create_texture(self, tiles: list): + # Always create a fresh surface since we free it after use bg_surface = sdl2.SDL_CreateRGBSurface(0, self.width, self.height, 32, 0, 0, 0, 0) for tile in tiles: dstrect = sdl2.SDL_Rect(tile[1], tile[2], self.cell_size, self.cell_size) @@ -273,4 +277,112 @@ class GameWindow: self.draw_text(text, self.fonts[20], (x + 10, y + 10), (0,0,0)) def get_view_center(self): - return self.w_offset + self.width // 2, self.h_offset + self.height // 2 \ No newline at end of file + return self.w_offset + self.width // 2, self.h_offset + self.height // 2 + + + + def generate_blood_surface(self): + """Genera dinamicamente una superficie di macchia di sangue usando SDL2""" + size = self.cell_size + + # Crea una superficie RGBA per la macchia di sangue + blood_surface = sdl2.SDL_CreateRGBSurface( + 0, size, size, 32, + 0x000000FF, # R mask + 0x0000FF00, # G mask + 0x00FF0000, # B mask + 0xFF000000 # A mask + ) + + if not blood_surface: + return None + + # Blocca la superficie per il disegno pixel per pixel + sdl2.SDL_LockSurface(blood_surface) + + # Ottieni i dati dei pixel + pixels = cast(blood_surface.contents.pixels, POINTER(c_uint32)) + pitch = blood_surface.contents.pitch // 4 # pitch in pixel (32-bit) + + # Colori del sangue (variazioni di rosso) + blood_colors = [ + 0xFF8B0000, # Rosso scuro + 0xFFB22222, # Rosso mattone + 0xFFDC143C, # Cremisi + 0xFFFF0000, # Rosso puro + 0xFF800000, # Marrone + ] + + # Genera la macchia con un algoritmo di diffusione + center_x, center_y = size // 2, size // 2 + + # Inizia dal centro e espandi verso l'esterno + max_radius = size // 3 + random.randint(-3, 5) + + for y in range(size): + for x in range(size): + # Calcola la distanza dal centro + distance = ((x - center_x) ** 2 + (y - center_y) ** 2) ** 0.5 + + # Probabilità di avere sangue basata sulla distanza + if distance <= max_radius: + # Più vicino al centro, più probabile avere sangue + probability = max(0, 1 - (distance / max_radius)) + + # Aggiungi rumore per forma irregolare + noise = random.random() * 0.7 + + if random.random() < probability * noise: + # Scegli un colore di sangue casuale + color = random.choice(blood_colors) + + # Aggiungi variazione di alpha per trasparenza + alpha = int(255 * probability * random.uniform(0.6, 1.0)) + color = (color & 0x00FFFFFF) | (alpha << 24) + + pixels[y * pitch + x] = color + else: + # Pixel trasparente + pixels[y * pitch + x] = 0x00000000 + else: + # Fuori dal raggio, trasparente + pixels[y * pitch + x] = 0x00000000 + + # Aggiungi alcune gocce sparse intorno alla macchia principale + for _ in range(random.randint(3, 8)): + drop_x = center_x + random.randint(-max_radius - 5, max_radius + 5) + drop_y = center_y + random.randint(-max_radius - 5, max_radius + 5) + + if 0 <= drop_x < size and 0 <= drop_y < size: + drop_size = random.randint(1, 3) + for dy in range(-drop_size, drop_size + 1): + for dx in range(-drop_size, drop_size + 1): + nx, ny = drop_x + dx, drop_y + dy + if 0 <= nx < size and 0 <= ny < size: + if random.random() < 0.6: + color = random.choice(blood_colors[:3]) # Colori più scuri per le gocce + alpha = random.randint(100, 200) + color = (color & 0x00FFFFFF) | (alpha << 24) + pixels[ny * pitch + nx] = color + + # Sblocca la superficie + sdl2.SDL_UnlockSurface(blood_surface) + + # Converte la superficie in una texture usando il factory del gioco + return blood_surface + + def draw_blood_surface(self, blood_surface, position): + # Create a new surface for the blood texture since bg_surface may have been freed + temp_surface = sdl2.SDL_CreateRGBSurface(0, self.cell_size, self.cell_size, 32, 0, 0, 0, 0) + if temp_surface is None: + sdl2.SDL_FreeSurface(blood_surface) + return None + + # Copy the blood surface to the temporary surface + sdl2.SDL_BlitSurface(blood_surface, None, temp_surface, None) + sdl2.SDL_FreeSurface(blood_surface) + + # Create texture from the temporary surface + texture = self.factory.from_surface(temp_surface) + sdl2.SDL_FreeSurface(temp_surface) + return texture \ No newline at end of file diff --git a/rats.py b/rats.py index 36376c2..05cb85c 100644 --- a/rats.py +++ b/rats.py @@ -50,6 +50,8 @@ class MiceMaze( return configs def start_game(self): + self.blood_stains = {} + self.background_texture = None for _ in range(5): self.spawn_rat() @@ -91,7 +93,7 @@ class MiceMaze( self.render_engine.dialog("Game Over: Mice are too many!", image=self.assets["BMP_WEWIN"]) else: self.render_engine.dialog(f"You Win! Points: {self.points}", image=self.assets["BMP_WEWIN"], scores=self.read_score()) - + return True diff --git a/units/bomb.py b/units/bomb.py index 6a95cf8..793f5f3 100644 --- a/units/bomb.py +++ b/units/bomb.py @@ -71,14 +71,12 @@ 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: - print(f"Victim {victim.id} at {x}, {y} dies") victim.die(score=score) if score < 160: score *= 2 for victim in self.game.unit_positions_before.get((x, y), []): if victim.id in self.game.units: if victim.partial_move < 0.5: - print(f"Victim {victim.id} at {x}, {y} dies") victim.die(score=score) if score < 160: score *= 2 diff --git a/units/rat.py b/units/rat.py index 8421d27..c2c5390 100644 --- a/units/rat.py +++ b/units/rat.py @@ -91,12 +91,18 @@ class Rat(Unit): def die(self, unit=None, score=10): """Handle rat death and spawn points.""" target_unit = unit if unit else self + death_position = target_unit.position_before + # Use base class cleanup if target_unit.id in self.game.units: self.game.units.pop(target_unit.id) + # Rat-specific behavior: spawn points - self.game.spawn_unit(Point, target_unit.position_before, value=score) + self.game.spawn_unit(Point, death_position, value=score) + # Add blood stain directly to background + self.game.add_blood_stain(death_position) + def draw(self): start_perf = self.game.render_engine.get_perf_counter() direction = self.calculate_rat_direction() diff --git a/units/unit.py b/units/unit.py index 862624b..313820a 100644 --- a/units/unit.py +++ b/units/unit.py @@ -64,7 +64,7 @@ class Unit(ABC): """Handle collisions with other units. Default implementation does nothing.""" pass - def die(self): + def die(self, score=None): """Remove unit from game and handle basic cleanup.""" if self.id in self.game.units: self.game.units.pop(self.id)