You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

833 lines
32 KiB

import os
import random
import pygame
from pygame import mixer
class GameWindow:
"""
Pygame-based game window implementation.
Provides a complete interface equivalent to sdl2_layer.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
self.h_offset = self.h_start_offset
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}")
# Pygame initialization
pygame.init()
mixer.init(frequency=22050, size=-16, channels=1, buffer=2048)
# Window and screen setup
self.window = pygame.display.set_mode(self.target_size)
pygame.display.set_caption(title)
self.screen = self.window
# Font system
self.fonts = self.generate_fonts("assets/decterm.ttf")
# Game state
self.running = True
self.delay = 30
self.performance = 0
self.last_status_text = ""
self.stats_sprite = None
self.mean_fps = 0
self.fpss = []
self.text_width = 0
self.text_height = 0
self.ammo_text = ""
self.stats_background = None
self.ammo_background = None
self.ammo_sprite = None
# White flash effect state
self.white_flash_active = False
self.white_flash_start_time = 0
self.white_flash_opacity = 255
# Input handling
self.trigger = key_callback
self.button_cursor = [0, 0]
self.buttons = {}
# Audio system initialization
self._init_audio_system()
self.audio = True
# Clock for frame rate control
self.clock = pygame.time.Clock()
# Input devices
self.load_joystick()
def show(self):
"""Show the window (for compatibility with SDL2 interface)"""
pygame.display.set_mode(self.target_size)
def _init_audio_system(self):
"""Initialize audio channels for different audio types"""
mixer.set_num_channels(8) # Ensure enough channels
self.audio_channels = {
"base": mixer.Channel(0),
"effects": mixer.Channel(1),
"music": mixer.Channel(2)
}
self.current_sounds = {}
# ======================
# TEXTURE & IMAGE METHODS
# ======================
def create_texture(self, tiles: list):
"""Create a texture from a list of tiles"""
bg_surface = pygame.Surface((self.width, self.height))
for tile in tiles:
bg_surface.blit(tile[0], (tile[1], tile[2]))
return bg_surface
# Helpers to support incremental background generation
def create_empty_background_surface(self):
"""Create and return an empty background surface to incrementally blit onto."""
return pygame.Surface((self.width, self.height))
def blit_tiles_batch(self, bg_surface, tiles_batch: list):
"""Blit a small batch of tiles onto the provided background surface.
tiles_batch: list of (surface, x, y)
Returns None. Designed to be called repeatedly with small batches to avoid long blocking operations.
"""
for tile, x, y in tiles_batch:
try:
bg_surface.blit(tile, (x, y))
except Exception:
# If tile is a SpriteWrapper, extract surface
try:
bg_surface.blit(tile.surface, (x, y))
except Exception:
pass
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)
# First try to use pygame's native loader which avoids a Pillow dependency.
try:
py_image = pygame.image.load(image_path)
# Ensure alpha if needed
try:
py_image = py_image.convert_alpha()
except Exception:
try:
py_image = py_image.convert()
except Exception:
pass
# Handle transparent color via colorkey if provided
if transparent_color:
# pygame expects a tuple of ints
try:
py_image.set_colorkey(transparent_color)
except Exception:
pass
# Scale image using pygame transforms
scale = max(1, self.cell_size // 20)
new_size = (py_image.get_width() * scale, py_image.get_height() * scale)
try:
py_image = pygame.transform.scale(py_image, new_size)
except Exception:
# If scaling fails, continue with original
pass
if not surface:
return SpriteWrapper(py_image)
return py_image
except Exception:
# Fallback to PIL-based loading if pygame can't handle the file or Pillow is present
try:
# Import Pillow lazily to avoid hard dependency at module import time
try:
from PIL import Image
except Exception:
Image = None
if Image is None:
raise
image = Image.open(image_path)
# Handle transparency
if transparent_color:
image = image.convert("RGBA")
datas = image.getdata()
new_data = []
for item in datas:
if item[:3] == transparent_color:
new_data.append((255, 255, 255, 0))
else:
new_data.append(item)
image.putdata(new_data)
# Scale image
scale = max(1, self.cell_size // 20)
image = image.resize((image.width * scale, image.height * scale), Image.NEAREST)
# Convert PIL image to pygame surface
mode = image.mode
size = image.size
data = image.tobytes()
if mode == "RGBA":
py_image = pygame.image.fromstring(data, size, mode)
elif mode == "RGB":
py_image = pygame.image.fromstring(data, size, mode)
else:
image = image.convert("RGBA")
data = image.tobytes()
py_image = pygame.image.fromstring(data, size, "RGBA")
if not surface:
return SpriteWrapper(py_image)
return py_image
except Exception:
# If both loaders fail, raise to notify caller
raise
def get_image_size(self, image):
"""Get the size of an image sprite"""
if isinstance(image, SpriteWrapper):
return image.size
return image.get_size()
# ======================
# FONT MANAGEMENT
# ======================
def generate_fonts(self, font_file):
"""Generate font objects for different sizes"""
fonts = {}
for i in range(10, 70, 1):
try:
fonts[i] = pygame.font.Font(font_file, i)
except:
fonts[i] = pygame.font.Font(None, i)
return fonts
# ======================
# DRAWING METHODS
# ======================
def draw_text(self, text, font, position, color):
"""Draw text at specified position with given font and color"""
if isinstance(color, tuple):
# Pygame color format
pass
else:
# Convert from any other format to RGB tuple
color = (color.r, color.g, color.b) if hasattr(color, 'r') else (0, 0, 0)
text_surface = font.render(text, True, color)
text_rect = text_surface.get_rect()
# Handle center positioning
if position == "center":
position = ("center", "center")
if isinstance(position, tuple):
if position[0] == "center":
text_rect.centerx = self.target_size[0] // 2
text_rect.y = position[1]
elif position[1] == "center":
text_rect.x = position[0]
text_rect.centery = self.target_size[1] // 2
else:
text_rect.topleft = position
self.screen.blit(text_surface, text_rect)
def draw_background(self, bg_texture):
"""Draw background texture with current view offset"""
self.screen.blit(bg_texture, (self.w_offset, self.h_offset))
def draw_image(self, x, y, sprite, tag=None, anchor="nw"):
"""Draw an image sprite at specified coordinates"""
if not self.is_in_visible_area(x, y):
return
if isinstance(sprite, SpriteWrapper):
surface = sprite.surface
else:
surface = sprite
self.screen.blit(surface, (x + self.w_offset, y + self.h_offset))
def draw_rectangle(self, x, y, width, height, tag, outline="red", filling=None):
"""Draw a rectangle with optional fill and outline"""
rect = pygame.Rect(x, y, width, height)
if filling:
pygame.draw.rect(self.screen, filling, rect)
else:
# Handle outline color
if isinstance(outline, str):
color_map = {
"red": (255, 0, 0),
"blue": (0, 0, 255),
"green": (0, 255, 0),
"black": (0, 0, 0),
"white": (255, 255, 255)
}
outline = color_map.get(outline, (255, 0, 0))
pygame.draw.rect(self.screen, outline, rect, 2)
def draw_pointer(self, x, y):
"""Draw a red pointer rectangle at specified coordinates"""
x = x + self.w_offset
y = y + self.h_offset
for i in range(3):
rect = pygame.Rect(x + i, y + i, self.cell_size - 2*i, self.cell_size - 2*i)
pygame.draw.rect(self.screen, (255, 0, 0), rect, 1)
def delete_tag(self, tag):
"""Placeholder for tag deletion (not needed in pygame implementation)"""
pass
# ======================
# UI METHODS
# ======================
def dialog(self, text, **kwargs):
"""Display a dialog box with text and optional extras"""
# Draw dialog background
dialog_rect = pygame.Rect(50, 50, self.target_size[0] - 100, self.target_size[1] - 100)
pygame.draw.rect(self.screen, (255, 255, 255), dialog_rect)
# Calculate layout positions to avoid overlaps
title_y = self.target_size[1] // 4 # Title at 1/4 of screen height
# Draw main text (title)
self.draw_text(text, self.fonts[self.target_size[1]//20],
("center", title_y), (0, 0, 0))
# Draw image if provided - position it below title
image_bottom_y = title_y + 60 # Default position if no image
if image := kwargs.get("image"):
image_size = self.get_image_size(image)
image_y = title_y + 50
self.draw_image(self.target_size[0] // 2 - image_size[0] // 2 - self.w_offset,
image_y - self.h_offset,
image, "win")
image_bottom_y = image_y + image_size[1] + 20
# Draw subtitle if provided - handle multi-line text, position below image
if subtitle := kwargs.get("subtitle"):
subtitle_lines = subtitle.split('\n')
base_y = image_bottom_y + 20
line_height = 25 # Fixed line height for consistent spacing
for i, line in enumerate(subtitle_lines):
if line.strip(): # Only draw non-empty lines
self.draw_text(line.strip(), self.fonts[self.target_size[1]//35],
("center", base_y + i * line_height), (0, 0, 0))
# Draw scores if provided - position at bottom
if scores := kwargs.get("scores"):
scores_start_y = self.target_size[1] * 3 // 4 # Bottom quarter of screen
title_surface = self.fonts[self.target_size[1]//25].render("High Scores:", True, (0, 0, 0))
title_rect = title_surface.get_rect(center=(self.target_size[0] // 2, scores_start_y))
self.screen.blit(title_surface, title_rect)
for i, score in enumerate(scores[:5]):
if len(score) >= 4: # New format: date, score, name, device
score_text = f"{score[2]}: {score[1]} pts ({score[3]})"
elif len(score) >= 3: # Medium format: date, score, name
score_text = f"{score[2]}: {score[1]} pts"
else: # Old format: date, score
score_text = f"Guest: {score[1]} pts"
self.draw_text(score_text, self.fonts[self.target_size[1]//45],
("center", scores_start_y + 30 + 25 * (i + 1)),
(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"""
color = (0, 0, 255) if self.button_cursor == list(coords) else (0, 0, 0)
self.draw_rectangle(x, y, width, height, "button", outline=color)
def update_status(self, text):
"""Update and display the status bar with FPS information"""
fps = int(self.clock.get_fps()) if self.clock.get_fps() > 0 else 0
if len(self.fpss) > 20:
self.mean_fps = round(sum(self.fpss) / len(self.fpss)) if self.fpss else fps
self.fpss.clear()
else:
self.fpss.append(fps)
status_text = f"FPS: {self.mean_fps} - {text}"
if status_text != self.last_status_text:
self.last_status_text = status_text
font = self.fonts[20]
self.stats_sprite = font.render(status_text, True, (0, 0, 0))
if self.text_width != self.stats_sprite.get_width() or self.text_height != self.stats_sprite.get_height():
self.text_width, self.text_height = self.stats_sprite.get_size()
self.stats_background = pygame.Surface((self.text_width + 10, self.text_height + 4))
self.stats_background.fill((255, 255, 255))
self.screen.blit(self.stats_background, (3, 3))
self.screen.blit(self.stats_sprite, (8, 5))
def update_ammo(self, ammo, assets):
"""Update and display the ammo count"""
ammo_text = f"{ammo['bomb']['count']}/{ammo['bomb']['max']} {ammo['mine']['count']}/{ammo['mine']['max']} {ammo['gas']['count']}/{ammo['gas']['max']} "
if self.ammo_text != ammo_text:
self.ammo_text = ammo_text
font = self.fonts[20]
self.ammo_sprite = font.render(ammo_text, True, (0, 0, 0))
text_width, text_height = self.ammo_sprite.get_size()
self.ammo_background = pygame.Surface((text_width + 10, text_height + 4))
self.ammo_background.fill((255, 255, 255))
text_width, text_height = self.ammo_sprite.get_size()
position = (self.target_size[0] - text_width - 10, self.target_size[1] - text_height - 5)
self.screen.blit(self.ammo_background, (position[0] - 5, position[1] - 2))
self.screen.blit(self.ammo_sprite, position)
# Draw ammo icons
bomb_sprite = assets["BMP_BOMB0"]
poison_sprite = assets["BMP_POISON"]
gas_sprite = assets["BMP_GAS"]
if isinstance(bomb_sprite, SpriteWrapper):
self.screen.blit(bomb_sprite.surface, (position[0]+25, position[1]))
else:
# Scale to 20x20 if needed
bomb_scaled = pygame.transform.scale(bomb_sprite, (20, 20))
self.screen.blit(bomb_scaled, (position[0]+25, position[1]))
if isinstance(poison_sprite, SpriteWrapper):
self.screen.blit(poison_sprite.surface, (position[0]+85, position[1]))
else:
poison_scaled = pygame.transform.scale(poison_sprite, (20, 20))
self.screen.blit(poison_scaled, (position[0]+85, position[1]))
if isinstance(gas_sprite, SpriteWrapper):
self.screen.blit(gas_sprite.surface, (position[0]+140, position[1]))
else:
gas_scaled = pygame.transform.scale(gas_sprite, (20, 20))
self.screen.blit(gas_scaled, (position[0]+140, position[1]))
# ======================
# VIEW & NAVIGATION
# ======================
def scroll_view(self, pointer):
"""Adjust the view offset based on pointer coordinates"""
x, y = pointer
# 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):
"""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"""
if not self.audio:
return
try:
sound_path = os.path.join("sound", sound_file)
sound = mixer.Sound(sound_path)
# Get the appropriate channel
channel = self.audio_channels.get(tag, self.audio_channels["base"])
# Stop any currently playing sound on this channel
channel.stop()
# Play the new sound
channel.play(sound)
# Store reference to prevent garbage collection
self.current_sounds[tag] = sound
except Exception as e:
print(f"Error playing sound {sound_file}: {e}")
def stop_sound(self):
"""Stop all audio playback"""
for channel in self.audio_channels.values():
channel.stop()
# ======================
# INPUT METHODS
# ======================
def load_joystick(self):
"""Initialize joystick support"""
pygame.joystick.init()
joystick_count = pygame.joystick.get_count()
if joystick_count > 0:
self.joystick = pygame.joystick.Joystick(0)
self.joystick.init()
print(f"Joystick initialized: {self.joystick.get_name()}")
else:
self.joystick = None
# ======================
# MAIN GAME LOOP
# ======================
def _normalize_key_name(self, key):
"""Normalize pygame key names to match SDL2 key names"""
# Pygame returns lowercase, SDL2 returns with proper case
key_map = {
"return": "Return",
"escape": "Escape",
"space": "Space",
"tab": "Tab",
"left shift": "Left_Shift",
"right shift": "Right_Shift",
"left ctrl": "Left_Ctrl",
"right ctrl": "Right_Ctrl",
"left alt": "Left_Alt",
"right alt": "Right_Alt",
"up": "Up",
"down": "Down",
"left": "Left",
"right": "Right",
"delete": "Delete",
"backspace": "Backspace",
"insert": "Insert",
"home": "Home",
"end": "End",
"pageup": "Page_Up",
"pagedown": "Page_Down",
"f1": "F1",
"f2": "F2",
"f3": "F3",
"f4": "F4",
"f5": "F5",
"f6": "F6",
"f7": "F7",
"f8": "F8",
"f9": "F9",
"f10": "F10",
"f11": "F11",
"f12": "F12",
}
# Return mapped value or capitalize first letter of original
normalized = key_map.get(key.lower(), key)
# Handle single letters (make uppercase)
if len(normalized) == 1:
normalized = normalized.upper()
return normalized
def mainloop(self, **kwargs):
"""Main game loop handling events and rendering"""
while self.running:
performance_start = pygame.time.get_ticks()
# Clear screen
self.screen.fill((0, 0, 0))
# Execute background update if provided
if "bg_update" in kwargs:
kwargs["bg_update"]()
# Execute main update
kwargs["update"]()
# Update and draw white flash effect
if self.update_white_flash():
self.draw_white_flash()
# Handle Pygame events
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
elif event.type == pygame.KEYDOWN:
key = pygame.key.name(event.key)
key = self._normalize_key_name(key)
key = key.replace(" ", "_")
self.trigger(f"keydown_{key}")
elif event.type == pygame.KEYUP:
key = pygame.key.name(event.key)
key = self._normalize_key_name(key)
key = key.replace(" ", "_")
self.trigger(f"keyup_{key}")
elif event.type == pygame.MOUSEMOTION:
self.trigger(f"mousemove_{event.pos[0]}, {event.pos[1]}")
elif event.type == pygame.JOYBUTTONDOWN:
self.trigger(f"joybuttondown_{event.button}")
elif event.type == pygame.JOYBUTTONUP:
self.trigger(f"joybuttonup_{event.button}")
elif event.type == pygame.JOYHATMOTION:
self.trigger(f"joyhatmotion_{event.hat}_{event.value}")
# Update display
pygame.display.flip()
# Control frame rate
self.clock.tick(60) # Target 60 FPS
# Calculate performance
self.performance = pygame.time.get_ticks() - performance_start
def step(self, update=None, bg_update=None):
"""Execute a single frame iteration. This is non-blocking and useful when
the caller (JS) schedules frames via requestAnimationFrame in the browser.
"""
performance_start = pygame.time.get_ticks()
# Clear screen
self.screen.fill((0, 0, 0))
# Background update
if bg_update:
try:
bg_update()
except Exception:
pass
# Main update
if update:
try:
update()
except Exception:
pass
# Update and draw white flash effect
if self.update_white_flash():
self.draw_white_flash()
# Handle Pygame events (single-frame processing)
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
elif event.type == pygame.KEYDOWN:
key = pygame.key.name(event.key)
key = self._normalize_key_name(key)
key = key.replace(" ", "_")
self.trigger(f"keydown_{key}")
elif event.type == pygame.KEYUP:
key = pygame.key.name(event.key)
key = self._normalize_key_name(key)
key = key.replace(" ", "_")
self.trigger(f"keyup_{key}")
elif event.type == pygame.MOUSEMOTION:
self.trigger(f"mousemove_{event.pos[0]}, {event.pos[1]}")
elif event.type == pygame.JOYBUTTONDOWN:
self.trigger(f"joybuttondown_{event.button}")
elif event.type == pygame.JOYBUTTONUP:
self.trigger(f"joybuttonup_{event.button}")
elif event.type == pygame.JOYHATMOTION:
self.trigger(f"joyhatmotion_{event.hat}_{event.value}")
# Update display once per frame
pygame.display.flip()
# Control frame rate
self.clock.tick(60)
# Calculate performance
self.performance = pygame.time.get_ticks() - performance_start
# ======================
# SPECIAL EFFECTS
# ======================
def trigger_white_flash(self):
"""Trigger the white flash effect"""
self.white_flash_active = True
self.white_flash_start_time = pygame.time.get_ticks()
self.white_flash_opacity = 255
def update_white_flash(self):
"""Update the white flash effect and return True if it should be drawn"""
if not self.white_flash_active:
return False
current_time = pygame.time.get_ticks()
elapsed_time = current_time - self.white_flash_start_time
if elapsed_time < 500: # First 500ms: full white
self.white_flash_opacity = 255
return True
elif elapsed_time < 2000: # Next 1500ms: fade out
fade_progress = (elapsed_time - 500) / 1500.0
self.white_flash_opacity = int(255 * (1.0 - fade_progress))
return True
else: # Effect is complete
self.white_flash_active = False
self.white_flash_opacity = 0
return False
def draw_white_flash(self):
"""Draw the white flash overlay"""
if self.white_flash_opacity > 0:
white_surface = pygame.Surface(self.target_size)
white_surface.fill((255, 255, 255))
white_surface.set_alpha(self.white_flash_opacity)
self.screen.blit(white_surface, (0, 0))
# ======================
# UTILITY METHODS
# ======================
def new_cycle(self, delay, callback):
"""Placeholder for cycle management (not needed in pygame implementation)"""
pass
def full_screen(self, flag):
"""Toggle fullscreen mode"""
if flag:
self.window = pygame.display.set_mode(self.target_size, pygame.FULLSCREEN)
else:
self.window = pygame.display.set_mode(self.target_size)
self.screen = self.window
def get_perf_counter(self):
"""Get performance counter for timing"""
return pygame.time.get_ticks()
def close(self):
"""Close the game window and cleanup"""
self.running = False
pygame.quit()
# ======================
# BLOOD EFFECT METHODS
# ======================
def generate_blood_surface(self):
"""Generate a dynamic blood splatter surface using Pygame"""
size = self.cell_size
# Create RGBA surface for blood splatter
blood_surface = pygame.Surface((size, size), pygame.SRCALPHA)
# Blood color variations
blood_colors = [
(139, 0, 0, 255), # Dark red
(34, 34, 34, 255), # Very dark gray
(20, 60, 60, 255), # Dark teal
(255, 0, 0, 255), # Pure red
(128, 0, 0, 255), # Reddish brown
]
# Generate splatter with diffusion algorithm
center_x, center_y = size // 2, size // 2
max_radius = size // 3 + random.randint(-3, 5)
for y in range(size):
for x in range(size):
# Calculate distance from center
distance = ((x - center_x) ** 2 + (y - center_y) ** 2) ** 0.5
# Calculate blood probability based on distance
if distance <= max_radius:
probability = max(0, 1 - (distance / max_radius))
noise = random.random() * 0.7
if random.random() < probability * noise:
color = random.choice(blood_colors)
alpha = int(255 * probability * random.uniform(0.6, 1.0))
blood_surface.set_at((x, y), (*color[:3], alpha))
# 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)
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])
alpha = random.randint(100, 200)
blood_surface.set_at((nx, ny), (*color[:3], alpha))
return blood_surface
def draw_blood_surface(self, blood_surface, position):
"""Convert blood surface to texture and return it"""
# In pygame, we can return the surface directly
return blood_surface
def combine_blood_surfaces(self, existing_surface, new_surface):
"""Combine two blood surfaces by blending them together"""
combined_surface = pygame.Surface((self.cell_size, self.cell_size), pygame.SRCALPHA)
# Blit existing blood first
combined_surface.blit(existing_surface, (0, 0))
# Blit new blood on top with alpha blending
combined_surface.blit(new_surface, (0, 0))
return combined_surface
def free_surface(self, surface):
"""Safely free a pygame surface (not needed in pygame, handled by GC)"""
pass
class SpriteWrapper:
"""
Wrapper class to make pygame surfaces compatible with SDL2 sprite interface
"""
def __init__(self, surface):
self.surface = surface
self.size = surface.get_size()
self.position = (0, 0)
def get_size(self):
return self.size