From e00d98739ac44f324801f15bba213a383752a335 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 15:13:18 +0100 Subject: [PATCH 01/17] 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 02/17] 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 03/17] 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()) From 60ca00b8fa1b013e4b58df7f8b1298b910c411dc Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 6 Nov 2020 16:20:07 +0100 Subject: [PATCH 04/17] Generic menu implementation --- dungeonbattle/menus.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 dungeonbattle/menus.py diff --git a/dungeonbattle/menus.py b/dungeonbattle/menus.py new file mode 100644 index 0000000..e188864 --- /dev/null +++ b/dungeonbattle/menus.py @@ -0,0 +1,14 @@ +class Menu: + + def __init__(self, values:list): + self.values = values + self.position = 0 + + def go_up(self): + self.position = max(0, self.position -1) + + def go_down(self): + self.position = min(len(self.values)-1, self.position +1) + + def validate(self): + return self.values[self.position] \ No newline at end of file From 901406351ee2f48d0a6d6f27dabab5e66ffe16e4 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 6 Nov 2020 16:40:43 +0100 Subject: [PATCH 05/17] Better menus --- dungeonbattle/menus.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/dungeonbattle/menus.py b/dungeonbattle/menus.py index e188864..f2da237 100644 --- a/dungeonbattle/menus.py +++ b/dungeonbattle/menus.py @@ -1,7 +1,11 @@ +from enum import Enum, auto + + class Menu: - def __init__(self, values:list): - self.values = values + values:list + + def __init__(self): self.position = 0 def go_up(self): @@ -11,4 +15,19 @@ class Menu: self.position = min(len(self.values)-1, self.position +1) def validate(self): - return self.values[self.position] \ No newline at end of file + return self.values[self.position] + +class MainMenuValues(Enum): + START = auto() + SETTINGS = auto() + EXIT = auto() + +class MainMenu(Menu): + + values = [ e for e in MainMenuValues ] + +class ArbitraryMenu: + + def __init__(self, values:list): + super().__init__(self) + self.values = values \ No newline at end of file From 6e5cd9084b1a4d9db970a1dbbdf20930c40a870d Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 6 Nov 2020 17:02:59 +0100 Subject: [PATCH 06/17] Finished cleaning conflicts --- dungeonbattle/game.py | 4 ++-- dungeonbattle/mapdisplay.py | 7 +------ main.py | 7 ++++++- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index e974e41..77daf35 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -18,10 +18,10 @@ class Game: def new_game(self): # TODO generate a new map procedurally self.m = Map.load("example_map.txt") - self.d = MapDisplay(self.m) self.player = Player() self.player.y = 1 self.player.x = 6 + self.d = MapDisplay(self.m, self.player) @classmethod def load_game(filename): @@ -32,7 +32,7 @@ class Game: while True: screen.clear() screen.refresh() - self.d.display(self.player.getPosY(), self.player.getPosX()) + self.d.display(self.player.y, self.player.x) key = screen.getkey() self.handle_key_pressed(key) diff --git a/dungeonbattle/mapdisplay.py b/dungeonbattle/mapdisplay.py index fb977a0..d61b7e6 100644 --- a/dungeonbattle/mapdisplay.py +++ b/dungeonbattle/mapdisplay.py @@ -5,23 +5,18 @@ from typing import Any from dungeonbattle.interfaces import Map -<<<<<<< HEAD - def __init__(self, m: Map): - self.map = m - self.pad = curses.newpad(m.height, m.width+1) -======= 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.player = player ->>>>>>> master 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.y, self.player.x, '🐿️') def display(self, y: int, x: int) -> None: deltay, deltax = (curses.LINES // 2) + 1, (curses.COLS // 2) + 1 diff --git a/main.py b/main.py index a3c6f79..56cc77c 100755 --- a/main.py +++ b/main.py @@ -1,5 +1,10 @@ #!/usr/bin/env python from dungeonbattle.game import Game +from dungeonbattle.term_manager import TermManager if __name__ == "__main__": - Game().init() + with TermManager() as term_manager: + game = Game() + game.init() + game.new_game() + game.run(term_manager.screen) From e3a1bf96c2dc23b10bc5d3fa5e4735e082926f8f Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 6 Nov 2020 17:24:20 +0100 Subject: [PATCH 07/17] Used settings for keys --- dungeonbattle/game.py | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index 77daf35..c22e0b1 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -4,6 +4,22 @@ from .interfaces import Map from .mapdisplay import MapDisplay from .settings import Settings from .term_manager import TermManager +from enum import Enum, auto + + +class GameMode(Enum): + MAINMENU = auto() + PLAY = auto() + SETTINGS = auto() + INVENTORY = auto() + + +class KeyValues(Enum): + UP = auto() + DOWN = auto() + LEFT = auto() + RIGHT = auto() + ENTER = auto() class Game: @@ -11,6 +27,7 @@ class Game: def init(self) -> None: Game.INSTANCE = self + self.state = GameMode.MAINMENU self.settings = Settings() self.settings.load_settings() self.settings.write_settings() @@ -34,17 +51,27 @@ class Game: screen.refresh() self.d.display(self.player.y, self.player.x) key = screen.getkey() - self.handle_key_pressed(key) + self.handle_key_pressed(self.translate_key(key)) - def handle_key_pressed(self, key: str) -> None: - # TODO load keys from settings - if key == 'z' or key == 'KEY_UP': + def translate_key(self, key:str) -> KeyValues: + if key in (self.settings.KEY_DOWN_SECONDARY, self.settings.KEY_DOWN_PRIMARY): + return KeyValues.DOWN + elif key in (self.settings.KEY_LEFT_PRIMARY, self.settings.KEY_LEFT_SECONDARY): + return KeyValues.LEFT + elif key in (self.settings.KEY_RIGHT_PRIMARY, self.settings.KEY_RIGHT_SECONDARY): + return KeyValues.RIGHT + elif key in (self.settings.KEY_UP_PRIMARY, self.settings.KEY_UP_SECONDARY): + return KeyValues.UP + + def handle_key_pressed(self, key: KeyValues) -> None: + + if key == KeyValues.UP: self.player.move_up() - if key == 's' or key == 'KEY_DOWN': + if key == KeyValues.DOWN: self.player.move_down() - if key == 'q' or key == 'KEY_LEFT': + if key == KeyValues.LEFT: self.player.move_left() - if key == 'd' or key == 'KEY_RIGHT': + if key == KeyValues.RIGHT: self.player.move_right() From 8641e7d13db02cfd6b0896ace7e0fd66ab416879 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 17:48:47 +0100 Subject: [PATCH 08/17] User player entity instead of temporary Player class --- dungeonbattle/entities/player.py | 12 ++++++++++++ dungeonbattle/game.py | 22 +++------------------- dungeonbattle/mapdisplay.py | 9 ++++----- 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/dungeonbattle/entities/player.py b/dungeonbattle/entities/player.py index 9037236..8b5bc3a 100644 --- a/dungeonbattle/entities/player.py +++ b/dungeonbattle/entities/player.py @@ -4,3 +4,15 @@ from ..interfaces import FightingEntity class Player(FightingEntity): maxhealth = 20 strength = 5 + + def move_up(self) -> None: + self.y -= 1 + + def move_down(self) -> None: + self.y += 1 + + def move_left(self) -> None: + self.x -= 1 + + def move_right(self) -> None: + self.x += 1 diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index 197f0de..5e0acad 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -1,5 +1,6 @@ from typing import Any +from .entities.player import Player from .interfaces import Map from .mapdisplay import MapDisplay from .settings import Settings @@ -22,8 +23,8 @@ class Game: m = Map.load("example_map.txt") player = Player() self.player = player - player.y = 1 - player.x = 6 + m.entities.append(player) + player.move(1, 6) d = MapDisplay(m, player) while True: screen.clear() @@ -43,20 +44,3 @@ class Game: if key == 'd' or key == 'KEY_RIGHT': self.player.move_right() - -class Player: - # FIXME Should be elsewhere, only useful to don't break the previous code - y: int = 0 - x: int = 0 - - def move_up(self) -> None: - self.y -= 1 - - def move_down(self) -> None: - self.y += 1 - - def move_left(self) -> None: - self.x -= 1 - - def move_right(self) -> None: - self.x += 1 diff --git a/dungeonbattle/mapdisplay.py b/dungeonbattle/mapdisplay.py index 404e267..fdc87ba 100644 --- a/dungeonbattle/mapdisplay.py +++ b/dungeonbattle/mapdisplay.py @@ -1,22 +1,21 @@ #!/usr/bin/env python import curses -from typing import Any +from dungeonbattle.entities.player import Player from dungeonbattle.interfaces import Map class MapDisplay: - def __init__(self, m: Map, player: Any): - # TODO Type the player field with the good type + def __init__(self, m: Map, player: Player): self.map = m self.pad = curses.newpad(m.height, m.width + 1) self.player = player def update_pad(self) -> None: self.pad.addstr(0, 0, self.map.draw_string()) + # TODO Not all entities should be a player for e in self.map.entities: - self.pad.addch(e.y, e.x, e.img) - self.pad.addstr(self.player.getPosY(), self.player.getPosX(), '🐿') + self.pad.addstr(e.y, e.x, '🐿') def display(self, y: int, x: int) -> None: deltay, deltax = (curses.LINES // 2) + 1, (curses.COLS // 2) + 1 From 54bb2d14164ffd904a26e203c98981645eb241ed Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 17:59:19 +0100 Subject: [PATCH 09/17] Collisions are working --- dungeonbattle/entities/player.py | 8 ++++---- dungeonbattle/game.py | 3 +-- dungeonbattle/interfaces.py | 22 ++++++++++++++++++---- dungeonbattle/tests/entities_test.py | 4 ++-- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/dungeonbattle/entities/player.py b/dungeonbattle/entities/player.py index 8b5bc3a..53f0ccf 100644 --- a/dungeonbattle/entities/player.py +++ b/dungeonbattle/entities/player.py @@ -6,13 +6,13 @@ class Player(FightingEntity): strength = 5 def move_up(self) -> None: - self.y -= 1 + self.check_move(self.y - 1, self.x, True) def move_down(self) -> None: - self.y += 1 + self.check_move(self.y + 1, self.x, True) def move_left(self) -> None: - self.x -= 1 + self.check_move(self.y, self.x - 1, True) def move_right(self) -> None: - self.x += 1 + self.check_move(self.y, self.x + 1, True) diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index 5e0acad..8f5bb5a 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -23,7 +23,7 @@ class Game: m = Map.load("example_map.txt") player = Player() self.player = player - m.entities.append(player) + m.add_entity(player) player.move(1, 6) d = MapDisplay(m, player) while True: @@ -43,4 +43,3 @@ class Game: self.player.move_left() if key == 'd' or key == 'KEY_RIGHT': self.player.move_right() - diff --git a/dungeonbattle/interfaces.py b/dungeonbattle/interfaces.py index 6546523..f9d177d 100644 --- a/dungeonbattle/interfaces.py +++ b/dungeonbattle/interfaces.py @@ -11,12 +11,18 @@ class Map: height: int tiles: list - def __init__(self, width: int, height: int, tiles: list, - entities: list = None): + def __init__(self, width: int, height: int, tiles: list): self.width = width self.height = height self.tiles = tiles - self.entities = entities or [] + self.entities = [] + + def add_entity(self, entity: "Entity") -> None: + """ + Register a new entity in the map. + """ + self.entities.append(entity) + entity.map = self @staticmethod def load(filename: str): @@ -39,7 +45,7 @@ class Map: tiles = [[Tile(c) for x, c in enumerate(line)] for y, line in enumerate(lines)] - return Map(width, height, tiles, []) + return Map(width, height, tiles) def draw_string(self) -> str: """ @@ -68,11 +74,19 @@ class Tile(Enum): class Entity: y: int x: int + map: Map def __init__(self): self.y = 0 self.x = 0 + def check_move(self, y: int, x: int, move_if_possible: bool = False)\ + -> bool: + tile = self.map.tiles[y][x] + if tile.can_walk() and move_if_possible: + self.move(y, x) + return tile.can_walk() + def move(self, y: int, x: int) -> None: self.y = y self.x = x diff --git a/dungeonbattle/tests/entities_test.py b/dungeonbattle/tests/entities_test.py index f7d75f1..043919a 100644 --- a/dungeonbattle/tests/entities_test.py +++ b/dungeonbattle/tests/entities_test.py @@ -59,8 +59,8 @@ class TestEntities(unittest.TestCase): """ item = Bomb() squirrel = Squirrel() - self.map.entities.append(item) - self.map.entities.append(squirrel) + self.map.add_entity(item) + self.map.add_entity(squirrel) squirrel.health = 2 squirrel.move(41, 42) item.act(self.map) From d06a42469a5fd53d9e9c76d4148d3e866fc3b417 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 18:03:30 +0100 Subject: [PATCH 10/17] Collisions are working --- dungeonbattle/entities/player.py | 16 ++++++++-------- dungeonbattle/tests/entities_test.py | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/dungeonbattle/entities/player.py b/dungeonbattle/entities/player.py index 53f0ccf..df96b29 100644 --- a/dungeonbattle/entities/player.py +++ b/dungeonbattle/entities/player.py @@ -5,14 +5,14 @@ class Player(FightingEntity): maxhealth = 20 strength = 5 - def move_up(self) -> None: - self.check_move(self.y - 1, self.x, True) + def move_up(self) -> bool: + return self.check_move(self.y - 1, self.x, True) - def move_down(self) -> None: - self.check_move(self.y + 1, self.x, True) + def move_down(self) -> bool: + return self.check_move(self.y + 1, self.x, True) - def move_left(self) -> None: - self.check_move(self.y, self.x - 1, True) + def move_left(self) -> bool: + return self.check_move(self.y, self.x - 1, True) - def move_right(self) -> None: - self.check_move(self.y, self.x + 1, True) + def move_right(self) -> bool: + return self.check_move(self.y, self.x + 1, True) diff --git a/dungeonbattle/tests/entities_test.py b/dungeonbattle/tests/entities_test.py index 043919a..7b7902b 100644 --- a/dungeonbattle/tests/entities_test.py +++ b/dungeonbattle/tests/entities_test.py @@ -76,6 +76,22 @@ class TestEntities(unittest.TestCase): Test some random stuff with players. """ player = Player() + self.map.add_entity(player) + player.move(1, 6) self.assertEqual(player.strength, 5) self.assertEqual(player.health, player.maxhealth) self.assertEqual(player.maxhealth, 20) + + # Test movements and ensure that collisions are working + self.assertFalse(player.move_up()) + self.assertTrue(player.move_left()) + self.assertFalse(player.move_left()) + for i in range(8): + self.assertTrue(player.move_down()) + self.assertFalse(player.move_down()) + self.assertTrue(player.move_right()) + self.assertTrue(player.move_right()) + self.assertTrue(player.move_right()) + self.assertFalse(player.move_right()) + self.assertTrue(player.move_down()) + self.assertTrue(player.move_down()) From 43001f6ede2c86f7b5e38aac1fe0a11bcebe815e Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 6 Nov 2020 18:06:28 +0100 Subject: [PATCH 11/17] Basic menu handling --- dungeonbattle/game.py | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index c22e0b1..1541898 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -1,11 +1,12 @@ from typing import Any +import sys from .interfaces import Map from .mapdisplay import MapDisplay from .settings import Settings from .term_manager import TermManager from enum import Enum, auto - +from . import menus class GameMode(Enum): MAINMENU = auto() @@ -28,6 +29,7 @@ class Game: def init(self) -> None: Game.INSTANCE = self self.state = GameMode.MAINMENU + self.main_menu = menus.MainMenu() self.settings = Settings() self.settings.load_settings() self.settings.write_settings() @@ -40,7 +42,7 @@ class Game: self.player.x = 6 self.d = MapDisplay(self.m, self.player) - @classmethod + @staticmethod def load_game(filename): # TODO loading map from a file raise NotImplementedError() @@ -64,15 +66,28 @@ class Game: return KeyValues.UP def handle_key_pressed(self, key: KeyValues) -> None: - - if key == KeyValues.UP: - self.player.move_up() - if key == KeyValues.DOWN: - self.player.move_down() - if key == KeyValues.LEFT: - self.player.move_left() - if key == KeyValues.RIGHT: - self.player.move_right() + if self.state == GameMode.PLAY: + if key == KeyValues.UP: + self.player.move_up() + if key == KeyValues.DOWN: + self.player.move_down() + if key == KeyValues.LEFT: + self.player.move_left() + if key == KeyValues.RIGHT: + self.player.move_right() + if self.state == GameMode.MAINMENU: + 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.SETTINGS: + self.state = GameMode.SETTINGS + elif option == menus.MainMenuValues.EXIT: + sys.exit(0) class Player: From 8d9b5166b73a06c083156d323447e557af1057a2 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 18:11:59 +0100 Subject: [PATCH 12/17] Linting --- dungeonbattle/game.py | 24 ++++++++++++++---------- dungeonbattle/menus.py | 30 +++++++++++++++--------------- dungeonbattle/texturepack.py | 12 ++++++------ 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index 1541898..1466dfd 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -1,13 +1,13 @@ +import sys from typing import Any -import sys from .interfaces import Map from .mapdisplay import MapDisplay from .settings import Settings -from .term_manager import TermManager from enum import Enum, auto from . import menus + class GameMode(Enum): MAINMENU = auto() PLAY = auto() @@ -34,7 +34,7 @@ class Game: self.settings.load_settings() self.settings.write_settings() - def new_game(self): + def new_game(self) -> None: # TODO generate a new map procedurally self.m = Map.load("example_map.txt") self.player = Player() @@ -43,11 +43,11 @@ class Game: self.d = MapDisplay(self.m, self.player) @staticmethod - def load_game(filename): + def load_game(filename: str) -> None: # TODO loading map from a file raise NotImplementedError() - def run(self, screen): + def run(self, screen: Any) -> None: while True: screen.clear() screen.refresh() @@ -55,14 +55,18 @@ class Game: key = screen.getkey() self.handle_key_pressed(self.translate_key(key)) - def translate_key(self, key:str) -> KeyValues: - if key in (self.settings.KEY_DOWN_SECONDARY, self.settings.KEY_DOWN_PRIMARY): + def translate_key(self, key: str) -> KeyValues: + if key in (self.settings.KEY_DOWN_SECONDARY, + self.settings.KEY_DOWN_PRIMARY): return KeyValues.DOWN - elif key in (self.settings.KEY_LEFT_PRIMARY, self.settings.KEY_LEFT_SECONDARY): + elif key in (self.settings.KEY_LEFT_PRIMARY, + self.settings.KEY_LEFT_SECONDARY): return KeyValues.LEFT - elif key in (self.settings.KEY_RIGHT_PRIMARY, self.settings.KEY_RIGHT_SECONDARY): + elif key in (self.settings.KEY_RIGHT_PRIMARY, + self.settings.KEY_RIGHT_SECONDARY): return KeyValues.RIGHT - elif key in (self.settings.KEY_UP_PRIMARY, self.settings.KEY_UP_SECONDARY): + elif key in (self.settings.KEY_UP_PRIMARY, + self.settings.KEY_UP_SECONDARY): return KeyValues.UP def handle_key_pressed(self, key: KeyValues) -> None: diff --git a/dungeonbattle/menus.py b/dungeonbattle/menus.py index f2da237..904fa17 100644 --- a/dungeonbattle/menus.py +++ b/dungeonbattle/menus.py @@ -2,32 +2,32 @@ from enum import Enum, auto class Menu: - - values:list + values: list def __init__(self): self.position = 0 - - def go_up(self): - self.position = max(0, self.position -1) - - def go_down(self): - self.position = min(len(self.values)-1, self.position +1) - - def validate(self): + + def go_up(self) -> None: + self.position = max(0, self.position - 1) + + def go_down(self) -> None: + self.position = min(len(self.values) - 1, self.position + 1) + + def validate(self) -> None: return self.values[self.position] + class MainMenuValues(Enum): START = auto() SETTINGS = auto() EXIT = auto() -class MainMenu(Menu): - values = [ e for e in MainMenuValues ] +class MainMenu(Menu): + values = [e for e in MainMenuValues] + class ArbitraryMenu: - - def __init__(self, values:list): + def __init__(self, values: list): super().__init__(self) - self.values = values \ No newline at end of file + self.values = values diff --git a/dungeonbattle/texturepack.py b/dungeonbattle/texturepack.py index 3a4eb99..d9b5818 100644 --- a/dungeonbattle/texturepack.py +++ b/dungeonbattle/texturepack.py @@ -1,8 +1,8 @@ -#This is the base ascii texturepack +# This is the base ascii texturepack ascii = { - "EMPTY" : ' ', - "WALL" : '#', - "FLOOR" : '.', - "PLAYER" : '@' - } + "EMPTY": ' ', + "WALL": '#', + "FLOOR": '.', + "PLAYER": '@' +} From 60d6c7509a3079e2b5e9c902d3f034713ee1a11c Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 18:25:02 +0100 Subject: [PATCH 13/17] Bind Return key to menus --- dungeonbattle/game.py | 2 ++ dungeonbattle/menus.py | 3 ++- dungeonbattle/settings.py | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index 1466dfd..0787f9c 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -68,6 +68,8 @@ class Game: elif key in (self.settings.KEY_UP_PRIMARY, self.settings.KEY_UP_SECONDARY): return KeyValues.UP + elif key == self.settings.KEY_ENTER: + return KeyValues.ENTER def handle_key_pressed(self, key: KeyValues) -> None: if self.state == GameMode.PLAY: diff --git a/dungeonbattle/menus.py b/dungeonbattle/menus.py index 904fa17..66dc199 100644 --- a/dungeonbattle/menus.py +++ b/dungeonbattle/menus.py @@ -1,4 +1,5 @@ from enum import Enum, auto +from typing import Any class Menu: @@ -13,7 +14,7 @@ class Menu: def go_down(self) -> None: self.position = min(len(self.values) - 1, self.position + 1) - def validate(self) -> None: + def validate(self) -> Any: return self.values[self.position] diff --git a/dungeonbattle/settings.py b/dungeonbattle/settings.py index 5c80b7c..db216df 100644 --- a/dungeonbattle/settings.py +++ b/dungeonbattle/settings.py @@ -27,6 +27,8 @@ class Settings: ['d', 'Touche principale pour aller vers la droite'] self.KEY_RIGHT_SECONDARY = \ ['KEY_RIGHT', 'Touche secondaire pour aller vers la droite'] + self.KEY_ENTER = \ + ['\n', 'Touche pour valider un menu'] self.TEXTURE_PACK = ['ASCII', 'Pack de textures utilisé'] def __getattribute__(self, item: str) -> Any: From 30a01108b802dbce648d93d65fff3bd171174b31 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 18:32:51 +0100 Subject: [PATCH 14/17] Test main menu scrolling --- dungeonbattle/menus.py | 4 ++-- dungeonbattle/tests/menus_test.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 dungeonbattle/tests/menus_test.py diff --git a/dungeonbattle/menus.py b/dungeonbattle/menus.py index 66dc199..82d6d4e 100644 --- a/dungeonbattle/menus.py +++ b/dungeonbattle/menus.py @@ -28,7 +28,7 @@ class MainMenu(Menu): values = [e for e in MainMenuValues] -class ArbitraryMenu: +class ArbitraryMenu(Menu): def __init__(self, values: list): - super().__init__(self) + super().__init__() self.values = values diff --git a/dungeonbattle/tests/menus_test.py b/dungeonbattle/tests/menus_test.py new file mode 100644 index 0000000..6ad9df7 --- /dev/null +++ b/dungeonbattle/tests/menus_test.py @@ -0,0 +1,24 @@ +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) From a1731dc9f72d49eca6d3b13412f7684546d034fc Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 18:38:10 +0100 Subject: [PATCH 15/17] Default coordinates are (1, 6) --- dungeonbattle/game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index 687dfaf..8fdbfd4 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -39,7 +39,7 @@ class Game: # TODO generate a new map procedurally self.m = Map.load("example_map.txt") self.player = Player() - self.player.move(y, x) + self.player.move(1, 6) self.m.add_entity(self.player) self.d = MapDisplay(self.m, self.player) From dc0d478d1ebfadccb4f49bbe73fa10df424c52f8 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 18:39:55 +0100 Subject: [PATCH 16/17] init -> __init__ --- dungeonbattle/game.py | 5 +---- main.py | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index 8fdbfd4..b1e8bc6 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -25,10 +25,7 @@ class KeyValues(Enum): class Game: - INSTANCE = None - - def init(self) -> None: - Game.INSTANCE = self + def __init__(self) -> None: self.state = GameMode.MAINMENU self.main_menu = menus.MainMenu() self.settings = Settings() diff --git a/main.py b/main.py index 56cc77c..2e44d9d 100755 --- a/main.py +++ b/main.py @@ -5,6 +5,5 @@ from dungeonbattle.term_manager import TermManager if __name__ == "__main__": with TermManager() as term_manager: game = Game() - game.init() game.new_game() game.run(term_manager.screen) From c97a9a2e6c6c5a1a0fa1a5ba47fd79a69ce05418 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 6 Nov 2020 19:12:56 +0100 Subject: [PATCH 17/17] Test some functions in the game --- dungeonbattle/game.py | 4 +- dungeonbattle/mapdisplay.py | 5 +- dungeonbattle/tests/game_test.py | 97 ++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 dungeonbattle/tests/game_test.py diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index b1e8bc6..e0e707d 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -32,13 +32,13 @@ class Game: self.settings.load_settings() self.settings.write_settings() - def new_game(self) -> None: + def new_game(self, init_pad: bool = True) -> None: # TODO generate a new map procedurally self.m = Map.load("example_map.txt") self.player = Player() self.player.move(1, 6) self.m.add_entity(self.player) - self.d = MapDisplay(self.m, self.player) + self.d = MapDisplay(self.m, self.player, init_pad) @staticmethod def load_game(filename: str) -> None: diff --git a/dungeonbattle/mapdisplay.py b/dungeonbattle/mapdisplay.py index fdc87ba..095b7cb 100644 --- a/dungeonbattle/mapdisplay.py +++ b/dungeonbattle/mapdisplay.py @@ -6,10 +6,11 @@ from dungeonbattle.interfaces import Map class MapDisplay: - def __init__(self, m: Map, player: Player): + def __init__(self, m: Map, player: Player, init_pad: bool = True): self.map = m - self.pad = curses.newpad(m.height, m.width + 1) self.player = player + if init_pad: + self.pad = curses.newpad(m.height, m.width + 1) def update_pad(self) -> None: self.pad.addstr(0, 0, self.map.draw_string()) diff --git a/dungeonbattle/tests/game_test.py b/dungeonbattle/tests/game_test.py new file mode 100644 index 0000000..4183cbe --- /dev/null +++ b/dungeonbattle/tests/game_test.py @@ -0,0 +1,97 @@ +import unittest + +from dungeonbattle.game import Game, KeyValues, GameMode +from dungeonbattle.menus import MainMenuValues + + +class TestGame(unittest.TestCase): + def setUp(self) -> None: + """ + Setup game. + """ + self.game = Game() + self.game.new_game(False) + + def test_load_game(self) -> None: + self.assertRaises(NotImplementedError, Game.load_game, "game.save") + + def test_key_translation(self) -> None: + """ + Test key bindings. + """ + self.assertEqual(self.game.translate_key( + self.game.settings.KEY_UP_PRIMARY), KeyValues.UP) + self.assertEqual(self.game.translate_key( + self.game.settings.KEY_UP_SECONDARY), KeyValues.UP) + self.assertEqual(self.game.translate_key( + self.game.settings.KEY_DOWN_PRIMARY), KeyValues.DOWN) + self.assertEqual(self.game.translate_key( + self.game.settings.KEY_DOWN_SECONDARY), KeyValues.DOWN) + self.assertEqual(self.game.translate_key( + self.game.settings.KEY_LEFT_PRIMARY), KeyValues.LEFT) + self.assertEqual(self.game.translate_key( + self.game.settings.KEY_LEFT_SECONDARY), KeyValues.LEFT) + self.assertEqual(self.game.translate_key( + self.game.settings.KEY_RIGHT_PRIMARY), KeyValues.RIGHT) + self.assertEqual(self.game.translate_key( + self.game.settings.KEY_RIGHT_SECONDARY), KeyValues.RIGHT) + self.assertEqual(self.game.translate_key( + self.game.settings.KEY_ENTER), KeyValues.ENTER) + + def test_key_press(self) -> None: + """ + Press a key and see what is done. + """ + self.assertEqual(self.game.state, GameMode.MAINMENU) + self.assertEqual(self.game.main_menu.validate(), + MainMenuValues.START) + self.game.handle_key_pressed(KeyValues.UP) + self.assertEqual(self.game.main_menu.validate(), + MainMenuValues.START) + self.game.handle_key_pressed(KeyValues.DOWN) + self.assertEqual(self.game.main_menu.validate(), + MainMenuValues.SETTINGS) + self.game.handle_key_pressed(KeyValues.ENTER) + self.assertEqual(self.game.state, GameMode.SETTINGS) + + self.game.state = GameMode.MAINMENU + + self.game.handle_key_pressed(KeyValues.DOWN) + self.assertEqual(self.game.main_menu.validate(), + MainMenuValues.EXIT) + self.assertRaises(SystemExit, self.game.handle_key_pressed, + KeyValues.ENTER) + + self.game.handle_key_pressed(KeyValues.UP) + self.assertEqual(self.game.main_menu.validate(), + MainMenuValues.SETTINGS) + self.game.handle_key_pressed(KeyValues.UP) + self.assertEqual(self.game.main_menu.validate(), + MainMenuValues.START) + + self.game.handle_key_pressed(KeyValues.ENTER) + self.assertEqual(self.game.state, GameMode.PLAY) + + y, x = self.game.player.y, self.game.player.x + self.game.handle_key_pressed(KeyValues.DOWN) + new_y, new_x = self.game.player.y, self.game.player.x + self.assertEqual(new_y, y + 1) + self.assertEqual(new_x, x) + + y, x = new_y, new_x + self.game.handle_key_pressed(KeyValues.RIGHT) + new_y, new_x = self.game.player.y, self.game.player.x + self.assertEqual(new_y, y) + self.assertEqual(new_x, x + 1) + + y, x = self.game.player.y, self.game.player.x + self.game.handle_key_pressed(KeyValues.UP) + new_y, new_x = self.game.player.y, self.game.player.x + self.assertEqual(new_y, y - 1) + self.assertEqual(new_x, x) + + y, x = self.game.player.y, self.game.player.x + self.game.handle_key_pressed(KeyValues.LEFT) + new_y, new_x = self.game.player.y, self.game.player.x + self.assertEqual(new_y, y) + self.assertEqual(new_x, x - 1)