from collections import Counter from collections.abc import Callable from typing import TYPE_CHECKING from .events import Event, LocationClearedEvent, VictoryEvent from .gameboard import Gameboard from .graphics import Graphic from .inputs import Direction from .items import Item class Player: current_x: int current_y: int current_health: int has_won: bool = False facing: Direction inventory: Counter[Item] gameboard: Gameboard push_event: Callable[[Event], None] def __init__(self, gameboard: Gameboard, push_event: Callable[[Event], None]) -> None: self.gameboard = gameboard self.inventory = Counter() self.push_event = push_event self.respawn() def respawn(self) -> None: self.current_x = 4 self.current_y = 9 self.current_health = self.max_health self.facing = Direction.DOWN @property def max_health(self) -> int: return 2 + 2 * self.inventory[Item.HEALTH_UPGRADE] def render(self) -> Graphic: if not self.gameboard.ready: return Graphic.EMPTY if self.facing == Direction.LEFT: return Graphic.PLAYER_LEFT if self.facing == Direction.UP: return Graphic.PLAYER_UP if self.facing == Direction.RIGHT: return Graphic.PLAYER_RIGHT return Graphic.PLAYER_DOWN def receive_item(self, item: Item) -> None: assert item != Item.REMOTE_ITEM, "Player should not directly receive the remote item" self.inventory[item] += 1 if item == Item.HEALTH_UPGRADE: self.current_health += 2 def has_item(self, item: Item) -> bool: return self.inventory[item] > 0 def remove_item(self, item: Item) -> None: self.inventory[item] -= 1 def damage(self, damage: int) -> None: if self.has_item(Item.SHIELD): damage = damage // 2 self.current_health = max(0, self.current_health - damage) if self.current_health <= 0: self.die() def die(self) -> None: self.respawn() self.gameboard.respawn_final_boss() self.gameboard.heal_alive_enemies() def location_cleared(self, location_id: int) -> None: event = LocationClearedEvent(location_id) self.push_event(event) def victory(self) -> None: self.has_won = True event = VictoryEvent() self.push_event(event)