From f67eae3803f0936f58996e1da94a4b1206eae2f0 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Mon, 16 Nov 2020 01:01:18 +0100 Subject: [PATCH 01/15] Added functionnal save system and broken load system --- dungeonbattle/__init__.pyc | Bin 0 -> 176 bytes dungeonbattle/bootstrap.pyc | Bin 0 -> 1075 bytes dungeonbattle/entities/monsters.py | 11 ++++ dungeonbattle/game.py | 58 ++++++++++++++++++--- dungeonbattle/interfaces.py | 79 ++++++++++++++++++++++++++++- dungeonbattle/menus.py | 19 +------ resources/example_map_3.txt | 45 ++++++++++++++++ save.json | 1 + test.py | 17 +++++++ 9 files changed, 204 insertions(+), 26 deletions(-) create mode 100644 dungeonbattle/__init__.pyc create mode 100644 dungeonbattle/bootstrap.pyc create mode 100644 resources/example_map_3.txt create mode 100644 save.json create mode 100644 test.py diff --git a/dungeonbattle/__init__.pyc b/dungeonbattle/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c5124407892592c0feed40fde1506c5cf0dcda29 GIT binary patch literal 176 zcmZSn%**w~e`$O&0~9az*Q}arS^?eQX3ySiyQcL24U7;-fl+wKP)cic%q{NbvoKzSW%8QTB f%*!l^kJl@xEa3o}Zj+mzQks)$2XaFR5HkP(Lkld_ literal 0 HcmV?d00001 diff --git a/dungeonbattle/bootstrap.pyc b/dungeonbattle/bootstrap.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80c152bc030ab5343a9311a65911f7db09afefab GIT binary patch literal 1075 zcmcgqO^?$s5FI;d%9iq>l@Jnlq+Dpk9U%mZmdmb0inNf6l})TBRbp51RA{C4w1_Kb z{se!H9{@9U%7^y8X+4=Y@yB~_9R0jE`~LNZs$hH~dcUIKPASy%7tjIp0tx|wv4l=S zA3+fX96=XDKY?OGIEH!(^#r~ETmyV65~vecrgvzs_7^>=pbTRQr)94Lx=-k&b(^ld zIx9`N(nF}fnYdD{QU72lVCtk|SOK!`7ty z#yJOaO7+1%Z!jN%taV(PQVKq}g0if!z0P!7HH{ri)o7EQ*lODo6PKMm&o+ZyYgEg( zW0uuhv(nZ)yDSkqeSjmnJoII;x$-iJo0rLw9rFV@7e`_yj>TN0F=0PVDMK`s!WmVR z>TSIx{mLtHSYlg|h{o1AZ}C^bZOF*kTHhaTU?R`M@$4ubQyBdS1}uC_sS5b?#<#Hn ni$f2`E`=NL None: + super().__init__() + def act(self, m: Map) -> None: """ By default, a monster will move randomly where it is possible @@ -35,24 +38,32 @@ class Monster(FightingEntity): class Beaver(Monster): + def __init__(self) -> None: + super().__init__() name = "beaver" maxhealth = 30 strength = 2 class Hedgehog(Monster): + def __init__(self) -> None: + super().__init__() name = "hedgehog" maxhealth = 10 strength = 3 class Rabbit(Monster): + def __init__(self) -> None: + super().__init__() name = "rabbit" maxhealth = 15 strength = 1 class TeddyBear(Monster): + def __init__(self) -> None: + super().__init__() name = "teddy_bear" maxhealth = 50 strength = 0 diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index 39e7b48..e050e50 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -1,5 +1,7 @@ from random import randint -from typing import Any, Optional +from typing import Any, Optional,Generator +import json +import os from .entities.player import Player from .enums import GameMode, KeyValues, DisplayActions @@ -37,11 +39,6 @@ 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. @@ -65,7 +62,7 @@ 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) @@ -88,3 +85,50 @@ 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 game_to_str(self) -> str: + d = dict() + d["Map"] = game.map + d["Player"] = game.player + + def save_state(self) -> dict(): + return self.map.save_state() + + def load_state(self, d: dict) -> None: + self.map.load_state(d) + + 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: + """ + Save the game to a file + """ + with open("save.json", "w") as f: + f.write(json.dumps(self.save_state())) diff --git a/dungeonbattle/interfaces.py b/dungeonbattle/interfaces.py index b057400..7a8ae8b 100644 --- a/dungeonbattle/interfaces.py +++ b/dungeonbattle/interfaces.py @@ -78,6 +78,17 @@ class Map: return Map(width, height, tiles, start_y, start_x) + @staticmethod + def load_dungeon_from_string(content: str) -> "Map": + """ + Transforms a string into the list of corresponding tiles + """ + lines = content.split("\n") + lines = [line for line in lines[1:] if line] + 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,14 +119,39 @@ class Map: for entity in self.entities: entity.act(self) + def save_state(self) -> dict: + 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 + for enti in self.entities: + d.update(enti.save_state()) + d["map"] = self.draw_string(TexturePack.ASCII_PACK) + return d + + def load_state(self, d: dict) -> None: + 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.map = self.load_dungeon_from_string(d["map"]) + #add entities class Tile(Enum): 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 @@ -206,6 +242,22 @@ class Entity: Rabbit, TeddyBear return [Beaver, Bomb, Heart, Hedgehog, Rabbit, TeddyBear] + def save_state(self) -> dict: + """ + Saves the coordinates of the entity + """ + d = dict() + d["x"] = self.x + d["y"] = self.y + return d + + def recover_state(self, d : dict) -> None: + """ + Loads the coordinates of the entity from a dictionnary + """ + self.x = d["x"] + self.y = d["y"] + class FightingEntity(Entity): maxhealth: int @@ -222,6 +274,15 @@ class FightingEntity(Entity): super().__init__() self.health = self.maxhealth self.dead = False + self.health = 0 + self.strength = 0 + self.dead = False + self.intelligence = 0 + self.charisma = 0 + self.dexterity = 0 + self.constitution = 0 + self.level = 1 + def hit(self, opponent: "FightingEntity") -> None: opponent.take_damage(self, self.strength) @@ -234,3 +295,17 @@ class FightingEntity(Entity): def die(self) -> None: self.dead = True self.map.remove_entity(self) + + def keys(self) -> list: + return ["maxhealth", "health", "level", "dead", "strength", "intelligence", "charisma", "dexterity", "constitution"] + + def save_state(self) -> dict: + d = super().save_state() + for name in self.keys(): + d[name] = self.__getattribute__(name) + return d + + def recover_state(self, d : dict) -> None: + super().recover_state(d) + for name in d.keys(): + self.__setattribute__(name, d[name]) diff --git a/dungeonbattle/menus.py b/dungeonbattle/menus.py index 1990b27..e543731 100644 --- a/dungeonbattle/menus.py +++ b/dungeonbattle/menus.py @@ -25,6 +25,8 @@ class Menu: class MainMenuValues(Enum): START = 'Jouer' + SAVE = 'Sauvegarder' + LOAD = 'Charger' SETTINGS = 'Paramètres' EXIT = 'Quitter' @@ -35,23 +37,6 @@ class MainMenuValues(Enum): class MainMenu(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): waiting_for_key: bool = False diff --git a/resources/example_map_3.txt b/resources/example_map_3.txt new file mode 100644 index 0000000..5a3ae82 --- /dev/null +++ b/resources/example_map_3.txt @@ -0,0 +1,45 @@ +1 1 +############################################################################################################## +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +#............................................................................................................# +############################################################################################################## diff --git a/save.json b/save.json new file mode 100644 index 0000000..0cdcad5 --- /dev/null +++ b/save.json @@ -0,0 +1 @@ +{"width": 80, "height": 40, "start_y": 1, "start_x": 17, "currentx": 29, "currenty": 25, "x": 74, "y": 22, "maxhealth": 50, "health": 0, "level": 1, "dead": false, "strength": 0, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0, "map": " ########### ######### \n #.........# #.......# \n #.........# ############.......# \n #.........###############..........#.......############## \n #.........#........................#....................# \n #.........#.............#..........#.......#............# \n ########.########.............#..................#............# \n #.........# #.............####.#######.......#............# \n #.........# #.............##.........###################### \n #.........# #####.##########.........# ########### \n #.........# #......# #.........# #.........# \n ########.##########......# #.........# #.........# \n #...........##......# #.........# #.........# \n #...........##......# #.........# #.........# \n #...........##......# #.........# ################.###### \n #...........##......# #.........# #.................############\n #...........##......# ########.########.......#.........#..........#\n #...........##......# #...............#.......#.........#..........#\n #...........######### #...............#.......#.........#..........#\n #...........# #...............#.......#....................#\n #####.####### #.......................#.........#..........#\n #.........# #...............###################..........#\n #.........############ #...............# #..........#\n #.........#..........# #...............# ############\n #....................#####.###########.############# \n ########.#########...................# #.............# \n #........# #..........#........# #.............######### \n #........# ######.##########........# #.............#.......# \n #........# #..........# #........# #.....................# \n #........# #..........# #........# #.............#.......# \n #........# #..........# #........# #.............#.......# \n #........# #..........# #........# #.............#.......# \n #........# #..........#########.##### #.............#.......# \n #........# #..........#.........# ##########.############.####### \n #........# #..........#.........# #..............# #..........# \n ########## #..........#.........# #..............# #..........# \n ############.........# #..............# #..........# \n #.........# #..............# #..........# \n ########### #..............# #..........# \n ################ ############ "} \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..2129a8d --- /dev/null +++ b/test.py @@ -0,0 +1,17 @@ +import json + +class hi: + PLOP = "hello" + PPPP = "ghjk" + + def __init__(self): + self.bl = "zrfcv" + + def prin(self) : + return json.dumps(self.__dict__) + +def f() : + d = hi() + print(d.prin()) + +f() From 4c4a140a4550b57cf5875f0f0ef193587c32009d Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Wed, 18 Nov 2020 12:19:27 +0100 Subject: [PATCH 02/15] Added documentation on a lot of classes and functions (and removed some files I commited by mistake) --- dungeonbattle/bootstrap.py | 5 +++ dungeonbattle/enums.py | 12 +++++++ dungeonbattle/game.py | 22 +++++++----- dungeonbattle/interfaces.py | 71 +++++++++++++++++++++++++++++++++++-- dungeonbattle/menus.py | 26 +++++++++++++- 5 files changed, 124 insertions(+), 12 deletions(-) diff --git a/dungeonbattle/bootstrap.py b/dungeonbattle/bootstrap.py index 0bc97be..7c65e0e 100644 --- a/dungeonbattle/bootstrap.py +++ b/dungeonbattle/bootstrap.py @@ -4,6 +4,11 @@ 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 circulary imports between the Game and + Display classes) + """ @staticmethod def run_game(): diff --git a/dungeonbattle/enums.py b/dungeonbattle/enums.py index 2a6b993..ed4e211 100644 --- a/dungeonbattle/enums.py +++ b/dungeonbattle/enums.py @@ -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() diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index e050e50..bbf4a8d 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -12,6 +12,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] @@ -42,8 +45,8 @@ class Game: 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() @@ -69,7 +72,7 @@ class Game: 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(): @@ -107,15 +110,16 @@ class Game: elif option == menus.MainMenuValues.EXIT: sys.exit(0) - def game_to_str(self) -> str: - d = dict() - d["Map"] = game.map - d["Player"] = game.player - def save_state(self) -> dict(): + """ + Saves the game to a dictionnary + """ return self.map.save_state() def load_state(self, d: dict) -> None: + """ + Loads the game from a dictionnary + """ self.map.load_state(d) def load_game(self) -> None: @@ -128,7 +132,7 @@ class Game: def save_game(self) -> None: """ - Save the game to a file + Saves the game to a file """ with open("save.json", "w") as f: f.write(json.dumps(self.save_state())) diff --git a/dungeonbattle/interfaces.py b/dungeonbattle/interfaces.py index 7a8ae8b..d234578 100644 --- a/dungeonbattle/interfaces.py +++ b/dungeonbattle/interfaces.py @@ -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 @@ -120,6 +120,9 @@ class Map: entity.act(self) def save_state(self) -> dict: + """ + Saves the map's attributes to a dictionnary + """ d = dict() d["width"] = self.width d["height"] = self.height @@ -133,6 +136,9 @@ class Map: return d def load_state(self, d: dict) -> None: + """ + Loads the map's attributes from a dictionnary + """ self.width = d["width"] self.height = d["height"] self.start_y = d["start_y"] @@ -143,6 +149,9 @@ class Map: #add entities class Tile(Enum): + """ + The internal representation of the tiles of the map + """ EMPTY = auto() WALL = auto() FLOOR = auto() @@ -158,9 +167,15 @@ class Tile(Enum): 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: @@ -171,10 +186,13 @@ class Tile(Enum): class Entity: + """ + An Entity object represents any entity present on the map + """ y: int x: int name: str - map: Map + map: Map def __init__(self): self.y = 0 @@ -182,29 +200,47 @@ class Entity: 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) @@ -229,14 +265,23 @@ 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 @@ -260,6 +305,10 @@ class Entity: 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 @@ -285,27 +334,45 @@ class FightingEntity(Entity): 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: + """ + If a fighting entity has no more health, it dies and is removed + """ self.dead = True self.map.remove_entity(self) def keys(self) -> list: + """ + Returns a fighting entities specific attributes + """ return ["maxhealth", "health", "level", "dead", "strength", "intelligence", "charisma", "dexterity", "constitution"] def save_state(self) -> dict: + """ + Saves the state of the entity into a dictionnary + """ d = super().save_state() for name in self.keys(): d[name] = self.__getattribute__(name) return d def recover_state(self, d : dict) -> None: + """ + Loads the state of an entity from a dictionnary + """ super().recover_state(d) for name in d.keys(): self.__setattribute__(name, d[name]) diff --git a/dungeonbattle/menus.py b/dungeonbattle/menus.py index e543731..59b364f 100644 --- a/dungeonbattle/menus.py +++ b/dungeonbattle/menus.py @@ -8,22 +8,37 @@ 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' @@ -35,13 +50,22 @@ class MainMenuValues(Enum): class MainMenu(Menu): + """ + A special instance of a menu : the main menu + """ values = [e for e in MainMenuValues] 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) @@ -61,7 +85,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. From 6f9317fbc2bdccd089aaef96a3d71b071cd227f2 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Wed, 18 Nov 2020 12:27:59 +0100 Subject: [PATCH 03/15] Added documentation for some classes again --- dungeonbattle/entities/items.py | 18 ++++++++++++ dungeonbattle/entities/monsters.py | 15 ++++++++++ dungeonbattle/entities/player.py | 3 ++ dungeonbattle/term_manager.py | 4 +++ resources/example_map_3.txt | 45 ------------------------------ test.py | 17 ----------- 6 files changed, 40 insertions(+), 62 deletions(-) delete mode 100644 resources/example_map_3.txt delete mode 100644 test.py diff --git a/dungeonbattle/entities/items.py b/dungeonbattle/entities/items.py index 4cfd26b..f09e657 100644 --- a/dungeonbattle/entities/items.py +++ b/dungeonbattle/entities/items.py @@ -5,6 +5,9 @@ from ..interfaces import Entity, FightingEntity, Map class Item(Entity): + """ + A class for items + """ held: bool held_by: Optional["Player"] @@ -13,6 +16,9 @@ class Item(Entity): self.held = False 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,6 +27,9 @@ 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) @@ -28,6 +37,9 @@ class Item(Entity): class Heart(Item): + """ + A heart item to return health to the player + """ name: str = "heart" healing: int = 5 @@ -40,6 +52,9 @@ class Heart(Item): class Bomb(Item): + """ + A bomb item intended to deal damage to ennemies at long range + """ name: str = "bomb" damage: int = 5 exploding: bool @@ -53,6 +68,9 @@ class Bomb(Item): self.exploding = True def act(self, m: Map) -> None: + """ + Special exploding action of the bomb + """ if self.exploding: for e in m.entities: if abs(e.x - self.x) + abs(e.y - self.y) <= 1 and \ diff --git a/dungeonbattle/entities/monsters.py b/dungeonbattle/entities/monsters.py index 0f31f8d..f006ed3 100644 --- a/dungeonbattle/entities/monsters.py +++ b/dungeonbattle/entities/monsters.py @@ -5,6 +5,9 @@ from ..interfaces import FightingEntity, Map class Monster(FightingEntity): + """ + The class for all monsters in the dungeon + """ def __init__(self) -> None: super().__init__() @@ -38,6 +41,9 @@ class Monster(FightingEntity): class Beaver(Monster): + """ + A beaver monster + """ def __init__(self) -> None: super().__init__() name = "beaver" @@ -46,6 +52,9 @@ class Beaver(Monster): class Hedgehog(Monster): + """ + A really mean hedgehog monster + """ def __init__(self) -> None: super().__init__() name = "hedgehog" @@ -54,6 +63,9 @@ class Hedgehog(Monster): class Rabbit(Monster): + """ + A rabbit monster + """ def __init__(self) -> None: super().__init__() name = "rabbit" @@ -62,6 +74,9 @@ class Rabbit(Monster): class TeddyBear(Monster): + """ + A cute teddybear monster + """ def __init__(self) -> None: super().__init__() name = "teddy_bear" diff --git a/dungeonbattle/entities/player.py b/dungeonbattle/entities/player.py index c1bde5e..0c2883b 100644 --- a/dungeonbattle/entities/player.py +++ b/dungeonbattle/entities/player.py @@ -5,6 +5,9 @@ from ..interfaces import FightingEntity class Player(FightingEntity): + """ + The class of the player + """ name = "player" maxhealth: int = 20 strength: int = 5 diff --git a/dungeonbattle/term_manager.py b/dungeonbattle/term_manager.py index a425272..b1f10b1 100644 --- a/dungeonbattle/term_manager.py +++ b/dungeonbattle/term_manager.py @@ -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 diff --git a/resources/example_map_3.txt b/resources/example_map_3.txt deleted file mode 100644 index 5a3ae82..0000000 --- a/resources/example_map_3.txt +++ /dev/null @@ -1,45 +0,0 @@ -1 1 -############################################################################################################## -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -#............................................................................................................# -############################################################################################################## diff --git a/test.py b/test.py deleted file mode 100644 index 2129a8d..0000000 --- a/test.py +++ /dev/null @@ -1,17 +0,0 @@ -import json - -class hi: - PLOP = "hello" - PPPP = "ghjk" - - def __init__(self): - self.bl = "zrfcv" - - def prin(self) : - return json.dumps(self.__dict__) - -def f() : - d = hi() - print(d.prin()) - -f() From 61969c46e62ab17f423da0670638d1e5d9243c64 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 18 Nov 2020 14:29:54 +0100 Subject: [PATCH 04/15] Dead is an entity property --- dungeonbattle/interfaces.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/dungeonbattle/interfaces.py b/dungeonbattle/interfaces.py index d234578..00751e4 100644 --- a/dungeonbattle/interfaces.py +++ b/dungeonbattle/interfaces.py @@ -312,7 +312,6 @@ class FightingEntity(Entity): maxhealth: int health: int strength: int - dead: bool intelligence: int charisma: int dexterity: int @@ -322,16 +321,17 @@ class FightingEntity(Entity): def __init__(self): super().__init__() self.health = self.maxhealth - self.dead = False self.health = 0 self.strength = 0 - self.dead = False self.intelligence = 0 self.charisma = 0 self.dexterity = 0 self.constitution = 0 self.level = 1 - + + @property + def dead(self) -> bool: + return self.health <= 0 def hit(self, opponent: "FightingEntity") -> None: """ @@ -351,7 +351,6 @@ class FightingEntity(Entity): """ If a fighting entity has no more health, it dies and is removed """ - self.dead = True self.map.remove_entity(self) def keys(self) -> list: @@ -371,8 +370,8 @@ class FightingEntity(Entity): def recover_state(self, d : dict) -> None: """ - Loads the state of an entity from a dictionnary + Loads the state of an entity from a dictionary """ super().recover_state(d) for name in d.keys(): - self.__setattribute__(name, d[name]) + setattr(self, name, d[name]) From a6cd075b8c743d768ad05027d5030da1840787fa Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 18 Nov 2020 14:54:21 +0100 Subject: [PATCH 05/15] Instantiate entity attributes in __init__ rather than in the class definition --- dungeonbattle/entities/items.py | 26 ++++++++------ dungeonbattle/entities/monsters.py | 56 +++++++++++++++++------------- dungeonbattle/entities/player.py | 21 +++++------ dungeonbattle/interfaces.py | 39 ++++++++++++--------- 4 files changed, 82 insertions(+), 60 deletions(-) diff --git a/dungeonbattle/entities/items.py b/dungeonbattle/entities/items.py index f09e657..dbfd455 100644 --- a/dungeonbattle/entities/items.py +++ b/dungeonbattle/entities/items.py @@ -9,11 +9,13 @@ 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: """ @@ -40,8 +42,11 @@ class Heart(Item): """ A heart item to return health to the player """ - name: str = "heart" - healing: int = 5 + 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: """ @@ -53,15 +58,16 @@ class Heart(Item): class Bomb(Item): """ - A bomb item intended to deal damage to ennemies at long range + A bomb item intended to deal damage to enemies at long range """ - name: str = "bomb" 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) diff --git a/dungeonbattle/entities/monsters.py b/dungeonbattle/entities/monsters.py index f006ed3..1f04372 100644 --- a/dungeonbattle/entities/monsters.py +++ b/dungeonbattle/entities/monsters.py @@ -6,11 +6,23 @@ from ..interfaces import FightingEntity, Map class Monster(FightingEntity): """ - The class for all monsters in the dungeon + 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) -> None: - super().__init__() - + 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 @@ -44,41 +56,37 @@ class Beaver(Monster): """ A beaver monster """ - def __init__(self) -> None: - super().__init__() - name = "beaver" - maxhealth = 30 - strength = 2 + 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): """ A really mean hedgehog monster """ - def __init__(self) -> None: - super().__init__() - name = "hedgehog" - maxhealth = 10 - strength = 3 + 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): """ A rabbit monster """ - def __init__(self) -> None: - super().__init__() - name = "rabbit" - maxhealth = 15 - strength = 1 + 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): """ A cute teddybear monster """ - def __init__(self) -> None: - super().__init__() - name = "teddy_bear" - maxhealth = 50 - strength = 0 + def __init__(self, strength: int = 0, maxhealth: int = 50, + *args, **kwargs) -> None: + super().__init__(name="teddy_bear", strength=strength, + maxhealth=maxhealth, *args, **kwargs) diff --git a/dungeonbattle/entities/player.py b/dungeonbattle/entities/player.py index 0c2883b..6abe397 100644 --- a/dungeonbattle/entities/player.py +++ b/dungeonbattle/entities/player.py @@ -8,22 +8,23 @@ class Player(FightingEntity): """ The class of the player """ - name = "player" - 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 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: """ diff --git a/dungeonbattle/interfaces.py b/dungeonbattle/interfaces.py index 00751e4..ae0185d 100644 --- a/dungeonbattle/interfaces.py +++ b/dungeonbattle/interfaces.py @@ -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 @@ -141,7 +141,7 @@ class Map: """ self.width = d["width"] self.height = d["height"] - self.start_y = d["start_y"] + self.start_y = d["start_y"] self.start_x = d["start_x"] self.currentx = d["currentx"] self.currenty = d["currenty"] @@ -192,11 +192,15 @@ class Entity: y: int x: int name: str - map: Map + 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): + 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: @@ -318,16 +322,19 @@ class FightingEntity(Entity): constitution: int level: int - def __init__(self): - super().__init__() - self.health = self.maxhealth - self.health = 0 - self.strength = 0 - self.intelligence = 0 - self.charisma = 0 - self.dexterity = 0 - self.constitution = 0 - self.level = 1 + 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: From 20aeb5fd4ac5a97c54e424c1ec6a8c8e494a984e Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 18 Nov 2020 14:56:59 +0100 Subject: [PATCH 06/15] Linting --- dungeonbattle/bootstrap.py | 5 +++-- dungeonbattle/enums.py | 4 ++-- dungeonbattle/game.py | 11 ++++++----- dungeonbattle/interfaces.py | 15 +++++++++------ dungeonbattle/menus.py | 1 - 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/dungeonbattle/bootstrap.py b/dungeonbattle/bootstrap.py index 7c65e0e..9828fae 100644 --- a/dungeonbattle/bootstrap.py +++ b/dungeonbattle/bootstrap.py @@ -5,8 +5,9 @@ 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 circulary imports between the Game and + 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) """ diff --git a/dungeonbattle/enums.py b/dungeonbattle/enums.py index ed4e211..e5f73d9 100644 --- a/dungeonbattle/enums.py +++ b/dungeonbattle/enums.py @@ -3,13 +3,13 @@ from typing import Optional from dungeonbattle.settings import Settings -#This file contains a few useful enumeration classes used elsewhere in the code +# 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) + It just calls the same action on the display object displayaction refers to. """ REFRESH = auto() UPDATE = auto() diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index bbf4a8d..193a5aa 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -1,7 +1,8 @@ from random import randint -from typing import Any, Optional,Generator +from typing import Any, Optional import json import os +import sys from .entities.player import Player from .enums import GameMode, KeyValues, DisplayActions @@ -110,18 +111,18 @@ class Game: elif option == menus.MainMenuValues.EXIT: sys.exit(0) - def save_state(self) -> dict(): + def save_state(self) -> dict: """ - Saves the game to a dictionnary + Saves the game to a dictionary """ return self.map.save_state() def load_state(self, d: dict) -> None: """ - Loads the game from a dictionnary + Loads the game from a dictionary """ self.map.load_state(d) - + def load_game(self) -> None: """ Loads the game from a file diff --git a/dungeonbattle/interfaces.py b/dungeonbattle/interfaces.py index ae0185d..a1d4475 100644 --- a/dungeonbattle/interfaces.py +++ b/dungeonbattle/interfaces.py @@ -146,7 +146,8 @@ class Map: self.currentx = d["currentx"] self.currenty = d["currenty"] self.map = self.load_dungeon_from_string(d["map"]) - #add entities + # add entities + class Tile(Enum): """ @@ -168,7 +169,8 @@ class Tile(Enum): def char(self, pack: TexturePack) -> str: """ - Translates a Tile to the corresponding character according to the texture pack + Translates a Tile to the corresponding character according + to the texture pack """ return getattr(pack, self.name) @@ -300,7 +302,7 @@ class Entity: d["y"] = self.y return d - def recover_state(self, d : dict) -> None: + def recover_state(self, d: dict) -> None: """ Loads the coordinates of the entity from a dictionnary """ @@ -364,18 +366,19 @@ class FightingEntity(Entity): """ Returns a fighting entities specific attributes """ - return ["maxhealth", "health", "level", "dead", "strength", "intelligence", "charisma", "dexterity", "constitution"] + return ["maxhealth", "health", "level", "dead", "strength", + "intelligence", "charisma", "dexterity", "constitution"] def save_state(self) -> dict: """ - Saves the state of the entity into a dictionnary + Saves the state of the entity into a dictionary """ d = super().save_state() for name in self.keys(): d[name] = self.__getattribute__(name) return d - def recover_state(self, d : dict) -> None: + def recover_state(self, d: dict) -> None: """ Loads the state of an entity from a dictionary """ diff --git a/dungeonbattle/menus.py b/dungeonbattle/menus.py index 59b364f..27680ac 100644 --- a/dungeonbattle/menus.py +++ b/dungeonbattle/menus.py @@ -1,4 +1,3 @@ -import sys from enum import Enum from typing import Any, Optional From 5a65957574e36d8d56e1538cf5a8aaad5b9ec2c9 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 18 Nov 2020 15:02:30 +0100 Subject: [PATCH 07/15] Replace tiles when loading map from a save file --- dungeonbattle/interfaces.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dungeonbattle/interfaces.py b/dungeonbattle/interfaces.py index a1d4475..2e7add9 100644 --- a/dungeonbattle/interfaces.py +++ b/dungeonbattle/interfaces.py @@ -79,7 +79,7 @@ class Map: return Map(width, height, tiles, start_y, start_x) @staticmethod - def load_dungeon_from_string(content: str) -> "Map": + def load_dungeon_from_string(content: str) -> List[List["Tile"]]: """ Transforms a string into the list of corresponding tiles """ @@ -121,7 +121,7 @@ class Map: def save_state(self) -> dict: """ - Saves the map's attributes to a dictionnary + Saves the map's attributes to a dictionary """ d = dict() d["width"] = self.width @@ -137,7 +137,7 @@ class Map: def load_state(self, d: dict) -> None: """ - Loads the map's attributes from a dictionnary + Loads the map's attributes from a dictionary """ self.width = d["width"] self.height = d["height"] @@ -145,7 +145,7 @@ class Map: self.start_x = d["start_x"] self.currentx = d["currentx"] self.currenty = d["currenty"] - self.map = self.load_dungeon_from_string(d["map"]) + self.tiles = self.load_dungeon_from_string(d["map"]) # add entities From 7ae4e47fc307ade82c11cccbdd4c5603170f0ea6 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 18 Nov 2020 15:04:15 +0100 Subject: [PATCH 08/15] Update MapDisplay after loading a map --- dungeonbattle/game.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index 193a5aa..c3e71ef 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -122,6 +122,7 @@ class Game: Loads the game from a dictionary """ self.map.load_state(d) + self.display_actions(DisplayActions.UPDATE) def load_game(self) -> None: """ From 657345e6f7aefe1a80635a322c7dd90d1f6fa012 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Wed, 18 Nov 2020 22:42:46 +0100 Subject: [PATCH 09/15] Fix for loading game in progress, there remains to change all entities __init__ to allow being initialized by a dictionnary (work in progress, breaks the game) --- dungeonbattle/entities/items.py | 21 ++++++ dungeonbattle/entities/monsters.py | 14 ++++ dungeonbattle/entities/player.py | 48 +++++++++++--- dungeonbattle/interfaces.py | 102 ++++++++++++++++++----------- save.json | 2 +- 5 files changed, 139 insertions(+), 48 deletions(-) diff --git a/dungeonbattle/entities/items.py b/dungeonbattle/entities/items.py index dbfd455..442e00b 100644 --- a/dungeonbattle/entities/items.py +++ b/dungeonbattle/entities/items.py @@ -37,6 +37,13 @@ class Item(Entity): self.map.remove_entity(self) player.inventory.append(self) + def save_state(self) -> None: + """ + Saves the state of the entity into a dictionary + """ + d = super().save_state() + d["held"] = self.held + class Heart(Item): """ @@ -55,6 +62,13 @@ class Heart(Item): player.health = min(player.maxhealth, player.health + self.healing) self.map.remove_entity(self) + def save_state(self) -> None: + """ + Saves the state of the entity into a dictionary + """ + d = super().save_state() + d["type"] = "Heart" + class Bomb(Item): """ @@ -82,3 +96,10 @@ class Bomb(Item): 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) -> None: + """ + Saves the state of the entity into a dictionary + """ + d = super().save_state() + d["type"] = "Bomb" diff --git a/dungeonbattle/entities/monsters.py b/dungeonbattle/entities/monsters.py index 1f04372..76fdb7a 100644 --- a/dungeonbattle/entities/monsters.py +++ b/dungeonbattle/entities/monsters.py @@ -81,6 +81,13 @@ class Rabbit(Monster): super().__init__(name="rabbit", strength=strength, maxhealth=maxhealth, *args, **kwargs) + def save_state(self) -> None: + """ + Saves the state of the entity into a dictionary + """ + d = super().save_state() + d["type"] = "Rabbit" + class TeddyBear(Monster): """ @@ -90,3 +97,10 @@ class TeddyBear(Monster): *args, **kwargs) -> None: super().__init__(name="teddy_bear", strength=strength, maxhealth=maxhealth, *args, **kwargs) + + def save_state(self) -> None: + """ + Saves the state of the entity into a dictionary + """ + d = super().save_state() + d["type"] = "Teddy" diff --git a/dungeonbattle/entities/player.py b/dungeonbattle/entities/player.py index 6abe397..0b84135 100644 --- a/dungeonbattle/entities/player.py +++ b/dungeonbattle/entities/player.py @@ -13,16 +13,34 @@ class Player(FightingEntity): inventory: list paths: Dict[Tuple[int, int], Tuple[int, int]] - 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 +## 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 __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + validkeys = {"current_xp" : 0,"max_xp" : 0} + + for dictionary in args : + for key in validkeys : + if key in dictionary : + self.__setattr__(key, dictionary[key]) + else : + self.__setattr__(key, validkeys[key]) + for key in validkeys: + if key in kwargs : + self.__setattr__(key, kwargs[key]) + else : + self.__setattr__(key, validkeys[key]) self.inventory = list() self.paths = dict() @@ -104,3 +122,13 @@ 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["type"] = "Player" + d["current_xp"] = self.current_xp + d["max_xp"] = self.max_xp + return d diff --git a/dungeonbattle/interfaces.py b/dungeonbattle/interfaces.py index 2e7add9..ae87700 100644 --- a/dungeonbattle/interfaces.py +++ b/dungeonbattle/interfaces.py @@ -130,8 +130,9 @@ class Map: d["start_x"] = self.start_x d["currentx"] = self.currentx d["currenty"] = self.currenty + d["entities"] = [] for enti in self.entities: - d.update(enti.save_state()) + d.append(enti.save_state()) d["map"] = self.draw_string(TexturePack.ASCII_PACK) return d @@ -146,7 +147,10 @@ class Map: self.currentx = d["currentx"] self.currenty = d["currenty"] self.tiles = self.load_dungeon_from_string(d["map"]) - # add entities + self.entities = [] + dictclasses = get_all_entity_classes_in_a_dict() + for entisave in d["entities"] : + self.add_entity(dictclasses[entisave["type"]](entisave)) class Tile(Enum): @@ -196,13 +200,27 @@ class Entity: name: str map: Map - # noinspection PyShadowingBuiltins - def __init__(self, y: int = 0, x: int = 0, name: Optional[str] = None, - map: Optional[Map] = None): - self.y = y - self.x = x - self.name = name - self.map = map +## # noinspection PyShadowingBuiltins +## def __init__(self, y: int = 0, x: int = 0, name: Optional[str] = None, +## map: Optional[Map] = None): +## self.y = y +## self.x = x +## self.name = name +## self.map = map + + def __init__(self, dictionary, **kwargs) -> None: + validkeys = self.attributes() + for key in validkeys : + self.__setattr__(key, dictionary[key]) + for key in validkeys: + self.__setattr__(key, kwargs[key]) + + @staticmethod + def attributes(self) -> list: + """ + Returns the list of attributes + """ + return ["x", "y", "name"] def check_move(self, y: int, x: int, move_if_possible: bool = False)\ -> bool: @@ -293,6 +311,14 @@ class Entity: 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 dictionnary + """ + from dungeonbattle.entities.player import Player + return {"Beaver" : Beaver, "Bomb" : Bomb, "Heart" : Heart, "Hedgehog" : Hedgehog, "Rabbit" : Rabbit, "Teddy" : TeddyBear, "Player" : Player} + def save_state(self) -> dict: """ Saves the coordinates of the entity @@ -302,13 +328,6 @@ class Entity: d["y"] = self.y return d - def recover_state(self, d: dict) -> None: - """ - Loads the coordinates of the entity from a dictionnary - """ - self.x = d["x"] - self.y = d["y"] - class FightingEntity(Entity): """ @@ -324,19 +343,36 @@ class FightingEntity(Entity): constitution: int level: int - 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 +## 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 + + def __init__(self, *args, **kwargs) -> None: + validkeys = {"maxhealth" : 0,"health" : 0,"strength" : 0 \ + ,"intelligence" : 0,"charisma" : 0,"dexterity" : 0\ + ,"constitution" : 0,"level" : 0} + #All the keys we wan to set in this init, with their default value + for dictionary in args : + for key in validkeys : + if key in dictionary : + self.__setattr__(key, dictionary[key]) + else : + self.__setattr__(key, validkeys[key]) + for key in validkeys: + if key in kwargs : + self.__setattr__(key, kwargs[key]) + else : + self.__setattr__(key, validkeys[key]) @property def dead(self) -> bool: @@ -377,11 +413,3 @@ class FightingEntity(Entity): for name in self.keys(): d[name] = self.__getattribute__(name) return d - - def recover_state(self, d: dict) -> None: - """ - Loads the state of an entity from a dictionary - """ - super().recover_state(d) - for name in d.keys(): - setattr(self, name, d[name]) diff --git a/save.json b/save.json index 0cdcad5..067eac1 100644 --- a/save.json +++ b/save.json @@ -1 +1 @@ -{"width": 80, "height": 40, "start_y": 1, "start_x": 17, "currentx": 29, "currenty": 25, "x": 74, "y": 22, "maxhealth": 50, "health": 0, "level": 1, "dead": false, "strength": 0, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0, "map": " ########### ######### \n #.........# #.......# \n #.........# ############.......# \n #.........###############..........#.......############## \n #.........#........................#....................# \n #.........#.............#..........#.......#............# \n ########.########.............#..................#............# \n #.........# #.............####.#######.......#............# \n #.........# #.............##.........###################### \n #.........# #####.##########.........# ########### \n #.........# #......# #.........# #.........# \n ########.##########......# #.........# #.........# \n #...........##......# #.........# #.........# \n #...........##......# #.........# #.........# \n #...........##......# #.........# ################.###### \n #...........##......# #.........# #.................############\n #...........##......# ########.########.......#.........#..........#\n #...........##......# #...............#.......#.........#..........#\n #...........######### #...............#.......#.........#..........#\n #...........# #...............#.......#....................#\n #####.####### #.......................#.........#..........#\n #.........# #...............###################..........#\n #.........############ #...............# #..........#\n #.........#..........# #...............# ############\n #....................#####.###########.############# \n ########.#########...................# #.............# \n #........# #..........#........# #.............######### \n #........# ######.##########........# #.............#.......# \n #........# #..........# #........# #.....................# \n #........# #..........# #........# #.............#.......# \n #........# #..........# #........# #.............#.......# \n #........# #..........# #........# #.............#.......# \n #........# #..........#########.##### #.............#.......# \n #........# #..........#.........# ##########.############.####### \n #........# #..........#.........# #..............# #..........# \n ########## #..........#.........# #..............# #..........# \n ############.........# #..............# #..........# \n #.........# #..............# #..........# \n ########### #..............# #..........# \n ################ ############ "} \ No newline at end of file +{"width": 80, "height": 40, "start_y": 1, "start_x": 17, "currentx": 54, "currenty": 38, "x": 56, "y": 19, "maxhealth": 15, "health": 15, "level": 0, "dead": false, "strength": 1, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0, "map": " ########### ######### \n #.........# #.......# \n #.........# ############.......# \n #.........###############..........#.......############## \n #.........#........................#....................# \n #.........#.............#..........#.......#............# \n ########.########.............#..................#............# \n #.........# #.............####.#######.......#............# \n #.........# #.............##.........###################### \n #.........# #####.##########.........# ########### \n #.........# #......# #.........# #.........# \n ########.##########......# #.........# #.........# \n #...........##......# #.........# #.........# \n #...........##......# #.........# #.........# \n #...........##......# #.........# ################.###### \n #...........##......# #.........# #.................############\n #...........##......# ########.########.......#.........#..........#\n #...........##......# #...............#.......#.........#..........#\n #...........######### #...............#.......#.........#..........#\n #...........# #...............#.......#....................#\n #####.####### #.......................#.........#..........#\n #.........# #...............###################..........#\n #.........############ #...............# #..........#\n #.........#..........# #...............# ############\n #....................#####.###########.############# \n ########.#########...................# #.............# \n #........# #..........#........# #.............######### \n #........# ######.##########........# #.............#.......# \n #........# #..........# #........# #.....................# \n #........# #..........# #........# #.............#.......# \n #........# #..........# #........# #.............#.......# \n #........# #..........# #........# #.............#.......# \n #........# #..........#########.##### #.............#.......# \n #........# #..........#.........# ##########.############.####### \n #........# #..........#.........# #..............# #..........# \n ########## #..........#.........# #..............# #..........# \n ############.........# #..............# #..........# \n #.........# #..............# #..........# \n ########### #..............# #..........# \n ################ ############ "} From 81b20b72bc261cb46cc81cefbc93a9210b3a26fd Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 00:10:37 +0100 Subject: [PATCH 10/15] Save entities --- dungeonbattle/entities/items.py | 17 +---- dungeonbattle/entities/monsters.py | 14 ---- dungeonbattle/entities/player.py | 41 ++++-------- dungeonbattle/interfaces.py | 101 ++++++++++++----------------- save.json | 2 +- 5 files changed, 56 insertions(+), 119 deletions(-) diff --git a/dungeonbattle/entities/items.py b/dungeonbattle/entities/items.py index 442e00b..217514d 100644 --- a/dungeonbattle/entities/items.py +++ b/dungeonbattle/entities/items.py @@ -37,12 +37,13 @@ class Item(Entity): self.map.remove_entity(self) player.inventory.append(self) - def save_state(self) -> None: + 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): @@ -62,13 +63,6 @@ class Heart(Item): player.health = min(player.maxhealth, player.health + self.healing) self.map.remove_entity(self) - def save_state(self) -> None: - """ - Saves the state of the entity into a dictionary - """ - d = super().save_state() - d["type"] = "Heart" - class Bomb(Item): """ @@ -96,10 +90,3 @@ class Bomb(Item): 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) -> None: - """ - Saves the state of the entity into a dictionary - """ - d = super().save_state() - d["type"] = "Bomb" diff --git a/dungeonbattle/entities/monsters.py b/dungeonbattle/entities/monsters.py index 76fdb7a..1f04372 100644 --- a/dungeonbattle/entities/monsters.py +++ b/dungeonbattle/entities/monsters.py @@ -81,13 +81,6 @@ class Rabbit(Monster): super().__init__(name="rabbit", strength=strength, maxhealth=maxhealth, *args, **kwargs) - def save_state(self) -> None: - """ - Saves the state of the entity into a dictionary - """ - d = super().save_state() - d["type"] = "Rabbit" - class TeddyBear(Monster): """ @@ -97,10 +90,3 @@ class TeddyBear(Monster): *args, **kwargs) -> None: super().__init__(name="teddy_bear", strength=strength, maxhealth=maxhealth, *args, **kwargs) - - def save_state(self) -> None: - """ - Saves the state of the entity into a dictionary - """ - d = super().save_state() - d["type"] = "Teddy" diff --git a/dungeonbattle/entities/player.py b/dungeonbattle/entities/player.py index 0b84135..873da32 100644 --- a/dungeonbattle/entities/player.py +++ b/dungeonbattle/entities/player.py @@ -13,34 +13,16 @@ class Player(FightingEntity): inventory: list paths: Dict[Tuple[int, int], Tuple[int, int]] -## 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 __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - validkeys = {"current_xp" : 0,"max_xp" : 0} - - for dictionary in args : - for key in validkeys : - if key in dictionary : - self.__setattr__(key, dictionary[key]) - else : - self.__setattr__(key, validkeys[key]) - for key in validkeys: - if key in kwargs : - self.__setattr__(key, kwargs[key]) - else : - self.__setattr__(key, validkeys[key]) + 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() @@ -122,13 +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["type"] = "Player" d["current_xp"] = self.current_xp d["max_xp"] = self.max_xp return d diff --git a/dungeonbattle/interfaces.py b/dungeonbattle/interfaces.py index ae87700..20cedde 100644 --- a/dungeonbattle/interfaces.py +++ b/dungeonbattle/interfaces.py @@ -132,7 +132,9 @@ class Map: d["currenty"] = self.currenty d["entities"] = [] for enti in self.entities: - d.append(enti.save_state()) + if enti.save_state() is None: + raise Exception(enti) + d["entities"].append(enti.save_state()) d["map"] = self.draw_string(TexturePack.ASCII_PACK) return d @@ -148,9 +150,9 @@ class Map: self.currenty = d["currenty"] self.tiles = self.load_dungeon_from_string(d["map"]) self.entities = [] - dictclasses = get_all_entity_classes_in_a_dict() - for entisave in d["entities"] : - self.add_entity(dictclasses[entisave["type"]](entisave)) + dictclasses = Entity.get_all_entity_classes_in_a_dict() + for entisave in d["entities"]: + self.add_entity(dictclasses[entisave["type"]](**entisave)) class Tile(Enum): @@ -200,27 +202,13 @@ class Entity: name: str map: Map -## # noinspection PyShadowingBuiltins -## def __init__(self, y: int = 0, x: int = 0, name: Optional[str] = None, -## map: Optional[Map] = None): -## self.y = y -## self.x = x -## self.name = name -## self.map = map - - def __init__(self, dictionary, **kwargs) -> None: - validkeys = self.attributes() - for key in validkeys : - self.__setattr__(key, dictionary[key]) - for key in validkeys: - self.__setattr__(key, kwargs[key]) - - @staticmethod - def attributes(self) -> list: - """ - Returns the list of attributes - """ - return ["x", "y", "name"] + # 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: @@ -314,10 +302,21 @@ class Entity: @staticmethod def get_all_entity_classes_in_a_dict() -> dict: """ - Returns all entities subclasses in a dictionnary + Returns all entities subclasses in a dictionary """ from dungeonbattle.entities.player import Player - return {"Beaver" : Beaver, "Bomb" : Bomb, "Heart" : Heart, "Hedgehog" : Hedgehog, "Rabbit" : Rabbit, "Teddy" : TeddyBear, "Player" : 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: """ @@ -326,6 +325,7 @@ class Entity: d = dict() d["x"] = self.x d["y"] = self.y + d["type"] = self.__class__.__name__ return d @@ -343,36 +343,19 @@ class FightingEntity(Entity): constitution: int level: int -## 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 - - def __init__(self, *args, **kwargs) -> None: - validkeys = {"maxhealth" : 0,"health" : 0,"strength" : 0 \ - ,"intelligence" : 0,"charisma" : 0,"dexterity" : 0\ - ,"constitution" : 0,"level" : 0} - #All the keys we wan to set in this init, with their default value - for dictionary in args : - for key in validkeys : - if key in dictionary : - self.__setattr__(key, dictionary[key]) - else : - self.__setattr__(key, validkeys[key]) - for key in validkeys: - if key in kwargs : - self.__setattr__(key, kwargs[key]) - else : - self.__setattr__(key, validkeys[key]) + 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: @@ -402,7 +385,7 @@ class FightingEntity(Entity): """ Returns a fighting entities specific attributes """ - return ["maxhealth", "health", "level", "dead", "strength", + return ["maxhealth", "health", "level", "strength", "intelligence", "charisma", "dexterity", "constitution"] def save_state(self) -> dict: @@ -411,5 +394,5 @@ class FightingEntity(Entity): """ d = super().save_state() for name in self.keys(): - d[name] = self.__getattribute__(name) + d[name] = getattr(self, name) return d diff --git a/save.json b/save.json index 067eac1..e0ac667 100644 --- a/save.json +++ b/save.json @@ -1 +1 @@ -{"width": 80, "height": 40, "start_y": 1, "start_x": 17, "currentx": 54, "currenty": 38, "x": 56, "y": 19, "maxhealth": 15, "health": 15, "level": 0, "dead": false, "strength": 1, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0, "map": " ########### ######### \n #.........# #.......# \n #.........# ############.......# \n #.........###############..........#.......############## \n #.........#........................#....................# \n #.........#.............#..........#.......#............# \n ########.########.............#..................#............# \n #.........# #.............####.#######.......#............# \n #.........# #.............##.........###################### \n #.........# #####.##########.........# ########### \n #.........# #......# #.........# #.........# \n ########.##########......# #.........# #.........# \n #...........##......# #.........# #.........# \n #...........##......# #.........# #.........# \n #...........##......# #.........# ################.###### \n #...........##......# #.........# #.................############\n #...........##......# ########.########.......#.........#..........#\n #...........##......# #...............#.......#.........#..........#\n #...........######### #...............#.......#.........#..........#\n #...........# #...............#.......#....................#\n #####.####### #.......................#.........#..........#\n #.........# #...............###################..........#\n #.........############ #...............# #..........#\n #.........#..........# #...............# ############\n #....................#####.###########.############# \n ########.#########...................# #.............# \n #........# #..........#........# #.............######### \n #........# ######.##########........# #.............#.......# \n #........# #..........# #........# #.....................# \n #........# #..........# #........# #.............#.......# \n #........# #..........# #........# #.............#.......# \n #........# #..........# #........# #.............#.......# \n #........# #..........#########.##### #.............#.......# \n #........# #..........#.........# ##########.############.####### \n #........# #..........#.........# #..............# #..........# \n ########## #..........#.........# #..............# #..........# \n ############.........# #..............# #..........# \n #.........# #..............# #..........# \n ########### #..............# #..........# \n ################ ############ "} +{"width": 80, "height": 40, "start_y": 1, "start_x": 17, "currentx": 14, "currenty": 11, "entities": [{"x": 14, "y": 11, "type": "Player", "maxhealth": 20, "health": 20, "level": 1, "strength": 5, "intelligence": 1, "charisma": 1, "dexterity": 1, "constitution": 1, "current_xp": 0, "max_xp": 10}, {"x": 50, "y": 37, "type": "Rabbit", "maxhealth": 15, "health": 15, "level": 0, "strength": 1, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0}, {"x": 16, "y": 22, "type": "Rabbit", "maxhealth": 15, "health": 15, "level": 0, "strength": 1, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0}, {"x": 12, "y": 7, "type": "Bomb", "held": false}, {"x": 69, "y": 38, "type": "Hedgehog", "maxhealth": 10, "health": 10, "level": 0, "strength": 3, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0}, {"x": 64, "y": 28, "type": "Hedgehog", "maxhealth": 10, "health": 10, "level": 0, "strength": 3, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0}, {"x": 37, "y": 29, "type": "TeddyBear", "maxhealth": 50, "health": 50, "level": 0, "strength": 0, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0}, {"x": 16, "y": 17, "type": "Rabbit", "maxhealth": 15, "health": 15, "level": 0, "strength": 1, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0}, {"x": 39, "y": 22, "type": "Rabbit", "maxhealth": 15, "health": 15, "level": 0, "strength": 1, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0}, {"x": 35, "y": 28, "type": "Heart", "held": false}], "map": " ########### ######### \n #.........# #.......# \n #.........# ############.......# \n #.........###############..........#.......############## \n #.........#........................#....................# \n #.........#.............#..........#.......#............# \n ########.########.............#..................#............# \n #.........# #.............####.#######.......#............# \n #.........# #.............##.........###################### \n #.........# #####.##########.........# ########### \n #.........# #......# #.........# #.........# \n ########.##########......# #.........# #.........# \n #...........##......# #.........# #.........# \n #...........##......# #.........# #.........# \n #...........##......# #.........# ################.###### \n #...........##......# #.........# #.................############\n #...........##......# ########.########.......#.........#..........#\n #...........##......# #...............#.......#.........#..........#\n #...........######### #...............#.......#.........#..........#\n #...........# #...............#.......#....................#\n #####.####### #.......................#.........#..........#\n #.........# #...............###################..........#\n #.........############ #...............# #..........#\n #.........#..........# #...............# ############\n #....................#####.###########.############# \n ########.#########...................# #.............# \n #........# #..........#........# #.............######### \n #........# ######.##########........# #.............#.......# \n #........# #..........# #........# #.....................# \n #........# #..........# #........# #.............#.......# \n #........# #..........# #........# #.............#.......# \n #........# #..........# #........# #.............#.......# \n #........# #..........#########.##### #.............#.......# \n #........# #..........#.........# ##########.############.####### \n #........# #..........#.........# #..............# #..........# \n ########## #..........#.........# #..............# #..........# \n ############.........# #..............# #..........# \n #.........# #..............# #..........# \n ########### #..............# #..........# \n ################ ############ "} \ No newline at end of file From cf6fe346d3273d9e0a332f710465ff3b7696066a Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 00:33:50 +0100 Subject: [PATCH 11/15] Replace player instance --- dungeonbattle/game.py | 2 ++ dungeonbattle/interfaces.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index c3e71ef..9c3155e 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -122,6 +122,8 @@ class Game: 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: diff --git a/dungeonbattle/interfaces.py b/dungeonbattle/interfaces.py index 20cedde..d4cb42c 100644 --- a/dungeonbattle/interfaces.py +++ b/dungeonbattle/interfaces.py @@ -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. From a80a604212187708868aad5665028a0c0101c4c9 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 00:34:00 +0100 Subject: [PATCH 12/15] Don't ignore first line --- dungeonbattle/interfaces.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dungeonbattle/interfaces.py b/dungeonbattle/interfaces.py index d4cb42c..3b83725 100644 --- a/dungeonbattle/interfaces.py +++ b/dungeonbattle/interfaces.py @@ -88,7 +88,6 @@ class Map: Transforms a string into the list of corresponding tiles """ lines = content.split("\n") - lines = [line for line in lines[1:] if line] tiles = [[Tile.from_ascii_char(c) for x, c in enumerate(line)] for y, line in enumerate(lines)] return tiles From 0d5812bfaa94538e126f0ae5b33b343e5e330761 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 01:11:11 +0100 Subject: [PATCH 13/15] Fix broken tests --- dungeonbattle/entities/items.py | 17 ++++++++++++ dungeonbattle/interfaces.py | 2 -- dungeonbattle/menus.py | 6 ----- dungeonbattle/tests/entities_test.py | 40 +++++++++++++++++++--------- dungeonbattle/tests/game_test.py | 36 +++++++++++++++++++++++-- dungeonbattle/tests/menus_test.py | 24 ----------------- 6 files changed, 78 insertions(+), 47 deletions(-) delete mode 100644 dungeonbattle/tests/menus_test.py diff --git a/dungeonbattle/entities/items.py b/dungeonbattle/entities/items.py index 217514d..8811905 100644 --- a/dungeonbattle/entities/items.py +++ b/dungeonbattle/entities/items.py @@ -63,6 +63,14 @@ 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): """ @@ -90,3 +98,12 @@ class Bomb(Item): 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 diff --git a/dungeonbattle/interfaces.py b/dungeonbattle/interfaces.py index 3b83725..873678a 100644 --- a/dungeonbattle/interfaces.py +++ b/dungeonbattle/interfaces.py @@ -135,8 +135,6 @@ class Map: d["currenty"] = self.currenty d["entities"] = [] for enti in self.entities: - if enti.save_state() is None: - raise Exception(enti) d["entities"].append(enti.save_state()) d["map"] = self.draw_string(TexturePack.ASCII_PACK) return d diff --git a/dungeonbattle/menus.py b/dungeonbattle/menus.py index 27680ac..68b7740 100644 --- a/dungeonbattle/menus.py +++ b/dungeonbattle/menus.py @@ -120,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 diff --git a/dungeonbattle/tests/entities_test.py b/dungeonbattle/tests/entities_test.py index d2c8171..b272541 100644 --- a/dungeonbattle/tests/entities_test.py +++ b/dungeonbattle/tests/entities_test.py @@ -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) diff --git a/dungeonbattle/tests/game_test.py b/dungeonbattle/tests/game_test.py index 80720ee..6784dd2 100644 --- a/dungeonbattle/tests/game_test.py +++ b/dungeonbattle/tests/game_test.py @@ -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) diff --git a/dungeonbattle/tests/menus_test.py b/dungeonbattle/tests/menus_test.py deleted file mode 100644 index 6ad9df7..0000000 --- a/dungeonbattle/tests/menus_test.py +++ /dev/null @@ -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) From ae9258cb258f8256b2914267c6fb8c74966b60fe Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 01:12:12 +0100 Subject: [PATCH 14/15] Don't commit game save --- .gitignore | 3 +++ save.json | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) delete mode 100644 save.json diff --git a/.gitignore b/.gitignore index 7221e66..99e64f0 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ __pycache__ # Don't commit settings settings.json + +# Don't commit game save +save.json diff --git a/save.json b/save.json deleted file mode 100644 index e0ac667..0000000 --- a/save.json +++ /dev/null @@ -1 +0,0 @@ -{"width": 80, "height": 40, "start_y": 1, "start_x": 17, "currentx": 14, "currenty": 11, "entities": [{"x": 14, "y": 11, "type": "Player", "maxhealth": 20, "health": 20, "level": 1, "strength": 5, "intelligence": 1, "charisma": 1, "dexterity": 1, "constitution": 1, "current_xp": 0, "max_xp": 10}, {"x": 50, "y": 37, "type": "Rabbit", "maxhealth": 15, "health": 15, "level": 0, "strength": 1, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0}, {"x": 16, "y": 22, "type": "Rabbit", "maxhealth": 15, "health": 15, "level": 0, "strength": 1, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0}, {"x": 12, "y": 7, "type": "Bomb", "held": false}, {"x": 69, "y": 38, "type": "Hedgehog", "maxhealth": 10, "health": 10, "level": 0, "strength": 3, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0}, {"x": 64, "y": 28, "type": "Hedgehog", "maxhealth": 10, "health": 10, "level": 0, "strength": 3, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0}, {"x": 37, "y": 29, "type": "TeddyBear", "maxhealth": 50, "health": 50, "level": 0, "strength": 0, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0}, {"x": 16, "y": 17, "type": "Rabbit", "maxhealth": 15, "health": 15, "level": 0, "strength": 1, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0}, {"x": 39, "y": 22, "type": "Rabbit", "maxhealth": 15, "health": 15, "level": 0, "strength": 1, "intelligence": 0, "charisma": 0, "dexterity": 0, "constitution": 0}, {"x": 35, "y": 28, "type": "Heart", "held": false}], "map": " ########### ######### \n #.........# #.......# \n #.........# ############.......# \n #.........###############..........#.......############## \n #.........#........................#....................# \n #.........#.............#..........#.......#............# \n ########.########.............#..................#............# \n #.........# #.............####.#######.......#............# \n #.........# #.............##.........###################### \n #.........# #####.##########.........# ########### \n #.........# #......# #.........# #.........# \n ########.##########......# #.........# #.........# \n #...........##......# #.........# #.........# \n #...........##......# #.........# #.........# \n #...........##......# #.........# ################.###### \n #...........##......# #.........# #.................############\n #...........##......# ########.########.......#.........#..........#\n #...........##......# #...............#.......#.........#..........#\n #...........######### #...............#.......#.........#..........#\n #...........# #...............#.......#....................#\n #####.####### #.......................#.........#..........#\n #.........# #...............###################..........#\n #.........############ #...............# #..........#\n #.........#..........# #...............# ############\n #....................#####.###########.############# \n ########.#########...................# #.............# \n #........# #..........#........# #.............######### \n #........# ######.##########........# #.............#.......# \n #........# #..........# #........# #.....................# \n #........# #..........# #........# #.............#.......# \n #........# #..........# #........# #.............#.......# \n #........# #..........# #........# #.............#.......# \n #........# #..........#########.##### #.............#.......# \n #........# #..........#.........# ##########.############.####### \n #........# #..........#.........# #..............# #..........# \n ########## #..........#.........# #..............# #..........# \n ############.........# #..............# #..........# \n #.........# #..............# #..........# \n ########### #..............# #..........# \n ################ ############ "} \ No newline at end of file From 038c2d0850aa200c60c90c8561dd34b838e5d2cc Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 01:13:46 +0100 Subject: [PATCH 15/15] Fix concurrent access to entity list issue --- dungeonbattle/entities/items.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dungeonbattle/entities/items.py b/dungeonbattle/entities/items.py index 8811905..9927ef4 100644 --- a/dungeonbattle/entities/items.py +++ b/dungeonbattle/entities/items.py @@ -94,7 +94,7 @@ class Bomb(Item): 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)