#!/usr/bin/env python from enum import Enum, auto from random import randint from typing import List from dungeonbattle.display.texturepack import TexturePack class Map: """ Object that represents a Map with its width, height and the whole tiles, with their custom properties. """ width: int height: int tiles: List[List["Tile"]] entities: List["Entity"] # coordinates of the point that should be # on the topleft corner of the screen currentx: int currenty: int def __init__(self, width: int, height: int, tiles: list): self.width = width self.height = height self.tiles = tiles self.entities = [] def add_entity(self, entity: "Entity") -> None: """ Register a new entity in the map. """ self.entities.append(entity) entity.map = self def remove_entity(self, entity: "Entity") -> None: """ Unregister an entity from the map. """ self.entities.remove(entity) def is_free(self, y: int, x: int) -> bool: """ Indicates that the case at the coordinates (y, x) is empty. """ return 0 <= y < self.height and 0 <= x < self.width and \ self.tiles[y][x].can_walk() and \ not any(entity.x == x and entity.y == y for entity in self.entities) @staticmethod def load(filename: str) -> "Map": """ Read a file that contains the content of a map, and build a Map object. """ with open(filename, "r") as f: file = f.read() return Map.load_from_string(file) @staticmethod def load_from_string(content: str) -> "Map": """ Load a map represented by its characters and build a Map object. """ lines = content.split("\n") lines = [line for line in lines if line] height = len(lines) width = len(lines[0]) tiles = [[Tile.from_ascii_char(c) for x, c in enumerate(line)] for y, line in enumerate(lines)] return Map(width, height, tiles) def draw_string(self, pack: TexturePack) -> str: """ Draw the current map as a string object that can be rendered in the window. """ return "\n".join("".join(tile.char(pack) for tile in line) for line in self.tiles) def spawn_random_entities(self, count: int) -> None: """ Put randomly {count} hedgehogs on the map, where it is available. """ for _ in range(count): y, x = 0, 0 while True: y, x = randint(0, self.height - 1), randint(0, self.width - 1) tile = self.tiles[y][x] if tile.can_walk(): break from dungeonbattle.entities.monsters import Hedgehog hedgehog = Hedgehog() hedgehog.move(y, x) self.add_entity(hedgehog) def tick(self) -> None: """ Trigger all entity events. """ for entity in self.entities: entity.act(self) class Tile(Enum): EMPTY = auto() WALL = auto() FLOOR = auto() @classmethod def from_ascii_char(cls, ch: str) -> "Tile": for tile in Tile: if tile.char(TexturePack.ASCII_PACK) == ch: return tile raise ValueError(ch) def char(self, pack: TexturePack) -> str: return getattr(pack, self.name) def is_wall(self) -> bool: return self == Tile.WALL def can_walk(self) -> bool: """ Check if an entity (player or not) can move in this tile. """ return not self.is_wall() and self != Tile.EMPTY class Entity: y: int x: int name: str map: Map def __init__(self): self.y = 0 self.x = 0 def check_move(self, y: int, x: int, move_if_possible: bool = False)\ -> bool: free = self.map.is_free(y, x) if free and move_if_possible: self.move(y, x) return free def move(self, y: int, x: int) -> bool: self.y = y self.x = x return True def move_up(self, force: bool = False) -> bool: return self.move(self.y - 1, self.x) if force else \ self.check_move(self.y - 1, self.x, True) def move_down(self, force: bool = False) -> bool: return self.move(self.y + 1, self.x) if force else \ self.check_move(self.y + 1, self.x, True) def move_left(self, force: bool = False) -> bool: return self.move(self.y, self.x - 1) if force else \ self.check_move(self.y, self.x - 1, True) def move_right(self, force: bool = False) -> bool: return self.move(self.y, self.x + 1) if force else \ self.check_move(self.y, self.x + 1, True) def act(self, m: Map) -> None: """ Define the action of the entity that is ran each tick. By default, does nothing. """ pass class FightingEntity(Entity): maxhealth: int health: int strength: int dead: bool intelligence: int charisma: int dexterity: int constitution: int level: int def __init__(self): super().__init__() self.health = self.maxhealth self.dead = False def hit(self, opponent: "FightingEntity") -> None: opponent.take_damage(self, self.strength) def take_damage(self, attacker: "Entity", amount: int) -> None: self.health -= amount if self.health <= 0: self.die() def die(self) -> None: self.dead = True self.map.remove_entity(self)