@ -1,24 +1,28 @@
import os
import random
import ctypes
from ctypes import *
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
from PIL import Image
class GameWindow :
def __init__ ( self , width , height , cell_size , title = " Default " , key_callback = None ) :
# Display configuration
self . cell_size = cell_size
self . width = width * cell_size
self . height = height * cell_size
# Screen resolution handling
actual_screen_size = os . environ . get ( " RESOLUTION " , " 640x480 " ) . split ( " x " )
actual_screen_size = tuple ( map ( int , actual_screen_size ) )
self . target_size = actual_screen_size if self . width > actual_screen_size [ 0 ] or self . height > actual_screen_size [ 1 ] else ( self . width , self . height )
# View offset calculations
self . w_start_offset = ( self . target_size [ 0 ] - self . width ) / / 2
self . h_start_offset = ( self . target_size [ 1 ] - self . height ) / / 2
self . w_offset = self . w_start_offset
@ -26,32 +30,56 @@ class GameWindow:
self . max_w_offset = self . target_size [ 0 ] - self . width
self . max_h_offset = self . target_size [ 1 ] - self . height
self . scale = self . target_size [ 1 ] / / self . cell_size
print ( f " Screen size: { self . width } x { self . height } " )
# SDL2 initialization
sdl2 . ext . init ( joystick = True )
sdl2 . SDL_Init ( sdl2 . SDL_INIT_AUDIO )
self . window = sdl2 . ext . Window ( title = title , size = self . target_size , ) # flags=sdl2.SDL_WINDOW_FULLSCREEN)
self . delay = 30
self . load_joystick ( )
# Window and renderer setup
self . window = sdl2 . ext . Window ( title = title , size = self . target_size )
self . window . show ( )
self . renderer = sdl2 . ext . Renderer ( self . window , flags = sdl2 . SDL_RENDERER_ACCELERATED )
self . factory = sdl2 . ext . SpriteFactory ( renderer = self . renderer )
# Font system
self . fonts = self . generate_fonts ( " assets/decterm.ttf " )
# Initial loading dialog
self . dialog ( " Loading assets... " )
self . renderer . present ( )
# Game state
self . running = True
self . key_down , self . key_up , self . axis_scroll = key_callback
self . delay = 30
self . performance = 0
self . audio_devs = { }
# Input handling
self . key_down , self . key_up , self . axis_scroll = key_callback
self . button_cursor = [ 0 , 0 ]
self . buttons = { }
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 )
# Audio system initialization
self . _init_audio_system ( )
# Input devices
self . load_joystick ( )
def _init_audio_system ( self ) :
""" Initialize audio devices for different audio channels """
audio_spec = SDL_AudioSpec ( freq = 22050 , aformat = sdl2 . AUDIO_U8 , channels = 1 , samples = 2048 )
self . audio_devs = { }
self . audio_devs [ " base " ] = sdl2 . SDL_OpenAudioDevice ( None , 0 , audio_spec , None , 0 )
self . audio_devs [ " effects " ] = sdl2 . SDL_OpenAudioDevice ( None , 0 , audio_spec , None , 0 )
self . audio_devs [ " music " ] = sdl2 . SDL_OpenAudioDevice ( None , 0 , audio_spec , None , 0 )
# ======================
# TEXTURE & IMAGE METHODS
# ======================
def create_texture ( self , tiles : list ) :
# Always create a fresh surface since we free it after use
""" Create a texture from a list of tiles """
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 )
@ -60,20 +88,12 @@ class GameWindow:
sdl2 . SDL_FreeSurface ( bg_surface )
return bg_texture
def load_joystick ( self ) :
sdl2 . SDL_Init ( sdl2 . SDL_INIT_JOYSTICK )
sdl2 . SDL_JoystickOpen ( 0 )
def generate_fonts ( self , font_file ) :
fonts = { }
for i in range ( 10 , 70 , 1 ) :
fonts . update ( { i : sdl2 . ext . FontManager ( font_path = font_file , size = i ) } )
return fonts
def load_image ( self , path , transparent_color = None , surface = False ) :
""" Load and process an image with optional transparency and scaling """
image_path = os . path . join ( " assets " , path )
image = Image . open ( image_path )
# Handle transparency
if transparent_color :
image = image . convert ( " RGBA " )
datas = image . getdata ( )
@ -84,99 +104,231 @@ class GameWindow:
else :
new_data . append ( item )
image . putdata ( new_data )
# Scale image
scale = self . cell_size / / 20
if surface :
return sdl2 . ext . pillow_to_surface ( image . resize ( ( image . width * scale , image . height * scale ) , Image . NEAREST ) )
image = image . resize ( ( image . width * scale , image . height * scale ) , Image . NEAREST )
if surface :
return sdl2 . ext . pillow_to_surface ( image )
return self . factory . from_surface ( sdl2 . ext . pillow_to_surface ( image ) )
def get_image_size ( self , image ) :
""" Get the size of an image sprite """
return image . size
# ======================
# FONT MANAGEMENT
# ======================
def generate_fonts ( self , font_file ) :
""" Generate font managers for different sizes """
fonts = { }
for i in range ( 10 , 70 , 1 ) :
fonts . update ( { i : sdl2 . ext . FontManager ( font_path = font_file , size = i ) } )
return fonts
# ======================
# DRAWING METHODS
# ======================
def draw_text ( self , text , font , position , color ) :
""" Draw text at specified position with given font and color """
sprite = self . factory . from_text ( text , color = color , fontmanager = font )
# Handle center positioning
if position == " center " :
position = ( " center " , " center " )
if position [ 0 ] == " center " :
position = ( self . target_size [ 0 ] / / 2 - sprite . size [ 0 ] / / 2 , position [ 1 ] )
if position [ 1 ] == " center " :
position = ( position [ 0 ] , self . target_size [ 1 ] / / 2 - sprite . size [ 1 ] / / 2 )
sprite . position = position
#print(sprite.position)
self . renderer . copy ( sprite , dstrect = sprite . position )
def draw_background ( self , bg_texture ) :
""" 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 ) )
def draw_image ( self , x , y , sprite , tag , anchor = " nw " ) :
""" Draw an image sprite at specified coordinates """
if not self . is_in_visible_area ( x , y ) :
return
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 )
def draw_rectangle ( self , x , y , width , height , tag , outline = " red " , filling = None ) :
""" Draw a rectangle with optional fill and outline """
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
y = y + self . h_offset
""" Draw a red pointer rectangle at specified coordinates """
x = x + self . w_offset
y = y + self . h_offset
for i in range ( 3 ) :
self . renderer . draw_rect ( ( x + i , y + i , self . cell_size - 2 * i , self . cell_size - 2 * i ) , color = sdl2 . ext . Color ( 255 , 0 , 0 ) )
self . renderer . draw_rect ( ( x + i , y + i , self . cell_size - 2 * i , self . cell_size - 2 * i ) ,
color = sdl2 . ext . Color ( 255 , 0 , 0 ) )
def delete_tag ( self , tag ) :
""" Placeholder for tag deletion (not implemented) """
pass
# ======================
# UI METHODS
# ======================
def dialog ( self , text , * * kwargs ) :
self . draw_rectangle ( 50 , 50 ,
self . target_size [ 0 ] - 100 , self . target_size [ 1 ] - 100 , " win " , filling = ( 255 , 255 , 255 ) )
""" Display a dialog box with text and optional extras """
# Draw dialog background
self . draw_rectangle ( 50 , 50 ,
self . target_size [ 0 ] - 100 , self . target_size [ 1 ] - 100 ,
" win " , filling = ( 255 , 255 , 255 ) )
# Draw main text
self . draw_text ( text , self . fonts [ self . target_size [ 1 ] / / 20 ] , " center " , sdl2 . ext . Color ( 0 , 0 , 0 ) )
# Draw subtitle if provided
if subtitle := kwargs . get ( " subtitle " ) :
self . draw_text ( subtitle , self . fonts [ self . target_size [ 1 ] / / 30 ] , ( " center " , self . target_size [ 1 ] / / 2 + 50 ) , sdl2 . ext . Color ( 0 , 0 , 0 ) )
self . draw_text ( subtitle , self . fonts [ self . target_size [ 1 ] / / 30 ] ,
( " center " , self . target_size [ 1 ] / / 2 + 50 ) , sdl2 . ext . Color ( 0 , 0 , 0 ) )
# Draw image if provided
if image := kwargs . get ( " image " ) :
image_size = self . get_image_size ( image )
self . draw_image ( self . target_size [ 0 ] / / 2 - image_size [ 0 ] / / 2 - self . w_offset ,
self . target_size [ 1 ] / / 2 - image_size [ 1 ] * 2 - self . h_offset ,
image , " win " )
self . target_size [ 1 ] / / 2 - image_size [ 1 ] * 2 - self . h_offset ,
image , " win " )
# Draw scores if provided
if scores := kwargs . get ( " scores " ) :
#self.draw_text("Scores:", self.fonts[self.target_size[1]//20], (self.target_size[0] // 2 - 50, self.target_size[1] // 2 + 50), sdl2.ext.Color(0, 0, 0))
sprite = self . factory . from_text ( " Scores: " , color = sdl2 . ext . Color ( 0 , 0 , 0 ) , fontmanager = self . fonts [ self . target_size [ 1 ] / / 20 ] )
sprite = self . factory . from_text ( " Scores: " , color = sdl2 . ext . Color ( 0 , 0 , 0 ) ,
fontmanager = self . fonts [ self . target_size [ 1 ] / / 20 ] )
sprite . position = ( self . target_size [ 0 ] / / 2 - 50 , self . target_size [ 1 ] / / 2 + 30 )
self . renderer . copy ( sprite , dstrect = sprite . position )
for i , score in enumerate ( scores [ : 5 ] ) :
score = " - " . join ( score )
self . draw_text ( score , self . fonts [ self . target_size [ 1 ] / / 40 ] , ( " center " , self . target_size [ 1 ] / / 2 + 50 + 30 * ( i + 1 ) ) , sdl2 . ext . Color ( 0 , 0 , 0 ) )
score_text = " - " . join ( score )
self . draw_text ( score_text , self . fonts [ self . target_size [ 1 ] / / 40 ] ,
( " center " , self . target_size [ 1 ] / / 2 + 50 + 30 * ( i + 1 ) ) ,
sdl2 . ext . Color ( 0 , 0 , 0 ) )
def start_dialog ( self , * * kwargs ) :
""" Display the welcome dialog """
self . dialog ( " Welcome to the Mice! " , subtitle = " A game by Matteo because was bored " , * * kwargs )
def draw_button ( self , x , y , text , width , height , coords ) :
""" Draw a button with text """
# TODO: Fix outline parameter usage
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_text ( text , self . fonts [ 20 ] , ( x + 10 , y + 10 ) , ( 0 , 0 , 0 ) )
def get_image_size ( self , image ) :
return image . size
def update_status ( self , text ) :
""" Update and display the status bar with FPS information """
fps = int ( 1000 / self . performance ) if self . performance != 0 else 0
text = f " FPS: { fps } - { text } "
status_ text = f " FPS: { fps } - { text } "
font = self . fonts [ 20 ]
sprite = self . factory . from_text ( text , color = sdl2 . ext . Color ( 0 , 0 , 0 ) , fontmanager = font )
sprite = self . factory . from_text ( status_ text, color = sdl2 . ext . Color ( 0 , 0 , 0 ) , fontmanager = font )
text_width , text_height = sprite . size
# Draw background for status text
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 ) )
self . draw_text ( status_text , font , ( 8 , 5 ) , sdl2 . ext . Color ( 0 , 0 , 0 ) )
# ======================
# VIEW & NAVIGATION
# ======================
def scroll_view ( self , pointer ) :
""" Adjust the view offset based on pointer coordinates """
x , y = pointer
def new_cycle ( self , delay , callback ) :
pass
def full_screen ( self , flag ) :
sdl2 . SDL_SetWindowFullscreen ( self . window . window , flag )
# Scale down and invert coordinates
x = - ( x / / 2 ) * self . cell_size
y = - ( y / / 2 ) * self . cell_size
# Clamp horizontal offset to valid range
if x < = self . max_w_offset + self . cell_size :
x = self . max_w_offset
# Clamp vertical offset to valid range
if y < self . max_h_offset :
y = self . max_h_offset
self . w_offset = x
self . h_offset = y
def is_in_visible_area ( self , x , y ) :
return - self . w_offset - self . cell_size < = x < = self . width - self . w_offset and - self . h_offset - self . cell_size < = y < = self . height - self . h_offset
def get_perf_counter ( self ) :
return sdl2 . SDL_GetPerformanceCounter ( )
""" Check if coordinates are within the visible area """
return ( - self . w_offset - self . cell_size < = x < = self . width - self . w_offset and
- self . h_offset - self . cell_size < = y < = self . height - self . h_offset )
def get_view_center ( self ) :
""" Get the center coordinates of the current view """
return self . w_offset + self . width / / 2 , self . h_offset + self . height / / 2
# ======================
# AUDIO METHODS
# ======================
def play_sound ( self , sound_file , tag = " base " ) :
""" Play a sound file on the specified audio channel """
sound_path = os . path . join ( " sound " , sound_file )
rw = sdl2 . SDL_RWFromFile ( byteify ( sound_path , " utf-8 " ) , b " rb " )
if not rw :
raise RuntimeError ( " Failed to open sound file " )
_buf = POINTER ( sdl2 . Uint8 ) ( )
_length = sdl2 . Uint32 ( )
spec = SDL_AudioSpec ( freq = 22050 , aformat = sdl2 . AUDIO_U8 , channels = 1 , samples = 2048 )
if sdl2 . SDL_LoadWAV_RW ( rw , 1 , byref ( spec ) , byref ( _buf ) , byref ( _length ) ) == None :
raise RuntimeError ( " Failed to load WAV " )
devid = self . audio_devs [ tag ]
# Clear any queued audio
sdl2 . SDL_ClearQueuedAudio ( devid )
# Start playing audio
sdl2 . SDL_QueueAudio ( devid , _buf , _length )
sdl2 . SDL_PauseAudioDevice ( devid , 0 )
def stop_sound ( self ) :
""" Stop all audio playback """
for dev in self . audio_devs . values ( ) :
sdl2 . SDL_PauseAudioDevice ( dev , 1 )
sdl2 . SDL_ClearQueuedAudio ( dev )
# ======================
# INPUT METHODS
# ======================
def load_joystick ( self ) :
""" Initialize joystick support """
sdl2 . SDL_Init ( sdl2 . SDL_INIT_JOYSTICK )
sdl2 . SDL_JoystickOpen ( 0 )
# ======================
# MAIN GAME LOOP
# ======================
def mainloop ( self , * * kwargs ) :
""" Main game loop handling events and rendering """
while self . running :
performance_start = sdl2 . SDL_GetPerformanceCounter ( )
self . renderer . clear ( )
# Execute background update if provided
if " bg_update " in kwargs :
kwargs [ " bg_update " ] ( )
# Execute main update
kwargs [ " update " ] ( )
# Handle SDL events
events = sdl2 . ext . get_events ( )
for event in events :
if event . type == sdl2 . SDL_QUIT :
@ -184,10 +336,9 @@ class GameWindow:
elif event . type == sdl2 . SDL_KEYDOWN and self . key_down :
key = sdl2 . SDL_GetKeyName ( event . key . keysym . sym ) . decode ( ' utf-8 ' )
self . key_down ( key )
elif event . type == sdl2 . SDL_KEYUP and self . key_down :
elif event . type == sdl2 . SDL_KEYUP and self . key_up :
key = sdl2 . SDL_GetKeyName ( event . key . keysym . sym ) . decode ( ' utf-8 ' )
self . key_up ( key )
print ( key )
elif event . type == sdl2 . SDL_MOUSEMOTION :
self . key_down ( " mouse " , coords = ( event . motion . x , event . motion . y ) )
elif event . type == sdl2 . SDL_JOYBUTTONDOWN :
@ -196,96 +347,57 @@ class GameWindow:
elif event . type == sdl2 . SDL_JOYBUTTONUP :
key = event . jbutton . button
self . key_up ( key )
# elif event.type == sdl2.SDL_JOYAXISMOTION:
# self.axis_scroll(event.jaxis.axis, event.jaxis.value)
# Disegna qui gli sprite
#rect = sdl2.SDL_Rect(self.w_offset, self.h_offset, self.target_size[0], self.target_size[1])
#sdl2.SDL_RenderSetClipRect(self.renderer.sdlrenderer, rect)
# Present the rendered frame
self . renderer . present ( )
self . performance = ( sdl2 . SDL_GetPerformanceCounter ( ) - performance_start ) / sdl2 . SDL_GetPerformanceFrequency ( ) * 1000
if self . performance < self . delay :
delay = self . delay - round ( self . performance )
else :
delay = 0
sdl2 . SDL_Delay ( delay )
def close ( self ) :
self . running = False
sdl2 . ext . quit ( )
# Calculate performance and delay
self . performance = ( ( sdl2 . SDL_GetPerformanceCounter ( ) - performance_start ) /
sdl2 . SDL_GetPerformanceFrequency ( ) * 1000 )
delay = max ( 0 , self . delay - round ( self . performance ) )
sdl2 . SDL_Delay ( delay )
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
# 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
# ======================
# SPECIAL EFFECTS
# ======================
# Clamp vertical offset
if y < self . max_h_offset :
y = self . max_h_offset
# ======================
# UTILITY METHODS
# ======================
self . w_offset = x
self . h_offset = y
def play_sound ( self , sound_file , tag = " base " ) :
sound_file = os . path . join ( " sound " , sound_file )
rw = sdl2 . SDL_RWFromFile ( byteify ( sound_file , " utf-8 " ) , b " rb " )
if not rw :
raise RuntimeError ( " Failed to open sound file " )
def new_cycle ( self , delay , callback ) :
""" Placeholder for cycle management (not implemented) """
pass
_buf = POINTER ( sdl2 . Uint8 ) ( )
_length = sdl2 . Uint32 ( )
def full_screen ( self , flag ) :
""" Toggle fullscreen mode """
sdl2 . SDL_SetWindowFullscreen ( self . window . window , flag )
spec = SDL_AudioSpec ( freq = 22050 , aformat = sdl2 . AUDIO_U8 , channels = 1 , samples = 2048 )
if sdl2 . SDL_LoadWAV_RW ( rw , 1 , byref ( spec ) , byref ( _buf ) , byref ( _length ) ) == None :
raise RuntimeError ( " Failed to load WAV " )
devid = self . audio_devs [ tag ]
# Clear any queued audio
sdl2 . SDL_ClearQueuedAudio ( devid )
def get_perf_counter ( self ) :
""" Get performance counter for timing """
return sdl2 . SDL_GetPerformanceCounter ( )
# Start playing audio
sdl2 . SDL_QueueAudio ( devid , _buf , _length )
sdl2 . SDL_PauseAudioDevice ( devid , 0 )
def stop_sound ( self ) :
for dev in self . audio_devs :
if not dev [ 0 ] :
sdl2 . SDL_PauseAudioDevice ( dev [ 1 ] , 1 )
sdl2 . SDL_ClearQueuedAudio ( dev [ 1 ] )
def start_dialog ( self , * * kwargs ) :
self . dialog ( " Welcome to the Mice! " , subtitle = " A game by Matteo because was bored " , * * kwargs )
center = self . get_view_center ( )
#self.draw_button(center[0], center[1] + 10 * self.scale, "Start", 120, 50, (0, 0))
def draw_button ( self , x , y , text , width , height , coords ) :
if self . button_cursor [ 0 ] == coords [ 0 ] and self . button_cursor [ 1 ] == coords [ 1 ] :
color = ( 0 , 0 , 255 )
self . draw_rectangle ( x , y , width , height , " button " , outline8u = color )
self . draw_text ( text , self . fonts [ 20 ] , ( x + 10 , y + 10 ) , ( 0 , 0 , 0 ) )
def close ( self ) :
""" Close the game window and cleanup """
self . running = False
sdl2 . ext . quit ( )
def get_view_center ( self ) :
return self . w_offset + self . width / / 2 , self . h_offset + self . height / / 2
# ======================
# MAIN GAME LOOP
# ======================
# ======================
# SPECIAL EFFECTS
# ======================
def generate_blood_surface ( self ) :
""" Genera dinamicamente una superficie di macchia di sangue usando SDL2 """
""" Generate a dynamic blood splatter surface using SDL2 """
size = self . cell_size
# Crea una superficie RGBA per la macchia di sangue
# Create RGBA surface for blood splatter
blood_surface = sdl2 . SDL_CreateRGBSurface (
0 , size , size , 32 ,
0x000000FF , # R mask
@ -297,58 +409,56 @@ class GameWindow:
if not blood_surface :
return None
# Blocca la superficie per il disegno pixel per pixel
# Lock surface for pixel manipulation
sdl2 . SDL_LockSurface ( blood_surface )
# Ottieni i dati dei pixel
# Get pixel data
pixels = cast ( blood_surface . contents . pixels , POINTER ( c_uint32 ) )
pitch = blood_surface . contents . pitch / / 4 # pitch in pixel (32-bit)
pitch = blood_surface . contents . pitch / / 4 # Convert pitch to pixels (32-bit)
# Colori del sangue (variazioni di rosso in formato ABGR )
# Blood color variations (ABGR format )
blood_colors = [
0xFF00008B , # Rosso scuro (A=FF, B=00, G=00, R=8B)
0xFF002222 , # Rosso mattone (A=FF, B=00, G=22, R=22)
0xFF003C14 , # Cremisi (A=FF, B=00, G=3C, R=14)
0xFF0000FF , # Rosso puro (A=FF, B=00, G=00, R=FF)
0xFF000080 , # Marrone rossastro (A=FF, B=00, G=00, R=80)
0xFF00008B , # Dark red
0xFF002222 , # Brick red
0xFF003C14 , # Crimson
0xFF0000FF , # Pure red
0xFF000080 , # Reddish brown
]
# Genera la macchia con un algoritmo di diffusione
# Generate splatter with diffusion algorithm
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
# Calculate distance from center
distance = ( ( x - center_x ) * * 2 + ( y - center_y ) * * 2 ) * * 0.5
# Probabilità di avere sangue basata sulla distanza
# Calculate blood probability based on distance
if distance < = max_radius :
# Più vicino al centro, più probabile avere sangue
# Closer to center = higher probability
probability = max ( 0 , 1 - ( distance / max_radius ) )
# Aggiungi rumore per forma irregolar e
# Add noise for irregular shap e
noise = random . random ( ) * 0.7
if random . random ( ) < probability * noise :
# Scegli un colore di sangue casuale
# Choose random blood color
color = random . choice ( blood_colors )
# Aggiungi variazione di alpha per trasparenza
# Add alpha variation for transparency
alpha = int ( 255 * probability * random . uniform ( 0.6 , 1.0 ) )
color = ( color & 0x00FFFFFF ) | ( alpha << 24 )
pixels [ y * pitch + x ] = color
else :
# Pixel trasparente
# Transparent pixel
pixels [ y * pitch + x ] = 0x00000000
else :
# Fuori dal raggio, trasparente
# Outside radius, transparent
pixels [ y * pitch + x ] = 0x00000000
# Aggiungi alcune gocce sparse intorno alla macchia principale
# Add scattered droplets around main splatter
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 )
@ -360,36 +470,36 @@ class GameWindow:
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
color = random . choice ( blood_colors [ : 3 ] ) # Darker colors for drops
alpha = random . randint ( 100 , 200 )
color = ( color & 0x00FFFFFF ) | ( alpha << 24 )
pixels [ ny * pitch + nx ] = color
# Sblocca la superfici e
# Unlock surfac e
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
""" Convert blood surface to texture and return it """
# Create temporary surface for blood texture
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
# Copy blood surface to temporary surface
sdl2 . SDL_BlitSurface ( blood_surface , None , temp_surface , None )
sdl2 . SDL_FreeSurface ( blood_surface )
# Create texture from the t emporary surface
# Create texture from temporary surface
texture = self . factory . from_surface ( temp_surface )
sdl2 . SDL_FreeSurface ( temp_surface )
return texture
def combine_blood_surfaces ( self , existing_surface , new_surface ) :
""" Combine two blood surfaces by blending them together """
# Create a new surface for the combined result
# Create combined su rfac e
combined_surface = sdl2 . SDL_CreateRGBSurface (
0 , self . cell_size , self . cell_size , 32 ,
0x000000FF , # R mask
@ -401,7 +511,7 @@ class GameWindow:
if combined_surface is None :
return existing_surface
# Lock both surfaces for pixel manipulation
# Lock surfaces for pixel manipulation
sdl2 . SDL_LockSurface ( existing_surface )
sdl2 . SDL_LockSurface ( new_surface )
sdl2 . SDL_LockSurface ( combined_surface )
@ -411,9 +521,9 @@ class GameWindow:
new_pixels = cast ( new_surface . contents . pixels , POINTER ( c_uint32 ) )
combined_pixels = cast ( combined_surface . contents . pixels , POINTER ( c_uint32 ) )
pitch = combined_surface . contents . pitch / / 4 # pitch in pixels (32-bit)
pitch = combined_surface . contents . pitch / / 4 # Convert pitch to pixels (32-bit)
# Combine pixels manually for better blending
# Combine pixels with additive blending
for y in range ( self . cell_size ) :
for x in range ( self . cell_size ) :
idx = y * pitch + x
@ -432,7 +542,7 @@ class GameWindow:
new_g = ( new_pixel >> 8 ) & 0xFF
new_b = new_pixel & 0xFF
# Blend the colors (additive blending for blood accumulation)
# Blend colors (additive blending for blood accumulation)
if new_a > 0 : # If new pixel has color
if existing_a > 0 : # If existing pixel has color
# Combine both colors, making it darker/more opaque
@ -462,7 +572,7 @@ class GameWindow:
sdl2 . SDL_UnlockSurface ( combined_surface )
return combined_surface
def free_surface ( self , surface ) :
""" Safely free an SDL surface """
if surface is not None :