Merge branch 'loadgame' into 'master'

Loadgame

See merge request ynerant/dungeon-battle!12
This commit is contained in:
ynerant 2020-11-19 01:16:49 +01:00
commit bbeab344d9
15 changed files with 478 additions and 118 deletions

3
.gitignore vendored
View File

@ -11,3 +11,6 @@ __pycache__
# Don't commit settings
settings.json
# Don't commit game save
save.json

BIN
dungeonbattle/__init__.pyc Normal file

Binary file not shown.

View File

@ -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

Binary file not shown.

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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()))

View File

@ -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

View File

@ -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,30 +49,22 @@ 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:
"""
The settings can change, so they are updated
"""
self.values = []
for i, key in enumerate(settings.settings_keys):
s = settings.get_comment(key)
@ -76,7 +84,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.
@ -112,9 +120,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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)