From 2728612699a94503badb509836e89bc0f16178fc Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 14:29:05 +0100 Subject: [PATCH 1/8] Create settings class --- dungeonbattle/settings.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 dungeonbattle/settings.py diff --git a/dungeonbattle/settings.py b/dungeonbattle/settings.py new file mode 100644 index 0000000..7e44427 --- /dev/null +++ b/dungeonbattle/settings.py @@ -0,0 +1,39 @@ +from typing import Any + + +class Settings: + def __init__(self): + self.KEY_UP_PRIMARY = 'z', 'Touche principale pour aller vers le haut' + self.KEY_UP_SECONDARY = 'KEY_UP', 'Touche secondaire pour aller vers le haut' + self.KEY_DOWN_PRIMARY = 's', 'Touche principale pour aller vers le bas' + self.KEY_DOWN_SECONDARY = 'KEY_DOWN', 'Touche secondaire pour aller vers le bas' + self.KEY_LEFT_PRIMARY = 'q', 'Touche principale pour aller vers la gauche' + self.KEY_LEFT_SECONDARY = 'KEY_LEFT', 'Touche secondaire pour aller vers la gauche' + self.KEY_RIGHT_PRIMARY = 'd', 'Touche principale pour aller vers la droite' + self.KEY_RIGHT_SECONDARY = 'KEY_RIGHT', 'Touche secondaire pour aller vers la droite' + self.TEXTURE_PACK = 'ASCII', 'Pack de textures utilisé' + + def __getattribute__(self, item: str) -> Any: + superattribute = super().__getattribute__(item) + if isinstance(superattribute, tuple) and item.isupper(): + return super().__getattribute__(item)[0] + return superattribute + + def get_comment(self, item: str) -> str: + """ + Retrieve the comment of a setting. + """ + if hasattr(self, item) and isinstance(object.__getattribute__(self, item), tuple): + return object.__getattribute__(self, item)[1] + for key in self.settings_keys: + print(key) + if getattr(self, key) == item: + return object.__getattribute__(self, key)[1] + + @property + def settings_keys(self): + """ + Get the list of all parameters. + """ + return (key for key in self.__dict__) + From af363dabfee3b8161f1bdfd1d729182ed1ce5644 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 14:56:21 +0100 Subject: [PATCH 2/8] Add write and load settings --- .gitignore | 3 ++ dungeonbattle/settings.py | 70 +++++++++++++++++++++++++++++++-------- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 3269e7f..7221e66 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ venv/ .pytest_cache/ __pycache__ + +# Don't commit settings +settings.json diff --git a/dungeonbattle/settings.py b/dungeonbattle/settings.py index 7e44427..e3808e8 100644 --- a/dungeonbattle/settings.py +++ b/dungeonbattle/settings.py @@ -1,24 +1,37 @@ -from typing import Any +import json +from typing import Any, Generator class Settings: + """ + This class stores the settings of the game. + Settings can be get by using for example settings.TEXTURE_PACK directly. + The comment can be get by using settings.get_comment('TEXTURE_PACK'). + We can define the setting by simply use settings.TEXTURE_PACK = 'new_key' + """ def __init__(self): - self.KEY_UP_PRIMARY = 'z', 'Touche principale pour aller vers le haut' - self.KEY_UP_SECONDARY = 'KEY_UP', 'Touche secondaire pour aller vers le haut' - self.KEY_DOWN_PRIMARY = 's', 'Touche principale pour aller vers le bas' - self.KEY_DOWN_SECONDARY = 'KEY_DOWN', 'Touche secondaire pour aller vers le bas' - self.KEY_LEFT_PRIMARY = 'q', 'Touche principale pour aller vers la gauche' - self.KEY_LEFT_SECONDARY = 'KEY_LEFT', 'Touche secondaire pour aller vers la gauche' - self.KEY_RIGHT_PRIMARY = 'd', 'Touche principale pour aller vers la droite' - self.KEY_RIGHT_SECONDARY = 'KEY_RIGHT', 'Touche secondaire pour aller vers la droite' - self.TEXTURE_PACK = 'ASCII', 'Pack de textures utilisé' + self.KEY_UP_PRIMARY = ['z', 'Touche principale pour aller vers le haut'] + self.KEY_UP_SECONDARY = ['KEY_UP', 'Touche secondaire pour aller vers le haut'] + self.KEY_DOWN_PRIMARY = ['s', 'Touche principale pour aller vers le bas'] + self.KEY_DOWN_SECONDARY = ['KEY_DOWN', 'Touche secondaire pour aller vers le bas'] + self.KEY_LEFT_PRIMARY = ['q', 'Touche principale pour aller vers la gauche'] + self.KEY_LEFT_SECONDARY = ['KEY_LEFT', 'Touche secondaire pour aller vers la gauche'] + self.KEY_RIGHT_PRIMARY = ['d', 'Touche principale pour aller vers la droite'] + self.KEY_RIGHT_SECONDARY = ['KEY_RIGHT', 'Touche secondaire pour aller vers la droite'] + self.TEXTURE_PACK = ['ASCII', 'Pack de textures utilisé'] def __getattribute__(self, item: str) -> Any: superattribute = super().__getattribute__(item) - if isinstance(superattribute, tuple) and item.isupper(): - return super().__getattribute__(item)[0] + if item.isupper() and item in self.settings_keys: + return superattribute[0] return superattribute + def __setattr__(self, name: str, value: Any) -> None: + if name in self.settings_keys: + object.__getattribute__(self, name)[0] = value + return + return super().__setattr__(name, value) + def get_comment(self, item: str) -> str: """ Retrieve the comment of a setting. @@ -26,14 +39,43 @@ class Settings: if hasattr(self, item) and isinstance(object.__getattribute__(self, item), tuple): return object.__getattribute__(self, item)[1] for key in self.settings_keys: - print(key) if getattr(self, key) == item: return object.__getattribute__(self, key)[1] @property - def settings_keys(self): + def settings_keys(self) -> Generator[str, Any, None]: """ Get the list of all parameters. """ return (key for key in self.__dict__) + def loads_from_string(self, json_str: str) -> None: + """ + Dump settings + """ + d = json.loads(json_str) + for key in d: + setattr(self, key, d[key]) + + def dumps_to_string(self) -> str: + """ + Dump settings + """ + d = dict() + for key in self.settings_keys: + d[key] = getattr(self, key) + return json.dumps(d) + + def load_settings(self) -> None: + """ + Loads the settings from a file + """ + with open("settings.json", "r") as f: + self.loads_from_string(f.read()) + + def write_settings(self) -> None: + """ + Dumps the settings into a file + """ + with open("settings.json", "w") as f: + f.write(self.dumps_to_string()) From 5ca28277062a005b1a6ed96c4004f285e455bfcd Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 14:59:27 +0100 Subject: [PATCH 3/8] Write and load settings at the start of the game --- dungeonbattle/game.py | 4 ++++ dungeonbattle/settings.py | 8 +++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index 9efd63f..b793fdd 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -1,5 +1,6 @@ from .interfaces import Map from .mapdisplay import MapDisplay +from .settings import Settings from .term_manager import TermManager @@ -8,6 +9,9 @@ class Game: def init(self) -> None: Game.INSTANCE = self + self.settings = Settings() + self.settings.load_settings() + self.settings.write_settings() with TermManager() as term_manager: self._start_game(term_manager.screen) diff --git a/dungeonbattle/settings.py b/dungeonbattle/settings.py index e3808e8..634118e 100644 --- a/dungeonbattle/settings.py +++ b/dungeonbattle/settings.py @@ -1,4 +1,5 @@ import json +import os from typing import Any, Generator @@ -64,14 +65,15 @@ class Settings: d = dict() for key in self.settings_keys: d[key] = getattr(self, key) - return json.dumps(d) + return json.dumps(d, indent=4) def load_settings(self) -> None: """ Loads the settings from a file """ - with open("settings.json", "r") as f: - self.loads_from_string(f.read()) + if os.path.isfile("settings.json"): + with open("settings.json", "r") as f: + self.loads_from_string(f.read()) def write_settings(self) -> None: """ From 9f6c94627909d8d898d6af5fd24bc2f3178d5b4c Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 15:05:10 +0100 Subject: [PATCH 4/8] Test settings --- dungeonbattle/settings_test.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 dungeonbattle/settings_test.py diff --git a/dungeonbattle/settings_test.py b/dungeonbattle/settings_test.py new file mode 100644 index 0000000..4841b5f --- /dev/null +++ b/dungeonbattle/settings_test.py @@ -0,0 +1,30 @@ +import unittest + +from dungeonbattle.settings import Settings + + +class TestSettings(unittest.TestCase): + def test_settings(self) -> None: + """ + Ensure that settings are well loaded. + """ + settings = Settings() + self.assertEqual(settings.KEY_UP_PRIMARY, 'z') + self.assertEqual(settings.KEY_DOWN_PRIMARY, 's') + self.assertEqual(settings.KEY_LEFT_PRIMARY, 'q') + self.assertEqual(settings.KEY_RIGHT_PRIMARY, 'd') + self.assertEqual(settings.KEY_UP_SECONDARY, 'KEY_UP') + self.assertEqual(settings.KEY_DOWN_SECONDARY, 'KEY_DOWN') + self.assertEqual(settings.KEY_LEFT_SECONDARY, 'KEY_LEFT') + self.assertEqual(settings.KEY_RIGHT_SECONDARY, 'KEY_RIGHT') + self.assertEqual(settings.TEXTURE_PACK, 'ASCII') + self.assertEqual(settings.get_comment(settings.TEXTURE_PACK), settings.get_comment('TEXTURE_PACK')) + self.assertEqual(settings.get_comment(settings.TEXTURE_PACK), 'Pack de textures utilisé') + + settings.TEXTURE_PACK = 'UNICODE' + self.assertEqual(settings.TEXTURE_PACK, 'UNICODE') + + settings.write_settings() + settings.load_settings() + + self.assertEqual(settings.TEXTURE_PACK, 'UNICODE') From 7469f4855fff4d5157558ba39a5ce356e1bc08f2 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 15:08:29 +0100 Subject: [PATCH 5/8] We don't use tuples for settings now --- dungeonbattle/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dungeonbattle/settings.py b/dungeonbattle/settings.py index 634118e..064ad69 100644 --- a/dungeonbattle/settings.py +++ b/dungeonbattle/settings.py @@ -37,7 +37,7 @@ class Settings: """ Retrieve the comment of a setting. """ - if hasattr(self, item) and isinstance(object.__getattribute__(self, item), tuple): + if item in self.settings_keys: return object.__getattribute__(self, item)[1] for key in self.settings_keys: if getattr(self, key) == item: From e00d98739ac44f324801f15bba213a383752a335 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 15:13:18 +0100 Subject: [PATCH 6/8] Move tests in a new directory, closes #8 --- dungeonbattle/tests/__init__.py | 0 dungeonbattle/{ => tests}/interfaces_test.py | 0 dungeonbattle/{ => tests}/settings_test.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 dungeonbattle/tests/__init__.py rename dungeonbattle/{ => tests}/interfaces_test.py (100%) rename dungeonbattle/{ => tests}/settings_test.py (100%) diff --git a/dungeonbattle/tests/__init__.py b/dungeonbattle/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dungeonbattle/interfaces_test.py b/dungeonbattle/tests/interfaces_test.py similarity index 100% rename from dungeonbattle/interfaces_test.py rename to dungeonbattle/tests/interfaces_test.py diff --git a/dungeonbattle/settings_test.py b/dungeonbattle/tests/settings_test.py similarity index 100% rename from dungeonbattle/settings_test.py rename to dungeonbattle/tests/settings_test.py From 8ccb74ea54b4fd1bd02e268f03a43e6a5b688fae Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 15:33:26 +0100 Subject: [PATCH 7/8] Linting --- dungeonbattle/entities/items.py | 35 +++++++++++++++------------- dungeonbattle/entities/monsters.py | 6 +++-- dungeonbattle/entities/player.py | 1 + dungeonbattle/game.py | 22 +++++++---------- dungeonbattle/interfaces.py | 25 ++++++++++++-------- dungeonbattle/mapdisplay.py | 27 +++++++++++---------- dungeonbattle/settings.py | 24 ++++++++++++------- dungeonbattle/tests/settings_test.py | 6 +++-- 8 files changed, 83 insertions(+), 63 deletions(-) diff --git a/dungeonbattle/entities/items.py b/dungeonbattle/entities/items.py index 23a1c6e..09750c7 100644 --- a/dungeonbattle/entities/items.py +++ b/dungeonbattle/entities/items.py @@ -1,33 +1,36 @@ -from ..interfaces import Entity, FightingEntity +from ..interfaces import Entity, FightingEntity, Map + class Item(Entity): - held:bool + held: bool def __init__(self, *args, **kwargs): - super().__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) self.held = False - - def drop(self, x:int, y:int): + + def drop(self, x: int, y: int) -> None: self.held = False self.move(x, y) - - def hold(self): + + def hold(self) -> None: self.held = True + class Bomb(Item): - damage:int = 5 - exploding:bool + damage: int = 5 + exploding: bool def __init__(self, *args, **kwargs): - super().__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) self.exploding = False - def drop(self, x:int, y:int): - super.drop(self, x, y) + def drop(self, x: int, y: int) -> None: + super().drop(x, y) self.exploding = True - - def act(self, map): + + def act(self, m: Map) -> None: if self.exploding: - for e in map.entities: - if abs (e.x - self.x) + abs (e.y - self.y) <= 1 and isinstance(e,FightingEntity): + for e in m.entities: + if abs(e.x - self.x) + abs(e.y - self.y) <= 1 and \ + isinstance(e, FightingEntity): e.take_damage(self, self.damage) diff --git a/dungeonbattle/entities/monsters.py b/dungeonbattle/entities/monsters.py index 9aa6c06..f4a7a1f 100644 --- a/dungeonbattle/entities/monsters.py +++ b/dungeonbattle/entities/monsters.py @@ -1,9 +1,11 @@ -from ..interfaces import FightingEntity +from ..interfaces import FightingEntity, Map + class Monster(FightingEntity): - def act(self, map): + def act(self, map: Map) -> None: pass + class Squirrel(Monster): maxhealth = 10 strength = 3 diff --git a/dungeonbattle/entities/player.py b/dungeonbattle/entities/player.py index b687366..9037236 100644 --- a/dungeonbattle/entities/player.py +++ b/dungeonbattle/entities/player.py @@ -1,5 +1,6 @@ from ..interfaces import FightingEntity + class Player(FightingEntity): maxhealth = 20 strength = 5 diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index b793fdd..197f0de 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -1,3 +1,5 @@ +from typing import Any + from .interfaces import Map from .mapdisplay import MapDisplay from .settings import Settings @@ -15,7 +17,7 @@ class Game: with TermManager() as term_manager: self._start_game(term_manager.screen) - def _start_game(self, screen): + def _start_game(self, screen: Any) -> None: # TODO Generate map, or make the possibility to load another one m = Map.load("example_map.txt") player = Player() @@ -26,11 +28,11 @@ class Game: while True: screen.clear() screen.refresh() - d.display(player.getPosY(), player.getPosX()) + d.display(player.y, player.y) key = screen.getkey() self.handle_key_pressed(key) - def handle_key_pressed(self, key): + def handle_key_pressed(self, key: str) -> None: # TODO load keys from settings if key == 'z' or key == 'KEY_UP': self.player.move_up() @@ -47,20 +49,14 @@ class Player: y: int = 0 x: int = 0 - def move_up(self): + def move_up(self) -> None: self.y -= 1 - def move_down(self): + def move_down(self) -> None: self.y += 1 - def move_left(self): + def move_left(self) -> None: self.x -= 1 - def move_right(self): + def move_right(self) -> None: self.x += 1 - - def getPosX(self): - return self.x - - def getPosY(self): - return self.y diff --git a/dungeonbattle/interfaces.py b/dungeonbattle/interfaces.py index f1474e0..5d57510 100644 --- a/dungeonbattle/interfaces.py +++ b/dungeonbattle/interfaces.py @@ -11,11 +11,12 @@ class Map: height: int tiles: list - def __init__(self, width: int, height: int, tiles: list, entities = []): + def __init__(self, width: int, height: int, tiles: list, + entities: list = None): self.width = width self.height = height self.tiles = tiles - self.entities = entities + self.entities = entities or [] @staticmethod def load(filename: str): @@ -40,7 +41,6 @@ class Map: return Map(width, height, tiles, []) - def draw_string(self) -> str: """ Draw the current map as a string object that can be rendered @@ -72,10 +72,15 @@ class Entity: def move(self, x: int, y: int) -> None: self.x = x self.y = y - - def act(self, m:Map): + + 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 @@ -84,13 +89,13 @@ class FightingEntity(Entity): def __init__(self): self.health = self.maxhealth - def hit(self, opponent) -> None: + def hit(self, opponent: "FightingEntity") -> None: opponent.take_damage(self, self.strength) - - def take_damage(self, attacker, amount:int) -> None: + + def take_damage(self, attacker: "Entity", amount: int) -> None: self.health -= amount if self.health <= 0: self.die() - + def die(self) -> None: - pass + pass diff --git a/dungeonbattle/mapdisplay.py b/dungeonbattle/mapdisplay.py index 2e3b8cc..404e267 100644 --- a/dungeonbattle/mapdisplay.py +++ b/dungeonbattle/mapdisplay.py @@ -1,30 +1,33 @@ #!/usr/bin/env python import curses +from typing import Any + from dungeonbattle.interfaces import Map -class MapDisplay: - def __init__(self, m: Map, player): +class MapDisplay: + def __init__(self, m: Map, player: Any): + # TODO Type the player field with the good type self.map = m - self.pad = curses.newpad(m.height, m.width+1) + self.pad = curses.newpad(m.height, m.width + 1) self.player = player - def update_pad(self): + def update_pad(self) -> None: self.pad.addstr(0, 0, self.map.draw_string()) for e in self.map.entities: self.pad.addch(e.y, e.x, e.img) self.pad.addstr(self.player.getPosY(), self.player.getPosX(), '🐿') - def display(self, y, x): - deltay, deltax = (curses.LINES // 2) + 1, (curses.COLS //2) + 1 - pminrow, pmincol = y-deltay, x-deltax + def display(self, y: int, x: int) -> None: + deltay, deltax = (curses.LINES // 2) + 1, (curses.COLS // 2) + 1 + pminrow, pmincol = y - deltay, x - deltax sminrow, smincol = max(-pminrow, 0), max(-pmincol, 0) deltay, deltax = curses.LINES - deltay, curses.COLS - deltax - smaxrow = self.map.height - (y + deltay) + curses.LINES -1 - smaxrow = min(smaxrow, curses.LINES-1) - smaxcol = self.map.width - (x + deltax) + curses.COLS -1 - smaxcol = min(smaxcol, curses.COLS-1) - pminrow = max(0, min(self.map.height, pminrow)) + smaxrow = self.map.height - (y + deltay) + curses.LINES - 1 + smaxrow = min(smaxrow, curses.LINES - 1) + smaxcol = self.map.width - (x + deltax) + curses.COLS - 1 + smaxcol = min(smaxcol, curses.COLS - 1) + pminrow = max(0, min(self.map.height, pminrow)) pmincol = max(0, min(self.map.width, pmincol)) self.pad.clear() self.update_pad() diff --git a/dungeonbattle/settings.py b/dungeonbattle/settings.py index 064ad69..5c80b7c 100644 --- a/dungeonbattle/settings.py +++ b/dungeonbattle/settings.py @@ -11,14 +11,22 @@ class Settings: We can define the setting by simply use settings.TEXTURE_PACK = 'new_key' """ def __init__(self): - self.KEY_UP_PRIMARY = ['z', 'Touche principale pour aller vers le haut'] - self.KEY_UP_SECONDARY = ['KEY_UP', 'Touche secondaire pour aller vers le haut'] - self.KEY_DOWN_PRIMARY = ['s', 'Touche principale pour aller vers le bas'] - self.KEY_DOWN_SECONDARY = ['KEY_DOWN', 'Touche secondaire pour aller vers le bas'] - self.KEY_LEFT_PRIMARY = ['q', 'Touche principale pour aller vers la gauche'] - self.KEY_LEFT_SECONDARY = ['KEY_LEFT', 'Touche secondaire pour aller vers la gauche'] - self.KEY_RIGHT_PRIMARY = ['d', 'Touche principale pour aller vers la droite'] - self.KEY_RIGHT_SECONDARY = ['KEY_RIGHT', 'Touche secondaire pour aller vers la droite'] + self.KEY_UP_PRIMARY = \ + ['z', 'Touche principale pour aller vers le haut'] + self.KEY_UP_SECONDARY = \ + ['KEY_UP', 'Touche secondaire pour aller vers le haut'] + self.KEY_DOWN_PRIMARY = \ + ['s', 'Touche principale pour aller vers le bas'] + self.KEY_DOWN_SECONDARY = \ + ['KEY_DOWN', 'Touche secondaire pour aller vers le bas'] + self.KEY_LEFT_PRIMARY = \ + ['q', 'Touche principale pour aller vers la gauche'] + self.KEY_LEFT_SECONDARY = \ + ['KEY_LEFT', 'Touche secondaire pour aller vers la gauche'] + self.KEY_RIGHT_PRIMARY = \ + ['d', 'Touche principale pour aller vers la droite'] + self.KEY_RIGHT_SECONDARY = \ + ['KEY_RIGHT', 'Touche secondaire pour aller vers la droite'] self.TEXTURE_PACK = ['ASCII', 'Pack de textures utilisé'] def __getattribute__(self, item: str) -> Any: diff --git a/dungeonbattle/tests/settings_test.py b/dungeonbattle/tests/settings_test.py index 4841b5f..5821521 100644 --- a/dungeonbattle/tests/settings_test.py +++ b/dungeonbattle/tests/settings_test.py @@ -18,8 +18,10 @@ class TestSettings(unittest.TestCase): self.assertEqual(settings.KEY_LEFT_SECONDARY, 'KEY_LEFT') self.assertEqual(settings.KEY_RIGHT_SECONDARY, 'KEY_RIGHT') self.assertEqual(settings.TEXTURE_PACK, 'ASCII') - self.assertEqual(settings.get_comment(settings.TEXTURE_PACK), settings.get_comment('TEXTURE_PACK')) - self.assertEqual(settings.get_comment(settings.TEXTURE_PACK), 'Pack de textures utilisé') + self.assertEqual(settings.get_comment(settings.TEXTURE_PACK), + settings.get_comment('TEXTURE_PACK')) + self.assertEqual(settings.get_comment(settings.TEXTURE_PACK), + 'Pack de textures utilisé') settings.TEXTURE_PACK = 'UNICODE' self.assertEqual(settings.TEXTURE_PACK, 'UNICODE') From ff435dc3280e19d3a0659e7128581323ddb0d3da Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 16:13:28 +0100 Subject: [PATCH 8/8] More tests and more coverage --- dungeonbattle/entities/items.py | 4 +- dungeonbattle/entities/monsters.py | 2 +- dungeonbattle/interfaces.py | 15 +++-- dungeonbattle/tests/entities_test.py | 81 ++++++++++++++++++++++++++ dungeonbattle/tests/interfaces_test.py | 21 ++++++- 5 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 dungeonbattle/tests/entities_test.py diff --git a/dungeonbattle/entities/items.py b/dungeonbattle/entities/items.py index 09750c7..3edec91 100644 --- a/dungeonbattle/entities/items.py +++ b/dungeonbattle/entities/items.py @@ -8,9 +8,9 @@ class Item(Entity): super().__init__(*args, **kwargs) self.held = False - def drop(self, x: int, y: int) -> None: + def drop(self, y: int, x: int) -> None: self.held = False - self.move(x, y) + self.move(y, x) def hold(self) -> None: self.held = True diff --git a/dungeonbattle/entities/monsters.py b/dungeonbattle/entities/monsters.py index f4a7a1f..59db0e7 100644 --- a/dungeonbattle/entities/monsters.py +++ b/dungeonbattle/entities/monsters.py @@ -2,7 +2,7 @@ from ..interfaces import FightingEntity, Map class Monster(FightingEntity): - def act(self, map: Map) -> None: + def act(self, m: Map) -> None: pass diff --git a/dungeonbattle/interfaces.py b/dungeonbattle/interfaces.py index 5d57510..6546523 100644 --- a/dungeonbattle/interfaces.py +++ b/dungeonbattle/interfaces.py @@ -66,12 +66,16 @@ class Tile(Enum): class Entity: - x: int y: int + x: int - def move(self, x: int, y: int) -> None: - self.x = x + def __init__(self): + self.y = 0 + self.x = 0 + + def move(self, y: int, x: int) -> None: self.y = y + self.x = x def act(self, m: Map) -> None: """ @@ -85,9 +89,12 @@ class FightingEntity(Entity): maxhealth: int health: int strength: int + dead: bool def __init__(self): + super().__init__() self.health = self.maxhealth + self.dead = False def hit(self, opponent: "FightingEntity") -> None: opponent.take_damage(self, self.strength) @@ -98,4 +105,4 @@ class FightingEntity(Entity): self.die() def die(self) -> None: - pass + self.dead = True diff --git a/dungeonbattle/tests/entities_test.py b/dungeonbattle/tests/entities_test.py new file mode 100644 index 0000000..f7d75f1 --- /dev/null +++ b/dungeonbattle/tests/entities_test.py @@ -0,0 +1,81 @@ +import unittest + +from dungeonbattle.entities.items import Bomb, Item +from dungeonbattle.entities.monsters import Squirrel +from dungeonbattle.entities.player import Player +from dungeonbattle.interfaces import Entity, Map + + +class TestEntities(unittest.TestCase): + def setUp(self) -> None: + """ + Load example map that can be used in tests. + """ + self.map = Map.load("example_map.txt") + + def test_basic_entities(self) -> None: + """ + Test some random stuff with basic entities. + """ + entity = Entity() + entity.move(42, 64) + self.assertEqual(entity.y, 42) + self.assertEqual(entity.x, 64) + self.assertIsNone(entity.act(self.map)) + + def test_fighting_entities(self) -> None: + """ + Test some random stuff with fighting entities. + """ + entity = Squirrel() + self.assertIsNone(entity.act(self.map)) + self.assertEqual(entity.maxhealth, 10) + 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.assertIsNone(entity.hit(entity)) + self.assertTrue(entity.dead) + + def test_items(self) -> None: + """ + Test some random stuff with items. + """ + item = Item() + self.assertFalse(item.held) + item.hold() + self.assertTrue(item.held) + item.drop(42, 42) + self.assertEqual(item.y, 42) + self.assertEqual(item.x, 42) + + def test_bombs(self) -> None: + """ + Test some random stuff with bombs. + """ + item = Bomb() + squirrel = Squirrel() + self.map.entities.append(item) + self.map.entities.append(squirrel) + squirrel.health = 2 + squirrel.move(41, 42) + item.act(self.map) + self.assertFalse(squirrel.dead) + item.drop(42, 42) + self.assertEqual(item.y, 42) + self.assertEqual(item.x, 42) + item.act(self.map) + self.assertTrue(squirrel.dead) + + def test_players(self) -> None: + """ + Test some random stuff with players. + """ + player = Player() + self.assertEqual(player.strength, 5) + self.assertEqual(player.health, player.maxhealth) + self.assertEqual(player.maxhealth, 20) diff --git a/dungeonbattle/tests/interfaces_test.py b/dungeonbattle/tests/interfaces_test.py index 849e28b..d6ab078 100644 --- a/dungeonbattle/tests/interfaces_test.py +++ b/dungeonbattle/tests/interfaces_test.py @@ -1,6 +1,6 @@ import unittest -from dungeonbattle.interfaces import Map +from dungeonbattle.interfaces import Map, Tile class TestInterfaces(unittest.TestCase): @@ -12,3 +12,22 @@ class TestInterfaces(unittest.TestCase): self.assertEqual(m.width, 2) self.assertEqual(m.height, 2) self.assertEqual(m.draw_string(), ".█\n█.") + + def test_load_map(self) -> None: + """ + Try to load a map from a file. + """ + m = Map.load("example_map.txt") + self.assertEqual(m.width, 52) + self.assertEqual(m.height, 17) + + def test_tiles(self) -> None: + """ + Test some things about tiles. + """ + self.assertFalse(Tile.FLOOR.is_wall()) + self.assertTrue(Tile.WALL.is_wall()) + self.assertFalse(Tile.EMPTY.is_wall()) + self.assertTrue(Tile.FLOOR.can_walk()) + self.assertFalse(Tile.WALL.can_walk()) + self.assertTrue(Tile.EMPTY.can_walk())