"""Gameplay screen - map rendering, camera control, tower placement, and enemy waves.""" import pygame_gui as pgi import pygame as pg from game.screens.base_screen import BaseScreen from game.enemy import Enemy, EnemyGroup from game.camera import Camera from game.tower import Tower, TowerGroup from game.util import pixel_to_tile class GamePlay(BaseScreen): # pylint: disable=too-many-instance-attributes """The main gameplay screen handling the game loop, input, and rendering. Attributes: game_surface (pg.Surface): The subsurface used for the game viewport. camera (Camera): Controls zoom and pan over the tilemap. movment (list[bool]): Movement flags for [left, right, up, down] (WASD). selected (Tower | None): The currently selected tower, or ``None``. bought (bool): Whether the player is currently placing a tower. tile_pos (tuple[int, int] | None): Tile under the mouse cursor, or ``None``. enemy_group (EnemyGroup): All active enemies. tower_group (TowerGroup): All placed towers. """ def __init__(self, game): super().__init__(game) # self.tile_multiplicator = 2.0 self.width, self.height = pg.display.get_surface().get_size() self.game_surface = pg.surface.Surface((int(self.width/4*3),int(self.height/6*5))) self.camera = Camera() self.center = (self.game_surface.get_width() /2, self.game_surface.get_height() /2) self.movment = [False, False, False, False] self.selected = None self.notunselect = True self.map = 'desert' self.path = 'assets/maps/' + str(self.map) + '.json' try: self.game.tilemap.load(self.path) except FileNotFoundError: print('File not Found') self.enemy_group = EnemyGroup() self.tower_group = TowerGroup() self.enemy = Enemy(self.game, 0.5, 200) self.enemy_group.add(self.enemy) self.buy_button = pgi.elements.UIButton( relative_rect=pg.Rect( self.width - 200, self.height -250, 150, 50 ), text="Buy Tower", manager=self.game.ui_manager ) self.cancel_button = pgi.elements.UIButton( relative_rect=pg.Rect( self.width - 200, self.height -175, 150, 50 ), text="Cancel", manager=self.game.ui_manager ) self.buy_button.hide() self.cancel_button.hide() self.bought = False self.elements = [ self.buy_button, self.cancel_button ] self.mpos = (0, 0) self.tile_pos = None self.tile_pos_decimal = (0, 0) def process_event(self, event): """Handle mouse clicks, keyboard input, and UI button presses. Translates mouse position to tile coordinates, manages tower placement and selection, camera movement keys, and zoom controls. Args: event (pg.event.Event): The pygame event to process. """ #calculate tile position from mouse position self.mpos = pg.mouse.get_pos() game_rect = self.game_surface.get_rect(topleft=(0, self.height / 6)) if game_rect.collidepoint(self.mpos): local_mpos = ( self.mpos[0] - game_rect.x, self.mpos[1] - game_rect.y ) world_pos = self.camera.screen_to_world(local_mpos) self.tile_pos_decimal = pixel_to_tile(world_pos, self.game.tilemap.tile_size) self.tile_pos = (int(self.tile_pos_decimal[0]), int(self.tile_pos_decimal[1])) else: self.tile_pos = None if event.type == pgi.UI_BUTTON_PRESSED: if event.ui_element == self.buy_button: self.bought = True # self.tower = Tower(self.game, self.mpos, self.tile_pos, 'cannon') if event.ui_element == self.cancel_button: self.bought = False self.tile_pos = (int(self.tile_pos_decimal[0]), int(self.tile_pos_decimal[1])) if event.type == pg.MOUSEBUTTONDOWN: if self.tile_pos is not None: if event.button == 1: self.selected = self.select_turret(self.tile_pos) self.clear_selection() if self.selected is not None: print(f'selected: {self.selected}') self.selected.selected = True print(f'{self.selected}') if self.bought: if self.game.tilemap.placeable(self.tile_pos): if not any(tw.tile_pos == self.tile_pos for tw in self.tower_group): tower = Tower(self.game, self.mpos, self.tile_pos, 'cannon', 40) self.tower_group.add(tower) self.bought = False if event.button == 3: for tower in self.tower_group: if tower.tile_pos == self.tile_pos: tower.kill() if event.type == pg.KEYDOWN: if event.key == pg.K_a: self.movment[0] = True if event.key == pg.K_d: self.movment[1] = True if event.key == pg.K_w: self.movment[2] = True if event.key == pg.K_s: self.movment[3] = True if event.key == pg.K_q: self.camera.zoom_offset(self.center, -0.25) if event.key == pg.K_e: self.camera.zoom_offset(self.center ,0.25) if event.type == pg.KEYUP: if event.key == pg.K_a: self.movment[0] = False if event.key == pg.K_d: self.movment[1] = False if event.key == pg.K_w: self.movment[2] = False if event.key == pg.K_s: self.movment[3] = False def update(self, dt): """Update enemies, towers, and camera position. Args: dt (float): Time elapsed since the last frame in seconds. """ self.enemy_group.update() self.tower_group.update(self.enemy_group) self.camera.move( (self.movment[1] - self.movment[0]) * 2, (self.movment[3] - self.movment[2]) * 2 ) def draw(self, surface): """Render the tilemap, enemies, towers, and tower placement preview. Args: surface (pg.Surface): The main window surface to draw onto. """ self.game_surface.fill((40, 40, 40)) # self.map_surface.fill((40, 40, 40)) self.game.tilemap.render_game(self.game_surface, self.camera) self.enemy_group.render(self.game_surface, self.camera) self.tower_group.render(self.game_surface, self.camera) if self.bought: if self.game.tilemap.placeable(self.tile_pos): self.game_surface.blit( pg.transform.scale2x(self.game.assets["cannon"]), ( self.mpos[0] - (self.game.tilemap.tile_size //2), self.mpos[1] - (self.game.tilemap.tile_size //2 + self.height/6) ) ) else: self.game_surface.blit( pg.transform.scale2x(self.game.assets["red_cannon"]), (self.mpos[0] - (self.game.tilemap.tile_size //2), self.mpos[1] - (self.game.tilemap.tile_size //2 + self.height/6)) ) surface.blit(self.game_surface, (0, self.height/6)) def select_turret(self, tile_pos): """Return the tower at tile_pos, or ``None`` if the tile is empty. Args: tile_pos (tuple[int, int]): Grid position to check. Returns: Tower | None: The tower occupying that tile, or ``None``. """ for tower in self.tower_group: if (tile_pos[0], tile_pos[1]) == (tower.tile_pos[0], tower.tile_pos[1]): return tower return None def clear_selection(self): """Deselect all towers.""" for tower in self.tower_group: tower.selected = False #private helpers jsut within class (planned not sure yet) def _remove_tower_at(self, tile_pos): """Remove the tower at tile_pos on right click, if one exists. Args: tile_pos (tuple[int, int]): Grid position to remove from. """ def _handle_keydown(self, key): """Set movement flags and trigger zoom on key press. Args: key (int): pygame key constant. """ def _handle_keyup(self, key): """Clear movement flags on key release. Args: key (int): pygame key constant. """