From 11ece2ee54c6096dc161fdee13f0d5956ca13a1b Mon Sep 17 00:00:00 2001 From: Berger Lena Date: Fri, 8 May 2026 15:03:41 +0200 Subject: [PATCH 1/6] feat: item - Create item.py - Add update to remove expired items - Add item settings for spawn interval, despawn timer, and size --- src/item.py | 19 +++++++++++++++++++ src/settings.py | 20 +++++++++++--------- 2 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 src/item.py diff --git a/src/item.py b/src/item.py new file mode 100644 index 0000000..1b24f20 --- /dev/null +++ b/src/item.py @@ -0,0 +1,19 @@ +import pygame +import random +from src.settings import ( + ITEM_SPAWN_INTERVAL, ITEM_SHOPPING_TIMER, ITEM_SIZE, + COLOR_HP_BAR_YELLOW, COLOR_HP_BAR_BG +) + +# remove expired items +def update(self, room_rect, blocked_rects): + now = pygame.time.get_ticks() + + remaining_items = [] + + for item in self.items: + if item.is_expired() == False: + remaining_items.append(item) + + # replace the old list with the new temp_list + self.items = remaining_items diff --git a/src/settings.py b/src/settings.py index e19f541..d3c221b 100644 --- a/src/settings.py +++ b/src/settings.py @@ -4,6 +4,7 @@ from typing import Mapping, Literal, Tuple DESK_WIDTH = 70 DESK_HEIGHT = 40 + # --- DOOR --- DOOR_WIDTH = 20 DOOR_HEIGHT = 100 @@ -11,13 +12,13 @@ DOOR_BORDER_WIDTH = 3 COLOR_DOOR_BEIGE = (245, 245, 220) COLOR_DOOR_BORDER = (100, 90, 80) -COLOR_HP_BAR_GREEN = (0, 255, 0) -COLOR_HP_BAR_BG = (50, 50, 50) + # Players Constants / Settings # Valid movement/animation directions. Direction = Literal["down", "left", "right", "up"] + # Sprite files are grouped by direction. # Example: "down" uses sprite_01.png, sprite_02.png, sprite_03.png. DIRECTION_FRAMES: Mapping[Direction, Tuple[int, int, int]] = { @@ -27,14 +28,15 @@ DIRECTION_FRAMES: Mapping[Direction, Tuple[int, int, int]] = { "up": (10, 11, 12), } + # Default drawing size and animation speed. DEFAULT_SPRITE_HEIGHT: int = 138 DEFAULT_FRAME_TIME: float = 0.16 -======= + # --- ECTS --- START_ECTS_VALUE = 1 # how many ECTS are generated at the beginning -ECTS_UPDATE_INTERVAL = 1000 # time in ms = 1 sec +ECTS_UPDATE_INTERVAL = 1000 # time in ms (1000 = 1 sec) BEER_BOTTLE_ECTS_VALUE = 2 BEER_COST = 50 @@ -60,12 +62,12 @@ PROJECTILE_SPEED = 7 COLOR_PROJECTILE = (200, 200, 0) TOWER_TIMER = 30000 # how long the tower stays and attack before it disappears +# --- ITEM SPAWN TIMER --- +ITEM_SPAWN_INTERVAL = 5000 # ms until a new item spawns +ITEM_SHOPPING_TIMER = 10000 # how long you have time to buy items before they despawn +ITEM_SIZE = 30 -# --- SHOPPING TIMER --- -ITEM_SHOPPING_TIMER = 10000 # for items like coffee, beer, tower - how long u have time to buy it - - -# -- ZOMBIE --- +# --- ZOMBIE --- ZOMBIE_EASY_HP = 30 ZOMBIE_EASY_DMG = 3 From 32525ccdbb545d913d0898743f24e96e98b1d3dd Mon Sep 17 00:00:00 2001 From: Berger Lena Date: Fri, 8 May 2026 15:13:31 +0200 Subject: [PATCH 2/6] feat: item - Add function for finding a random item spawn position --- src/item.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/item.py b/src/item.py index 1b24f20..4be10a6 100644 --- a/src/item.py +++ b/src/item.py @@ -5,6 +5,35 @@ from src.settings import ( COLOR_HP_BAR_YELLOW, COLOR_HP_BAR_BG ) + +# try 50 times to find a free position to spawn item +def _random_position(room_rect, blocked_rects): + attempts = 0 + max_attempts = 50 + + while attempts < max_attempts: + # generate random x/y coordinates with room boundaries + # * 2 to have a margin to walls + x = random.randint(room_rect.left + ITEM_SIZE, room_rect.right - ITEM_SIZE * 2) + y = random.randint(room_rect.top + ITEM_SIZE, room_rect.bottom - ITEM_SIZE * 2) + + # create a rect at this position for testing collision + test_rect = pygame.Rect(x, y, ITEM_SIZE, ITEM_SIZE) + blocked = False + for b in blocked_rects: + if test_rect.colliderect(b): + blocked = True + break + + # if position is not blocked, return the position + if not blocked: + return x, y + + attempts += 1 + + # no spot after 50 attempts -> return + return None + # remove expired items def update(self, room_rect, blocked_rects): now = pygame.time.get_ticks() From 3ca2ef1332249af826af186a630d401bd0a75120 Mon Sep 17 00:00:00 2001 From: Berger Lena Date: Fri, 8 May 2026 15:25:06 +0200 Subject: [PATCH 3/6] feature: item - add Item base class with collision rect, spawn timer, and sprite loading --- src/item.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/item.py b/src/item.py index 4be10a6..4fa2ec1 100644 --- a/src/item.py +++ b/src/item.py @@ -30,7 +30,7 @@ def _random_position(room_rect, blocked_rects): return x, y attempts += 1 - + # no spot after 50 attempts -> return return None @@ -46,3 +46,33 @@ def update(self, room_rect, blocked_rects): # replace the old list with the new temp_list self.items = remaining_items + + +class Item: + def __init__(self, x, y, item_type): + self.x = x + self.y = y + self.item_type = item_type + + # collision rect for player (2 times item size for width and height) + self.rect = pygame.Rect(self.x, self.y, ITEM_SIZE, ITEM_SIZE) + + # timer to store time when item appeared + self.spawn_time = pygame.time.get_ticks() + + # try to load sprite + try: + path = f"assets/items/{item_type}.png" + self.image = pygame.image.load(path).convert_alpha() + self.image = pygame.transform.scale(self.image, (ITEM_SIZE, ITEM_SIZE)) + except Exception as e: + print(f"Error loading item sprite {item_type}: {e}") + self.image = None + + + + + + + + From 2a6c93998bec1008199122aba9c3acf7d2b70a42 Mon Sep 17 00:00:00 2001 From: Berger Lena Date: Fri, 8 May 2026 15:47:29 +0200 Subject: [PATCH 4/6] refactor: create ItemManger class - add itemManager class and moved randomPosition and update into it - add is_expired to check if time ran out to buy spawned item --- src/item.py | 86 +++++++++++++++++++++++++------------------------ src/settings.py | 5 +++ 2 files changed, 49 insertions(+), 42 deletions(-) diff --git a/src/item.py b/src/item.py index 4fa2ec1..f3b9add 100644 --- a/src/item.py +++ b/src/item.py @@ -6,48 +6,6 @@ from src.settings import ( ) -# try 50 times to find a free position to spawn item -def _random_position(room_rect, blocked_rects): - attempts = 0 - max_attempts = 50 - - while attempts < max_attempts: - # generate random x/y coordinates with room boundaries - # * 2 to have a margin to walls - x = random.randint(room_rect.left + ITEM_SIZE, room_rect.right - ITEM_SIZE * 2) - y = random.randint(room_rect.top + ITEM_SIZE, room_rect.bottom - ITEM_SIZE * 2) - - # create a rect at this position for testing collision - test_rect = pygame.Rect(x, y, ITEM_SIZE, ITEM_SIZE) - blocked = False - for b in blocked_rects: - if test_rect.colliderect(b): - blocked = True - break - - # if position is not blocked, return the position - if not blocked: - return x, y - - attempts += 1 - - # no spot after 50 attempts -> return - return None - -# remove expired items -def update(self, room_rect, blocked_rects): - now = pygame.time.get_ticks() - - remaining_items = [] - - for item in self.items: - if item.is_expired() == False: - remaining_items.append(item) - - # replace the old list with the new temp_list - self.items = remaining_items - - class Item: def __init__(self, x, y, item_type): self.x = x @@ -69,10 +27,54 @@ class Item: print(f"Error loading item sprite {item_type}: {e}") self.image = None + # to check if the time ran out to buy the item + def is_expired(self): + return pygame.time.get_ticks() - self.spawn_time >= ITEM_SPAWN_INTERVAL +class ItemManger: + def __init__(self): + self.items = [] + self.last_spawn_time = pygame.time.get_ticks() + + # try 50 times to find a free position to spawn item + def _random_position(room_rect, blocked_rects): + attempts = 0 + max_attempts = 50 + + while attempts < max_attempts: + # generate random x/y coordinates with room boundaries + # * 2 to have a margin to walls + x = random.randint(room_rect.left + ITEM_SIZE, room_rect.right - ITEM_SIZE * 2) + y = random.randint(room_rect.top + ITEM_SIZE, room_rect.bottom - ITEM_SIZE * 2) + + # create a rect at this position for testing collision + test_rect = pygame.Rect(x, y, ITEM_SIZE, ITEM_SIZE) + blocked = False + for b in blocked_rects: + if test_rect.colliderect(b): + blocked = True + break + + # if position is not blocked, return the position + if not blocked: + return x, y + + attempts += 1 + + # no spot after 50 attempts -> return + return None + # remove expired items + def update(self, room_rect, blocked_rects): + now = pygame.time.get_ticks() + remaining_items = [] + for item in self.items: + if item.is_expired() == False: + remaining_items.append(item) + # replace the old list with the new temp_list + self.items = remaining_items diff --git a/src/settings.py b/src/settings.py index d3c221b..1cabc74 100644 --- a/src/settings.py +++ b/src/settings.py @@ -41,10 +41,12 @@ ECTS_UPDATE_INTERVAL = 1000 # time in ms (1000 = 1 sec) BEER_BOTTLE_ECTS_VALUE = 2 BEER_COST = 50 + # --- BUTTONS --- COLOR_BUTTON = (0, 255, 0) COLOR_BUTTON_HOVER = (200, 255, 0) + # --- STARTSCREEN --- COLOR_STARTSCREEN_BACKGROUND = ((0, 0, 0)) COLOR_STARTSCREEN_FONT_TITLE = (0, 255, 0) @@ -55,6 +57,7 @@ COLOR_STARTSCREEN_FONT_LABEL = (0, 255, 255) COFFEE_COST = 30 COFFEE_HEAL_VALUE = 30 + # --- TOWER --- TOWER_COST = 150 PROJECTILE_DMG = 15 @@ -62,11 +65,13 @@ PROJECTILE_SPEED = 7 COLOR_PROJECTILE = (200, 200, 0) TOWER_TIMER = 30000 # how long the tower stays and attack before it disappears + # --- ITEM SPAWN TIMER --- ITEM_SPAWN_INTERVAL = 5000 # ms until a new item spawns ITEM_SHOPPING_TIMER = 10000 # how long you have time to buy items before they despawn ITEM_SIZE = 30 + # --- ZOMBIE --- ZOMBIE_EASY_HP = 30 ZOMBIE_EASY_DMG = 3 From c0a5c12d4ea124cb461f0a409ab11996a1a990b6 Mon Sep 17 00:00:00 2001 From: Berger Lena Date: Fri, 8 May 2026 16:03:42 +0200 Subject: [PATCH 5/6] feature: item - add draw method to Item class with fallback colors - fix ItemManager class name typo and missing self parameter in _random_position - add draw and clear methods to ItemManager - rename ITEM_SPAWN_INTERVAL to ITEM_SHOPPING_TIMER in is_expired --- src/item.py | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/item.py b/src/item.py index f3b9add..4f937b9 100644 --- a/src/item.py +++ b/src/item.py @@ -29,16 +29,34 @@ class Item: # to check if the time ran out to buy the item def is_expired(self): - return pygame.time.get_ticks() - self.spawn_time >= ITEM_SPAWN_INTERVAL + return pygame.time.get_ticks() - self.spawn_time >= ITEM_SHOPPING_TIMER + # cam offset to convert world coordinates to screen coordinates + def draw(self, screen, cam_x, cam_y): + draw_x = self.x - cam_x + draw_y = self.y - cam_y -class ItemManger: + if self.image is not None: + screen.blit(self.image, (draw_x, draw_y)) + else: + # fallback using settings colors + # coffee brown, beer yellow + if self.item_type == "coffee": + color = (120, 70, 30) + else: + color = COLOR_HP_BAR_YELLOW + + draw_rect = (draw_x, draw_y, ITEM_SIZE, ITEM_SIZE) + pygame.draw.rect(screen, color, draw_rect) + pygame.draw.rect(screen, COLOR_HP_BAR_BG, draw_rect, 2) + +class ItemManager: def __init__(self): self.items = [] self.last_spawn_time = pygame.time.get_ticks() # try 50 times to find a free position to spawn item - def _random_position(room_rect, blocked_rects): + def _random_position(self, room_rect, blocked_rects): attempts = 0 max_attempts = 50 @@ -66,7 +84,7 @@ class ItemManger: return None - # remove expired items + # remove expired items and spawn new ones def update(self, room_rect, blocked_rects): now = pygame.time.get_ticks() @@ -76,5 +94,17 @@ class ItemManger: if item.is_expired() == False: remaining_items.append(item) - # replace the old list with the new temp_list - self.items = remaining_items + # replace the old list with the new temp_list + self.items = remaining_items + + # todo spawn logic for ned ones with random time? (in settings we can set the time range?) + + # draw active items + def draw(self, screen, cam_x, cam_y): + for item in self.items: + item.draw(screen, cam_x, cam_y) + + # clear items when changing room + def clear(self): + self.items = [] + self.last_spawn_time = pygame.time.get_ticks() \ No newline at end of file From f52ef23e6cf88b888b2c7f22e03d18972dca60b5 Mon Sep 17 00:00:00 2001 From: Berger Lena Date: Fri, 8 May 2026 16:23:02 +0200 Subject: [PATCH 6/6] feat: item - Add logic to spawn items at random intervals - Add mechanic to collect items with 'E' key - Refactor _random_position to a function --- src/item.py | 87 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/src/item.py b/src/item.py index 4f937b9..e0cec9e 100644 --- a/src/item.py +++ b/src/item.py @@ -18,7 +18,7 @@ class Item: # timer to store time when item appeared self.spawn_time = pygame.time.get_ticks() - # try to load sprite + # try to load sprite with item type try: path = f"assets/items/{item_type}.png" self.image = pygame.image.load(path).convert_alpha() @@ -50,40 +50,41 @@ class Item: pygame.draw.rect(screen, color, draw_rect) pygame.draw.rect(screen, COLOR_HP_BAR_BG, draw_rect, 2) + +# try 50 times to find a free position to spawn item +def _random_position(room_rect, blocked_rects): + attempts = 0 + max_attempts = 50 + + while attempts < max_attempts: + # generate random x/y coordinates with room boundaries + # * 2 to have a margin to walls + x = random.randint(room_rect.left + ITEM_SIZE, room_rect.right - ITEM_SIZE * 2) + y = random.randint(room_rect.top + ITEM_SIZE, room_rect.bottom - ITEM_SIZE * 2) + + # create a rect at this position for testing collision + test_rect = pygame.Rect(x, y, ITEM_SIZE, ITEM_SIZE) + blocked = False + for b in blocked_rects: + if test_rect.colliderect(b): + blocked = True + break + + # if position is not blocked, return the position + if not blocked: + return x, y + + attempts += 1 + + # no spot after 50 attempts -> return + return None + + class ItemManager: def __init__(self): self.items = [] self.last_spawn_time = pygame.time.get_ticks() - # try 50 times to find a free position to spawn item - def _random_position(self, room_rect, blocked_rects): - attempts = 0 - max_attempts = 50 - - while attempts < max_attempts: - # generate random x/y coordinates with room boundaries - # * 2 to have a margin to walls - x = random.randint(room_rect.left + ITEM_SIZE, room_rect.right - ITEM_SIZE * 2) - y = random.randint(room_rect.top + ITEM_SIZE, room_rect.bottom - ITEM_SIZE * 2) - - # create a rect at this position for testing collision - test_rect = pygame.Rect(x, y, ITEM_SIZE, ITEM_SIZE) - blocked = False - for b in blocked_rects: - if test_rect.colliderect(b): - blocked = True - break - - # if position is not blocked, return the position - if not blocked: - return x, y - - attempts += 1 - - # no spot after 50 attempts -> return - return None - - # remove expired items and spawn new ones def update(self, room_rect, blocked_rects): now = pygame.time.get_ticks() @@ -97,9 +98,31 @@ class ItemManager: # replace the old list with the new temp_list self.items = remaining_items - # todo spawn logic for ned ones with random time? (in settings we can set the time range?) + # spawn logic for new items + if room_rect is not None: + if now - self.last_spawn_time >= ITEM_SPAWN_INTERVAL: + pos = _random_position(room_rect, blocked_rects) + if pos is not None: + x, y = pos + item_type = random.choice(["coffee", "beer"]) + self.items.append(Item(x, y, item_type)) - # draw active items + self.last_spawn_time = now + + # check if player press "e" to buy + def check_collect(self, player_rect, keys): + if keys[pygame.K_e]: + for item in self.items: + if player_rect.colliderect(item.rect): + return item + return None + + # delete item from list after buying it + def remove_item(self, item): + if item in self.items: + self.items.remove(item) + + # draw all active items def draw(self, screen, cam_x, cam_y): for item in self.items: item.draw(screen, cam_x, cam_y)