Merge branch 'master' into 'settings-menu'
# Conflicts: # dungeonbattle/menus.py
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								dungeonbattle/__init__.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								dungeonbattle/__init__.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -4,6 +4,12 @@ from dungeonbattle.term_manager import TermManager
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Bootstrap:
 | 
			
		||||
    """
 | 
			
		||||
    The bootstrap object is used to bootstrap the game so that it starts
 | 
			
		||||
    properly.
 | 
			
		||||
    (It was initially created to avoid circular imports between the Game and
 | 
			
		||||
    Display classes)
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def run_game():
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								dungeonbattle/bootstrap.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								dungeonbattle/bootstrap.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -5,14 +5,22 @@ from ..interfaces import Entity, FightingEntity, Map
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Item(Entity):
 | 
			
		||||
    """
 | 
			
		||||
    A class for items
 | 
			
		||||
    """
 | 
			
		||||
    held: bool
 | 
			
		||||
    held_by: Optional["Player"]
 | 
			
		||||
    held_by: Optional[Player]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
    def __init__(self, held: bool = False, held_by: Optional[Player] = None,
 | 
			
		||||
                 *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.held = False
 | 
			
		||||
        self.held = held
 | 
			
		||||
        self.held_by = held_by
 | 
			
		||||
 | 
			
		||||
    def drop(self, y: int, x: int) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        The item is dropped from the inventory onto the floor
 | 
			
		||||
        """
 | 
			
		||||
        if self.held:
 | 
			
		||||
            self.held_by.inventory.remove(self)
 | 
			
		||||
            self.held = False
 | 
			
		||||
@@ -21,15 +29,32 @@ class Item(Entity):
 | 
			
		||||
        self.move(y, x)
 | 
			
		||||
 | 
			
		||||
    def hold(self, player: "Player") -> None:
 | 
			
		||||
        """
 | 
			
		||||
        The item is taken from the floor and put into the inventory
 | 
			
		||||
        """
 | 
			
		||||
        self.held = True
 | 
			
		||||
        self.held_by = player
 | 
			
		||||
        self.map.remove_entity(self)
 | 
			
		||||
        player.inventory.append(self)
 | 
			
		||||
 | 
			
		||||
    def save_state(self) -> dict:
 | 
			
		||||
        """
 | 
			
		||||
        Saves the state of the entity into a dictionary
 | 
			
		||||
        """
 | 
			
		||||
        d = super().save_state()
 | 
			
		||||
        d["held"] = self.held
 | 
			
		||||
        return d
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Heart(Item):
 | 
			
		||||
    name: str = "heart"
 | 
			
		||||
    healing: int = 5
 | 
			
		||||
    """
 | 
			
		||||
    A heart item to return health to the player
 | 
			
		||||
    """
 | 
			
		||||
    healing: int
 | 
			
		||||
 | 
			
		||||
    def __init__(self, healing: int = 5, *args, **kwargs):
 | 
			
		||||
        super().__init__(name="heart", *args, **kwargs)
 | 
			
		||||
        self.healing = healing
 | 
			
		||||
 | 
			
		||||
    def hold(self, player: "Player") -> None:
 | 
			
		||||
        """
 | 
			
		||||
@@ -38,23 +63,47 @@ class Heart(Item):
 | 
			
		||||
        player.health = min(player.maxhealth, player.health + self.healing)
 | 
			
		||||
        self.map.remove_entity(self)
 | 
			
		||||
 | 
			
		||||
    def save_state(self) -> dict:
 | 
			
		||||
        """
 | 
			
		||||
        Saves the state of the header into a dictionary
 | 
			
		||||
        """
 | 
			
		||||
        d = super().save_state()
 | 
			
		||||
        d["healing"] = self.healing
 | 
			
		||||
        return d
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Bomb(Item):
 | 
			
		||||
    name: str = "bomb"
 | 
			
		||||
    """
 | 
			
		||||
    A bomb item intended to deal damage to enemies at long range
 | 
			
		||||
    """
 | 
			
		||||
    damage: int = 5
 | 
			
		||||
    exploding: bool
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.exploding = False
 | 
			
		||||
    def __init__(self, damage: int = 5, exploding: bool = False,
 | 
			
		||||
                 *args, **kwargs):
 | 
			
		||||
        super().__init__(name="bomb", *args, **kwargs)
 | 
			
		||||
        self.damage = damage
 | 
			
		||||
        self.exploding = exploding
 | 
			
		||||
 | 
			
		||||
    def drop(self, x: int, y: int) -> None:
 | 
			
		||||
        super().drop(x, y)
 | 
			
		||||
        self.exploding = True
 | 
			
		||||
 | 
			
		||||
    def act(self, m: Map) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Special exploding action of the bomb
 | 
			
		||||
        """
 | 
			
		||||
        if self.exploding:
 | 
			
		||||
            for e in m.entities:
 | 
			
		||||
            for e in m.entities.copy():
 | 
			
		||||
                if abs(e.x - self.x) + abs(e.y - self.y) <= 1 and \
 | 
			
		||||
                        isinstance(e, FightingEntity):
 | 
			
		||||
                    e.take_damage(self, self.damage)
 | 
			
		||||
 | 
			
		||||
    def save_state(self) -> dict:
 | 
			
		||||
        """
 | 
			
		||||
        Saves the state of the bomb into a dictionary
 | 
			
		||||
        """
 | 
			
		||||
        d = super().save_state()
 | 
			
		||||
        d["exploding"] = self.exploding
 | 
			
		||||
        d["damage"] = self.damage
 | 
			
		||||
        return d
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,24 @@ from ..interfaces import FightingEntity, Map
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Monster(FightingEntity):
 | 
			
		||||
    """
 | 
			
		||||
    The class for all monsters in the dungeon.
 | 
			
		||||
    A monster must override this class, and the parameters are given
 | 
			
		||||
    in the __init__ function.
 | 
			
		||||
    An example of the specification of a monster that has a strength of 4
 | 
			
		||||
    and 20 max HP:
 | 
			
		||||
 | 
			
		||||
    class MyMonster(Monster):
 | 
			
		||||
        def __init__(self, strength: int = 4, maxhealth: int = 20,
 | 
			
		||||
                     *args, **kwargs) -> None:
 | 
			
		||||
            super().__init__(name="my_monster", strength=strength,
 | 
			
		||||
                             maxhealth=maxhealth, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    With that way, attributes can be overwritten when the entity got created.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def act(self, m: Map) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        By default, a monster will move randomly where it is possible
 | 
			
		||||
@@ -35,24 +53,40 @@ class Monster(FightingEntity):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Beaver(Monster):
 | 
			
		||||
    name = "beaver"
 | 
			
		||||
    maxhealth = 30
 | 
			
		||||
    strength = 2
 | 
			
		||||
    """
 | 
			
		||||
    A beaver monster
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, strength: int = 2, maxhealth: int = 20,
 | 
			
		||||
                 *args, **kwargs) -> None:
 | 
			
		||||
        super().__init__(name="beaver", strength=strength,
 | 
			
		||||
                         maxhealth=maxhealth, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Hedgehog(Monster):
 | 
			
		||||
    name = "hedgehog"
 | 
			
		||||
    maxhealth = 10
 | 
			
		||||
    strength = 3
 | 
			
		||||
    """
 | 
			
		||||
    A really mean hedgehog monster
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, strength: int = 3, maxhealth: int = 10,
 | 
			
		||||
                 *args, **kwargs) -> None:
 | 
			
		||||
        super().__init__(name="hedgehog", strength=strength,
 | 
			
		||||
                         maxhealth=maxhealth, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Rabbit(Monster):
 | 
			
		||||
    name = "rabbit"
 | 
			
		||||
    maxhealth = 15
 | 
			
		||||
    strength = 1
 | 
			
		||||
    """
 | 
			
		||||
    A rabbit monster
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, strength: int = 1, maxhealth: int = 15,
 | 
			
		||||
                 *args, **kwargs) -> None:
 | 
			
		||||
        super().__init__(name="rabbit", strength=strength,
 | 
			
		||||
                         maxhealth=maxhealth, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TeddyBear(Monster):
 | 
			
		||||
    name = "teddy_bear"
 | 
			
		||||
    maxhealth = 50
 | 
			
		||||
    strength = 0
 | 
			
		||||
    """
 | 
			
		||||
    A cute teddybear monster
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, strength: int = 0, maxhealth: int = 50,
 | 
			
		||||
                 *args, **kwargs) -> None:
 | 
			
		||||
        super().__init__(name="teddy_bear", strength=strength,
 | 
			
		||||
                         maxhealth=maxhealth, *args, **kwargs)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,22 +5,26 @@ from ..interfaces import FightingEntity
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Player(FightingEntity):
 | 
			
		||||
    name = "player"
 | 
			
		||||
    maxhealth: int = 20
 | 
			
		||||
    strength: int = 5
 | 
			
		||||
    intelligence: int = 1
 | 
			
		||||
    charisma: int = 1
 | 
			
		||||
    dexterity: int = 1
 | 
			
		||||
    constitution: int = 1
 | 
			
		||||
    level: int = 1
 | 
			
		||||
    """
 | 
			
		||||
    The class of the player
 | 
			
		||||
    """
 | 
			
		||||
    current_xp: int = 0
 | 
			
		||||
    max_xp: int = 10
 | 
			
		||||
    inventory: list
 | 
			
		||||
    paths: Dict[Tuple[int, int], Tuple[int, int]]
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
    def __init__(self, maxhealth: int = 20, strength: int = 5,
 | 
			
		||||
                 intelligence: int = 1, charisma: int = 1, dexterity: int = 1,
 | 
			
		||||
                 constitution: int = 1, level: int = 1, current_xp: int = 0,
 | 
			
		||||
                 max_xp: int = 10, *args, **kwargs) -> None:
 | 
			
		||||
        super().__init__(name="player", maxhealth=maxhealth, strength=strength,
 | 
			
		||||
                         intelligence=intelligence, charisma=charisma,
 | 
			
		||||
                         dexterity=dexterity, constitution=constitution,
 | 
			
		||||
                         level=level, *args, **kwargs)
 | 
			
		||||
        self.current_xp = current_xp
 | 
			
		||||
        self.max_xp = max_xp
 | 
			
		||||
        self.inventory = list()
 | 
			
		||||
        self.paths = dict()
 | 
			
		||||
 | 
			
		||||
    def move(self, y: int, x: int) -> None:
 | 
			
		||||
        """
 | 
			
		||||
@@ -100,3 +104,12 @@ class Player(FightingEntity):
 | 
			
		||||
                distances[(new_y, new_x)] = distances[(y, x)] + 1
 | 
			
		||||
                queue.append((new_y, new_x))
 | 
			
		||||
        self.paths = predecessors
 | 
			
		||||
 | 
			
		||||
    def save_state(self) -> dict:
 | 
			
		||||
        """
 | 
			
		||||
        Saves the state of the entity into a dictionary
 | 
			
		||||
        """
 | 
			
		||||
        d = super().save_state()
 | 
			
		||||
        d["current_xp"] = self.current_xp
 | 
			
		||||
        d["max_xp"] = self.max_xp
 | 
			
		||||
        return d
 | 
			
		||||
 
 | 
			
		||||
@@ -3,13 +3,22 @@ from typing import Optional
 | 
			
		||||
 | 
			
		||||
from dungeonbattle.settings import Settings
 | 
			
		||||
 | 
			
		||||
# This file contains a few useful enumeration classes used elsewhere in the code
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DisplayActions(Enum):
 | 
			
		||||
    """
 | 
			
		||||
    Display actions options for the callable displayaction Game uses
 | 
			
		||||
    It just calls the same action on the display object displayaction refers to.
 | 
			
		||||
    """
 | 
			
		||||
    REFRESH = auto()
 | 
			
		||||
    UPDATE = auto()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GameMode(Enum):
 | 
			
		||||
    """
 | 
			
		||||
    Game mode options
 | 
			
		||||
    """
 | 
			
		||||
    MAINMENU = auto()
 | 
			
		||||
    PLAY = auto()
 | 
			
		||||
    SETTINGS = auto()
 | 
			
		||||
@@ -17,6 +26,9 @@ class GameMode(Enum):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class KeyValues(Enum):
 | 
			
		||||
    """
 | 
			
		||||
    Key values options used in the game
 | 
			
		||||
    """
 | 
			
		||||
    UP = auto()
 | 
			
		||||
    DOWN = auto()
 | 
			
		||||
    LEFT = auto()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,8 @@
 | 
			
		||||
from random import randint
 | 
			
		||||
from typing import Any, Optional
 | 
			
		||||
import json
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
from .entities.player import Player
 | 
			
		||||
from .enums import GameMode, KeyValues, DisplayActions
 | 
			
		||||
@@ -10,6 +13,9 @@ from typing import Callable
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Game:
 | 
			
		||||
    """
 | 
			
		||||
    The game object controls all actions in the game.
 | 
			
		||||
    """
 | 
			
		||||
    map: Map
 | 
			
		||||
    player: Player
 | 
			
		||||
    display_actions: Callable[[DisplayActions], None]
 | 
			
		||||
@@ -37,16 +43,11 @@ class Game:
 | 
			
		||||
        self.player.move(self.map.start_y, self.map.start_x)
 | 
			
		||||
        self.map.spawn_random_entities(randint(3, 10))
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def load_game(filename: str) -> None:
 | 
			
		||||
        # TODO loading map from a file
 | 
			
		||||
        raise NotImplementedError()
 | 
			
		||||
 | 
			
		||||
    def run(self, screen: Any) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Main infinite loop.
 | 
			
		||||
        We wait for a player action, then we do what that should be done
 | 
			
		||||
        when the given key got pressed.
 | 
			
		||||
        We wait for the player's action, then we do what that should be done
 | 
			
		||||
        when the given key gets pressed.
 | 
			
		||||
        """
 | 
			
		||||
        while True:  # pragma no cover
 | 
			
		||||
            screen.clear()
 | 
			
		||||
@@ -65,14 +66,14 @@ class Game:
 | 
			
		||||
        if self.state == GameMode.PLAY:
 | 
			
		||||
            self.handle_key_pressed_play(key)
 | 
			
		||||
        elif self.state == GameMode.MAINMENU:
 | 
			
		||||
            self.main_menu.handle_key_pressed(key, self)
 | 
			
		||||
            self.handle_key_pressed_main_menu(key)
 | 
			
		||||
        elif self.state == GameMode.SETTINGS:
 | 
			
		||||
            self.settings_menu.handle_key_pressed(key, raw_key, self)
 | 
			
		||||
        self.display_actions(DisplayActions.REFRESH)
 | 
			
		||||
 | 
			
		||||
    def handle_key_pressed_play(self, key: KeyValues) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        In play mode, arrows or zqsd should move the main character.
 | 
			
		||||
        In play mode, arrows or zqsd move the main character.
 | 
			
		||||
        """
 | 
			
		||||
        if key == KeyValues.UP:
 | 
			
		||||
            if self.player.move_up():
 | 
			
		||||
@@ -88,3 +89,54 @@ class Game:
 | 
			
		||||
                self.map.tick()
 | 
			
		||||
        elif key == KeyValues.SPACE:
 | 
			
		||||
            self.state = GameMode.MAINMENU
 | 
			
		||||
 | 
			
		||||
    def handle_key_pressed_main_menu(self, key: KeyValues) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        In the main menu, we can navigate through options.
 | 
			
		||||
        """
 | 
			
		||||
        if key == KeyValues.DOWN:
 | 
			
		||||
            self.main_menu.go_down()
 | 
			
		||||
        if key == KeyValues.UP:
 | 
			
		||||
            self.main_menu.go_up()
 | 
			
		||||
        if key == KeyValues.ENTER:
 | 
			
		||||
            option = self.main_menu.validate()
 | 
			
		||||
            if option == menus.MainMenuValues.START:
 | 
			
		||||
                self.state = GameMode.PLAY
 | 
			
		||||
            elif option == menus.MainMenuValues.SAVE:
 | 
			
		||||
                self.save_game()
 | 
			
		||||
            elif option == menus.MainMenuValues.LOAD:
 | 
			
		||||
                self.load_game()
 | 
			
		||||
            elif option == menus.MainMenuValues.SETTINGS:
 | 
			
		||||
                self.state = GameMode.SETTINGS
 | 
			
		||||
            elif option == menus.MainMenuValues.EXIT:
 | 
			
		||||
                sys.exit(0)
 | 
			
		||||
 | 
			
		||||
    def save_state(self) -> dict:
 | 
			
		||||
        """
 | 
			
		||||
        Saves the game to a dictionary
 | 
			
		||||
        """
 | 
			
		||||
        return self.map.save_state()
 | 
			
		||||
 | 
			
		||||
    def load_state(self, d: dict) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Loads the game from a dictionary
 | 
			
		||||
        """
 | 
			
		||||
        self.map.load_state(d)
 | 
			
		||||
        # noinspection PyTypeChecker
 | 
			
		||||
        self.player = self.map.find_entities(Player)[0]
 | 
			
		||||
        self.display_actions(DisplayActions.UPDATE)
 | 
			
		||||
 | 
			
		||||
    def load_game(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Loads the game from a file
 | 
			
		||||
        """
 | 
			
		||||
        if os.path.isfile("save.json"):
 | 
			
		||||
            with open("save.json", "r") as f:
 | 
			
		||||
                self.load_state(json.loads(f.read()))
 | 
			
		||||
 | 
			
		||||
    def save_game(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Saves the game to a file
 | 
			
		||||
        """
 | 
			
		||||
        with open("save.json", "w") as f:
 | 
			
		||||
            f.write(json.dumps(self.save_state()))
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
from enum import Enum, auto
 | 
			
		||||
from math import sqrt
 | 
			
		||||
from random import choice, randint
 | 
			
		||||
from typing import List
 | 
			
		||||
from typing import List, Optional
 | 
			
		||||
 | 
			
		||||
from dungeonbattle.display.texturepack import TexturePack
 | 
			
		||||
 | 
			
		||||
@@ -10,7 +10,7 @@ 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.
 | 
			
		||||
    and tiles, that have their custom properties.
 | 
			
		||||
    """
 | 
			
		||||
    width: int
 | 
			
		||||
    height: int
 | 
			
		||||
@@ -45,6 +45,10 @@ class Map:
 | 
			
		||||
        """
 | 
			
		||||
        self.entities.remove(entity)
 | 
			
		||||
 | 
			
		||||
    def find_entities(self, entity_class: type) -> list:
 | 
			
		||||
        return [entity for entity in self.entities
 | 
			
		||||
                if isinstance(entity, entity_class)]
 | 
			
		||||
 | 
			
		||||
    def is_free(self, y: int, x: int) -> bool:
 | 
			
		||||
        """
 | 
			
		||||
        Indicates that the case at the coordinates (y, x) is empty.
 | 
			
		||||
@@ -78,6 +82,16 @@ class Map:
 | 
			
		||||
 | 
			
		||||
        return Map(width, height, tiles, start_y, start_x)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def load_dungeon_from_string(content: str) -> List[List["Tile"]]:
 | 
			
		||||
        """
 | 
			
		||||
        Transforms a string into the list of corresponding tiles
 | 
			
		||||
        """
 | 
			
		||||
        lines = content.split("\n")
 | 
			
		||||
        tiles = [[Tile.from_ascii_char(c)
 | 
			
		||||
                  for x, c in enumerate(line)] for y, line in enumerate(lines)]
 | 
			
		||||
        return tiles
 | 
			
		||||
 | 
			
		||||
    def draw_string(self, pack: TexturePack) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        Draw the current map as a string object that can be rendered
 | 
			
		||||
@@ -108,23 +122,69 @@ class Map:
 | 
			
		||||
        for entity in self.entities:
 | 
			
		||||
            entity.act(self)
 | 
			
		||||
 | 
			
		||||
    def save_state(self) -> dict:
 | 
			
		||||
        """
 | 
			
		||||
        Saves the map's attributes to a dictionary
 | 
			
		||||
        """
 | 
			
		||||
        d = dict()
 | 
			
		||||
        d["width"] = self.width
 | 
			
		||||
        d["height"] = self.height
 | 
			
		||||
        d["start_y"] = self.start_y
 | 
			
		||||
        d["start_x"] = self.start_x
 | 
			
		||||
        d["currentx"] = self.currentx
 | 
			
		||||
        d["currenty"] = self.currenty
 | 
			
		||||
        d["entities"] = []
 | 
			
		||||
        for enti in self.entities:
 | 
			
		||||
            d["entities"].append(enti.save_state())
 | 
			
		||||
        d["map"] = self.draw_string(TexturePack.ASCII_PACK)
 | 
			
		||||
        return d
 | 
			
		||||
 | 
			
		||||
    def load_state(self, d: dict) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Loads the map's attributes from a dictionary
 | 
			
		||||
        """
 | 
			
		||||
        self.width = d["width"]
 | 
			
		||||
        self.height = d["height"]
 | 
			
		||||
        self.start_y = d["start_y"]
 | 
			
		||||
        self.start_x = d["start_x"]
 | 
			
		||||
        self.currentx = d["currentx"]
 | 
			
		||||
        self.currenty = d["currenty"]
 | 
			
		||||
        self.tiles = self.load_dungeon_from_string(d["map"])
 | 
			
		||||
        self.entities = []
 | 
			
		||||
        dictclasses = Entity.get_all_entity_classes_in_a_dict()
 | 
			
		||||
        for entisave in d["entities"]:
 | 
			
		||||
            self.add_entity(dictclasses[entisave["type"]](**entisave))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Tile(Enum):
 | 
			
		||||
    """
 | 
			
		||||
    The internal representation of the tiles of the map
 | 
			
		||||
    """
 | 
			
		||||
    EMPTY = auto()
 | 
			
		||||
    WALL = auto()
 | 
			
		||||
    FLOOR = auto()
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def from_ascii_char(cls, ch: str) -> "Tile":
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def from_ascii_char(ch: str) -> "Tile":
 | 
			
		||||
        """
 | 
			
		||||
        Maps an ascii character to its equivalent in the texture pack
 | 
			
		||||
        """
 | 
			
		||||
        for tile in Tile:
 | 
			
		||||
            if tile.char(TexturePack.ASCII_PACK) == ch:
 | 
			
		||||
                return tile
 | 
			
		||||
        raise ValueError(ch)
 | 
			
		||||
 | 
			
		||||
    def char(self, pack: TexturePack) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        Translates a Tile to the corresponding character according
 | 
			
		||||
        to the texture pack
 | 
			
		||||
        """
 | 
			
		||||
        return getattr(pack, self.name)
 | 
			
		||||
 | 
			
		||||
    def is_wall(self) -> bool:
 | 
			
		||||
        """
 | 
			
		||||
        Is this Tile a wall?
 | 
			
		||||
        """
 | 
			
		||||
        return self == Tile.WALL
 | 
			
		||||
 | 
			
		||||
    def can_walk(self) -> bool:
 | 
			
		||||
@@ -135,40 +195,65 @@ class Tile(Enum):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Entity:
 | 
			
		||||
    """
 | 
			
		||||
    An Entity object represents any entity present on the map
 | 
			
		||||
    """
 | 
			
		||||
    y: int
 | 
			
		||||
    x: int
 | 
			
		||||
    name: str
 | 
			
		||||
    map: Map
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.y = 0
 | 
			
		||||
        self.x = 0
 | 
			
		||||
    # noinspection PyShadowingBuiltins
 | 
			
		||||
    def __init__(self, y: int = 0, x: int = 0, name: Optional[str] = None,
 | 
			
		||||
                 map: Optional[Map] = None, *ignored, **ignored2):
 | 
			
		||||
        self.y = y
 | 
			
		||||
        self.x = x
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.map = map
 | 
			
		||||
 | 
			
		||||
    def check_move(self, y: int, x: int, move_if_possible: bool = False)\
 | 
			
		||||
            -> bool:
 | 
			
		||||
        """
 | 
			
		||||
        Checks if moving to (y,x) is authorized
 | 
			
		||||
        """
 | 
			
		||||
        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:
 | 
			
		||||
        """
 | 
			
		||||
        Moves an entity to (y,x) coordinates
 | 
			
		||||
        """
 | 
			
		||||
        self.y = y
 | 
			
		||||
        self.x = x
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def move_up(self, force: bool = False) -> bool:
 | 
			
		||||
        """
 | 
			
		||||
        Moves the entity up one tile, if possible
 | 
			
		||||
        """
 | 
			
		||||
        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:
 | 
			
		||||
        """
 | 
			
		||||
        Moves the entity down one tile, if possible
 | 
			
		||||
        """
 | 
			
		||||
        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:
 | 
			
		||||
        """
 | 
			
		||||
        Moves the entity left one tile, if possible
 | 
			
		||||
        """
 | 
			
		||||
        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:
 | 
			
		||||
        """
 | 
			
		||||
        Moves the entity right one tile, if possible
 | 
			
		||||
        """
 | 
			
		||||
        return self.move(self.y, self.x + 1) if force else \
 | 
			
		||||
            self.check_move(self.y, self.x + 1, True)
 | 
			
		||||
 | 
			
		||||
@@ -193,44 +278,122 @@ class Entity:
 | 
			
		||||
        return sqrt(self.distance_squared(other))
 | 
			
		||||
 | 
			
		||||
    def is_fighting_entity(self) -> bool:
 | 
			
		||||
        """
 | 
			
		||||
        Is this entity a fighting entity?
 | 
			
		||||
        """
 | 
			
		||||
        return isinstance(self, FightingEntity)
 | 
			
		||||
 | 
			
		||||
    def is_item(self) -> bool:
 | 
			
		||||
        """
 | 
			
		||||
        Is this entity an item?
 | 
			
		||||
        """
 | 
			
		||||
        from dungeonbattle.entities.items import Item
 | 
			
		||||
        return isinstance(self, Item)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_all_entity_classes():
 | 
			
		||||
        """
 | 
			
		||||
        Returns all entities subclasses
 | 
			
		||||
        """
 | 
			
		||||
        from dungeonbattle.entities.items import Heart, Bomb
 | 
			
		||||
        from dungeonbattle.entities.monsters import Beaver, Hedgehog, \
 | 
			
		||||
            Rabbit, TeddyBear
 | 
			
		||||
        return [Beaver, Bomb, Heart, Hedgehog, Rabbit, TeddyBear]
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_all_entity_classes_in_a_dict() -> dict:
 | 
			
		||||
        """
 | 
			
		||||
        Returns all entities subclasses in a dictionary
 | 
			
		||||
        """
 | 
			
		||||
        from dungeonbattle.entities.player import Player
 | 
			
		||||
        from dungeonbattle.entities.monsters import Beaver, Hedgehog, Rabbit, \
 | 
			
		||||
            TeddyBear
 | 
			
		||||
        from dungeonbattle.entities.items import Bomb, Heart
 | 
			
		||||
        return {
 | 
			
		||||
            "Beaver": Beaver,
 | 
			
		||||
            "Bomb": Bomb,
 | 
			
		||||
            "Heart": Heart,
 | 
			
		||||
            "Hedgehog": Hedgehog,
 | 
			
		||||
            "Rabbit": Rabbit,
 | 
			
		||||
            "TeddyBear": TeddyBear,
 | 
			
		||||
            "Player": Player,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def save_state(self) -> dict:
 | 
			
		||||
        """
 | 
			
		||||
        Saves the coordinates of the entity
 | 
			
		||||
        """
 | 
			
		||||
        d = dict()
 | 
			
		||||
        d["x"] = self.x
 | 
			
		||||
        d["y"] = self.y
 | 
			
		||||
        d["type"] = self.__class__.__name__
 | 
			
		||||
        return d
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FightingEntity(Entity):
 | 
			
		||||
    """
 | 
			
		||||
    A FightingEntity is an entity that can fight, and thus has a health,
 | 
			
		||||
    level and stats
 | 
			
		||||
    """
 | 
			
		||||
    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 __init__(self, maxhealth: int = 0, health: Optional[int] = None,
 | 
			
		||||
                 strength: int = 0, intelligence: int = 0, charisma: int = 0,
 | 
			
		||||
                 dexterity: int = 0, constitution: int = 0, level: int = 0,
 | 
			
		||||
                 *args, **kwargs) -> None:
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.maxhealth = maxhealth
 | 
			
		||||
        self.health = maxhealth if health is None else health
 | 
			
		||||
        self.strength = strength
 | 
			
		||||
        self.intelligence = intelligence
 | 
			
		||||
        self.charisma = charisma
 | 
			
		||||
        self.dexterity = dexterity
 | 
			
		||||
        self.constitution = constitution
 | 
			
		||||
        self.level = level
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def dead(self) -> bool:
 | 
			
		||||
        return self.health <= 0
 | 
			
		||||
 | 
			
		||||
    def hit(self, opponent: "FightingEntity") -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Deals damage to the opponent, based on the stats
 | 
			
		||||
        """
 | 
			
		||||
        opponent.take_damage(self, self.strength)
 | 
			
		||||
 | 
			
		||||
    def take_damage(self, attacker: "Entity", amount: int) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Take damage from the attacker, based on the stats
 | 
			
		||||
        """
 | 
			
		||||
        self.health -= amount
 | 
			
		||||
        if self.health <= 0:
 | 
			
		||||
            self.die()
 | 
			
		||||
 | 
			
		||||
    def die(self) -> None:
 | 
			
		||||
        self.dead = True
 | 
			
		||||
        """
 | 
			
		||||
        If a fighting entity has no more health, it dies and is removed
 | 
			
		||||
        """
 | 
			
		||||
        self.map.remove_entity(self)
 | 
			
		||||
 | 
			
		||||
    def keys(self) -> list:
 | 
			
		||||
        """
 | 
			
		||||
        Returns a fighting entities specific attributes
 | 
			
		||||
        """
 | 
			
		||||
        return ["maxhealth", "health", "level", "strength",
 | 
			
		||||
                "intelligence", "charisma", "dexterity", "constitution"]
 | 
			
		||||
 | 
			
		||||
    def save_state(self) -> dict:
 | 
			
		||||
        """
 | 
			
		||||
        Saves the state of the entity into a dictionary
 | 
			
		||||
        """
 | 
			
		||||
        d = super().save_state()
 | 
			
		||||
        for name in self.keys():
 | 
			
		||||
            d[name] = getattr(self, name)
 | 
			
		||||
        return d
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,3 @@
 | 
			
		||||
import sys
 | 
			
		||||
from enum import Enum
 | 
			
		||||
from typing import Any, Optional
 | 
			
		||||
 | 
			
		||||
@@ -8,23 +7,40 @@ from .settings import Settings
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Menu:
 | 
			
		||||
    """
 | 
			
		||||
    A Menu object is the logical representation of a menu in the game
 | 
			
		||||
    """
 | 
			
		||||
    values: list
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.position = 0
 | 
			
		||||
 | 
			
		||||
    def go_up(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Moves the pointer of the menu on the previous value
 | 
			
		||||
        """
 | 
			
		||||
        self.position = max(0, self.position - 1)
 | 
			
		||||
 | 
			
		||||
    def go_down(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Moves the pointer of the menu on the next value
 | 
			
		||||
        """
 | 
			
		||||
        self.position = min(len(self.values) - 1, self.position + 1)
 | 
			
		||||
 | 
			
		||||
    def validate(self) -> Any:
 | 
			
		||||
        """
 | 
			
		||||
        Selects the value that is pointed by the menu pointer
 | 
			
		||||
        """
 | 
			
		||||
        return self.values[self.position]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MainMenuValues(Enum):
 | 
			
		||||
    """
 | 
			
		||||
    Values of the main menu
 | 
			
		||||
    """
 | 
			
		||||
    START = 'Jouer'
 | 
			
		||||
    SAVE = 'Sauvegarder'
 | 
			
		||||
    LOAD = 'Charger'
 | 
			
		||||
    SETTINGS = 'Paramètres'
 | 
			
		||||
    EXIT = 'Quitter'
 | 
			
		||||
 | 
			
		||||
@@ -33,27 +49,16 @@ class MainMenuValues(Enum):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MainMenu(Menu):
 | 
			
		||||
    """
 | 
			
		||||
    A special instance of a menu : the main menu
 | 
			
		||||
    """
 | 
			
		||||
    values = [e for e in MainMenuValues]
 | 
			
		||||
 | 
			
		||||
    def handle_key_pressed(self, key: KeyValues, game: Any) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        In the main menu, we can navigate through options.
 | 
			
		||||
        """
 | 
			
		||||
        if key == KeyValues.DOWN:
 | 
			
		||||
            self.go_down()
 | 
			
		||||
        if key == KeyValues.UP:
 | 
			
		||||
            self.go_up()
 | 
			
		||||
        if key == KeyValues.ENTER:
 | 
			
		||||
            option = self.validate()
 | 
			
		||||
            if option == MainMenuValues.START:
 | 
			
		||||
                game.state = GameMode.PLAY
 | 
			
		||||
            elif option == MainMenuValues.SETTINGS:
 | 
			
		||||
                game.state = GameMode.SETTINGS
 | 
			
		||||
            elif option == MainMenuValues.EXIT:
 | 
			
		||||
                sys.exit(0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SettingsMenu(Menu):
 | 
			
		||||
    """
 | 
			
		||||
    A special instance of a menu : the settings menu
 | 
			
		||||
    """
 | 
			
		||||
    waiting_for_key: bool = False
 | 
			
		||||
 | 
			
		||||
    def update_values(self, settings: Settings) -> None:
 | 
			
		||||
@@ -63,7 +68,7 @@ class SettingsMenu(Menu):
 | 
			
		||||
    def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str,
 | 
			
		||||
                           game: Any) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Update settings
 | 
			
		||||
        In the setting menu, we van select a setting and change it
 | 
			
		||||
        """
 | 
			
		||||
        if not self.waiting_for_key:
 | 
			
		||||
            # Navigate normally through the menu.
 | 
			
		||||
@@ -99,9 +104,3 @@ class SettingsMenu(Menu):
 | 
			
		||||
            game.settings.write_settings()
 | 
			
		||||
            self.waiting_for_key = False
 | 
			
		||||
            self.update_values(game.settings)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ArbitraryMenu(Menu):
 | 
			
		||||
    def __init__(self, values: list):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
        self.values = values
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,10 @@ from types import TracebackType
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TermManager:  # pragma: no cover
 | 
			
		||||
    """
 | 
			
		||||
    The TermManager object initializes the terminal, returns a screen object and
 | 
			
		||||
    de-initializes the terminal after use
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.screen = curses.initscr()
 | 
			
		||||
        # convert escapes sequences to curses abstraction
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import unittest
 | 
			
		||||
 | 
			
		||||
from dungeonbattle.entities.items import Bomb, Heart, Item
 | 
			
		||||
from dungeonbattle.entities.monsters import Hedgehog
 | 
			
		||||
from dungeonbattle.entities.monsters import Beaver, Hedgehog, Rabbit, TeddyBear
 | 
			
		||||
from dungeonbattle.entities.player import Player
 | 
			
		||||
from dungeonbattle.interfaces import Entity, Map
 | 
			
		||||
 | 
			
		||||
@@ -35,21 +35,18 @@ class TestEntities(unittest.TestCase):
 | 
			
		||||
        """
 | 
			
		||||
        Test some random stuff with fighting entities.
 | 
			
		||||
        """
 | 
			
		||||
        entity = Hedgehog()
 | 
			
		||||
        entity = Beaver()
 | 
			
		||||
        self.map.add_entity(entity)
 | 
			
		||||
        self.assertEqual(entity.maxhealth, 10)
 | 
			
		||||
        self.assertEqual(entity.maxhealth, 20)
 | 
			
		||||
        self.assertEqual(entity.maxhealth, entity.health)
 | 
			
		||||
        self.assertEqual(entity.strength, 3)
 | 
			
		||||
        self.assertIsNone(entity.hit(entity))
 | 
			
		||||
        self.assertFalse(entity.dead)
 | 
			
		||||
        self.assertIsNone(entity.hit(entity))
 | 
			
		||||
        self.assertFalse(entity.dead)
 | 
			
		||||
        self.assertIsNone(entity.hit(entity))
 | 
			
		||||
        self.assertFalse(entity.dead)
 | 
			
		||||
        self.assertEqual(entity.strength, 2)
 | 
			
		||||
        for _ in range(9):
 | 
			
		||||
            self.assertIsNone(entity.hit(entity))
 | 
			
		||||
            self.assertFalse(entity.dead)
 | 
			
		||||
        self.assertIsNone(entity.hit(entity))
 | 
			
		||||
        self.assertTrue(entity.dead)
 | 
			
		||||
 | 
			
		||||
        entity = Hedgehog()
 | 
			
		||||
        entity = Rabbit()
 | 
			
		||||
        self.map.add_entity(entity)
 | 
			
		||||
        entity.move(15, 44)
 | 
			
		||||
        # Move randomly
 | 
			
		||||
@@ -61,13 +58,17 @@ class TestEntities(unittest.TestCase):
 | 
			
		||||
        self.map.tick()
 | 
			
		||||
        self.assertTrue(entity.y == 2 and entity.x == 6)
 | 
			
		||||
 | 
			
		||||
        # Hedgehog should fight
 | 
			
		||||
        # Rabbit should fight
 | 
			
		||||
        old_health = self.player.health
 | 
			
		||||
        self.map.tick()
 | 
			
		||||
        self.assertTrue(entity.y == 2 and entity.x == 6)
 | 
			
		||||
        self.assertEqual(old_health - entity.strength, self.player.health)
 | 
			
		||||
 | 
			
		||||
        # Fight the hedgehog
 | 
			
		||||
        # Fight the rabbit
 | 
			
		||||
        old_health = entity.health
 | 
			
		||||
        self.player.move_down()
 | 
			
		||||
        self.assertEqual(entity.health, old_health - self.player.strength)
 | 
			
		||||
        self.assertFalse(entity.dead)
 | 
			
		||||
        old_health = entity.health
 | 
			
		||||
        self.player.move_down()
 | 
			
		||||
        self.assertEqual(entity.health, old_health - self.player.strength)
 | 
			
		||||
@@ -104,17 +105,25 @@ class TestEntities(unittest.TestCase):
 | 
			
		||||
        """
 | 
			
		||||
        item = Bomb()
 | 
			
		||||
        hedgehog = Hedgehog()
 | 
			
		||||
        teddy_bear = TeddyBear()
 | 
			
		||||
        self.map.add_entity(item)
 | 
			
		||||
        self.map.add_entity(hedgehog)
 | 
			
		||||
        self.map.add_entity(teddy_bear)
 | 
			
		||||
        hedgehog.health = 2
 | 
			
		||||
        teddy_bear.health = 2
 | 
			
		||||
        hedgehog.move(41, 42)
 | 
			
		||||
        teddy_bear.move(42, 41)
 | 
			
		||||
        item.act(self.map)
 | 
			
		||||
        self.assertFalse(hedgehog.dead)
 | 
			
		||||
        self.assertFalse(teddy_bear.dead)
 | 
			
		||||
        item.drop(42, 42)
 | 
			
		||||
        self.assertEqual(item.y, 42)
 | 
			
		||||
        self.assertEqual(item.x, 42)
 | 
			
		||||
        item.act(self.map)
 | 
			
		||||
        self.assertTrue(hedgehog.dead)
 | 
			
		||||
        self.assertTrue(teddy_bear.dead)
 | 
			
		||||
        bomb_state = item.save_state()
 | 
			
		||||
        self.assertEqual(bomb_state["damage"], item.damage)
 | 
			
		||||
 | 
			
		||||
    def test_hearts(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
@@ -128,6 +137,8 @@ class TestEntities(unittest.TestCase):
 | 
			
		||||
        self.assertNotIn(item, self.map.entities)
 | 
			
		||||
        self.assertEqual(self.player.health,
 | 
			
		||||
                         self.player.maxhealth - item.healing)
 | 
			
		||||
        heart_state = item.save_state()
 | 
			
		||||
        self.assertEqual(heart_state["healing"], item.healing)
 | 
			
		||||
 | 
			
		||||
    def test_players(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
@@ -158,3 +169,6 @@ class TestEntities(unittest.TestCase):
 | 
			
		||||
        self.assertEqual(player.current_xp, 10)
 | 
			
		||||
        self.assertEqual(player.max_xp, 40)
 | 
			
		||||
        self.assertEqual(player.level, 4)
 | 
			
		||||
 | 
			
		||||
        player_state = player.save_state()
 | 
			
		||||
        self.assertEqual(player_state["current_xp"], 10)
 | 
			
		||||
 
 | 
			
		||||
@@ -21,8 +21,20 @@ class TestGame(unittest.TestCase):
 | 
			
		||||
        self.game.display_actions = display.handle_display_action
 | 
			
		||||
 | 
			
		||||
    def test_load_game(self) -> None:
 | 
			
		||||
        self.assertRaises(NotImplementedError, Game.load_game, "game.save")
 | 
			
		||||
        self.assertRaises(NotImplementedError, Display(None).display)
 | 
			
		||||
        """
 | 
			
		||||
        Save a game and reload it.
 | 
			
		||||
        """
 | 
			
		||||
        old_state = self.game.save_state()
 | 
			
		||||
 | 
			
		||||
        self.game.handle_key_pressed(KeyValues.DOWN)
 | 
			
		||||
        self.assertEqual(self.game.main_menu.validate(), MainMenuValues.SAVE)
 | 
			
		||||
        self.game.handle_key_pressed(KeyValues.ENTER)  # Save game
 | 
			
		||||
        self.game.handle_key_pressed(KeyValues.DOWN)
 | 
			
		||||
        self.assertEqual(self.game.main_menu.validate(), MainMenuValues.LOAD)
 | 
			
		||||
        self.game.handle_key_pressed(KeyValues.ENTER)  # Load game
 | 
			
		||||
 | 
			
		||||
        new_state = self.game.save_state()
 | 
			
		||||
        self.assertEqual(old_state, new_state)
 | 
			
		||||
 | 
			
		||||
    def test_bootstrap_fail(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
@@ -82,6 +94,12 @@ class TestGame(unittest.TestCase):
 | 
			
		||||
        self.assertEqual(self.game.main_menu.validate(),
 | 
			
		||||
                         MainMenuValues.START)
 | 
			
		||||
        self.game.handle_key_pressed(KeyValues.DOWN)
 | 
			
		||||
        self.assertEqual(self.game.main_menu.validate(),
 | 
			
		||||
                         MainMenuValues.SAVE)
 | 
			
		||||
        self.game.handle_key_pressed(KeyValues.DOWN)
 | 
			
		||||
        self.assertEqual(self.game.main_menu.validate(),
 | 
			
		||||
                         MainMenuValues.LOAD)
 | 
			
		||||
        self.game.handle_key_pressed(KeyValues.DOWN)
 | 
			
		||||
        self.assertEqual(self.game.main_menu.validate(),
 | 
			
		||||
                         MainMenuValues.SETTINGS)
 | 
			
		||||
        self.game.handle_key_pressed(KeyValues.ENTER)
 | 
			
		||||
@@ -100,6 +118,12 @@ class TestGame(unittest.TestCase):
 | 
			
		||||
        self.assertEqual(self.game.main_menu.validate(),
 | 
			
		||||
                         MainMenuValues.SETTINGS)
 | 
			
		||||
        self.game.handle_key_pressed(KeyValues.UP)
 | 
			
		||||
        self.assertEqual(self.game.main_menu.validate(),
 | 
			
		||||
                         MainMenuValues.LOAD)
 | 
			
		||||
        self.game.handle_key_pressed(KeyValues.UP)
 | 
			
		||||
        self.assertEqual(self.game.main_menu.validate(),
 | 
			
		||||
                         MainMenuValues.SAVE)
 | 
			
		||||
        self.game.handle_key_pressed(KeyValues.UP)
 | 
			
		||||
        self.assertEqual(self.game.main_menu.validate(),
 | 
			
		||||
                         MainMenuValues.START)
 | 
			
		||||
 | 
			
		||||
@@ -146,6 +170,8 @@ class TestGame(unittest.TestCase):
 | 
			
		||||
 | 
			
		||||
        # Open settings menu
 | 
			
		||||
        self.game.handle_key_pressed(KeyValues.DOWN)
 | 
			
		||||
        self.game.handle_key_pressed(KeyValues.DOWN)
 | 
			
		||||
        self.game.handle_key_pressed(KeyValues.DOWN)
 | 
			
		||||
        self.game.handle_key_pressed(KeyValues.ENTER)
 | 
			
		||||
        self.assertEqual(self.game.state, GameMode.SETTINGS)
 | 
			
		||||
 | 
			
		||||
@@ -214,3 +240,9 @@ class TestGame(unittest.TestCase):
 | 
			
		||||
            new_y, new_x = self.game.player.y, self.game.player.x
 | 
			
		||||
            self.assertEqual(new_y, y)
 | 
			
		||||
            self.assertEqual(new_x, x)
 | 
			
		||||
 | 
			
		||||
    def test_not_implemented(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Check that some functions are not implemented, only for coverage.
 | 
			
		||||
        """
 | 
			
		||||
        self.assertRaises(NotImplementedError, Display.display, None)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +0,0 @@
 | 
			
		||||
import unittest
 | 
			
		||||
 | 
			
		||||
from dungeonbattle.menus import ArbitraryMenu, MainMenu, MainMenuValues
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestMenus(unittest.TestCase):
 | 
			
		||||
    def test_scroll_menu(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Test to scroll the menu.
 | 
			
		||||
        """
 | 
			
		||||
        arbitrary_menu = ArbitraryMenu([])
 | 
			
		||||
        self.assertEqual(arbitrary_menu.position, 0)
 | 
			
		||||
 | 
			
		||||
        main_menu = MainMenu()
 | 
			
		||||
        self.assertEqual(main_menu.position, 0)
 | 
			
		||||
        self.assertEqual(main_menu.validate(), MainMenuValues.START)
 | 
			
		||||
        main_menu.go_up()
 | 
			
		||||
        self.assertEqual(main_menu.validate(), MainMenuValues.START)
 | 
			
		||||
        main_menu.go_down()
 | 
			
		||||
        self.assertEqual(main_menu.validate(), MainMenuValues.SETTINGS)
 | 
			
		||||
        main_menu.go_down()
 | 
			
		||||
        self.assertEqual(main_menu.validate(), MainMenuValues.EXIT)
 | 
			
		||||
        main_menu.go_down()
 | 
			
		||||
        self.assertEqual(main_menu.validate(), MainMenuValues.EXIT)
 | 
			
		||||
		Reference in New Issue
	
	Block a user