This commit is contained in:
2025-08-17 18:49:07 +02:00
parent a910c8f74a
commit 8a32aad877
13 changed files with 259 additions and 30 deletions
+2
View File
@@ -30,6 +30,8 @@ class KeyBindings:
self.spawn_bomb(self.pointer)
elif key in keybindings.get("spawn_mine", []):
self.spawn_mine(self.pointer)
elif key in keybindings.get("spawn_nuclear_bomb", []):
self.spawn_nuclear_bomb(self.pointer)
elif key in keybindings.get("pause", []):
self.game_status = "paused" if self.game_status == "game" else "game"
elif key in keybindings.get("start_game", []):
+5
View File
@@ -6,11 +6,16 @@ class Graphics():
self.tunnel = self.render_engine.load_image("Rat/BMP_TUNNEL.png", surface=True)
self.grasses = [self.render_engine.load_image(f"Rat/BMP_1_GRASS_{i+1}.png", surface=True) for i in range(4)]
self.rat_assets = {}
self.rat_assets_textures = {}
self.bomb_assets = {}
for sex in ["MALE", "FEMALE", "BABY"]:
self.rat_assets[sex] = {}
for direction in ["UP", "DOWN", "LEFT", "RIGHT"]:
self.rat_assets[sex][direction] = self.render_engine.load_image(f"Rat/BMP_{sex}_{direction}.png", transparent_color=(128, 128, 128))
for sex in ["MALE", "FEMALE", "BABY"]:
self.rat_assets_textures[sex] = {}
for direction in ["UP", "DOWN", "LEFT", "RIGHT"]:
self.rat_assets_textures[sex][direction] = self.render_engine.load_image(f"Rat/BMP_{sex}_{direction}.png", transparent_color=(128, 128, 128), surface=False)
for n in range(5):
self.bomb_assets[n] = self.render_engine.load_image(f"Rat/BMP_BOMB{n}.png", transparent_color=(128, 128, 128))
self.assets = {}
+1 -1
View File
@@ -16,7 +16,7 @@ class Scoring:
for row in rows:
table.append(row.split(" - "))
table.sort(key=lambda x: int(x[1]), reverse=True)
return table
return table[:3]
def add_point(self, value):
self.points += value
+119 -11
View File
@@ -54,6 +54,18 @@ class GameWindow:
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 = ""
# White flash effect state
self.white_flash_active = False
self.white_flash_start_time = 0
self.white_flash_opacity = 255
# Input handling
self.key_down, self.key_up, self.axis_scroll = key_callback
@@ -151,7 +163,7 @@ class GameWindow:
"""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"):
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
@@ -225,20 +237,50 @@ class GameWindow:
# 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))
#self.draw_text(text, self.fonts[20], (x + 10, y + 10), (0, 0, 0))
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
status_text = f"FPS: {fps} - {text}"
font = self.fonts[20]
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(status_text, font, (8, 5), sdl2.ext.Color(0, 0, 0))
# at 10% of probability print fps
if len(self.fpss) > 20:
self.mean_fps = round(sum(self.fpss) / len(self.fpss)) if self.fpss else fps
#print(f"FPS: {self.mean_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 = self.factory.from_text(status_text, color=sdl2.ext.Color(0, 0, 0), fontmanager=font)
if self.text_width != self.stats_sprite.size[0] or self.text_height != self.stats_sprite.size[1]:
self.text_width, self.text_height = self.stats_sprite.size
# create a background for the status text using texture
self.stats_background = self.factory.from_color(sdl2.ext.Color(255, 255, 255), (self.text_width + 10, self.text_height + 4))
# self.renderer.fill((3, 3, self.text_width + 10, self.text_height + 4), sdl2.ext.Color(255, 255, 255))
self.renderer.copy(self.stats_background, dstrect=sdl2.SDL_Rect(3, 3, self.text_width + 10, self.text_height + 4))
self.renderer.copy(self.stats_sprite, dstrect=sdl2.SDL_Rect(8, 5, self.text_width, self.text_height))
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['nuclear']['count']}/{ammo['nuclear']['max']} "
if self.ammo_text != ammo_text:
self.ammo_text = ammo_text
font = self.fonts[20]
self.ammo_sprite = self.factory.from_text(ammo_text, color=sdl2.ext.Color(0, 0, 0), fontmanager=font)
text_width, text_height = self.ammo_sprite.size
self.ammo_background = self.factory.from_color(sdl2.ext.Color(255, 255, 255), (text_width + 10, text_height + 4))
text_width, text_height = self.ammo_sprite.size
position = (self.target_size[0] - text_width - 10, self.target_size[1] - text_height - 5)
#self.renderer.fill((position[0] - 5, position[1] - 2, text_width + 10, text_height + 4), sdl2.ext.Color(255, 255, 255))
self.renderer.copy(self.ammo_background, dstrect=sdl2.SDL_Rect(position[0] - 5, position[1] - 2, text_width + 10, text_height + 4))
self.renderer.copy(self.ammo_sprite, dstrect=sdl2.SDL_Rect(position[0], position[1], text_width, text_height))
self.renderer.copy(assets["BMP_BOMB0"], dstrect=sdl2.SDL_Rect(position[0]+25, position[1], 20, 20))
self.renderer.copy(assets["mine"], dstrect=sdl2.SDL_Rect(position[0]+80, position[1], 20, 20))
self.renderer.copy(assets["BMP_NUCLEAR"], dstrect=sdl2.SDL_Rect(position[0]+130, position[1], 20, 20))
# ======================
# VIEW & NAVIGATION
# ======================
@@ -328,6 +370,10 @@ class GameWindow:
# Execute main update
kwargs["update"]()
# Update and draw white flash effect
if self.update_white_flash():
self.draw_white_flash()
# Handle SDL events
events = sdl2.ext.get_events()
for event in events:
@@ -335,7 +381,11 @@ class GameWindow:
self.running = False
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)
# Check for Right Ctrl key to trigger white flash
if event.key.keysym.sym == sdl2.SDLK_RCTRL:
self.trigger_white_flash()
else:
self.key_down(key)
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)
@@ -362,6 +412,64 @@ class GameWindow:
# SPECIAL EFFECTS
# ======================
def trigger_white_flash(self):
"""Trigger the white flash effect"""
self.white_flash_active = True
self.white_flash_start_time = sdl2.SDL_GetTicks()
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 = sdl2.SDL_GetTicks()
elapsed_time = current_time - self.white_flash_start_time
if elapsed_time < 1000: # First 1 second : full white
self.white_flash_opacity = 255
return True
elif elapsed_time < 3000: # Next 2 seconds: fade out
# Calculate fade based on remaining time (2000ms fade duration)
fade_progress = (elapsed_time - 1000) / 2000.0 # 0.0 to 1.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:
# Create a white surface with the current opacity
white_surface = sdl2.SDL_CreateRGBSurface(
0, self.target_size[0], self.target_size[1], 32,
0x000000FF, # R mask
0x0000FF00, # G mask
0x00FF0000, # B mask
0xFF000000 # A mask
)
if white_surface:
# Fill surface with white
sdl2.SDL_FillRect(white_surface, None,
sdl2.SDL_MapRGBA(white_surface.contents.format,
255, 255, 255, self.white_flash_opacity))
# Convert to texture and draw
white_texture = self.factory.from_surface(white_surface)
white_texture.position = (0, 0)
# Enable alpha blending for the texture
sdl2.SDL_SetTextureBlendMode(white_texture.texture, sdl2.SDL_BLENDMODE_BLEND)
# Draw the white overlay
self.renderer.copy(white_texture, dstrect=sdl2.SDL_Rect(0, 0, self.target_size[0], self.target_size[1]))
# Clean up
sdl2.SDL_FreeSurface(white_surface)
# ======================
# UTILITY METHODS
# ======================
+23 -4
View File
@@ -17,19 +17,38 @@ class UnitManager:
self.spawn_unit(rat_class, position)
def spawn_bomb(self, position):
if self.ammo["bomb"]["count"] <= 0:
return
self.render_engine.play_sound("PUTDOWN.WAV")
self.spawn_unit(bomb.Timer, position)
self.ammo["bomb"]["count"] -= 1
def spawn_nuclear_bomb(self, position):
"""Spawn a nuclear bomb at the specified position"""
if self.ammo["nuclear"]["count"] <= 0:
return
if self.map.is_wall(position[0], position[1]):
return
self.render_engine.play_sound("NUCLEAR.WAV")
self.ammo["nuclear"]["count"] -= 1
self.spawn_unit(bomb.NuclearBomb, position)
def spawn_mine(self, position):
if self.ammo["mine"]["count"] <= 0:
return
if self.map.is_wall(position[0], position[1]):
return
self.render_engine.play_sound("PUTDOWN.WAV")
self.spawn_unit(mine.Mine, position)
self.ammo["mine"]["count"] -= 1
self.spawn_unit(mine.Mine, position, on_bottom=True)
def spawn_unit(self, unit, position, **kwargs):
def spawn_unit(self, unit, position, on_bottom=False, **kwargs):
id = uuid.uuid4()
self.units[id] = unit(self, position, id, **kwargs)
if on_bottom:
self.units = {id: unit(self, position, id, **kwargs), **self.units}
else:
self.units[id] = unit(self, position, id, **kwargs)
def choose_start(self):
if not hasattr(self, '_valid_positions'):
self._valid_positions = [