From aa81d455f6ee51238a7f7530e6d4ed7216ab5e1c Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 13 Nov 2020 15:40:44 +0100 Subject: [PATCH 001/171] Changed settings menu to let the display decide how the settings are printed --- dungeonbattle/display/display_manager.py | 4 ++-- dungeonbattle/display/menudisplay.py | 4 ++++ dungeonbattle/menus.py | 23 +++++------------------ 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/dungeonbattle/display/display_manager.py b/dungeonbattle/display/display_manager.py index b2cb125..3f22b9b 100644 --- a/dungeonbattle/display/display_manager.py +++ b/dungeonbattle/display/display_manager.py @@ -1,7 +1,7 @@ import curses from dungeonbattle.display.mapdisplay import MapDisplay from dungeonbattle.display.statsdisplay import StatsDisplay -from dungeonbattle.display.menudisplay import MenuDisplay, MainMenuDisplay +from dungeonbattle.display.menudisplay import SettingsMenuDisplay, MainMenuDisplay from dungeonbattle.display.texturepack import TexturePack from typing import Any from dungeonbattle.game import Game, GameMode @@ -18,7 +18,7 @@ class DisplayManager: self.statsdisplay = StatsDisplay(screen, pack) self.mainmenudisplay = MainMenuDisplay(self.game.main_menu, screen, pack) - self.settingsmenudisplay = MenuDisplay(screen, pack) + self.settingsmenudisplay = SettingsMenuDisplay(screen, pack) self.displays = [self.statsdisplay, self.mapdisplay, self.mainmenudisplay, self.settingsmenudisplay] self.update_game_components() diff --git a/dungeonbattle/display/menudisplay.py b/dungeonbattle/display/menudisplay.py index 973dd31..d8f11b0 100644 --- a/dungeonbattle/display/menudisplay.py +++ b/dungeonbattle/display/menudisplay.py @@ -59,6 +59,10 @@ class MenuDisplay(Display): def values(self) -> List[str]: return [str(a) for a in self.menu.values] +class SettingsMenuDisplay(MenuDisplay): + @property + def values(self) -> List[str]: + return [ a[1][1] + (" : " + ("?" if self.menu.waiting_for_key else a[1][0]) if a[1][0] else "") for a in self.menu.values ] class MainMenuDisplay(Display): def __init__(self, menu: MainMenu, *args): diff --git a/dungeonbattle/menus.py b/dungeonbattle/menus.py index 1990b27..5e66c24 100644 --- a/dungeonbattle/menus.py +++ b/dungeonbattle/menus.py @@ -57,21 +57,8 @@ class SettingsMenu(Menu): waiting_for_key: bool = False def update_values(self, settings: Settings) -> None: - self.values = [] - for i, key in enumerate(settings.settings_keys): - s = settings.get_comment(key) - s += " : " - if self.waiting_for_key and i == self.position: - s += "?" - else: - s += getattr(settings, key).replace("\n", "\\n") - s += 8 * " " # Write over old text - self.values.append(s) - self.values.append("") - self.values.append("Changer le pack de textures n'aura effet") - self.values.append("qu'après avoir relancé le jeu.") - self.values.append("") - self.values.append("Retour (espace)") + self.values = list(settings.__dict__.items()) + self.values.append(("RETURN",["","Retour"])) def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str, game: Any) -> None: @@ -90,9 +77,9 @@ class SettingsMenu(Menu): self.go_down() if key == KeyValues.UP: self.go_up() - if key == KeyValues.ENTER and self.position < len(self.values) - 3: + if key == KeyValues.ENTER and self.position < len(self.values) - 1: # Change a setting - option = list(game.settings.settings_keys)[self.position] + option = self.values[self.position][0] if option == "TEXTURE_PACK": game.settings.TEXTURE_PACK = \ TexturePack.get_next_pack_name( @@ -103,7 +90,7 @@ class SettingsMenu(Menu): self.waiting_for_key = True self.update_values(game.settings) else: - option = list(game.settings.settings_keys)[self.position] + option = self.values[self.position][0] # Don't use an already mapped key if any(getattr(game.settings, opt) == raw_key for opt in game.settings.settings_keys if opt != option): From 60fb993bdb290187f469f75f9f34df26681e3a4a Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 13 Nov 2020 17:21:50 +0100 Subject: [PATCH 002/171] Exec tests --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e3e740..e97ab99 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # Dungeon Battle -M1 Software engineering project +Projet de génie logiciel de M1 ## Création d'un environnement de développement @@ -33,3 +33,12 @@ Il est toujours préférable de travailler dans un environnement Python isolé d (env)$ pip3 install -r requirements.txt (env)$ deactivate # sortir de l'environnement ``` + +### Exécution des tests + +Les tests sont gérés par `pytest` dans le module `dungeonbattle.tests`. + +`tox` est un outil permettant de configurer l'exécution des tests. Ainsi, après +installation de tox dans votre environnement virtuel via `pip install tox`, +il vous suffit d'exécuter `tox -e py3` pour lancer les tests et `tox -e linters` +pour vérifier la syntaxe du code. From 04e3b05ab5b6730a9b7229c979525d69a2da1082 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 13 Nov 2020 17:49:48 +0100 Subject: [PATCH 003/171] Install latest version of fonts-noto-color-emoji --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/README.md b/README.md index e97ab99..6cc3006 100644 --- a/README.md +++ b/README.md @@ -42,3 +42,45 @@ Les tests sont gérés par `pytest` dans le module `dungeonbattle.tests`. installation de tox dans votre environnement virtuel via `pip install tox`, il vous suffit d'exécuter `tox -e py3` pour lancer les tests et `tox -e linters` pour vérifier la syntaxe du code. + + +## Lancement du jeu + +Il suffit d'exécuter `python3 main.py`. + +## Gestion des émojis + +Le jeu dispose de deux modes graphiques : en mode `ascii` et `squirrel`. +Le mode `squirrel` affiche des émojis pour un meilleur affichage. Toutefois, +il est possible que vous n'ayez pas les bonnes polices. + +### Sous Windows + +Sous Windows, vous devriez avoir les bonnes polices installées nativement. + +### Sous Arch Linux + +Il est recommandé d'utiliser le terminal `xfce4-terminal`. Il suffit d'installer +le paquets de polices + +```bash +sudo pacman -Sy noto-fonts-emoji +``` + +Le jeu doit ensuite se lancer normalement sans action supplémentaire. + +### Sous Ubuntu/Debian + +À nouveau, le terminal `xfce4-terminal` est recommandé. Le paquet +`fonts-noto-color-emoji`. Toutefois, le rythme de mise à jour de Debian étant +lent, le paquet le plus récent ne contient pas tous les émojis. Sur Debian, +il faudra donc installer le paquet le plus récent, ce qui fonctionne sans +dépendance supplémentaire : + +```bash +wget http://ftp.fr.debian.org/debian/pool/main/f/fonts-noto-color-emoji/fonts-noto-color-emoji_0~20200916-1_all.deb +dpkg -i fonts-noto-color-emoji_0~20200916-1_all.deb +rm fonts-noto-color-emoji_0~20200916-1_all.deb +``` + +Il reste le problème de l'écureuil. From fc71c8ae88ac78e317bb3d0f97f45a3e7dbe1647 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 13 Nov 2020 18:11:37 +0100 Subject: [PATCH 004/171] Add squirrel emoji fix --- README.md | 18 +++++- fix-squirrel-emojis.conf | 118 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 fix-squirrel-emojis.conf diff --git a/README.md b/README.md index 6cc3006..7f138df 100644 --- a/README.md +++ b/README.md @@ -83,4 +83,20 @@ dpkg -i fonts-noto-color-emoji_0~20200916-1_all.deb rm fonts-noto-color-emoji_0~20200916-1_all.deb ``` -Il reste le problème de l'écureuil. +Il reste le problème de l'écureuil. Sous Ubuntu et Debian, le caractère écureuil +existe déjà, mais ne s'affiche pas proprement. On peut appliquer un patch qui +permet d'afficher les émojis correctement dans son terminal. Pour cela, il + suffit de faire : + +```bash +ln -s fix-squirrel-emojis.conf /etc/fonts/conf.avail/42-fix-squirrel-emojis.conf +ln -s /etc/fonts/conf.avail/42-fix-squirrel-emojis.conf /etc/fonts/conf.d/42-fix-squirrel-emojis.conf +``` + +Après redémarrage du terminal, l'écureuil devrait s'afficher correctement. + +Pour supprimer le patch : + +```bash +rm /etc/fonts/conf.d/42-fix-squirrel-emojis.conf +``` diff --git a/fix-squirrel-emojis.conf b/fix-squirrel-emojis.conf new file mode 100644 index 0000000..f47023e --- /dev/null +++ b/fix-squirrel-emojis.conf @@ -0,0 +1,118 @@ + + + + + + + + + emoji + Noto Color Emoji + + + + + + + sans + Noto Color Emoji + + + + serif + Noto Color Emoji + + + + sans-serif + Noto Color Emoji + + + + monospace + Noto Color Emoji + + + + + + + + + + Symbola + + + + + + + + + + Android Emoji + Noto Color Emoji + + + + Apple Color Emoji + Noto Color Emoji + + + + EmojiSymbols + Noto Color Emoji + + + + Emoji Two + Noto Color Emoji + + + + EmojiTwo + Noto Color Emoji + + + + Noto Color Emoji + Noto Color Emoji + + + + Segoe UI Emoji + Noto Color Emoji + + + + Segoe UI Symbol + Noto Color Emoji + + + + Symbola + Noto Color Emoji + + + + Twemoji + Noto Color Emoji + + + + Twemoji Mozilla + Noto Color Emoji + + + + TwemojiMozilla + Noto Color Emoji + + + + Twitter Color Emoji + Noto Color Emoji + + + + From 63e98ae97f3da3395c9855a8eee8020293726dab Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 13 Nov 2020 18:19:08 +0100 Subject: [PATCH 005/171] Unfortunately 42 is too low... --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7f138df..7531c65 100644 --- a/README.md +++ b/README.md @@ -89,8 +89,8 @@ permet d'afficher les émojis correctement dans son terminal. Pour cela, il suffit de faire : ```bash -ln -s fix-squirrel-emojis.conf /etc/fonts/conf.avail/42-fix-squirrel-emojis.conf -ln -s /etc/fonts/conf.avail/42-fix-squirrel-emojis.conf /etc/fonts/conf.d/42-fix-squirrel-emojis.conf +ln -s fix-squirrel-emojis.conf /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf +ln -s /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf /etc/fonts/conf.d/75-fix-squirrel-emojis.conf ``` Après redémarrage du terminal, l'écureuil devrait s'afficher correctement. @@ -98,5 +98,5 @@ Après redémarrage du terminal, l'écureuil devrait s'afficher correctement. Pour supprimer le patch : ```bash -rm /etc/fonts/conf.d/42-fix-squirrel-emojis.conf +rm /etc/fonts/conf.d/75-fix-squirrel-emojis.conf ``` From f55b1356c3b95819fcd3424b146e528b575dd86a Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 13 Nov 2020 18:20:20 +0100 Subject: [PATCH 006/171] Non-relative symbolic link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7531c65..63d7374 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ permet d'afficher les émojis correctement dans son terminal. Pour cela, il suffit de faire : ```bash -ln -s fix-squirrel-emojis.conf /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf +ln -s $PWD/fix-squirrel-emojis.conf /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf ln -s /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf /etc/fonts/conf.d/75-fix-squirrel-emojis.conf ``` From 04d2bc1789ebfffe6da3c3944b8ac2541f7f4310 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 13 Nov 2020 19:08:40 +0100 Subject: [PATCH 007/171] linting --- dungeonbattle/display/display_manager.py | 3 ++- dungeonbattle/display/menudisplay.py | 14 ++++++++++---- dungeonbattle/menus.py | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/dungeonbattle/display/display_manager.py b/dungeonbattle/display/display_manager.py index 3f22b9b..4b2604f 100644 --- a/dungeonbattle/display/display_manager.py +++ b/dungeonbattle/display/display_manager.py @@ -1,7 +1,8 @@ import curses from dungeonbattle.display.mapdisplay import MapDisplay from dungeonbattle.display.statsdisplay import StatsDisplay -from dungeonbattle.display.menudisplay import SettingsMenuDisplay, MainMenuDisplay +from dungeonbattle.display.menudisplay import SettingsMenuDisplay, \ + MainMenuDisplay from dungeonbattle.display.texturepack import TexturePack from typing import Any from dungeonbattle.game import Game, GameMode diff --git a/dungeonbattle/display/menudisplay.py b/dungeonbattle/display/menudisplay.py index d8f11b0..880b4bb 100644 --- a/dungeonbattle/display/menudisplay.py +++ b/dungeonbattle/display/menudisplay.py @@ -59,27 +59,33 @@ class MenuDisplay(Display): def values(self) -> List[str]: return [str(a) for a in self.menu.values] + class SettingsMenuDisplay(MenuDisplay): @property def values(self) -> List[str]: - return [ a[1][1] + (" : " + ("?" if self.menu.waiting_for_key else a[1][0]) if a[1][0] else "") for a in self.menu.values ] + return [a[1][1] + (" : " + + ("?" if self.menu.waiting_for_key else a[1][0]) + if a[1][0] else "") for a in self.menu.values] + class MainMenuDisplay(Display): def __init__(self, menu: MainMenu, *args): super().__init__(*args) self.menu = menu - self.pad = self.newpad(self.rows, self.cols) with open("resources/ascii_art.txt", "r") as file: self.title = file.read().split("\n") + self.pad = self.newpad(max(self.rows, len(self.title) + 30), + max(len(self.title[0]) + 5, self.cols)) + self.menudisplay = MenuDisplay(self.screen, self.pack) self.menudisplay.update_menu(self.menu) def display(self) -> None: for i in range(len(self.title)): - self.pad.addstr(4 + i, self.width // 2 - - len(self.title[0]) // 2 - 1, self.title[i]) + self.pad.addstr(4 + i, max(self.width // 2 + - len(self.title[0]) // 2 - 1, 0), self.title[i]) self.pad.refresh(0, 0, self.y, self.x, self.height, self.width) menuwidth = min(self.menudisplay.preferred_width, self.width) menuy, menux = len(self.title) + 8, self.width // 2 - menuwidth // 2 - 1 diff --git a/dungeonbattle/menus.py b/dungeonbattle/menus.py index 5e66c24..af20978 100644 --- a/dungeonbattle/menus.py +++ b/dungeonbattle/menus.py @@ -58,7 +58,7 @@ class SettingsMenu(Menu): def update_values(self, settings: Settings) -> None: self.values = list(settings.__dict__.items()) - self.values.append(("RETURN",["","Retour"])) + self.values.append(("RETURN", ["", "Retour"])) def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str, game: Any) -> None: From f67eae3803f0936f58996e1da94a4b1206eae2f0 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Mon, 16 Nov 2020 01:01:18 +0100 Subject: [PATCH 008/171] 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 009/171] 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 010/171] 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 011/171] 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 012/171] 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 013/171] 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 014/171] 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 015/171] 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 016/171] 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 017/171] 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 018/171] 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 019/171] 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 020/171] 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 021/171] 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 022/171] 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) From 6a2bd6407172a8ec105d8637b47b86001684f7bd Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 01:17:46 +0100 Subject: [PATCH 023/171] Linting --- dungeonbattle/display/menudisplay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dungeonbattle/display/menudisplay.py b/dungeonbattle/display/menudisplay.py index 880b4bb..36694d0 100644 --- a/dungeonbattle/display/menudisplay.py +++ b/dungeonbattle/display/menudisplay.py @@ -77,7 +77,7 @@ class MainMenuDisplay(Display): self.title = file.read().split("\n") self.pad = self.newpad(max(self.rows, len(self.title) + 30), - max(len(self.title[0]) + 5, self.cols)) + max(len(self.title[0]) + 5, self.cols)) self.menudisplay = MenuDisplay(self.screen, self.pack) self.menudisplay.update_menu(self.menu) From 38ca2d36e8d554a1324846ac2126d36c66d6f351 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 13 Nov 2020 17:21:50 +0100 Subject: [PATCH 024/171] Exec tests --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e3e740..e97ab99 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # Dungeon Battle -M1 Software engineering project +Projet de génie logiciel de M1 ## Création d'un environnement de développement @@ -33,3 +33,12 @@ Il est toujours préférable de travailler dans un environnement Python isolé d (env)$ pip3 install -r requirements.txt (env)$ deactivate # sortir de l'environnement ``` + +### Exécution des tests + +Les tests sont gérés par `pytest` dans le module `dungeonbattle.tests`. + +`tox` est un outil permettant de configurer l'exécution des tests. Ainsi, après +installation de tox dans votre environnement virtuel via `pip install tox`, +il vous suffit d'exécuter `tox -e py3` pour lancer les tests et `tox -e linters` +pour vérifier la syntaxe du code. From d9fcefbe43c456fe26b3ca799319e65664c92018 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 13 Nov 2020 17:49:48 +0100 Subject: [PATCH 025/171] Install latest version of fonts-noto-color-emoji --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/README.md b/README.md index e97ab99..6cc3006 100644 --- a/README.md +++ b/README.md @@ -42,3 +42,45 @@ Les tests sont gérés par `pytest` dans le module `dungeonbattle.tests`. installation de tox dans votre environnement virtuel via `pip install tox`, il vous suffit d'exécuter `tox -e py3` pour lancer les tests et `tox -e linters` pour vérifier la syntaxe du code. + + +## Lancement du jeu + +Il suffit d'exécuter `python3 main.py`. + +## Gestion des émojis + +Le jeu dispose de deux modes graphiques : en mode `ascii` et `squirrel`. +Le mode `squirrel` affiche des émojis pour un meilleur affichage. Toutefois, +il est possible que vous n'ayez pas les bonnes polices. + +### Sous Windows + +Sous Windows, vous devriez avoir les bonnes polices installées nativement. + +### Sous Arch Linux + +Il est recommandé d'utiliser le terminal `xfce4-terminal`. Il suffit d'installer +le paquets de polices + +```bash +sudo pacman -Sy noto-fonts-emoji +``` + +Le jeu doit ensuite se lancer normalement sans action supplémentaire. + +### Sous Ubuntu/Debian + +À nouveau, le terminal `xfce4-terminal` est recommandé. Le paquet +`fonts-noto-color-emoji`. Toutefois, le rythme de mise à jour de Debian étant +lent, le paquet le plus récent ne contient pas tous les émojis. Sur Debian, +il faudra donc installer le paquet le plus récent, ce qui fonctionne sans +dépendance supplémentaire : + +```bash +wget http://ftp.fr.debian.org/debian/pool/main/f/fonts-noto-color-emoji/fonts-noto-color-emoji_0~20200916-1_all.deb +dpkg -i fonts-noto-color-emoji_0~20200916-1_all.deb +rm fonts-noto-color-emoji_0~20200916-1_all.deb +``` + +Il reste le problème de l'écureuil. From 8d73712789677df3a1b17084c657929bc8c063ce Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 13 Nov 2020 18:11:37 +0100 Subject: [PATCH 026/171] Add squirrel emoji fix --- README.md | 18 +++++- fix-squirrel-emojis.conf | 118 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 fix-squirrel-emojis.conf diff --git a/README.md b/README.md index 6cc3006..7f138df 100644 --- a/README.md +++ b/README.md @@ -83,4 +83,20 @@ dpkg -i fonts-noto-color-emoji_0~20200916-1_all.deb rm fonts-noto-color-emoji_0~20200916-1_all.deb ``` -Il reste le problème de l'écureuil. +Il reste le problème de l'écureuil. Sous Ubuntu et Debian, le caractère écureuil +existe déjà, mais ne s'affiche pas proprement. On peut appliquer un patch qui +permet d'afficher les émojis correctement dans son terminal. Pour cela, il + suffit de faire : + +```bash +ln -s fix-squirrel-emojis.conf /etc/fonts/conf.avail/42-fix-squirrel-emojis.conf +ln -s /etc/fonts/conf.avail/42-fix-squirrel-emojis.conf /etc/fonts/conf.d/42-fix-squirrel-emojis.conf +``` + +Après redémarrage du terminal, l'écureuil devrait s'afficher correctement. + +Pour supprimer le patch : + +```bash +rm /etc/fonts/conf.d/42-fix-squirrel-emojis.conf +``` diff --git a/fix-squirrel-emojis.conf b/fix-squirrel-emojis.conf new file mode 100644 index 0000000..f47023e --- /dev/null +++ b/fix-squirrel-emojis.conf @@ -0,0 +1,118 @@ + + + + + + + + + emoji + Noto Color Emoji + + + + + + + sans + Noto Color Emoji + + + + serif + Noto Color Emoji + + + + sans-serif + Noto Color Emoji + + + + monospace + Noto Color Emoji + + + + + + + + + + Symbola + + + + + + + + + + Android Emoji + Noto Color Emoji + + + + Apple Color Emoji + Noto Color Emoji + + + + EmojiSymbols + Noto Color Emoji + + + + Emoji Two + Noto Color Emoji + + + + EmojiTwo + Noto Color Emoji + + + + Noto Color Emoji + Noto Color Emoji + + + + Segoe UI Emoji + Noto Color Emoji + + + + Segoe UI Symbol + Noto Color Emoji + + + + Symbola + Noto Color Emoji + + + + Twemoji + Noto Color Emoji + + + + Twemoji Mozilla + Noto Color Emoji + + + + TwemojiMozilla + Noto Color Emoji + + + + Twitter Color Emoji + Noto Color Emoji + + + + From dd915692035a7c0b5e10a5924bc2781f7ff272db Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 13 Nov 2020 18:19:08 +0100 Subject: [PATCH 027/171] Unfortunately 42 is too low... --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7f138df..7531c65 100644 --- a/README.md +++ b/README.md @@ -89,8 +89,8 @@ permet d'afficher les émojis correctement dans son terminal. Pour cela, il suffit de faire : ```bash -ln -s fix-squirrel-emojis.conf /etc/fonts/conf.avail/42-fix-squirrel-emojis.conf -ln -s /etc/fonts/conf.avail/42-fix-squirrel-emojis.conf /etc/fonts/conf.d/42-fix-squirrel-emojis.conf +ln -s fix-squirrel-emojis.conf /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf +ln -s /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf /etc/fonts/conf.d/75-fix-squirrel-emojis.conf ``` Après redémarrage du terminal, l'écureuil devrait s'afficher correctement. @@ -98,5 +98,5 @@ Après redémarrage du terminal, l'écureuil devrait s'afficher correctement. Pour supprimer le patch : ```bash -rm /etc/fonts/conf.d/42-fix-squirrel-emojis.conf +rm /etc/fonts/conf.d/75-fix-squirrel-emojis.conf ``` From d95747159f2ec7da21960a2d7c7ba56fdb51aa8a Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 13 Nov 2020 18:20:20 +0100 Subject: [PATCH 028/171] Non-relative symbolic link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7531c65..63d7374 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ permet d'afficher les émojis correctement dans son terminal. Pour cela, il suffit de faire : ```bash -ln -s fix-squirrel-emojis.conf /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf +ln -s $PWD/fix-squirrel-emojis.conf /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf ln -s /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf /etc/fonts/conf.d/75-fix-squirrel-emojis.conf ``` From 41d1696c9b569a0647cee9e1b621b06ffe6259b9 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Mon, 16 Nov 2020 01:01:18 +0100 Subject: [PATCH 029/171] 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 ++++++++++++++++++++++++++++- resources/example_map_3.txt | 45 ++++++++++++++++ save.json | 1 + test.py | 17 +++++++ 8 files changed, 202 insertions(+), 9 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 46dd0a5..c49399a 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 @@ -38,11 +40,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. @@ -66,7 +63,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) @@ -89,3 +86,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/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 8f932604f6f3024fa887b9a6a550fd115d063b54 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Wed, 18 Nov 2020 12:19:27 +0100 Subject: [PATCH 030/171] 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 | 49 +++++++++++++------------ 5 files changed, 126 insertions(+), 33 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 c49399a..18b8174 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 is a display interface set by the bootstrapper @@ -43,8 +46,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() @@ -70,7 +73,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(): @@ -108,15 +111,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: @@ -129,7 +133,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 0d7a542..d17f24c 100644 --- a/dungeonbattle/menus.py +++ b/dungeonbattle/menus.py @@ -8,24 +8,41 @@ 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 = 'Nouvelle partie' RESUME = 'Continuer' + SAVE = 'Sauvegarder' + LOAD = 'Charger' SETTINGS = 'Paramètres' EXIT = 'Quitter' @@ -34,34 +51,22 @@ class MainMenuValues(Enum): class MainMenu(Menu): + """ + A special instance of a menu : the main menu + """ values = [e for e in MainMenuValues] - def handle_key_pressed(self, key: KeyValues, game: Any) -> None: - """ - In the main menu, we can navigate through options. - """ - if key == KeyValues.DOWN: - self.go_down() - if key == KeyValues.UP: - self.go_up() - if key == KeyValues.ENTER: - option = self.validate() - if option == MainMenuValues.START: - game.new_game() - game.state = GameMode.PLAY - game.display_actions(DisplayActions.UPDATE) - elif option == MainMenuValues.RESUME: - game.state = GameMode.PLAY - elif option == MainMenuValues.SETTINGS: - game.state = GameMode.SETTINGS - elif option == MainMenuValues.EXIT: - sys.exit(0) - class SettingsMenu(Menu): + """ + A special instance of a menu : the settings menu + """ waiting_for_key: bool = False def update_values(self, settings: Settings) -> None: + """ + The settings can change, so they are updated + """ self.values = [] for i, key in enumerate(settings.settings_keys): s = settings.get_comment(key) @@ -81,7 +86,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 aac01d8bef2592fee9866a7706621b5b9cebae5c Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Wed, 18 Nov 2020 12:27:59 +0100 Subject: [PATCH 031/171] 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 0488d8a9e2598c184ae374654e91c2709c6d0ae8 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 18 Nov 2020 14:29:54 +0100 Subject: [PATCH 032/171] 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 be6c949b1811d48743cbdbe814f97753fb4360f7 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 18 Nov 2020 14:54:21 +0100 Subject: [PATCH 033/171] 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 958dcdfee0636564d1d9ee3375d285cf01f61a9b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 18 Nov 2020 14:56:59 +0100 Subject: [PATCH 034/171] 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 18b8174..a232d50 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 @@ -111,18 +112,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 d17f24c..94c1a7a 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 e52e0c833cc3c89ea17ccdeb9586222801c1e2c1 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 18 Nov 2020 15:02:30 +0100 Subject: [PATCH 035/171] 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 57a20c53f33701276b78d9513e82a099926c7b2b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 18 Nov 2020 15:04:15 +0100 Subject: [PATCH 036/171] 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 a232d50..c577cb7 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -123,6 +123,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 a4482849aedf0be889f6c5a10a72fd2f167d5f08 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Wed, 18 Nov 2020 22:42:46 +0100 Subject: [PATCH 037/171] 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 58fbba8cc9e63610fd4d0dcc5f259bea3f5d3a88 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 00:10:37 +0100 Subject: [PATCH 038/171] 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 3126b89bc1e710193eca8d1b6467f9c1bc88f9ad Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 00:33:50 +0100 Subject: [PATCH 039/171] 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 c577cb7..45c0115 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -123,6 +123,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 075c061a148edacc38eb7e751082aa79ad588f12 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 00:34:00 +0100 Subject: [PATCH 040/171] 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 109543a3853ad6ce5ee153ebea6017962e87c02b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 01:11:11 +0100 Subject: [PATCH 041/171] 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 94c1a7a..798a9ed 100644 --- a/dungeonbattle/menus.py +++ b/dungeonbattle/menus.py @@ -121,9 +121,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 90b046f3e7e9b2830c9bad7ce7c7b1ff566be95f Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 01:12:12 +0100 Subject: [PATCH 042/171] 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 80ae824ed8b7a068d4ca32e1d816d63f4712c81f Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 01:13:46 +0100 Subject: [PATCH 043/171] 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) From a51ca5dbe3bcc8ab9159baf061648f3a1e7aa50d Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 01:32:52 +0100 Subject: [PATCH 044/171] Menu key handler moved --- dungeonbattle/game.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dungeonbattle/game.py b/dungeonbattle/game.py index 45c0115..dcd6a59 100644 --- a/dungeonbattle/game.py +++ b/dungeonbattle/game.py @@ -102,6 +102,10 @@ class Game: if key == KeyValues.ENTER: option = self.main_menu.validate() if option == menus.MainMenuValues.START: + self.new_game() + self.display_actions(DisplayActions.UPDATE) + self.state = GameMode.PLAY + if option == menus.MainMenuValues.RESUME: self.state = GameMode.PLAY elif option == menus.MainMenuValues.SAVE: self.save_game() From 3520a76613997f94f413b1a565a7022f05c8c113 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 01:39:12 +0100 Subject: [PATCH 045/171] Test to create/resume a game --- dungeonbattle/tests/game_test.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/dungeonbattle/tests/game_test.py b/dungeonbattle/tests/game_test.py index 6784dd2..8e7970d 100644 --- a/dungeonbattle/tests/game_test.py +++ b/dungeonbattle/tests/game_test.py @@ -26,6 +26,7 @@ class TestGame(unittest.TestCase): """ old_state = self.game.save_state() + self.game.handle_key_pressed(KeyValues.DOWN) 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 @@ -94,6 +95,9 @@ 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.RESUME) + self.game.handle_key_pressed(KeyValues.DOWN) self.assertEqual(self.game.main_menu.validate(), MainMenuValues.SAVE) self.game.handle_key_pressed(KeyValues.DOWN) @@ -124,6 +128,9 @@ class TestGame(unittest.TestCase): self.assertEqual(self.game.main_menu.validate(), MainMenuValues.SAVE) self.game.handle_key_pressed(KeyValues.UP) + self.assertEqual(self.game.main_menu.validate(), + MainMenuValues.RESUME) + self.game.handle_key_pressed(KeyValues.UP) self.assertEqual(self.game.main_menu.validate(), MainMenuValues.START) @@ -162,6 +169,29 @@ class TestGame(unittest.TestCase): self.game.handle_key_pressed(KeyValues.SPACE) self.assertEqual(self.game.state, GameMode.MAINMENU) + def test_new_game(self) -> None: + """ + Ensure that the start button starts a new game. + """ + old_map = self.game.map + old_player = self.game.player + self.game.handle_key_pressed(KeyValues.ENTER) # Start new game + new_map = self.game.map + new_player = self.game.player + # Ensure that + self.assertNotEqual(old_map, new_map) + self.assertNotEqual(old_player, new_player) + + self.game.handle_key_pressed(KeyValues.SPACE) + old_map = new_map + old_player = new_player + self.game.handle_key_pressed(KeyValues.DOWN) + self.game.handle_key_pressed(KeyValues.ENTER) # Resume game + new_map = self.game.map + new_player = self.game.player + self.assertEqual(old_map, new_map) + self.assertEqual(old_player, new_player) + def test_settings_menu(self) -> None: """ Ensure that the settings menu is working properly. @@ -172,6 +202,7 @@ class TestGame(unittest.TestCase): 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.DOWN) self.game.handle_key_pressed(KeyValues.ENTER) self.assertEqual(self.game.state, GameMode.SETTINGS) From 9232f67dc9cdaefcdaf5433709e56ca8f4bfe910 Mon Sep 17 00:00:00 2001 From: ynerant Date: Thu, 19 Nov 2020 01:51:10 +0100 Subject: [PATCH 046/171] Add LICENSE --- LICENSE | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7d821ed --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + Squirrel Battle + Copyright (C) 2020 ynerant + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Squirrel Battle Copyright (C) 2020 ynerant + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From d0ee9ec5623a1a109d31cf690300ad130c6f18cc Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 02:18:08 +0100 Subject: [PATCH 047/171] Rename dungeonbattle to squirrelbattle --- README.md | 4 ++-- main.py | 2 +- {dungeonbattle => squirrelbattle}/__init__.py | 0 {resources => squirrelbattle/assets}/ascii_art.txt | 0 .../assets}/example_map.txt | 0 .../assets}/example_map_2.txt | 0 {dungeonbattle => squirrelbattle}/bootstrap.py | 6 +++--- .../display/__init__.py | 0 .../display/display.py | 4 ++-- .../display/display_manager.py | 12 ++++++------ .../display/mapdisplay.py | 2 +- .../display/menudisplay.py | 4 ++-- .../display/statsdisplay.py | 2 +- .../display/texturepack.py | 0 .../entities/__init__.py | 0 .../entities/items.py | 0 .../entities/monsters.py | 0 .../entities/player.py | 0 {dungeonbattle => squirrelbattle}/enums.py | 2 +- {dungeonbattle => squirrelbattle}/game.py | 2 +- {dungeonbattle => squirrelbattle}/interfaces.py | 14 +++++++------- {dungeonbattle => squirrelbattle}/menus.py | 0 {dungeonbattle => squirrelbattle}/settings.py | 0 {dungeonbattle => squirrelbattle}/term_manager.py | 0 .../tests/__init__.py | 0 .../tests/entities_test.py | 10 +++++----- .../tests/game_test.py | 14 +++++++------- .../tests/interfaces_test.py | 6 +++--- {dungeonbattle => squirrelbattle}/tests/screen.py | 0 .../tests/settings_test.py | 2 +- 30 files changed, 43 insertions(+), 43 deletions(-) rename {dungeonbattle => squirrelbattle}/__init__.py (100%) rename {resources => squirrelbattle/assets}/ascii_art.txt (100%) rename {resources => squirrelbattle/assets}/example_map.txt (100%) rename {resources => squirrelbattle/assets}/example_map_2.txt (100%) rename {dungeonbattle => squirrelbattle}/bootstrap.py (77%) rename {dungeonbattle => squirrelbattle}/display/__init__.py (100%) rename {dungeonbattle => squirrelbattle}/display/display.py (93%) rename {dungeonbattle => squirrelbattle}/display/display_manager.py (88%) rename {dungeonbattle => squirrelbattle}/display/mapdisplay.py (97%) rename {dungeonbattle => squirrelbattle}/display/menudisplay.py (96%) rename {dungeonbattle => squirrelbattle}/display/statsdisplay.py (97%) rename {dungeonbattle => squirrelbattle}/display/texturepack.py (100%) rename {dungeonbattle => squirrelbattle}/entities/__init__.py (100%) rename {dungeonbattle => squirrelbattle}/entities/items.py (100%) rename {dungeonbattle => squirrelbattle}/entities/monsters.py (100%) rename {dungeonbattle => squirrelbattle}/entities/player.py (100%) rename {dungeonbattle => squirrelbattle}/enums.py (97%) rename {dungeonbattle => squirrelbattle}/game.py (98%) rename {dungeonbattle => squirrelbattle}/interfaces.py (96%) rename {dungeonbattle => squirrelbattle}/menus.py (100%) rename {dungeonbattle => squirrelbattle}/settings.py (100%) rename {dungeonbattle => squirrelbattle}/term_manager.py (100%) rename {dungeonbattle => squirrelbattle}/tests/__init__.py (100%) rename {dungeonbattle => squirrelbattle}/tests/entities_test.py (94%) rename {dungeonbattle => squirrelbattle}/tests/game_test.py (96%) rename {dungeonbattle => squirrelbattle}/tests/interfaces_test.py (85%) rename {dungeonbattle => squirrelbattle}/tests/screen.py (100%) rename {dungeonbattle => squirrelbattle}/tests/settings_test.py (96%) diff --git a/README.md b/README.md index 63d7374..6c1d9b3 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ [![pipeline status](https://gitlab.crans.org/ynerant/dungeon-battle/badges/master/pipeline.svg)](https://gitlab.crans.org/ynerant/dungeon-battle/-/commits/master) [![coverage report](https://gitlab.crans.org/ynerant/dungeon-battle/badges/master/coverage.svg)](https://gitlab.crans.org/ynerant/dungeon-battle/-/commits/master) -# Dungeon Battle +# Squirrel Battle -Projet de génie logiciel de M1 +Attention aux couteaux des écureuils ! ## Création d'un environnement de développement diff --git a/main.py b/main.py index e918f0d..64972f1 100755 --- a/main.py +++ b/main.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -from dungeonbattle.bootstrap import Bootstrap +from squirrelbattle.bootstrap import Bootstrap if __name__ == "__main__": Bootstrap.run_game() diff --git a/dungeonbattle/__init__.py b/squirrelbattle/__init__.py similarity index 100% rename from dungeonbattle/__init__.py rename to squirrelbattle/__init__.py diff --git a/resources/ascii_art.txt b/squirrelbattle/assets/ascii_art.txt similarity index 100% rename from resources/ascii_art.txt rename to squirrelbattle/assets/ascii_art.txt diff --git a/resources/example_map.txt b/squirrelbattle/assets/example_map.txt similarity index 100% rename from resources/example_map.txt rename to squirrelbattle/assets/example_map.txt diff --git a/resources/example_map_2.txt b/squirrelbattle/assets/example_map_2.txt similarity index 100% rename from resources/example_map_2.txt rename to squirrelbattle/assets/example_map_2.txt diff --git a/dungeonbattle/bootstrap.py b/squirrelbattle/bootstrap.py similarity index 77% rename from dungeonbattle/bootstrap.py rename to squirrelbattle/bootstrap.py index 9828fae..45c2ad1 100644 --- a/dungeonbattle/bootstrap.py +++ b/squirrelbattle/bootstrap.py @@ -1,6 +1,6 @@ -from dungeonbattle.game import Game -from dungeonbattle.display.display_manager import DisplayManager -from dungeonbattle.term_manager import TermManager +from squirrelbattle.game import Game +from squirrelbattle.display.display_manager import DisplayManager +from squirrelbattle.term_manager import TermManager class Bootstrap: diff --git a/dungeonbattle/display/__init__.py b/squirrelbattle/display/__init__.py similarity index 100% rename from dungeonbattle/display/__init__.py rename to squirrelbattle/display/__init__.py diff --git a/dungeonbattle/display/display.py b/squirrelbattle/display/display.py similarity index 93% rename from dungeonbattle/display/display.py rename to squirrelbattle/display/display.py index 6aba26a..47e4bce 100644 --- a/dungeonbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -1,8 +1,8 @@ import curses from typing import Any, Optional, Union -from dungeonbattle.display.texturepack import TexturePack -from dungeonbattle.tests.screen import FakePad +from squirrelbattle.display.texturepack import TexturePack +from squirrelbattle.tests.screen import FakePad class Display: diff --git a/dungeonbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py similarity index 88% rename from dungeonbattle/display/display_manager.py rename to squirrelbattle/display/display_manager.py index 4b2604f..9ec0f88 100644 --- a/dungeonbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -1,12 +1,12 @@ import curses -from dungeonbattle.display.mapdisplay import MapDisplay -from dungeonbattle.display.statsdisplay import StatsDisplay -from dungeonbattle.display.menudisplay import SettingsMenuDisplay, \ +from squirrelbattle.display.mapdisplay import MapDisplay +from squirrelbattle.display.statsdisplay import StatsDisplay +from squirrelbattle.display.menudisplay import SettingsMenuDisplay, \ MainMenuDisplay -from dungeonbattle.display.texturepack import TexturePack +from squirrelbattle.display.texturepack import TexturePack from typing import Any -from dungeonbattle.game import Game, GameMode -from dungeonbattle.enums import DisplayActions +from squirrelbattle.game import Game, GameMode +from squirrelbattle.enums import DisplayActions class DisplayManager: diff --git a/dungeonbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py similarity index 97% rename from dungeonbattle/display/mapdisplay.py rename to squirrelbattle/display/mapdisplay.py index 36cd616..e6cc278 100644 --- a/dungeonbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -from dungeonbattle.interfaces import Map +from squirrelbattle.interfaces import Map from .display import Display diff --git a/dungeonbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py similarity index 96% rename from dungeonbattle/display/menudisplay.py rename to squirrelbattle/display/menudisplay.py index 36694d0..9108ac8 100644 --- a/dungeonbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -1,6 +1,6 @@ from typing import List -from dungeonbattle.menus import Menu, MainMenu +from squirrelbattle.menus import Menu, MainMenu from .display import Display @@ -73,7 +73,7 @@ class MainMenuDisplay(Display): super().__init__(*args) self.menu = menu - with open("resources/ascii_art.txt", "r") as file: + with open("squirrelbattle/assets/ascii_art.txt", "r") as file: self.title = file.read().split("\n") self.pad = self.newpad(max(self.rows, len(self.title) + 30), diff --git a/dungeonbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py similarity index 97% rename from dungeonbattle/display/statsdisplay.py rename to squirrelbattle/display/statsdisplay.py index 70c6f0c..77ddb30 100644 --- a/dungeonbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -2,7 +2,7 @@ import curses from .display import Display -from dungeonbattle.entities.player import Player +from squirrelbattle.entities.player import Player class StatsDisplay(Display): diff --git a/dungeonbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py similarity index 100% rename from dungeonbattle/display/texturepack.py rename to squirrelbattle/display/texturepack.py diff --git a/dungeonbattle/entities/__init__.py b/squirrelbattle/entities/__init__.py similarity index 100% rename from dungeonbattle/entities/__init__.py rename to squirrelbattle/entities/__init__.py diff --git a/dungeonbattle/entities/items.py b/squirrelbattle/entities/items.py similarity index 100% rename from dungeonbattle/entities/items.py rename to squirrelbattle/entities/items.py diff --git a/dungeonbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py similarity index 100% rename from dungeonbattle/entities/monsters.py rename to squirrelbattle/entities/monsters.py diff --git a/dungeonbattle/entities/player.py b/squirrelbattle/entities/player.py similarity index 100% rename from dungeonbattle/entities/player.py rename to squirrelbattle/entities/player.py diff --git a/dungeonbattle/enums.py b/squirrelbattle/enums.py similarity index 97% rename from dungeonbattle/enums.py rename to squirrelbattle/enums.py index e5f73d9..350c196 100644 --- a/dungeonbattle/enums.py +++ b/squirrelbattle/enums.py @@ -1,7 +1,7 @@ from enum import Enum, auto from typing import Optional -from dungeonbattle.settings import Settings +from squirrelbattle.settings import Settings # This file contains a few useful enumeration classes used elsewhere in the code diff --git a/dungeonbattle/game.py b/squirrelbattle/game.py similarity index 98% rename from dungeonbattle/game.py rename to squirrelbattle/game.py index dcd6a59..05da3e8 100644 --- a/dungeonbattle/game.py +++ b/squirrelbattle/game.py @@ -38,7 +38,7 @@ class Game: Create a new game on the screen. """ # TODO generate a new map procedurally - self.map = Map.load("resources/example_map_2.txt") + self.map = Map.load("squirrelbattle/assets/example_map_2.txt") self.player = Player() self.map.add_entity(self.player) self.player.move(self.map.start_y, self.map.start_x) diff --git a/dungeonbattle/interfaces.py b/squirrelbattle/interfaces.py similarity index 96% rename from dungeonbattle/interfaces.py rename to squirrelbattle/interfaces.py index 873678a..10001fb 100644 --- a/dungeonbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -4,7 +4,7 @@ from math import sqrt from random import choice, randint from typing import List, Optional -from dungeonbattle.display.texturepack import TexturePack +from squirrelbattle.display.texturepack import TexturePack class Map: @@ -287,7 +287,7 @@ class Entity: """ Is this entity an item? """ - from dungeonbattle.entities.items import Item + from squirrelbattle.entities.items import Item return isinstance(self, Item) @staticmethod @@ -295,8 +295,8 @@ class Entity: """ Returns all entities subclasses """ - from dungeonbattle.entities.items import Heart, Bomb - from dungeonbattle.entities.monsters import Beaver, Hedgehog, \ + from squirrelbattle.entities.items import Heart, Bomb + from squirrelbattle.entities.monsters import Beaver, Hedgehog, \ Rabbit, TeddyBear return [Beaver, Bomb, Heart, Hedgehog, Rabbit, TeddyBear] @@ -305,10 +305,10 @@ class Entity: """ Returns all entities subclasses in a dictionary """ - from dungeonbattle.entities.player import Player - from dungeonbattle.entities.monsters import Beaver, Hedgehog, Rabbit, \ + from squirrelbattle.entities.player import Player + from squirrelbattle.entities.monsters import Beaver, Hedgehog, Rabbit, \ TeddyBear - from dungeonbattle.entities.items import Bomb, Heart + from squirrelbattle.entities.items import Bomb, Heart return { "Beaver": Beaver, "Bomb": Bomb, diff --git a/dungeonbattle/menus.py b/squirrelbattle/menus.py similarity index 100% rename from dungeonbattle/menus.py rename to squirrelbattle/menus.py diff --git a/dungeonbattle/settings.py b/squirrelbattle/settings.py similarity index 100% rename from dungeonbattle/settings.py rename to squirrelbattle/settings.py diff --git a/dungeonbattle/term_manager.py b/squirrelbattle/term_manager.py similarity index 100% rename from dungeonbattle/term_manager.py rename to squirrelbattle/term_manager.py diff --git a/dungeonbattle/tests/__init__.py b/squirrelbattle/tests/__init__.py similarity index 100% rename from dungeonbattle/tests/__init__.py rename to squirrelbattle/tests/__init__.py diff --git a/dungeonbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py similarity index 94% rename from dungeonbattle/tests/entities_test.py rename to squirrelbattle/tests/entities_test.py index b272541..6cf2fe4 100644 --- a/dungeonbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -1,9 +1,9 @@ import unittest -from dungeonbattle.entities.items import Bomb, Heart, Item -from dungeonbattle.entities.monsters import Beaver, Hedgehog, Rabbit, TeddyBear -from dungeonbattle.entities.player import Player -from dungeonbattle.interfaces import Entity, Map +from squirrelbattle.entities.items import Bomb, Heart, Item +from squirrelbattle.entities.monsters import Beaver, Hedgehog, Rabbit, TeddyBear +from squirrelbattle.entities.player import Player +from squirrelbattle.interfaces import Entity, Map class TestEntities(unittest.TestCase): @@ -11,7 +11,7 @@ class TestEntities(unittest.TestCase): """ Load example map that can be used in tests. """ - self.map = Map.load("resources/example_map.txt") + self.map = Map.load("squirrelbattle/assets/example_map.txt") self.player = Player() self.map.add_entity(self.player) self.player.move(self.map.start_y, self.map.start_x) diff --git a/dungeonbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py similarity index 96% rename from dungeonbattle/tests/game_test.py rename to squirrelbattle/tests/game_test.py index 8e7970d..424d86a 100644 --- a/dungeonbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -1,13 +1,13 @@ import os import unittest -from dungeonbattle.bootstrap import Bootstrap -from dungeonbattle.display.display import Display -from dungeonbattle.display.display_manager import DisplayManager -from dungeonbattle.entities.player import Player -from dungeonbattle.game import Game, KeyValues, GameMode -from dungeonbattle.menus import MainMenuValues -from dungeonbattle.settings import Settings +from squirrelbattle.bootstrap import Bootstrap +from squirrelbattle.display.display import Display +from squirrelbattle.display.display_manager import DisplayManager +from squirrelbattle.entities.player import Player +from squirrelbattle.game import Game, KeyValues, GameMode +from squirrelbattle.menus import MainMenuValues +from squirrelbattle.settings import Settings class TestGame(unittest.TestCase): diff --git a/dungeonbattle/tests/interfaces_test.py b/squirrelbattle/tests/interfaces_test.py similarity index 85% rename from dungeonbattle/tests/interfaces_test.py rename to squirrelbattle/tests/interfaces_test.py index b487eac..a55a7b3 100644 --- a/dungeonbattle/tests/interfaces_test.py +++ b/squirrelbattle/tests/interfaces_test.py @@ -1,7 +1,7 @@ import unittest -from dungeonbattle.display.texturepack import TexturePack -from dungeonbattle.interfaces import Map, Tile +from squirrelbattle.display.texturepack import TexturePack +from squirrelbattle.interfaces import Map, Tile class TestInterfaces(unittest.TestCase): @@ -18,7 +18,7 @@ class TestInterfaces(unittest.TestCase): """ Try to load a map from a file. """ - m = Map.load("resources/example_map.txt") + m = Map.load("squirrelbattle/assets/example_map.txt") self.assertEqual(m.width, 52) self.assertEqual(m.height, 17) diff --git a/dungeonbattle/tests/screen.py b/squirrelbattle/tests/screen.py similarity index 100% rename from dungeonbattle/tests/screen.py rename to squirrelbattle/tests/screen.py diff --git a/dungeonbattle/tests/settings_test.py b/squirrelbattle/tests/settings_test.py similarity index 96% rename from dungeonbattle/tests/settings_test.py rename to squirrelbattle/tests/settings_test.py index 9a56048..76bcaba 100644 --- a/dungeonbattle/tests/settings_test.py +++ b/squirrelbattle/tests/settings_test.py @@ -1,6 +1,6 @@ import unittest -from dungeonbattle.settings import Settings +from squirrelbattle.settings import Settings class TestSettings(unittest.TestCase): From a7afc0c577a34ba93d6e97cbb6063e38d2398f20 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 02:30:29 +0100 Subject: [PATCH 048/171] Prepare PyPI deployment --- .gitignore | 6 ++++++ setup.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 setup.py diff --git a/.gitignore b/.gitignore index 99e64f0..53db978 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,12 @@ venv/ .pytest_cache/ __pycache__ +*.pyc + +# Ignore build data +build/ +dist/ +*.egg-info/ # Don't commit settings settings.json diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..e0f8b35 --- /dev/null +++ b/setup.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +import os + +from setuptools import find_packages, setup + +with open("README.md", "r") as f: + long_description = f.read() + +setup( + name="squirrel-battle", + version="1.0.0", + author="ynerant", + author_email="ynerant@crans.org", + description="Watch out for squirrel's knifes!", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://gitlab.crans.org/ynerant/dungeon-battle", + packages=find_packages(), + license='GPLv3', + classifiers=[ + "Development Status :: 4 - Beta", + "Environment :: Console :: Curses", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Natural Language :: French", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Topic :: Games/Entertainment", + ], + python_requires='>=3.6', + include_package_data=True, + data_files=["squirrelbattle/assets/" + file + for file in os.listdir("squirrelbattle/assets")], + entry_points={ + "console_scripts": [ + "squirrel-battle = squirrelbattle.bootstrap:Bootstrap.run_game", + ] + } + +) From a8ea328460f0acffc0361e8f6c5b95e5c93ca827 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 02:41:42 +0100 Subject: [PATCH 049/171] Game name is Squirrel Battle --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 1e43e33..181f5f7 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,7 @@ deps = pytest pytest-cov commands = - pytest --cov=dungeonbattle/ --cov-report=term-missing dungeonbattle/ + pytest --cov=squirrelbattle/ --cov-report=term-missing squirrelbattle/ [testenv:linters] deps = @@ -23,7 +23,7 @@ deps = pep8-naming pyflakes commands = - flake8 main.py dungeonbattle + flake8 main.py squirrelbattle [flake8] ignore = W503 ANN002 ANN003 ANN101 ANN102 ANN204 ANN205 From d92a5e16290f4c138fe82389535ec9845b2eb789 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 02:47:36 +0100 Subject: [PATCH 050/171] Add only one interrogation mark in settings menu --- squirrelbattle/display/menudisplay.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 9108ac8..97fb789 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -64,7 +64,8 @@ class SettingsMenuDisplay(MenuDisplay): @property def values(self) -> List[str]: return [a[1][1] + (" : " - + ("?" if self.menu.waiting_for_key else a[1][0]) + + ("?" if self.menu.waiting_for_key + and a == self.menu.validate() else a[1][0]) if a[1][0] else "") for a in self.menu.values] From 00f843754a2372abfe06d9c8796a574e86c204a2 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 02:49:59 +0100 Subject: [PATCH 051/171] Use a ResourceManager to find assets --- squirrelbattle/display/menudisplay.py | 3 ++- squirrelbattle/game.py | 3 ++- squirrelbattle/resources.py | 13 +++++++++++++ squirrelbattle/tests/entities_test.py | 3 ++- squirrelbattle/tests/interfaces_test.py | 3 ++- 5 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 squirrelbattle/resources.py diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 97fb789..fca1ddf 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -2,6 +2,7 @@ from typing import List from squirrelbattle.menus import Menu, MainMenu from .display import Display +from ..resources import ResourceManager class MenuDisplay(Display): @@ -74,7 +75,7 @@ class MainMenuDisplay(Display): super().__init__(*args) self.menu = menu - with open("squirrelbattle/assets/ascii_art.txt", "r") as file: + with open(ResourceManager.get_asset_path("ascii_art.txt"), "r") as file: self.title = file.read().split("\n") self.pad = self.newpad(max(self.rows, len(self.title) + 30), diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 05da3e8..b22c4ed 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -7,6 +7,7 @@ import sys from .entities.player import Player from .enums import GameMode, KeyValues, DisplayActions from .interfaces import Map +from .resources import ResourceManager from .settings import Settings from . import menus from typing import Callable @@ -38,7 +39,7 @@ class Game: Create a new game on the screen. """ # TODO generate a new map procedurally - self.map = Map.load("squirrelbattle/assets/example_map_2.txt") + self.map = Map.load(ResourceManager.get_asset_path("example_map_2.txt")) self.player = Player() self.map.add_entity(self.player) self.player.move(self.map.start_y, self.map.start_x) diff --git a/squirrelbattle/resources.py b/squirrelbattle/resources.py new file mode 100644 index 0000000..c71c267 --- /dev/null +++ b/squirrelbattle/resources.py @@ -0,0 +1,13 @@ +from pathlib import Path + + +class ResourceManager: + """ + The ResourceManager loads resources at their right place, + and stores files in config directory. + """ + BASE_DIR = Path(__file__).resolve().parent / 'assets' + + @classmethod + def get_asset_path(cls, filename: str) -> str: + return str(cls.BASE_DIR / filename) diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 6cf2fe4..5cd6ad5 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -4,6 +4,7 @@ from squirrelbattle.entities.items import Bomb, Heart, Item from squirrelbattle.entities.monsters import Beaver, Hedgehog, Rabbit, TeddyBear from squirrelbattle.entities.player import Player from squirrelbattle.interfaces import Entity, Map +from squirrelbattle.resources import ResourceManager class TestEntities(unittest.TestCase): @@ -11,7 +12,7 @@ class TestEntities(unittest.TestCase): """ Load example map that can be used in tests. """ - self.map = Map.load("squirrelbattle/assets/example_map.txt") + self.map = Map.load(ResourceManager.get_asset_path("example_map.txt")) self.player = Player() self.map.add_entity(self.player) self.player.move(self.map.start_y, self.map.start_x) diff --git a/squirrelbattle/tests/interfaces_test.py b/squirrelbattle/tests/interfaces_test.py index a55a7b3..62f2092 100644 --- a/squirrelbattle/tests/interfaces_test.py +++ b/squirrelbattle/tests/interfaces_test.py @@ -2,6 +2,7 @@ import unittest from squirrelbattle.display.texturepack import TexturePack from squirrelbattle.interfaces import Map, Tile +from squirrelbattle.resources import ResourceManager class TestInterfaces(unittest.TestCase): @@ -18,7 +19,7 @@ class TestInterfaces(unittest.TestCase): """ Try to load a map from a file. """ - m = Map.load("squirrelbattle/assets/example_map.txt") + m = Map.load(ResourceManager.get_asset_path("example_map.txt")) self.assertEqual(m.width, 52) self.assertEqual(m.height, 17) From 0fbbf4925d496fdeea7e49e13671835ffb093aa9 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 03:13:01 +0100 Subject: [PATCH 052/171] Store configuration in user configuration directory --- squirrelbattle/game.py | 7 ++++--- squirrelbattle/resources.py | 8 ++++++++ squirrelbattle/settings.py | 9 ++++++--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index b22c4ed..f06843d 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -136,13 +136,14 @@ class Game: """ Loads the game from a file """ - if os.path.isfile("save.json"): - with open("save.json", "r") as f: + file_path = ResourceManager.get_config_path("save.json") + if os.path.isfile(file_path): + with open(file_path, "r") as f: self.load_state(json.loads(f.read())) def save_game(self) -> None: """ Saves the game to a file """ - with open("save.json", "w") as f: + with open(ResourceManager.get_config_path("save.json"), "w") as f: f.write(json.dumps(self.save_state())) diff --git a/squirrelbattle/resources.py b/squirrelbattle/resources.py index c71c267..fc6f708 100644 --- a/squirrelbattle/resources.py +++ b/squirrelbattle/resources.py @@ -7,7 +7,15 @@ class ResourceManager: and stores files in config directory. """ BASE_DIR = Path(__file__).resolve().parent / 'assets' + # FIXME This might not work on not-UNIX based systems. + CONFIG_DIR = Path.home() / '.config' / 'squirrel-battle' @classmethod def get_asset_path(cls, filename: str) -> str: return str(cls.BASE_DIR / filename) + + @classmethod + def get_config_path(cls, filename: str) -> str: + cls.CONFIG_DIR.mkdir(parents=True) if not cls.CONFIG_DIR.is_dir() \ + else None + return str(cls.CONFIG_DIR / filename) diff --git a/squirrelbattle/settings.py b/squirrelbattle/settings.py index 258d88f..6c2e31c 100644 --- a/squirrelbattle/settings.py +++ b/squirrelbattle/settings.py @@ -2,6 +2,8 @@ import json import os from typing import Any, Generator +from .resources import ResourceManager + class Settings: """ @@ -81,13 +83,14 @@ class Settings: """ Loads the settings from a file """ - if os.path.isfile("settings.json"): - with open("settings.json", "r") as f: + file_path = ResourceManager.get_config_path("settings.json") + if os.path.isfile(file_path): + with open(file_path, "r") as f: self.loads_from_string(f.read()) def write_settings(self) -> None: """ Dumps the settings into a file """ - with open("settings.json", "w") as f: + with open(ResourceManager.get_config_path("settings.json"), "w") as f: f.write(self.dumps_to_string()) From 082ac5e2608a7b531a66f150a2155690bdcd7a52 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 03:14:34 +0100 Subject: [PATCH 053/171] Start at version 3.14 --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index e0f8b35..29c391c 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ with open("README.md", "r") as f: setup( name="squirrel-battle", - version="1.0.0", + version="3.14", author="ynerant", author_email="ynerant@crans.org", description="Watch out for squirrel's knifes!", @@ -39,5 +39,4 @@ setup( "squirrel-battle = squirrelbattle.bootstrap:Bootstrap.run_game", ] } - ) From 926fba1e8292d8778bf835d1adb2dd4ad63d16ab Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 03:19:44 +0100 Subject: [PATCH 054/171] Update README to add PyPI installation --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c1d9b3..e34fcf8 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,20 @@ pour vérifier la syntaxe du code. ## Lancement du jeu -Il suffit d'exécuter `python3 main.py`. +Après clonage du projet, il suffit d'exécuter `python3 main.py`. + +Sinon, le jeu est déployé dans PyPI, et il suffit d'exécuter : + +``` +pip install squirrel-battle +``` + +pour télécharger et installer le jeu. Lancer `squirrel-battle` suffit ensuite +à lancer le jeu depuis n'importe où. Pour mettre à jour : + +``` +pip install --upgrade squirrel-battle +``` ## Gestion des émojis From b869331abb51ee75c3a3a8e84c5da37fc031a106 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 03:29:00 +0100 Subject: [PATCH 055/171] Make Debian package --- .gitlab-ci.yml | 13 +++++++++++++ debian/README.debian | 5 +++++ debian/changelog | 5 +++++ debian/compat | 1 + debian/control | 15 +++++++++++++++ debian/copyright | 29 +++++++++++++++++++++++++++++ debian/rules | 5 +++++ 7 files changed, 73 insertions(+) create mode 100644 debian/README.debian create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/rules diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5ca51af..eef21e0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,3 +31,16 @@ linters: - pip install tox script: tox -e linters allow_failure: true + +build-deb: + image: debian:buster-slim + stage: build + before_script: + - apt-get update && apt-get -y --no-install-recommends install build-essential debmake dh-python debhelper python3-all python3-setuptools + script: + - dpkg-buildpackage + - mkdir build && cp ../*.deb build/ + artifacts: + paths: + - build/*.deb + expire_in: 1 week diff --git a/debian/README.debian b/debian/README.debian new file mode 100644 index 0000000..8404efb --- /dev/null +++ b/debian/README.debian @@ -0,0 +1,5 @@ +Squirrel Battle + +Watch out for squirrel's knifes! + + -- Yohann D'ANELLO Thu, 19 Nov 2020 03:30:42 +0100 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..fbc646d --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +squirrel-battle (3.14) beta; urgency=low + + * Initial release. + + -- Yohann D'ANELLO Thu, 19 Nov 2020 03:30:42 +0100 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..9a03714 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +10 \ No newline at end of file diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..82f9581 --- /dev/null +++ b/debian/control @@ -0,0 +1,15 @@ +Source: squirrel-battle +Section: devel +Priority: optional +Maintainer: ynerant +Build-Depends: debhelper (>=10~), dh-python, python3-all, python3-setuptools +Standards-Version: 4.1.4 +Homepage: https://gitlab.crans.org/ynerant/dungeon-battle +X-Python3-Version: >= 3.6 + +Package: squirrel-battle +Architecture: all +Multi-Arch: foreign +Depends: ${misc:Depends}, ${python3:Depends} +Description: Squirrel Battle + Watch out for squirrel's knifes! \ No newline at end of file diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..275cafc --- /dev/null +++ b/debian/copyright @@ -0,0 +1,29 @@ + +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: shirenn +Upstream-Contact: shirenn +Source: https://gitlab.crans.org/pa/attestation + +Files: * +Copyright: 2020 Shirenn +License: GPL-3+ + This program is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later + version. + . + This program is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more + details. + . + You should have received a copy of the GNU General Public + License along with this package; if not, write to the Free + Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301 USA + . + On Debian systems, the full text of the GNU General Public + License version 3 can be found in the file + `/usr/share/common-licenses/GPL-3'. diff --git a/debian/rules b/debian/rules new file mode 100644 index 0000000..e29871f --- /dev/null +++ b/debian/rules @@ -0,0 +1,5 @@ +#!/usr/bin/make -f +export DH_VERBOSE = 1 + +%: + dh $@ --with python3 --buildsystem=pybuild From 49380d7c7a7c1f10b2131e076117730add806c4b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 03:41:28 +0100 Subject: [PATCH 056/171] Make Debian package --- debian/copyright | 8 ++++---- debian/rules | 0 dungeonbattle/__init__.pyc | Bin 176 -> 0 bytes dungeonbattle/bootstrap.pyc | Bin 1075 -> 0 bytes 4 files changed, 4 insertions(+), 4 deletions(-) mode change 100644 => 100755 debian/rules delete mode 100644 dungeonbattle/__init__.pyc delete mode 100644 dungeonbattle/bootstrap.pyc diff --git a/debian/copyright b/debian/copyright index 275cafc..0ae9c45 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,11 +1,11 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: shirenn -Upstream-Contact: shirenn -Source: https://gitlab.crans.org/pa/attestation +Upstream-Name: Yohann D'ANELLO +Upstream-Contact: Yohann D'ANELLO +Source: https://gitlab.crans.org/ynerant/dungeon-battle Files: * -Copyright: 2020 Shirenn +Copyright: 2020 Yohann D'ANELLO License: GPL-3+ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public diff --git a/debian/rules b/debian/rules old mode 100644 new mode 100755 diff --git a/dungeonbattle/__init__.pyc b/dungeonbattle/__init__.pyc deleted file mode 100644 index c5124407892592c0feed40fde1506c5cf0dcda29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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_ diff --git a/dungeonbattle/bootstrap.pyc b/dungeonbattle/bootstrap.pyc deleted file mode 100644 index 80c152bc030ab5343a9311a65911f7db09afefab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 Date: Thu, 19 Nov 2020 03:45:36 +0100 Subject: [PATCH 057/171] Debian package depends on fonts-noto-color-emoji --- debian/control | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 82f9581..658a619 100644 --- a/debian/control +++ b/debian/control @@ -3,6 +3,7 @@ Section: devel Priority: optional Maintainer: ynerant Build-Depends: debhelper (>=10~), dh-python, python3-all, python3-setuptools +Depends: fonts-noto-color-emoji Standards-Version: 4.1.4 Homepage: https://gitlab.crans.org/ynerant/dungeon-battle X-Python3-Version: >= 3.6 @@ -10,6 +11,6 @@ X-Python3-Version: >= 3.6 Package: squirrel-battle Architecture: all Multi-Arch: foreign -Depends: ${misc:Depends}, ${python3:Depends} +Depends: fonts-noto-color-emoji, ${python3:Depends} Description: Squirrel Battle Watch out for squirrel's knifes! \ No newline at end of file From 58a841150b7699e5ba3c65cc71c2de2834bb4274 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 03:54:54 +0100 Subject: [PATCH 058/171] Rename debian package to python3-squirrelbattle --- debian/control | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/control b/debian/control index 658a619..6a1e372 100644 --- a/debian/control +++ b/debian/control @@ -1,4 +1,4 @@ -Source: squirrel-battle +Source: python3-squirrelbattle Section: devel Priority: optional Maintainer: ynerant @@ -8,7 +8,7 @@ Standards-Version: 4.1.4 Homepage: https://gitlab.crans.org/ynerant/dungeon-battle X-Python3-Version: >= 3.6 -Package: squirrel-battle +Package: python3-squirrelbattle Architecture: all Multi-Arch: foreign Depends: fonts-noto-color-emoji, ${python3:Depends} From 20a9743b9fdf63b7419e3a5c3b012139e9ffa042 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 04:01:02 +0100 Subject: [PATCH 059/171] Add build Gitlab CI stage --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index eef21e0..6cb844c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,7 @@ stages: - test - quality-assurance + - build py37: stage: test From d4b75bac236e2e352295b94528ed26b1a0ea57de Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 04:03:35 +0100 Subject: [PATCH 060/171] Wrong app name in Debian changelog --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index fbc646d..7686c57 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -squirrel-battle (3.14) beta; urgency=low +python3-squirrelbattle (3.14) beta; urgency=low * Initial release. From 6065f5a0be5d05ec9840349098bee43f66f23612 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 04:45:44 +0100 Subject: [PATCH 061/171] Rename project to squirrel-battle --- README.md | 8 ++++---- debian/control | 2 +- debian/copyright | 2 +- setup.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e34fcf8..c84e9de 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![pipeline status](https://gitlab.crans.org/ynerant/dungeon-battle/badges/master/pipeline.svg)](https://gitlab.crans.org/ynerant/dungeon-battle/-/commits/master) -[![coverage report](https://gitlab.crans.org/ynerant/dungeon-battle/badges/master/coverage.svg)](https://gitlab.crans.org/ynerant/dungeon-battle/-/commits/master) +[![pipeline status](https://gitlab.crans.org/ynerant/squirrel-battle/badges/master/pipeline.svg)](https://gitlab.crans.org/ynerant/squirrel-battle/-/commits/master) +[![coverage report](https://gitlab.crans.org/ynerant/squirrel-battle/badges/master/coverage.svg)](https://gitlab.crans.org/ynerant/squirrel-battle/-/commits/master) # Squirrel Battle @@ -21,7 +21,7 @@ Il est toujours préférable de travailler dans un environnement Python isolé d 2. **Clonage du dépot** là où vous voulez : ```bash - $ git clone git@gitlab.crans.org:ynerant/dungeon-battle.git && cd dungeon-battle + $ git clone git@gitlab.crans.org:ynerant/squirrel-battle.git && cd squirrel-battle ``` 3. **Création d'un environment de travail Python décorrélé du système.** @@ -36,7 +36,7 @@ Il est toujours préférable de travailler dans un environnement Python isolé d ### Exécution des tests -Les tests sont gérés par `pytest` dans le module `dungeonbattle.tests`. +Les tests sont gérés par `pytest` dans le module `squirrelbattle.tests`. `tox` est un outil permettant de configurer l'exécution des tests. Ainsi, après installation de tox dans votre environnement virtuel via `pip install tox`, diff --git a/debian/control b/debian/control index 6a1e372..cbd3846 100644 --- a/debian/control +++ b/debian/control @@ -5,7 +5,7 @@ Maintainer: ynerant Build-Depends: debhelper (>=10~), dh-python, python3-all, python3-setuptools Depends: fonts-noto-color-emoji Standards-Version: 4.1.4 -Homepage: https://gitlab.crans.org/ynerant/dungeon-battle +Homepage: https://gitlab.crans.org/ynerant/squirrel-battle X-Python3-Version: >= 3.6 Package: python3-squirrelbattle diff --git a/debian/copyright b/debian/copyright index 0ae9c45..c616d56 100644 --- a/debian/copyright +++ b/debian/copyright @@ -2,7 +2,7 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: Yohann D'ANELLO Upstream-Contact: Yohann D'ANELLO -Source: https://gitlab.crans.org/ynerant/dungeon-battle +Source: https://gitlab.crans.org/ynerant/squirrel-battle Files: * Copyright: 2020 Yohann D'ANELLO diff --git a/setup.py b/setup.py index 29c391c..f2c27bc 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( description="Watch out for squirrel's knifes!", long_description=long_description, long_description_content_type="text/markdown", - url="https://gitlab.crans.org/ynerant/dungeon-battle", + url="https://gitlab.crans.org/ynerant/squirrel-battle", packages=find_packages(), license='GPLv3', classifiers=[ From 444472437e4ffd1f8bc9f7860a3e838b72298d11 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 05:05:29 +0100 Subject: [PATCH 062/171] Don't use sitepackages for tox --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 181f5f7..1d2f15a 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ envlist = skipsdist = True [testenv] -sitepackages = True +sitepackages = False deps = -r{toxinidir}/requirements.txt pytest From cbeafdc380033da7925093ebb57318c31e1ee3d1 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 05:17:22 +0100 Subject: [PATCH 063/171] Squirrel Battle is in the AUR --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index c84e9de..44dbeca 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,9 @@ pour télécharger et installer le jeu. Lancer `squirrel-battle` suffit ensuite pip install --upgrade squirrel-battle ``` +Sous Arch Linux, le paquet `python-squirrel-battle-git` dans l'AUR permet +également d'installer directement le jeu. + ## Gestion des émojis Le jeu dispose de deux modes graphiques : en mode `ascii` et `squirrel`. From 6e71146aa2e81917f328983d7078fdd9cf9eeaf0 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Thu, 19 Nov 2020 12:03:05 +0100 Subject: [PATCH 064/171] First pass on the logs The newly-added logs manage a list of messages. Entities do register a message to it when hitting each other. Display is created, but not yet added to the layout actually displayed. --- squirrelbattle/display/display_manager.py | 6 ++++- squirrelbattle/display/logsdisplay.py | 17 +++++++++++++ squirrelbattle/entities/monsters.py | 2 +- squirrelbattle/entities/player.py | 2 +- squirrelbattle/game.py | 4 ++- squirrelbattle/interfaces.py | 30 ++++++++++++++++++++--- 6 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 squirrelbattle/display/logsdisplay.py diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 9ec0f88..408086c 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -3,6 +3,7 @@ from squirrelbattle.display.mapdisplay import MapDisplay from squirrelbattle.display.statsdisplay import StatsDisplay from squirrelbattle.display.menudisplay import SettingsMenuDisplay, \ MainMenuDisplay +from squirrelbattle.display.logsdisplay import LogsDisplay from squirrelbattle.display.texturepack import TexturePack from typing import Any from squirrelbattle.game import Game, GameMode @@ -20,8 +21,10 @@ class DisplayManager: self.mainmenudisplay = MainMenuDisplay(self.game.main_menu, screen, pack) self.settingsmenudisplay = SettingsMenuDisplay(screen, pack) + self.logsdisplay = LogsDisplay(screen, pack) self.displays = [self.statsdisplay, self.mapdisplay, - self.mainmenudisplay, self.settingsmenudisplay] + self.mainmenudisplay, self.settingsmenudisplay, + self.logsdisplay] self.update_game_components() def handle_display_action(self, action: DisplayActions) -> None: @@ -36,6 +39,7 @@ class DisplayManager: self.mapdisplay.update_map(self.game.map) self.statsdisplay.update_player(self.game.player) self.settingsmenudisplay.update_menu(self.game.settings_menu) + self.logsdisplay.update_logs(self.game.logs) def refresh(self) -> None: if self.game.state == GameMode.PLAY: diff --git a/squirrelbattle/display/logsdisplay.py b/squirrelbattle/display/logsdisplay.py new file mode 100644 index 0000000..bacbbf7 --- /dev/null +++ b/squirrelbattle/display/logsdisplay.py @@ -0,0 +1,17 @@ +from squirrelbattle.display.display import Display +from squirrelbattle.interfaces import Logs + + +class LogsDisplay(Display): + + def __init__(self, *args) -> None: + super().__init__(*args) + self.pad = self.newpad(self.rows, self.cols) + + def update_logs(self, logs: Logs) -> None: + self.logs = logs + + def display(self) -> None: + messages = self.logs.messages[-self.height:].reverse() + for i, y in enumerate(range(self.y + self.height - 1, self.y - 1, - 1)): + self.pad.addstr(y, self.x, messages[i][:self.width]) diff --git a/squirrelbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py index 1f04372..8192b8e 100644 --- a/squirrelbattle/entities/monsters.py +++ b/squirrelbattle/entities/monsters.py @@ -44,7 +44,7 @@ class Monster(FightingEntity): next_y, next_x = target.paths[(self.y, self.x)] moved = self.check_move(next_y, next_x, True) if not moved and self.distance_squared(target) <= 1: - self.hit(target) + self.map.logs.add_message(self.hit(target)) else: for _ in range(100): if choice([self.move_up, self.move_down, diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 873da32..1f9ff0e 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -70,7 +70,7 @@ class Player(FightingEntity): for entity in self.map.entities: if entity.y == y and entity.x == x: if entity.is_fighting_entity(): - self.hit(entity) + self.map.logs.add_message(self.hit(entity)) if entity.dead: self.add_xp(randint(3, 7)) return True diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index f06843d..320ecff 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -6,7 +6,7 @@ import sys from .entities.player import Player from .enums import GameMode, KeyValues, DisplayActions -from .interfaces import Map +from .interfaces import Map, Logs from .resources import ResourceManager from .settings import Settings from . import menus @@ -33,6 +33,7 @@ class Game: self.settings.load_settings() self.settings.write_settings() self.settings_menu.update_values(self.settings) + self.logs = Logs() def new_game(self) -> None: """ @@ -40,6 +41,7 @@ class Game: """ # TODO generate a new map procedurally self.map = Map.load(ResourceManager.get_asset_path("example_map_2.txt")) + self.map.logs = self.logs self.player = Player() self.map.add_entity(self.player) self.player.move(self.map.start_y, self.map.start_x) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 10001fb..cf94aed 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -7,6 +7,26 @@ from typing import List, Optional from squirrelbattle.display.texturepack import TexturePack +class Logs: + """ + The logs object stores the messages to display. It is encapsulating a list + of such messages, to allow multiple pointers to keep track of it even if + the list was to be reassigned. + """ + + def __init__(self) -> None: + self.messages = [] + + def add_message(self, msg: str) -> None: + self.messages.append(msg) + + def add_messages(self, msg: List[str]) -> None: + self.messages += msg + + def clear(self) -> None: + self.messages = [] + + class Map: """ Object that represents a Map with its width, height @@ -18,6 +38,7 @@ class Map: start_x: int tiles: List[List["Tile"]] entities: List["Entity"] + logs: Logs # coordinates of the point that should be # on the topleft corner of the screen currentx: int @@ -362,19 +383,22 @@ class FightingEntity(Entity): def dead(self) -> bool: return self.health <= 0 - def hit(self, opponent: "FightingEntity") -> None: + def hit(self, opponent: "FightingEntity") -> str: """ Deals damage to the opponent, based on the stats """ - opponent.take_damage(self, self.strength) + return f"{self.name} hits {opponent.name}. "\ + + opponent.take_damage(self, self.strength) - def take_damage(self, attacker: "Entity", amount: int) -> None: + def take_damage(self, attacker: "Entity", amount: int) -> str: """ Take damage from the attacker, based on the stats """ self.health -= amount if self.health <= 0: self.die() + return f"{self.name} takes {amount} damage."\ + + (f" {self.name} dies." if self.health <= 0 else "") def die(self) -> None: """ From 589f825765b216388f1830e5e1e381d249e568ab Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Thu, 19 Nov 2020 12:55:06 +0100 Subject: [PATCH 065/171] Added logs to the layout, clear logs on new game (tests still aren't fixed) --- squirrelbattle/display/display.py | 2 +- squirrelbattle/display/display_manager.py | 4 +++- squirrelbattle/display/logsdisplay.py | 11 ++++++++--- squirrelbattle/game.py | 1 + 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 47e4bce..1e47189 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -33,7 +33,7 @@ class Display: self.width = width self.height = height if hasattr(self, "pad") and resize_pad: - self.pad.resize(self.height - 1, self.width - 1) + self.pad.resize(self.height, self.width) def refresh(self, *args, resize_pad: bool = True) -> None: if len(args) == 4: diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 408086c..a4636eb 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -47,7 +47,9 @@ class DisplayManager: self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, self.cols, resize_pad=False) self.statsdisplay.refresh(self.rows * 4 // 5, 0, - self.rows // 5, self.cols) + self.rows // 10, self.cols) + self.logsdisplay.refresh(self.rows * 9 // 10, 0, + self.rows // 10, self.cols) if self.game.state == GameMode.MAINMENU: self.mainmenudisplay.refresh(0, 0, self.rows, self.cols) if self.game.state == GameMode.SETTINGS: diff --git a/squirrelbattle/display/logsdisplay.py b/squirrelbattle/display/logsdisplay.py index bacbbf7..36adcaa 100644 --- a/squirrelbattle/display/logsdisplay.py +++ b/squirrelbattle/display/logsdisplay.py @@ -12,6 +12,11 @@ class LogsDisplay(Display): self.logs = logs def display(self) -> None: - messages = self.logs.messages[-self.height:].reverse() - for i, y in enumerate(range(self.y + self.height - 1, self.y - 1, - 1)): - self.pad.addstr(y, self.x, messages[i][:self.width]) + print(type(self.logs.messages), flush=True) + messages = self.logs.messages[-self.height:] + messages = messages[::-1] + self.pad.clear() + for i in range(min(self.height, len(messages))): + self.pad.addstr(self.height - i - 1, self.x, messages[i][:self.width]) + self.pad.refresh(0, 0, self.y, self.x, self.y + self.height, + self.x + self.width) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 320ecff..0bb3024 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -42,6 +42,7 @@ class Game: # TODO generate a new map procedurally self.map = Map.load(ResourceManager.get_asset_path("example_map_2.txt")) self.map.logs = self.logs + self.logs.clear() self.player = Player() self.map.add_entity(self.player) self.player.move(self.map.start_y, self.map.start_x) From a99e4a27e0e4c82611ef99c122229fbbba17ba7d Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 15:03:43 +0100 Subject: [PATCH 066/171] Begin Sphinx documentation --- .gitignore | 3 +++ docs/Makefile | 20 +++++++++++++++ docs/conf.py | 59 +++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 20 +++++++++++++++ docs/requirements.txt | 1 + 5 files changed, 103 insertions(+) create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/requirements.txt diff --git a/.gitignore b/.gitignore index 53db978..f30aa49 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ settings.json # Don't commit game save save.json + +# Don't commit docs output +docs/_build diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..bcd87db --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,59 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'Squirrel Battle' +copyright = "2020" +author = "Yohann D'ANELLO, Mathilde DEPRÈS, Nicolas MARGULIES, Charles PEYRAT" + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'fr' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build'] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..2c73f2e --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,20 @@ +.. Squirrel Battle documentation master file, created by + sphinx-quickstart on Thu Nov 19 15:00:26 2020. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Squirrel Battle's documentation! +=========================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..6966869 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +sphinx From f83e712948e23d25f369a745d53bdbcf11c059cc Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 15:21:10 +0100 Subject: [PATCH 067/171] Try to have an index page --- docs/index.rst | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 2c73f2e..eb45600 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,9 +1,4 @@ -.. Squirrel Battle documentation master file, created by - sphinx-quickstart on Thu Nov 19 15:00:26 2020. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to Squirrel Battle's documentation! +Bienvenue dans la documentation de Squirrel Battle ! =========================================== .. toctree:: From a784c2590132434faa5893ead877381279782adc Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 15:36:44 +0100 Subject: [PATCH 068/171] Add Emoji troubleshooting page --- docs/index.rst | 2 +- docs/troubleshooting.rst | 59 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 docs/troubleshooting.rst diff --git a/docs/index.rst b/docs/index.rst index eb45600..cc0d0f7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,5 +1,5 @@ Bienvenue dans la documentation de Squirrel Battle ! -=========================================== +==================================================== .. toctree:: :maxdepth: 2 diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst new file mode 100644 index 0000000..0afb0e5 --- /dev/null +++ b/docs/troubleshooting.rst @@ -0,0 +1,59 @@ +#################### +Résolution d'erreurs +#################### + +====== +Émojis +====== + +Le jeu s'exécutant en terminal, il est courant d'obtenir des problèmes d'affichage. +Sous Windows, les émojis s'affichent normalement correctement. Il suffit en +général d'installer les bons paquets de police. + +--------------- +Sous Arch Linux +--------------- + +Il est recommandé d'utiliser le terminal `xfce4-terminal`. Il suffit d'installer +le paquets de polices : + +.. code:: bash + + sudo pacman -Sy noto-fonts-emoji + +Le jeu doit ensuite se lancer normalement sans action supplémentaire. + +------------------ +Sous Ubuntu/Debian +------------------ + +À nouveau, le terminal `xfce4-terminal` est recommandé. Le paquet +`fonts-noto-color-emoji`. Toutefois, le rythme de mise à jour de Debian étant +lent, le paquet le plus récent ne contient pas tous les émojis. Sur Debian, +il faudra donc installer le paquet le plus récent, ce qui fonctionne sans +dépendance supplémentaire : + + +.. code:: bash + + wget http://ftp.fr.debian.org/debian/pool/main/f/fonts-noto-color-emoji/fonts-noto-color-emoji_0~20200916-1_all.deb + dpkg -i fonts-noto-color-emoji_0~20200916-1_all.deb + rm fonts-noto-color-emoji_0~20200916-1_all.deb + +Il reste le problème de l'écureuil. Sous Ubuntu et Debian, le caractère écureuil +existe déjà, mais ne s'affiche pas proprement. On peut appliquer un patch qui +permet d'afficher les émojis correctement dans son terminal. Pour cela, il +suffit de faire : + +.. code:: bash + + ln -s $PWD/fix-squirrel-emojis.conf /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf + ln -s /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf /etc/fonts/conf.d/75-fix-squirrel-emojis.conf + +Après redémarrage du terminal, l'écureuil devrait s'afficher correctement. + +Pour supprimer le patch : + +.. code:: bash + + rm /etc/fonts/conf.d/75-fix-squirrel-emojis.conf From 18fad64b1ac07bdf2e21869471591df97dbe063a Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 15:56:22 +0100 Subject: [PATCH 069/171] Use ReadTheDocs doc theme --- docs/conf.py | 3 ++- docs/requirements.txt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index bcd87db..7be9f27 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,6 +28,7 @@ author = "Yohann D'ANELLO, Mathilde DEPRÈS, Nicolas MARGULIES, Charles PEYRAT" # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + "sphinx_rtd_theme", ] # Add any paths that contain templates here, relative to this directory. @@ -51,7 +52,7 @@ exclude_patterns = ['_build'] # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, diff --git a/docs/requirements.txt b/docs/requirements.txt index 6966869..cbf1e36 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1,2 @@ sphinx +sphinx-rtd-theme From 75fd97c028feb87f80bad92824c7bf6babc23c5b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 16:15:12 +0100 Subject: [PATCH 070/171] Add table of content tree --- docs/index.rst | 11 ++--------- docs/troubleshooting.rst | 12 ++++-------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index cc0d0f7..b3d7625 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,13 +3,6 @@ Bienvenue dans la documentation de Squirrel Battle ! .. toctree:: :maxdepth: 2 - :caption: Contents: + :caption: Squirrel Battle - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` + troubleshooting diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index 0afb0e5..3f401f8 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -1,18 +1,15 @@ -#################### Résolution d'erreurs -#################### +==================== -====== Émojis -====== +------ Le jeu s'exécutant en terminal, il est courant d'obtenir des problèmes d'affichage. Sous Windows, les émojis s'affichent normalement correctement. Il suffit en général d'installer les bons paquets de police. ---------------- Sous Arch Linux ---------------- +^^^^^^^^^^^^^^^ Il est recommandé d'utiliser le terminal `xfce4-terminal`. Il suffit d'installer le paquets de polices : @@ -23,9 +20,8 @@ le paquets de polices : Le jeu doit ensuite se lancer normalement sans action supplémentaire. ------------------- Sous Ubuntu/Debian ------------------- +^^^^^^^^^^^^^^^^^^ À nouveau, le terminal `xfce4-terminal` est recommandé. Le paquet `fonts-noto-color-emoji`. Toutefois, le rythme de mise à jour de Debian étant From a3821676f4d29a0665c616cd033ad94a99fcd67e Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Thu, 19 Nov 2020 16:17:02 +0100 Subject: [PATCH 071/171] The strength of the player is now increased when he levels up --- squirrelbattle/entities/player.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 873da32..a36f21e 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -28,7 +28,8 @@ class Player(FightingEntity): def move(self, y: int, x: int) -> None: """ - When the player moves, move the camera of the map. + Moves the view of the map (the point on which the camera is centered) + according to the moves of the player. """ super().move(y, x) self.map.currenty = y @@ -44,6 +45,7 @@ class Player(FightingEntity): self.current_xp -= self.max_xp self.max_xp = self.level * 10 self.health = self.maxhealth + self.strength = self.strength + 1 # TODO Remove it, that's only fun self.map.spawn_random_entities(randint(3 * self.level, 10 * self.level)) @@ -62,7 +64,7 @@ class Player(FightingEntity): """ If the player tries to move but a fighting entity is there, the player fights this entity. - It rewards some XP if it is dead. + If the entity dies, the player is rewarded with some XP """ # Don't move if we are dead if self.dead: From 1ec24a1fa877f0c310746e2d325243e9c1d97241 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 17:46:24 +0100 Subject: [PATCH 072/171] Include emoji fix in Debian package --- debian/postinst | 3 +++ debian/prerm | 3 +++ 2 files changed, 6 insertions(+) create mode 100755 debian/postinst create mode 100755 debian/prerm diff --git a/debian/postinst b/debian/postinst new file mode 100755 index 0000000..b026ba1 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,3 @@ +#!/bin/sh +cp fix-squirrel-emojis.conf /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf +ln -s /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf /etc/fonts/conf.d/75-fix-squirrel-emojis.conf diff --git a/debian/prerm b/debian/prerm new file mode 100755 index 0000000..2dc2425 --- /dev/null +++ b/debian/prerm @@ -0,0 +1,3 @@ +#!/bin/sh +rm /etc/fonts/conf.d/75-fix-squirrel-emojis.conf +rm /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf From 2e95edab83aa0505410189692cbeda924d262e22 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 19:00:23 +0100 Subject: [PATCH 073/171] Fix emoji fix for Debian-based systems installation --- .../75-fix-squirrel-emojis.conf | 0 debian/changelog | 2 +- debian/control | 4 ++-- debian/install | 2 ++ debian/postinst | 3 --- debian/prerm | 3 --- 6 files changed, 5 insertions(+), 9 deletions(-) rename fix-squirrel-emojis.conf => debian/75-fix-squirrel-emojis.conf (100%) create mode 100644 debian/install delete mode 100755 debian/postinst delete mode 100755 debian/prerm diff --git a/fix-squirrel-emojis.conf b/debian/75-fix-squirrel-emojis.conf similarity index 100% rename from fix-squirrel-emojis.conf rename to debian/75-fix-squirrel-emojis.conf diff --git a/debian/changelog b/debian/changelog index 7686c57..d1afdad 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -python3-squirrelbattle (3.14) beta; urgency=low +python3-squirrel-battle (3.14) beta; urgency=low * Initial release. diff --git a/debian/control b/debian/control index cbd3846..e36e424 100644 --- a/debian/control +++ b/debian/control @@ -1,4 +1,4 @@ -Source: python3-squirrelbattle +Source: python3-squirrel-battle Section: devel Priority: optional Maintainer: ynerant @@ -8,7 +8,7 @@ Standards-Version: 4.1.4 Homepage: https://gitlab.crans.org/ynerant/squirrel-battle X-Python3-Version: >= 3.6 -Package: python3-squirrelbattle +Package: python3-squirrel-battle Architecture: all Multi-Arch: foreign Depends: fonts-noto-color-emoji, ${python3:Depends} diff --git a/debian/install b/debian/install new file mode 100644 index 0000000..628e923 --- /dev/null +++ b/debian/install @@ -0,0 +1,2 @@ +debian/75-fix-squirrel-emojis.conf etc/fonts/conf.avail +debian/75-fix-squirrel-emojis.conf etc/fonts/conf.d \ No newline at end of file diff --git a/debian/postinst b/debian/postinst deleted file mode 100755 index b026ba1..0000000 --- a/debian/postinst +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -cp fix-squirrel-emojis.conf /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf -ln -s /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf /etc/fonts/conf.d/75-fix-squirrel-emojis.conf diff --git a/debian/prerm b/debian/prerm deleted file mode 100755 index 2dc2425..0000000 --- a/debian/prerm +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -rm /etc/fonts/conf.d/75-fix-squirrel-emojis.conf -rm /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf From a8e784ef4efecae8757d83822e16c4e72f5bec37 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 19:29:55 +0100 Subject: [PATCH 074/171] Declare package data in setup.py --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index f2c27bc..dfb15ef 100644 --- a/setup.py +++ b/setup.py @@ -32,8 +32,7 @@ setup( ], python_requires='>=3.6', include_package_data=True, - data_files=["squirrelbattle/assets/" + file - for file in os.listdir("squirrelbattle/assets")], + package_data={"squirrelbattle": ["assets/*"]}, entry_points={ "console_scripts": [ "squirrel-battle = squirrelbattle.bootstrap:Bootstrap.run_game", From c6453174e584f51bea5311755db51933c4cc5baf Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 19:50:01 +0100 Subject: [PATCH 075/171] Installation documentation --- docs/index.rst | 11 ++++- docs/install-dev.rst | 31 ++++++++++++++ docs/install.rst | 90 ++++++++++++++++++++++++++++++++++++++++ docs/troubleshooting.rst | 5 ++- 4 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 docs/install-dev.rst create mode 100644 docs/install.rst diff --git a/docs/index.rst b/docs/index.rst index b3d7625..5f45fde 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,7 +2,14 @@ Bienvenue dans la documentation de Squirrel Battle ! ==================================================== .. toctree:: - :maxdepth: 2 - :caption: Squirrel Battle + :maxdepth: 3 + :caption: Développer + install-dev + +.. toctree:: + :maxdepth: 3 + :caption: Jouer + + install troubleshooting diff --git a/docs/install-dev.rst b/docs/install-dev.rst new file mode 100644 index 0000000..db611e0 --- /dev/null +++ b/docs/install-dev.rst @@ -0,0 +1,31 @@ +Installation d'un environnement de développement +================================================ + +Il est toujours préférable de travailler dans un environnement Python isolé du reste de son instalation. + +1. **Installation des dépendances de la distribution.** + Vous devez déjà installer Python et le module qui permet de créer des environnements virtuels. + On donne ci-dessous l'exemple pour une distribution basée sur Debian, mais vous pouvez facilement adapter pour ArchLinux ou autre. + +.. code:: bash + + $ sudo apt update + $ sudo apt install --no-install-recommends -y python3-setuptools python3-venv python3-dev git + +2. **Clonage du dépot** là où vous voulez : + +.. code:: bash + + $ git clone git@gitlab.crans.org:ynerant/squirrel-battle.git && cd squirrel-battle + +3. **Création d'un environment de travail Python décorrélé du système.** + On n'utilise pas `--system-site-packages` ici pour ne pas avoir des clashs de versions de modules avec le système. + +.. code:: bash + + $ python3 -m venv env + $ source env/bin/activate # entrer dans l'environnement + (env)$ pip3 install -r requirements.txt + (env)$ deactivate # sortir de l'environnement + +Le lancement du jeu se fait en lançant la commande ``python3 main.py``. \ No newline at end of file diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 0000000..0b2e7ff --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,90 @@ +Installation client +=================== + +Installation +------------ + +Différents paquets sont déployés, dans PyPI pour tout système utilisant Python, +un paquet Debian et un paquet Arch Linux. + +Depuis PIP +~~~~~~~~~~ + +.. _PyPI: https://pypi.org/project/squirrel-battle/ + +Le projet `Squirrel Battle` est déployé dans PyPI_. Il suffit d'installer +Squirrel Battle en exécutant : + +.. code:: bash + + pip install --user squirrel-battle + +Les mises à jour s'obtiennent également via PIP en exécutant : + +.. code:: bash + + pip install --user --upgrade squirrel-battle + +Le jeu peut se lancer ensuite en exécutant la commande ``squirrel-battle``. + +Toutefois, le paquet PyPI n'inclut pas les polices d'émojis. Il est recommandé +d'installer des polices telles que ``noto-fonts-emoji`` afin de prendre en charge +les émojis dans votre terminal. + + +Sur Arch Linux +~~~~~~~~~~~~~~ + +.. _AUR: https://aur.archlinux.org/ +.. _python-squirrel-battle: https://aur.archlinux.org/packages/python-squirrel-battle/ +.. _python-squirrel-battle-git: https://aur.archlinux.org/packages/python-squirrel-battle-git/ +.. _yay: https://aur.archlinux.org/packages/yay/ + +Deux paquets sont publiés dans l'AUR_ (Arch User Repository) : + +- python-squirrel-battle_ +- python-squirrel-battle-git_ + +Le premier paquet est mis à jour à chaque nouvelle version déployée, le second +est utile pour des fins de développement et est en permanence à jour +avec la branche ``master`` du Git. + +Les deux ne sont pas présents dans les dépôts officiels de Arch Linux, mais vous +pouvez les récupérer avec un outil tel que yay_. + +Les paquets incluent la dépendance ``noto-fonts-emoji``, qui permet d'afficher +les émojis dans le terminal. + +Le jeu peut être ensuite lancé via la commande ``squirrel-battle``. + + +Sur Ubuntu/Debian +^^^^^^^^^^^^^^^^^ + +.. _paquet: https://gitlab.crans.org/ynerant/squirrel-battle/-/jobs/artifacts/master/raw/build/python3-squirrelbattle_3.14_all.deb?job=build-deb + +Un paquet_ est généré par l'intégration continue de Gitlab à chaque commit. +Ils sont également attachés à chaque nouvelle release. + +Il dépend du paquet ``fonts-noto-color-emoji``, permettant d'afficher les émojis +dans le terminal. Il peut être installé via APT normalement sur une distribution +récente, toutefois sur les versions les plus vieilles, incluant Debian Buster, +certains émojis n'apparaissent pas. Il est essentiel de maintenir ce paquet à +jour. Pour installer manuellement la dernière version de ce paquet, +il suffit d'exécuter : + +.. code:: bash + + wget http://ftp.fr.debian.org/debian/pool/main/f/fonts-noto-color-emoji/fonts-noto-color-emoji_0~20200916-1_all.deb + dpkg -i fonts-noto-color-emoji_0~20200916-1_all.deb + rm fonts-noto-color-emoji_0~20200916-1_all.deb + +Pour installer ce paquet, il suffit de le télécharger et d'appeler ``dpkg`` : + +.. code:: bash + + dpkg -i python3-squirrelbattle_3.14_all.deb + +Ce paquet inclut un patch pour afficher les émojis écureuil correctement. + +Après cela, le jeu peut être lancé grâce à la commande ``squirrel-battle``. diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index 3f401f8..8a26c19 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -29,7 +29,6 @@ lent, le paquet le plus récent ne contient pas tous les émojis. Sur Debian, il faudra donc installer le paquet le plus récent, ce qui fonctionne sans dépendance supplémentaire : - .. code:: bash wget http://ftp.fr.debian.org/debian/pool/main/f/fonts-noto-color-emoji/fonts-noto-color-emoji_0~20200916-1_all.deb @@ -43,7 +42,7 @@ suffit de faire : .. code:: bash - ln -s $PWD/fix-squirrel-emojis.conf /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf + ln -s $PWD/debian/75-fix-squirrel-emojis.conf /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf ln -s /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf /etc/fonts/conf.d/75-fix-squirrel-emojis.conf Après redémarrage du terminal, l'écureuil devrait s'afficher correctement. @@ -53,3 +52,5 @@ Pour supprimer le patch : .. code:: bash rm /etc/fonts/conf.d/75-fix-squirrel-emojis.conf + +À noter que ce patch est inclus dans le paquet Debian. From e4b8cc0654f431094b459757ae615d163a6e18ad Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 19:52:24 +0100 Subject: [PATCH 076/171] Fix section heading --- docs/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/install.rst b/docs/install.rst index 0b2e7ff..d73b5a9 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -59,7 +59,7 @@ Le jeu peut être ensuite lancé via la commande ``squirrel-battle``. Sur Ubuntu/Debian -^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~ .. _paquet: https://gitlab.crans.org/ynerant/squirrel-battle/-/jobs/artifacts/master/raw/build/python3-squirrelbattle_3.14_all.deb?job=build-deb From be6252881fc0fa8094f280e0e19f7e0d01e23afe Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 20:01:27 +0100 Subject: [PATCH 077/171] Add entity pages --- docs/entities/index.rst | 10 ++++++++++ docs/entities/items.rst | 2 ++ docs/entities/monsters.rst | 2 ++ docs/entities/player.rst | 2 ++ docs/index.rst | 1 + 5 files changed, 17 insertions(+) create mode 100644 docs/entities/index.rst create mode 100644 docs/entities/items.rst create mode 100644 docs/entities/monsters.rst create mode 100644 docs/entities/player.rst diff --git a/docs/entities/index.rst b/docs/entities/index.rst new file mode 100644 index 0000000..d8392ed --- /dev/null +++ b/docs/entities/index.rst @@ -0,0 +1,10 @@ +Entités +======= + +.. toctree:: + :maxdepth: 3 + :caption: Entités + + player + monsters + items \ No newline at end of file diff --git a/docs/entities/items.rst b/docs/entities/items.rst new file mode 100644 index 0000000..0f8b42d --- /dev/null +++ b/docs/entities/items.rst @@ -0,0 +1,2 @@ +Objets +====== diff --git a/docs/entities/monsters.rst b/docs/entities/monsters.rst new file mode 100644 index 0000000..0dcfb1e --- /dev/null +++ b/docs/entities/monsters.rst @@ -0,0 +1,2 @@ +Monstres +======== diff --git a/docs/entities/player.rst b/docs/entities/player.rst new file mode 100644 index 0000000..5a56870 --- /dev/null +++ b/docs/entities/player.rst @@ -0,0 +1,2 @@ +Joueur +====== diff --git a/docs/index.rst b/docs/index.rst index 5f45fde..ed7a713 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,4 +12,5 @@ Bienvenue dans la documentation de Squirrel Battle ! :caption: Jouer install + entities/index troubleshooting From 1e5bb26f5706ab6c7e8e0611763847f9a19050b7 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Thu, 19 Nov 2020 20:02:44 +0100 Subject: [PATCH 078/171] Test logs --- squirrelbattle/display/logsdisplay.py | 3 ++- squirrelbattle/interfaces.py | 1 + squirrelbattle/tests/entities_test.py | 9 +++++++-- squirrelbattle/tests/game_test.py | 12 ++++++++++++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/display/logsdisplay.py b/squirrelbattle/display/logsdisplay.py index 36adcaa..0d95915 100644 --- a/squirrelbattle/display/logsdisplay.py +++ b/squirrelbattle/display/logsdisplay.py @@ -17,6 +17,7 @@ class LogsDisplay(Display): messages = messages[::-1] self.pad.clear() for i in range(min(self.height, len(messages))): - self.pad.addstr(self.height - i - 1, self.x, messages[i][:self.width]) + self.pad.addstr(self.height - i - 1, self.x, + messages[i][:self.width]) self.pad.refresh(0, 0, self.y, self.x, self.y + self.height, self.x + self.width) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index cf94aed..d1baa2a 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -52,6 +52,7 @@ class Map: self.start_x = start_x self.tiles = tiles self.entities = [] + self.logs = Logs() def add_entity(self, entity: "Entity") -> None: """ diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 5cd6ad5..2c4b5d6 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -42,9 +42,11 @@ class TestEntities(unittest.TestCase): self.assertEqual(entity.maxhealth, entity.health) self.assertEqual(entity.strength, 2) for _ in range(9): - self.assertIsNone(entity.hit(entity)) + self.assertEqual(entity.hit(entity), + "beaver hits beaver. beaver takes 2 damage.") self.assertFalse(entity.dead) - self.assertIsNone(entity.hit(entity)) + self.assertEqual(entity.hit(entity), "beaver hits beaver. " + + "beaver takes 2 damage. beaver dies.") self.assertTrue(entity.dead) entity = Rabbit() @@ -64,6 +66,9 @@ class TestEntities(unittest.TestCase): self.map.tick() self.assertTrue(entity.y == 2 and entity.x == 6) self.assertEqual(old_health - entity.strength, self.player.health) + self.assertEqual(self.map.logs.messages[-1], + f"{entity.name} hits {self.player.name}. \ +{self.player.name} takes {entity.strength} damage.") # Fight the rabbit old_health = entity.health diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 424d86a..f29a443 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -256,6 +256,18 @@ class TestGame(unittest.TestCase): self.game.handle_key_pressed(KeyValues.ENTER) self.assertEqual(self.game.state, GameMode.MAINMENU) + def test_logs(self) -> None: + """ + Tests the use of logs + """ + self.game.logs.add_message("Hello World !") + self.assertEqual(self.game.logs.messages, ["Hello World !"]) + self.game.logs.add_messages(["Hello", "World"]) + self.assertEqual(self.game.logs.messages, ["Hello World !", + "Hello", "World"]) + self.game.logs.clear() + self.assertEqual(self.game.logs.messages, []) + def test_dead_screen(self) -> None: """ Kill player and render dead screen. From 984b12421bd4f09ce48948bcfb363b22e0915f61 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Thu, 19 Nov 2020 20:14:47 +0100 Subject: [PATCH 079/171] Reaching 100% coverage and renamed an unused loop variable --- squirrelbattle/tests/entities_test.py | 2 +- squirrelbattle/tests/game_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 2c4b5d6..0c8ee3c 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -161,7 +161,7 @@ class TestEntities(unittest.TestCase): self.assertFalse(player.move_up()) self.assertTrue(player.move_left()) self.assertFalse(player.move_left()) - for i in range(8): + for _ in range(8): self.assertTrue(player.move_down()) self.assertFalse(player.move_down()) self.assertTrue(player.move_right()) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index f29a443..61c6c8e 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -17,6 +17,7 @@ class TestGame(unittest.TestCase): """ self.game = Game() self.game.new_game() + self.game.logs.add_message("Hello World !") display = DisplayManager(None, self.game) self.game.display_actions = display.handle_display_action @@ -260,7 +261,6 @@ class TestGame(unittest.TestCase): """ Tests the use of logs """ - self.game.logs.add_message("Hello World !") self.assertEqual(self.game.logs.messages, ["Hello World !"]) self.game.logs.add_messages(["Hello", "World"]) self.assertEqual(self.game.logs.messages, ["Hello World !", From 3662d482d36eb1e26b1e58b918e4736f192e4f38 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 21:14:56 +0100 Subject: [PATCH 080/171] Add documentation for entities --- docs/entities/index.rst | 71 +++++++++++++++++++++++++++++++++++++- docs/entities/items.rst | 48 ++++++++++++++++++++++++++ docs/entities/monsters.rst | 53 ++++++++++++++++++++++++++++ docs/entities/player.rst | 50 +++++++++++++++++++++++++++ 4 files changed, 221 insertions(+), 1 deletion(-) diff --git a/docs/entities/index.rst b/docs/entities/index.rst index d8392ed..1d63bbf 100644 --- a/docs/entities/index.rst +++ b/docs/entities/index.rst @@ -7,4 +7,73 @@ Entités player monsters - items \ No newline at end of file + items + +Entité +------ + +Une entité est un élément placé sur la carte. Ce peut être le joueur, un monstre +ou bien un objet sur la carte. Chaque entité dispose des attributs suivants : + +* ``name: str`` + + Il s'agit du type de l'entité. + +* ``y: int`` +* ``x: int`` + + Cela représente les coordonnées de l'entité sur la carte. + +* ``map: Map`` + + Il s'agit de la carte sur laquelle est placée l'entité. + +.. _objet: items.html + +Il existe à l'heure actuelle deux types d'entité : une `entité attaquante`_ ou +bien un objet_. + + +Entité attaquante +----------------- + +.. _monstre: monsters.html +.. _joueur: player.html + +Une entité attaquante (``FightingEntity``) est un type d'entités représentant +les personnages présents sur la carte, pouvant alors se battre. Ce peut être +un monstre_ ou bien le joueur_. + +Elles disposent toutes, en plus des paramètres d'entité, des attributs suivants : + +* ``maxhealth: int`` + + Représente la vie maximale de l'entité, qui est aussi la vie de départ. + +* ``health: int`` + + Représente la vie actuelle de l'entité. + +* ``strength: int`` + + Représente la force de l'entité, le nombre de dégâts à faire à chaque coup. + +* ``intelligence: int`` +* ``charisma: int`` +* ``dexterity: int`` +* ``constitution: int`` + + Tous ces paramètres sont des statistiques de l'entité, n'ayant pas de réelle + influence pour le moment. + +* ``level: int`` + + Niveau de l'entité. + +Chaque type d'entité disposera de ses propres attributs de départ. + +On considère une entité comme morte à partir du moment où sa vie descend +en-dessous de 0 point de vie. À ce moment-là, l'entité est retirée de la carte. + +Lorsqu'une entité en frappe une autre, celle-ci inflige autant de dégâts qu'elle +n'a de force, et autant de points de vie sont perdus. diff --git a/docs/entities/items.rst b/docs/entities/items.rst index 0f8b42d..521ca91 100644 --- a/docs/entities/items.rst +++ b/docs/entities/items.rst @@ -1,2 +1,50 @@ Objets ====== + +.. _joueur: player.html +.. _pack de textures: ../texture_pack.html + +Un objet est une entité présente sur la carte que le joueur_ peut ramasser. +Il lui suffit pour cela de s'approcher, et une fois sur la case de l'objet, +celui-ci est placé dans l'inventaire. + +Un objet dispose de deux paramètres : + +* ``held: bool`` + + Indique si l'objet est placé dans l'inventaire ou s'il est au sol. + +* ``held_by: Optional[Player]`` + + Si l'objet est dans l'inventaire, renvoie son propriétaire. + + +Deux types d'objets sont pour l'instant présents : + + +Bombe +----- + +.. _entités attaquantes: index.html#entite-attaquante + +Une bombe est un objet que l'on peut ramasser. Une fois ramassée, elle est placée +dans l'inventaire. Le joueur peut ensuite lâcher la bombe, qui fera alors +3 dégâts à toutes les `entités attaquantes`_ situées à moins de une case. + +Elle est représentée dans le `pack de textures`_ ASCII par le caractère ``o`` +et dans le `pack de textures`_ écureuil par l'émoji ``💣``. + +.. note:: + + La gestion de l'inventaire n'ayant pas encore été implémentée, il n'est à + l'heure actuelle pas possible de lancer une bombe. + + +Cœur +---- + +Une cœur est un objet que l'on ne peut pas ramasser. Dès que le joueur s'en +approche, il est régénéré automatiquement de 3 points de vie, et le cœur disparaît. + +Elle est représentée dans le `pack de textures`_ ASCII par le caractère ``❤`` +et dans le `pack de textures`_ écureuil par l'émoji ``💜``. diff --git a/docs/entities/monsters.rst b/docs/entities/monsters.rst index 0dcfb1e..b719c1b 100644 --- a/docs/entities/monsters.rst +++ b/docs/entities/monsters.rst @@ -1,2 +1,55 @@ Monstres ======== + +.. _`entité attaquante`: index.html#entites-attaquantes +.. _`pack de textures`: ../texture-pack.html + +Chaque monstre est une `entité attaquante`_, et hérite donc de ses attributs. + +À chaque tick de jeu, chaque monstre se déplace d'une case, si possible. +Si le monstre est loin du joueur, ce déplacement est fait aléatoirement. +Sinon, si le monstre est à moins de 5 cases du joueur, alors il se dirige +au plus vite sur le joueur pour le frapper selon l'algorithme de Dijkstra, +et s'il est suffisamment proche frappe le joueur et lui fait autant de dégâts +qu'il n'a de force. + +On dénombre actuellement 4 types de monstres : + +Hérisson +-------- + +Son nom est fixé à `hedghog`. Il a par défaut une force à **3** et **10** points de vie. + +Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``*``. + +Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🦔``. + + +Castor +------ + +Son nom est fixé à `beaver`. Il a par défaut une force à **2** et **20** points de vie. + +Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``_``. + +Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🦫``. + + +Lapin +----- + +Son nom est fixé à `rabbit`. Il a par défaut une force à **1** et **15** points de vie. + +Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``Y``. + +Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🐇``. + + +Nounours +-------- + +Son nom est fixé à `teddy_bear`. Il n'a pas de force et **50** points de vie. + +Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``8``. + +Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🧸``. diff --git a/docs/entities/player.rst b/docs/entities/player.rst index 5a56870..3b115d4 100644 --- a/docs/entities/player.rst +++ b/docs/entities/player.rst @@ -1,2 +1,52 @@ Joueur ====== + +.. _`entité attaquante`: index.html#entites-attaquantes +.. _`paramètres`: ../settings.html +.. _`pack de textures`: ../texture-pack.html +.. _`objet`: items.html + +Le joueur est une `entité attaquante`_, contrôlée par l'utilisateur humain. + +Il est représenté dans le `pack de textures`_ ASCII par le caractère ``@``, +et dans le `pack de textures`_ écureuil par le fameux émoji écureuil ``🐿``. + +En plus des attributs d'une `entité attaquante`_, le joueur dispose des atrributs +supplémentaires : + +* ``current_xp: int`` + + Correspond à l'expérience accumulée par le joueur depuis le dernier niveau obtenu. + +* ``max_xp: int`` + + Expérience requise au joueur pour changer de niveau. Vaut 10 fois le niveau actuel. + +* ``inventory: List[Item]`` + + Contient l'ensemble des objets détenus par le joueur. + + +Déplacement +----------- + +Selon les paramètres_, il est possible de bouger le joueur dans les 4 directions +en appuyant sur ``z``, ``q``, ``s``, ``d`` ou sur les flèches directionnelles. + +Le joueur se retrouvera bloqué s'il avance contre un mur. Si il avance sur un +objet_, alors il prend l'objet_ et avance sur la case. + +S'il rencontre une autre `entité attaquante`_, alors il frappe l'entité en +infligeant autant de dégâts qu'il n'a de force. À chaque fois qu'une entité est +tuée, le joueur gagne aléatoirement entre 3 et 7 points d'expérience. + + +Expérience +---------- + +À chaque monstre tué, le joueur gagne entre 3 et 7 points d'expérience aléatoirement. +Lorsque le joueur atteint la quantité d'expérience requise pour monter de niveau, +le joueur gagne un niveau, regagne toute sa vie, consomme son expérience et la +nouvelle quantité d'expérience requise est 10 fois le niveau actuel. De plus, +entre 5 et 10 fois le niveau actuel entités apparaissent aléatoirement sur la +carte à la montée de niveau. From eddf04fc3601fb66192fd3462f88b283fb14d452 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 21:19:55 +0100 Subject: [PATCH 081/171] Fix authors --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 7be9f27..4877d19 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,7 +19,7 @@ project = 'Squirrel Battle' copyright = "2020" -author = "Yohann D'ANELLO, Mathilde DEPRÈS, Nicolas MARGULIES, Charles PEYRAT" +author = "Yohann D'ANELLO,\nMathilde DEPRES,\nNicolas MARGULIES,\nCharles PEYRAT" # -- General configuration --------------------------------------------------- From 1d8ecf49f9d2391cb8d4d7d1dc615c3f1dae293b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 21:47:36 +0100 Subject: [PATCH 082/171] Put documentation link in README, add some badges --- README.md | 117 +++---------------------------------------------- docs/index.rst | 29 ++++++++++++ 2 files changed, 36 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index 44dbeca..d340eee 100644 --- a/README.md +++ b/README.md @@ -1,118 +1,15 @@ [![pipeline status](https://gitlab.crans.org/ynerant/squirrel-battle/badges/master/pipeline.svg)](https://gitlab.crans.org/ynerant/squirrel-battle/-/commits/master) [![coverage report](https://gitlab.crans.org/ynerant/squirrel-battle/badges/master/coverage.svg)](https://gitlab.crans.org/ynerant/squirrel-battle/-/commits/master) +[![Documentation Status](https://readthedocs.org/projects/squirrel-battle/badge/?version=latest)](https://squirrel-battle.readthedocs.io/fr/latest/?badge=latest) +[![PyPI](https://img.shields.io/pypi/v/dungeon-battle)](https://pypi.org/project/squirrel-battle/) +[![PYPI downloads](https://img.shields.io/pypi/dm/squirrel-battle)](https://pypi.org/project/squirrel-battle/) +[![AUR version](https://img.shields.io/aur/version/python-squirrel-battle)](https://aur.archlinux.org/packages/python-squirrel-battle/) +[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.txt) # Squirrel Battle Attention aux couteaux des écureuils ! -## Création d'un environnement de développement +## Documentation -Il est toujours préférable de travailler dans un environnement Python isolé du reste de son instalation. - -1. **Installation des dépendances de la distribution.** - Vous devez déjà installer Python et le module qui permet de créer des environnements virtuels. - On donne ci-dessous l'exemple pour une distribution basée sur Debian, mais vous pouvez facilement adapter pour ArchLinux ou autre. - - ```bash - $ sudo apt update - $ sudo apt install --no-install-recommends -y python3-setuptools python3-venv python3-dev git - ``` - -2. **Clonage du dépot** là où vous voulez : - - ```bash - $ git clone git@gitlab.crans.org:ynerant/squirrel-battle.git && cd squirrel-battle - ``` - -3. **Création d'un environment de travail Python décorrélé du système.** - On n'utilise pas `--system-site-packages` ici pour ne pas avoir des clashs de versions de modules avec le système. - - ```bash - $ python3 -m venv env - $ source env/bin/activate # entrer dans l'environnement - (env)$ pip3 install -r requirements.txt - (env)$ deactivate # sortir de l'environnement - ``` - -### Exécution des tests - -Les tests sont gérés par `pytest` dans le module `squirrelbattle.tests`. - -`tox` est un outil permettant de configurer l'exécution des tests. Ainsi, après -installation de tox dans votre environnement virtuel via `pip install tox`, -il vous suffit d'exécuter `tox -e py3` pour lancer les tests et `tox -e linters` -pour vérifier la syntaxe du code. - - -## Lancement du jeu - -Après clonage du projet, il suffit d'exécuter `python3 main.py`. - -Sinon, le jeu est déployé dans PyPI, et il suffit d'exécuter : - -``` -pip install squirrel-battle -``` - -pour télécharger et installer le jeu. Lancer `squirrel-battle` suffit ensuite -à lancer le jeu depuis n'importe où. Pour mettre à jour : - -``` -pip install --upgrade squirrel-battle -``` - -Sous Arch Linux, le paquet `python-squirrel-battle-git` dans l'AUR permet -également d'installer directement le jeu. - -## Gestion des émojis - -Le jeu dispose de deux modes graphiques : en mode `ascii` et `squirrel`. -Le mode `squirrel` affiche des émojis pour un meilleur affichage. Toutefois, -il est possible que vous n'ayez pas les bonnes polices. - -### Sous Windows - -Sous Windows, vous devriez avoir les bonnes polices installées nativement. - -### Sous Arch Linux - -Il est recommandé d'utiliser le terminal `xfce4-terminal`. Il suffit d'installer -le paquets de polices - -```bash -sudo pacman -Sy noto-fonts-emoji -``` - -Le jeu doit ensuite se lancer normalement sans action supplémentaire. - -### Sous Ubuntu/Debian - -À nouveau, le terminal `xfce4-terminal` est recommandé. Le paquet -`fonts-noto-color-emoji`. Toutefois, le rythme de mise à jour de Debian étant -lent, le paquet le plus récent ne contient pas tous les émojis. Sur Debian, -il faudra donc installer le paquet le plus récent, ce qui fonctionne sans -dépendance supplémentaire : - -```bash -wget http://ftp.fr.debian.org/debian/pool/main/f/fonts-noto-color-emoji/fonts-noto-color-emoji_0~20200916-1_all.deb -dpkg -i fonts-noto-color-emoji_0~20200916-1_all.deb -rm fonts-noto-color-emoji_0~20200916-1_all.deb -``` - -Il reste le problème de l'écureuil. Sous Ubuntu et Debian, le caractère écureuil -existe déjà, mais ne s'affiche pas proprement. On peut appliquer un patch qui -permet d'afficher les émojis correctement dans son terminal. Pour cela, il - suffit de faire : - -```bash -ln -s $PWD/fix-squirrel-emojis.conf /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf -ln -s /etc/fonts/conf.avail/75-fix-squirrel-emojis.conf /etc/fonts/conf.d/75-fix-squirrel-emojis.conf -``` - -Après redémarrage du terminal, l'écureuil devrait s'afficher correctement. - -Pour supprimer le patch : - -```bash -rm /etc/fonts/conf.d/75-fix-squirrel-emojis.conf -``` +La documentation du projet est présente sur [squirrel-battle.readthedocs.io](https://squirrel-battle.readthedocs.io). diff --git a/docs/index.rst b/docs/index.rst index ed7a713..e40d5a4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,6 +1,35 @@ Bienvenue dans la documentation de Squirrel Battle ! ==================================================== +.. image:: https://gitlab.crans.org/ynerant/squirrel-battle/badges/master/pipeline.svg + :target: https://gitlab.crans.org/ynerant/squirrel-battle/-/commits/master + :alt: Pipeline status + +.. image:: https://gitlab.crans.org/ynerant/squirrel-battle/badges/master/coverage.svg + :target: https://gitlab.crans.org/ynerant/squirrel-battle/-/commits/master + :alt: Coverage report + +.. image:: https://readthedocs.org/projects/squirrel-battle/badge/?version=latest + :target: https://squirrel-battle.readthedocs.io/fr/latest/?badge=latest + :alt: Documentation Status + +.. image:: https://img.shields.io/pypi/v/dungeon-battle + :target: https://pypi.org/project/squirrel-battle/ + :alt: PyPI + +.. image:: https://img.shields.io/pypi/dm/dungeon-battle + :target: https://pypi.org/project/squirrel-battle/ + :alt: PyPI downloads + +.. image:: https://img.shields.io/aur/version/python-squirrel-battle + :target: https://aur.archlinux.org/packages/python-squirrel-battle/ + :alt: AUR version + +.. image:: https://img.shields.io/badge/License-GPL%20v3-blue.svg + :target: https://www.gnu.org/licenses/gpl-3.0.txt + :alt: License: GPL v3 + + .. toctree:: :maxdepth: 3 :caption: Développer From d4570828e21d368f7ea39f5f6bed2e084a8ba762 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 22:03:48 +0100 Subject: [PATCH 083/171] Documentation on texture packs --- docs/index.rst | 1 + docs/texture-pack.rst | 57 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 docs/texture-pack.rst diff --git a/docs/index.rst b/docs/index.rst index e40d5a4..88dbe70 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -42,4 +42,5 @@ Bienvenue dans la documentation de Squirrel Battle ! install entities/index + texture-pack troubleshooting diff --git a/docs/texture-pack.rst b/docs/texture-pack.rst new file mode 100644 index 0000000..7eb7e92 --- /dev/null +++ b/docs/texture-pack.rst @@ -0,0 +1,57 @@ +Pack de textures +================ + +.. _entité: entity/index.html +.. _tuile: map.html#tuiles +.. _carte: map.html +.. _paramètres: settings.html + +.. _Joueur: entities/player.html +.. _Hérisson: entities/monsters.html#herisson +.. _Cœur: entities/items.html#coeur +.. _Bombe: entities/items.html#bombe +.. _Lapin: entities/monsters.html#lapin +.. _Castor: entities/monsters.html#castor +.. _Nounours: entities/monsters.html#nounours + +Chaque entité_ et chaque tuile_ de la carte_ est associé à un caractère pour +être affiché dans le terminal. Cependant, afin de pouvoir proposer plusieurs +expériences graphiques (notamment en fonction du support des émojis), différents +packs de textures sont proposés. + +Il est possible de changer de pack dans les paramètres. + +Les packs de textures sont au nombre de deux : + +Pack ASCII +---------- + +* Tuiles + * Vide : *espace* + * Mur : ``#`` + * Sol : ``.`` +* Entités + * Joueur_ : ``@`` + * Hérisson_ : ``*`` + * Cœur_ : ``❤`` + * Bombe_ : ``o`` + * Lapin_ : ``Y`` + * Castor_ : ``_`` + * Nounours_ : ``8`` + + +Pack Écureuil +------------- + +* Tuiles + * Vide : *espace* + * Mur : ``🧱`` + * Sol : ``██`` +* Entités + * Joueur_ : ``🐿`` + * Hérisson_ : ``🦔`` + * Cœur_ : ``💜`` + * Bombe_ : ``💣`` + * Lapin_ : ``🐇`` + * Castor_ : ``🦫`` + * Nounours_ : ``🧸`` From 6b2e420efe348eff6128b01f38778338c2f78ec0 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 22:21:03 +0100 Subject: [PATCH 084/171] Documentation on maps --- docs/index.rst | 1 + docs/map.rst | 46 +++++++++++++++++++++++++++++++++++++++++++ docs/texture-pack.rst | 8 ++++++++ 3 files changed, 55 insertions(+) create mode 100644 docs/map.rst diff --git a/docs/index.rst b/docs/index.rst index 88dbe70..854ca47 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -41,6 +41,7 @@ Bienvenue dans la documentation de Squirrel Battle ! :caption: Jouer install + maps entities/index texture-pack troubleshooting diff --git a/docs/map.rst b/docs/map.rst new file mode 100644 index 0000000..e8b90cc --- /dev/null +++ b/docs/map.rst @@ -0,0 +1,46 @@ +Carte +===== + +.. _entités: entity/index.html +.. _pack de textures: texture-pack.html + +Dans Squirrel game, le joueur se déplace dans un donjon, constitué de plusieurs +cartes. Pour le moment, le jeu se déroule sur une unique carte pré-définie, +non générée aléatoirement. + +Une carte est un rectangle composé de tuiles_. + +La carte est chargée depuis sa représentation ASCII dans un fichier texte. + +Au lancement du jeu, une quantité aléatoire d'entités_ sont générées et placées +aléatoirement sur la carte. + +Tuiles +------ + +Une tuile représente une case du jeu, avec ses différentes propriétés physiques. +On compte actuellement 3 types de tuiles : + +Vide +~~~~ + +Le vide est représenté par un espace vide quelque que soit le `pack de textures`_ +utilisé. Cette tuile n'est utilisée que pour délimiter les bords de la carte, +aucune entité ne peut se trouver sur cette tuile. + + +Sol +~~~ + +Le sol représente les emplacements où les entités peuvent se déplacer librement. +Il est représenté par un point ``.`` dans le `pack de textures`_ ASCII et par +deux caractères rectangulaires blancs ``██`` dans le `pack de textures`_ +écureuil. + + +Mur +~~~ + +Les murs délimitent les salles du donjon. Personne ne peut les traverser. +Ils sont représentés par un dièse ``#`` dans le `pack de textures`_ ASCII et +par une brique carrée ``🧱`` dans le `pack de textures`_ écureuil. diff --git a/docs/texture-pack.rst b/docs/texture-pack.rst index 7eb7e92..3bb5b4d 100644 --- a/docs/texture-pack.rst +++ b/docs/texture-pack.rst @@ -3,6 +3,7 @@ Pack de textures .. _entité: entity/index.html .. _tuile: map.html#tuiles +.. _tuiles: map.html#tuiles .. _carte: map.html .. _paramètres: settings.html @@ -21,11 +22,16 @@ packs de textures sont proposés. Il est possible de changer de pack dans les paramètres. +Les packs de textures peuvent influencer la taille que prennent les tuiles_, +en raison du fait que les émojis ne sont pas monospace. + Les packs de textures sont au nombre de deux : Pack ASCII ---------- +Chaque tuile fait un caractère de large. + * Tuiles * Vide : *espace* * Mur : ``#`` @@ -43,6 +49,8 @@ Pack ASCII Pack Écureuil ------------- +Chaque tuile fait 2 caractères de large pour afficher les émojis proprement. + * Tuiles * Vide : *espace* * Mur : ``🧱`` From 722dabb6e84e917167244c5439bd9b8a962885e1 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 22:32:25 +0100 Subject: [PATCH 085/171] Prepare not-documented pages --- docs/display/index.rst | 14 ++++++++++++++ docs/display/logs.rst | 4 ++++ docs/display/map.rst | 4 ++++ docs/display/menu.rst | 4 ++++ docs/display/stats.rst | 4 ++++ docs/index.rst | 3 +++ docs/settings.rst | 4 ++++ docs/tests.rst | 12 ++++++++++++ 8 files changed, 49 insertions(+) create mode 100644 docs/display/index.rst create mode 100644 docs/display/logs.rst create mode 100644 docs/display/map.rst create mode 100644 docs/display/menu.rst create mode 100644 docs/display/stats.rst create mode 100644 docs/settings.rst create mode 100644 docs/tests.rst diff --git a/docs/display/index.rst b/docs/display/index.rst new file mode 100644 index 0000000..d7d1ca3 --- /dev/null +++ b/docs/display/index.rst @@ -0,0 +1,14 @@ +Gestion de l'affichage +====================== + +Pas encore documenté. + + +.. toctree:: + :maxdepth: 3 + :caption: Affichage + + menu + map + stats + logs diff --git a/docs/display/logs.rst b/docs/display/logs.rst new file mode 100644 index 0000000..3ad130d --- /dev/null +++ b/docs/display/logs.rst @@ -0,0 +1,4 @@ +Affichage de l'historique +========================= + +Pas encore documenté. diff --git a/docs/display/map.rst b/docs/display/map.rst new file mode 100644 index 0000000..1daa85a --- /dev/null +++ b/docs/display/map.rst @@ -0,0 +1,4 @@ +Affichage de la carte +===================== + +Pas encore documenté. diff --git a/docs/display/menu.rst b/docs/display/menu.rst new file mode 100644 index 0000000..84be36c --- /dev/null +++ b/docs/display/menu.rst @@ -0,0 +1,4 @@ +Affichage des menus +=================== + +Pas encore documenté. diff --git a/docs/display/stats.rst b/docs/display/stats.rst new file mode 100644 index 0000000..1b5f697 --- /dev/null +++ b/docs/display/stats.rst @@ -0,0 +1,4 @@ +Affichage des statistiques +========================== + +Pas encore documenté. diff --git a/docs/index.rst b/docs/index.rst index 854ca47..4bafe82 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -35,6 +35,8 @@ Bienvenue dans la documentation de Squirrel Battle ! :caption: Développer install-dev + tests + display .. toctree:: :maxdepth: 3 @@ -44,4 +46,5 @@ Bienvenue dans la documentation de Squirrel Battle ! maps entities/index texture-pack + settings troubleshooting diff --git a/docs/settings.rst b/docs/settings.rst new file mode 100644 index 0000000..a8644d4 --- /dev/null +++ b/docs/settings.rst @@ -0,0 +1,4 @@ +Paramètres +========== + +Pas encore documenté. diff --git a/docs/tests.rst b/docs/tests.rst new file mode 100644 index 0000000..3bdd0d2 --- /dev/null +++ b/docs/tests.rst @@ -0,0 +1,12 @@ +Exécution des tests +=================== + +.. note:: + La documentation va être revue ici. + +Les tests sont gérés par ``pytest`` dans le module ``squirrelbattle.tests``. + +``tox`` est un outil permettant de configurer l'exécution des tests. Ainsi, après +installation de tox dans votre environnement virtuel via ``pip install tox``, +il vous suffit d'exécuter ``tox -e py3`` pour lancer les tests et ``tox -e linters`` +pour vérifier la syntaxe du code. From fa973cacde7ac6baad3e67881f982c6415e8ec80 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 22:35:21 +0100 Subject: [PATCH 086/171] Fix table of contents --- docs/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 4bafe82..41aed36 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -36,14 +36,14 @@ Bienvenue dans la documentation de Squirrel Battle ! install-dev tests - display + display/index .. toctree:: :maxdepth: 3 :caption: Jouer install - maps + map entities/index texture-pack settings From dd92ac767e79db7a943ea94fc0f6459044b91efb Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 22:37:34 +0100 Subject: [PATCH 087/171] We use curses to manage display on terminal --- docs/display/index.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/display/index.rst b/docs/display/index.rst index d7d1ca3..376466c 100644 --- a/docs/display/index.rst +++ b/docs/display/index.rst @@ -1,7 +1,14 @@ Gestion de l'affichage ====================== -Pas encore documenté. +.. _curses: https://docs.python.org/3/howto/curses.html + +L'intégralité de l'affichage du jeu est géré grâce à la bibliothèque native de +Python curses_. + + +.. warning:: + Plus de documentation à venir. .. toctree:: From 5ec42420203272b4fb287e432d7c78130be3649f Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 19 Nov 2020 22:47:03 +0100 Subject: [PATCH 088/171] Write rules of the game --- docs/entities/player.rst | 2 +- docs/index.rst | 1 + docs/rules.rst | 24 ++++++++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 docs/rules.rst diff --git a/docs/entities/player.rst b/docs/entities/player.rst index 3b115d4..d3a644a 100644 --- a/docs/entities/player.rst +++ b/docs/entities/player.rst @@ -49,4 +49,4 @@ Lorsque le joueur atteint la quantité d'expérience requise pour monter de nive le joueur gagne un niveau, regagne toute sa vie, consomme son expérience et la nouvelle quantité d'expérience requise est 10 fois le niveau actuel. De plus, entre 5 et 10 fois le niveau actuel entités apparaissent aléatoirement sur la -carte à la montée de niveau. +carte à la montée de niveau. Enfin, le joueur gagne en force en montant de niveau. diff --git a/docs/index.rst b/docs/index.rst index 41aed36..ce312c3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -43,6 +43,7 @@ Bienvenue dans la documentation de Squirrel Battle ! :caption: Jouer install + rules map entities/index texture-pack diff --git a/docs/rules.rst b/docs/rules.rst new file mode 100644 index 0000000..77cfc6b --- /dev/null +++ b/docs/rules.rst @@ -0,0 +1,24 @@ +Règles du jeu +============= + +.. _carte: map.html +.. _objets: entities/items.html +.. _monstres: entities/monsters.html +.. _entités: entities/index.html + +Dans `Squirrel Game`, le joueur incarne un écureuil coincé dans un donjon, +prêt à tout pour s'en sortir. Sa vision de rongeur lui permet d'observer +l'intégralité de la carte_, et à l'aide d'objets_, il va pouvoir affronter +les monstres_ présents dans le donjon et gagner en expérience et en force. + +Le jeu fonctionne par niveau. À chaque niveau ``n`` du joueur, entre ``3n`` et +``7n`` entités apparaissent aléatoirement sur la carte. + +En tuant des ennemis, ce qu'il parvient à faire en fonçant directement sur eux +ayant mangé trop de noisettes (ou étant armé d'un couteau), l'écureuil va +pouvoir gagner en expérience et au fur et à mesure qu'il monte de niveau, +a force augmentera. + +Arriverez-vous à sauver ce malheureux petit écureuil perdu ? + +Bon courage sachant que le jeu est sans fin ... From d697c5026809f8cac3263671d365d031428d01d3 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 20 Nov 2020 14:29:30 +0100 Subject: [PATCH 089/171] Fix badges in README and doc index --- README.md | 2 +- docs/index.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d340eee..6ad3063 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![pipeline status](https://gitlab.crans.org/ynerant/squirrel-battle/badges/master/pipeline.svg)](https://gitlab.crans.org/ynerant/squirrel-battle/-/commits/master) [![coverage report](https://gitlab.crans.org/ynerant/squirrel-battle/badges/master/coverage.svg)](https://gitlab.crans.org/ynerant/squirrel-battle/-/commits/master) [![Documentation Status](https://readthedocs.org/projects/squirrel-battle/badge/?version=latest)](https://squirrel-battle.readthedocs.io/fr/latest/?badge=latest) -[![PyPI](https://img.shields.io/pypi/v/dungeon-battle)](https://pypi.org/project/squirrel-battle/) +[![PyPI](https://img.shields.io/pypi/v/squirrel-battle)](https://pypi.org/project/squirrel-battle/) [![PYPI downloads](https://img.shields.io/pypi/dm/squirrel-battle)](https://pypi.org/project/squirrel-battle/) [![AUR version](https://img.shields.io/aur/version/python-squirrel-battle)](https://aur.archlinux.org/packages/python-squirrel-battle/) [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.txt) diff --git a/docs/index.rst b/docs/index.rst index ce312c3..e7e3569 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -13,11 +13,11 @@ Bienvenue dans la documentation de Squirrel Battle ! :target: https://squirrel-battle.readthedocs.io/fr/latest/?badge=latest :alt: Documentation Status -.. image:: https://img.shields.io/pypi/v/dungeon-battle +.. image:: https://img.shields.io/pypi/v/squirrel-battle :target: https://pypi.org/project/squirrel-battle/ :alt: PyPI -.. image:: https://img.shields.io/pypi/dm/dungeon-battle +.. image:: https://img.shields.io/pypi/dm/squirrel-battle :target: https://pypi.org/project/squirrel-battle/ :alt: PyPI downloads From 762fa9acd430e813a804e2b9c95ef4e10876bf84 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 20 Nov 2020 15:26:02 +0100 Subject: [PATCH 090/171] A test --- squirrelbattle/display/texturepack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 0ae8f56..4296393 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -65,7 +65,7 @@ TexturePack.SQUIRREL_PACK = TexturePack( EMPTY=' ', WALL='🧱', FLOOR='██', - PLAYER='🐿 ️', + PLAYER='🐿️ ️', HEDGEHOG='🦔', HEART='💜', BOMB='💣', From 62599ea72c5f5fdeaa085186b64ec7c9eb2c3605 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 20 Nov 2020 16:05:21 +0100 Subject: [PATCH 091/171] Clear menu pads before putting the new text in them, see #15 --- squirrelbattle/display/menudisplay.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index fca1ddf..082772b 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -34,6 +34,7 @@ class MenuDisplay(Display): if self.height - 2 >= self.trueheight - self.menu.position else 0 # Menu box + self.menubox.clear() self.menubox.addstr(0, 0, "┏" + "━" * (self.width - 2) + "┓") for i in range(1, self.height - 1): self.menubox.addstr(i, 0, "┃" + " " * (self.width - 2) + "┃") @@ -43,6 +44,7 @@ class MenuDisplay(Display): self.menubox.refresh(0, 0, self.y, self.x, self.height + self.y, self.width + self.x) + self.pad.clear() self.update_pad() self.pad.refresh(cornery, 0, self.y + 1, self.x + 2, self.height - 2 + self.y, From 7e636078363d2b105cd137dc0501122092809e29 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 20 Nov 2020 16:52:04 +0100 Subject: [PATCH 092/171] Added vertical and horizontal lines as display elements --- squirrelbattle/display/display.py | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 1e47189..eba86b9 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -50,3 +50,43 @@ class Display: @property def cols(self) -> int: return curses.COLS if self.screen else 42 + + +class VerticalSplit(Display): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.pad = self.newpad(self.rows, 1) + + @property + def width(self) -> int: + return 1 + + @width.setter + def width(self, val: Any) -> None: + pass + + def display(self) -> None: + for i in range(self.height): + self.pad.addstr(i, 0, "┃") + self.pad.refresh(0, 0, self.y, self.x, self.y + self.height, self.x) + + +class HorizontalSplit(Display): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.pad = self.newpad(1, self.cols) + + @property + def height(self) -> int: + return 1 + + @height.setter + def height(self, val: Any) -> None: + pass + + def display(self) -> None: + for i in range(self.width): + self.pad.addstr(0, i, "━") + self.pad.refresh(0, 0, self.y, self.x, self.y, self.x + self.width) From 685606fdb60622d2533dc8aab790d705dfb1712b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 20 Nov 2020 17:32:26 +0100 Subject: [PATCH 093/171] Documentation on packaging --- docs/deployment.rst | 311 ++++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 312 insertions(+) create mode 100644 docs/deployment.rst diff --git a/docs/deployment.rst b/docs/deployment.rst new file mode 100644 index 0000000..1a4860d --- /dev/null +++ b/docs/deployment.rst @@ -0,0 +1,311 @@ +Déploiement du projet +===================== + +.. _PyPI: https://pypi.org/project/squirrel-battle/ +.. _AUR: https://aur.archlinux.org/packages/python-squirrel-battle/ +.. _Debian: https://gitlab.crans.org/ynerant/squirrel-battle/-/jobs/artifacts/master/raw/build/python3-squirrelbattle_3.14_all.deb?job=build-deb +.. _installation: install.html + +À chaque nouvelle version du projet, il est compilé et déployé dans PyPI_, dans +l'AUR_ et un paquet Debian_ est créé, voir la page d'installation_. + + +PyPI +---- + +Définition du paquet +~~~~~~~~~~~~~~~~~~~~ + +.. _setup.py: https://gitlab.crans.org/ynerant/squirrel-battle/-/blob/master/setup.py + +La documentation sur le packaging dans PyPI_ est disponible `ici +`_. + +Le fichier `setup.py`_ contient l'ensemble des instructions d'installation du +paquet ainsi que des détails à fournir à PyPI : + +.. code:: python + + #!/usr/bin/env python3 + import os + + from setuptools import find_packages, setup + + with open("README.md", "r") as f: + long_description = f.read() + + setup( + name="squirrel-battle", + version="3.14", + author="ynerant", + author_email="ynerant@crans.org", + description="Watch out for squirrel's knifes!", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://gitlab.crans.org/ynerant/squirrel-battle", + packages=find_packages(), + license='GPLv3', + classifiers=[ + "Development Status :: 4 - Beta", + "Environment :: Console :: Curses", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Natural Language :: French", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Topic :: Games/Entertainment", + ], + python_requires='>=3.6', + include_package_data=True, + package_data={"squirrelbattle": ["assets/*"]}, + entry_points={ + "console_scripts": [ + "squirrel-battle = squirrelbattle.bootstrap:Bootstrap.run_game", + ] + } + ) + +Ce fichier contient le nom du paquet, sa version, l'auteur et son contact, +sa description en une ligne et sa description longue, le lien d'accueil du projet, +sa licence, ses classificateurs et son exécutable. + +Le paramètre ``entry_points`` définit un exécutable nommé ``squirrel-battle``, +qui permet de lancer le jeu. + + +Installation locale du paquet +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +L'installation du paquet localement dans son environnement Python (virtuel ou non) +peut se faire en exécutant ``pip install -e .``. + + +Génération des binaires +~~~~~~~~~~~~~~~~~~~~~~~ + +Les paquets ``setuptools`` (``python3-setuptools`` pour APT, ``python-setuptools`` +pour pacman) et ``wheel`` (``python3-wheel`` pour APT, ``python-wheel`` pour pacman) +sont nécessaires. Une fois installés, il faut appeler la commande : + +.. code:: bash + + python3 setup.py sdist bdist_wheel + +Une fois cela fait, le dossier ``dist/`` contiendra les archives à transmettre à PyPI. + + +Publier sur PyPI +~~~~~~~~~~~~~~~~ + +Il faut avant tout avoir un compte sur PyPI. Dans `votre compte PyPI +`_, il faut générer un jeton d'accès API. + +Dans le fichier ``.pypirc`` dans le répertoire principal de l'utilisateur, +il faut ajouter le jeton d'accès : + +.. code:: + + [pypi] + username = __token__ + password = pypi-my-pypi-api-access-token + +Cela permet de s'authentifier directement par ce jeton. + +Ensuite, il faut installer ``twine``, qui permet de publier sur PyPI. + +Il suffit ensuite d'appeler : + +.. code:: bash + + twine upload dist/* + +pour envoyer le paquet sur PyPI. + + +.. note:: + + À des fins de tests, il est possible d'utiliser le dépôt ``_. + Les différences sont au niveau de l'authentification, où il faut l'en-tête + ``[testpypi]`` dans le ``.pypirc``, et il faut envoyer le paquet avec + ``twine upload --repository testpypi dist/``. + + +Publier dans l'AUR +------------------ + +Fonctionnement du packaging +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. _python-squirrel-battle: https://aur.archlinux.org/packages/python-squirrel-battle/ +.. _python-squirrel-battle-git: https://aur.archlinux.org/packages/python-squirrel-battle-git/ + +Deux paquets sont publiés dans l'AUR (Arch User Repository) : + +- python-squirrel-battle_ +- python-squirrel-battle-git_ + +Le packaging dans Arch Linux se fait en commitant un fichier ``PKGBUILD`` dans +le dépôt à l'adresse ``ssh://aur@aur.archlinux.org/packagename.git``, +en remplaçant ``packagename`` par le nom du paquet. + +Le second paquet compile directement le jeu à partir de la branche ``master`` +du dépôt Git. Le fichier ``PKGBUILD`` dispose de cette structure : + +.. code:: + + # Maintainer: Yohann D'ANELLO + + pkgbase=squirrel-battle + pkgname=python-squirrel-battle-git + pkgver=3.14 + pkgrel=2 + pkgdesc="Watch out for squirrel's knives!" + arch=('any') + url="https://gitlab.crans.org/ynerant/squirrel-battle" + license=('GPLv3') + depends=('python') + makedepends=('python-setuptools') + depends=('noto-fonts-emoji') + checkdepends=('python-tox') + ssource=("git+https://gitlab.crans.org/ynerant/squirrel-battle.git") + sha256sums=("SKIP") + + pkgver() { + cd pkgbase + git describe --long --tags | sed -r 's/^v//;s/([^-]*-g)/r\1/;s/-/./g' + } + build() { + cd $pkgbase + python setup.py build + } + + check() { + cd $pkgbase + tox -e py3 + tox -e linters + } + + package() { + cd $pkgbase + python setup.py install --skip-build \ + --optimize=1 \ + --root="${pkgdir}" + install -vDm 644 README.md \ + -t "${pkgdir}/usr/share/doc/${pkgname}" + install -vDm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" + } + +Ces instructions permettent de cloner le dépôt, l'installer et exécuter des tests, +en plus de définir les attributs du paquet. + +Le fichier ``PKGBUILD`` du paquet ``python-squirrel-battle``, synchronisé avec +les releases, est plus ou moins similaire : + +.. code:: + + # Maintainer: Yohann D'ANELLO + + pkgbase=squirrel-battle + pkgname=python-squirrel-battle + pkgver=3.14 + pkgrel=2 + pkgdesc="Watch out for squirrel's knives!" + arch=('any') + url="https://gitlab.crans.org/ynerant/squirrel-battle" + license=('GPLv3') + depends=('python') + makedepends=('python-setuptools') + depends=('noto-fonts-emoji') + checkdepends=('python-tox') + source=("https://gitlab.crans.org/ynerant/squirrel-battle/-/archive/v3.14/$pkgbase-v$pkgver.tar.gz") + sha256sums=("6090534d598c0b3a8f5acdb553c12908ba8107d62d08e17747d1dbb397bddef0") + + build() { + cd $pkgbase-v$pkgver + python setup.py build + } + + check() { + cd $pkgbase-v$pkgver + tox -e py3 + tox -e linters + } + + package() { + cd $pkgbase-v$pkgver + python setup.py install --skip-build \ + --optimize=1 \ + --root="${pkgdir}" + install -vDm 644 README.md \ + -t "${pkgdir}/usr/share/doc/${pkgname}" + install -vDm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}" + } + +Il se contente ici de télécharger l'archive de la dernière release, et de travailler +dessus. + + +Mettre à jour +~~~~~~~~~~~~~ + +Pour mettre à jour le dépôt, une fois les dépôts +``ssh://aur@aur.archlinux.org/python-squirrel-battle.git`` et +``ssh://aur@aur.archlinux.org/python-squirrel-battle-git.git`` clonés, +il suffit de mettre à jour le paramètre ``pkgver`` pour la bonne version, +de régénérer le fichier ``.SRCINFO`` en faisant +``makepkg --printsrcinfo > .SRCINFO``, puis de committer/pousser. + + +Construction du paquet Debian +----------------------------- + +Structure du paquet +------------------- + +L'ensemble des instructions pour construire le paquet Debian est situé dans le +dossier ``debian/``. + +Le fichier ``changelog`` est à modifier à chaque nouvelle version, le fichier +``compat`` contient la version minimale de Debian requise (``10`` pour Debian +Buster), le fichier ``copyright`` contient la liste des fichiers distribués sous +quelle licence (ici GPLv3), le fichier ``control`` contient les informations du +paquet, le fichier ``install`` les fichiers de configuration à installer +(ici le fix de l'affichage de l'écurueil), et enfin le fichier ``rules`` l'ensemble +des instructions à exécuter pour installer. + +Le paquet ``fonts-noto-color-emoji`` est en dépendance pour le bon affichage +des émojis. + +Mettre à jour le paquet +----------------------- + +Pour changer la version du paquet, il faut ajouter des lignes dans le fichier +``changelog``. + + +Construire le paquet +-------------------- + +Il faut partir d'une installation de Debian. + +D'abord on installe les paquets nécessaires : + +.. code:: + + apt update + apt --no-install-recommends install build-essential debmake dh-python debhelper python3-all python3-setuptools + +On peut ensuite construire le paquet : + +.. code:: bash + + dpkg-buildpackage + mkdir build && cp ../*.deb build/ + +Le paquet sera installé dans ``build/python3-squirrel-battle_3.14_all.deb``. + +Le paquet Debian_ est construit par l'intégration continue Gitlab et ajouté +à chaque release. diff --git a/docs/index.rst b/docs/index.rst index e7e3569..5a3af7b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -37,6 +37,7 @@ Bienvenue dans la documentation de Squirrel Battle ! install-dev tests display/index + deployment .. toctree:: :maxdepth: 3 From ebb5f2e7d301a71097aef629c21ce4062a88f1ff Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 20 Nov 2020 17:55:21 +0100 Subject: [PATCH 094/171] Documentation on documentation --- docs/documentation.rst | 31 +++++++++++++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 32 insertions(+) create mode 100644 docs/documentation.rst diff --git a/docs/documentation.rst b/docs/documentation.rst new file mode 100644 index 0000000..74965be --- /dev/null +++ b/docs/documentation.rst @@ -0,0 +1,31 @@ +Documentation +============= + +La documentation est gérée grâce à Sphinx. Le thème est le thème officiel de +ReadTheDocs ``sphinx-rtd-theme``. + +Générer localement la documentation +----------------------------------- + +On commence par se rendre au bon endroit et installer les bonnes dépendances : + +.. code:: bash + + cd docs + pip install -r requirements.txt + +La documentation se génère à partir d'appels à ``make``, selon le type de +documentation voulue. + +Par exemple, ``make html`` construit la documentation web, ``make latexpdf`` +construit un livre PDF avec cette documentation. + + +Documentation externe +--------------------- + +À chaque commit, un webhook est envoyé à ``_, qui construit +tout seul la documentation Sphinx, la publiant à l'adresse +``_. + +De plus, les documentations sont sauvegardées à chaque release taguée. diff --git a/docs/index.rst b/docs/index.rst index 5a3af7b..ff6bcf3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -38,6 +38,7 @@ Bienvenue dans la documentation de Squirrel Battle ! tests display/index deployment + documentation .. toctree:: :maxdepth: 3 From fb3f3ee5e838ac38bc14e588d94c9abe912a0887 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 20 Nov 2020 18:02:08 +0100 Subject: [PATCH 095/171] Beaver is a tiger --- docs/entities/monsters.rst | 10 +++++----- docs/texture-pack.rst | 6 +++--- squirrelbattle/display/texturepack.py | 4 ++-- squirrelbattle/entities/monsters.py | 6 +++--- squirrelbattle/interfaces.py | 8 ++++---- squirrelbattle/tests/entities_test.py | 10 +++++----- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/entities/monsters.rst b/docs/entities/monsters.rst index b719c1b..b6f287c 100644 --- a/docs/entities/monsters.rst +++ b/docs/entities/monsters.rst @@ -25,14 +25,14 @@ Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``*``. Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🦔``. -Castor ------- +Tigre +----- -Son nom est fixé à `beaver`. Il a par défaut une force à **2** et **20** points de vie. +Son nom est fixé à `tiger`. Il a par défaut une force à **2** et **20** points de vie. -Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``_``. +Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``n``. -Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🦫``. +Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🐅``. Lapin diff --git a/docs/texture-pack.rst b/docs/texture-pack.rst index 3bb5b4d..377a3cf 100644 --- a/docs/texture-pack.rst +++ b/docs/texture-pack.rst @@ -12,7 +12,7 @@ Pack de textures .. _Cœur: entities/items.html#coeur .. _Bombe: entities/items.html#bombe .. _Lapin: entities/monsters.html#lapin -.. _Castor: entities/monsters.html#castor +.. _Tigre: entities/monsters.html#tigre .. _Nounours: entities/monsters.html#nounours Chaque entité_ et chaque tuile_ de la carte_ est associé à un caractère pour @@ -42,7 +42,7 @@ Chaque tuile fait un caractère de large. * Cœur_ : ``❤`` * Bombe_ : ``o`` * Lapin_ : ``Y`` - * Castor_ : ``_`` + * Tigre_ : ``n`` * Nounours_ : ``8`` @@ -61,5 +61,5 @@ Chaque tuile fait 2 caractères de large pour afficher les émojis proprement. * Cœur_ : ``💜`` * Bombe_ : ``💣`` * Lapin_ : ``🐇`` - * Castor_ : ``🦫`` + * Tigre_ : ``🐅`` * Nounours_ : ``🧸`` diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 0ae8f56..ce2dd28 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -51,7 +51,7 @@ TexturePack.ASCII_PACK = TexturePack( HEART='❤', BOMB='o', RABBIT='Y', - BEAVER='_', + TIGER='n', TEDDY_BEAR='8', ) @@ -70,6 +70,6 @@ TexturePack.SQUIRREL_PACK = TexturePack( HEART='💜', BOMB='💣', RABBIT='🐇', - BEAVER='🦫', + TIGER='🐅', TEDDY_BEAR='🧸', ) diff --git a/squirrelbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py index 8192b8e..cf4ba39 100644 --- a/squirrelbattle/entities/monsters.py +++ b/squirrelbattle/entities/monsters.py @@ -52,13 +52,13 @@ class Monster(FightingEntity): break -class Beaver(Monster): +class Tiger(Monster): """ - A beaver monster + A tiger monster """ def __init__(self, strength: int = 2, maxhealth: int = 20, *args, **kwargs) -> None: - super().__init__(name="beaver", strength=strength, + super().__init__(name="tiger", strength=strength, maxhealth=maxhealth, *args, **kwargs) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index d1baa2a..5cbe48f 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -318,9 +318,9 @@ class Entity: Returns all entities subclasses """ from squirrelbattle.entities.items import Heart, Bomb - from squirrelbattle.entities.monsters import Beaver, Hedgehog, \ + from squirrelbattle.entities.monsters import Tiger, Hedgehog, \ Rabbit, TeddyBear - return [Beaver, Bomb, Heart, Hedgehog, Rabbit, TeddyBear] + return [Tiger, Bomb, Heart, Hedgehog, Rabbit, TeddyBear] @staticmethod def get_all_entity_classes_in_a_dict() -> dict: @@ -328,11 +328,11 @@ class Entity: Returns all entities subclasses in a dictionary """ from squirrelbattle.entities.player import Player - from squirrelbattle.entities.monsters import Beaver, Hedgehog, Rabbit, \ + from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \ TeddyBear from squirrelbattle.entities.items import Bomb, Heart return { - "Beaver": Beaver, + "Tiger": Tiger, "Bomb": Bomb, "Heart": Heart, "Hedgehog": Hedgehog, diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 0c8ee3c..b92a2c4 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -1,7 +1,7 @@ import unittest from squirrelbattle.entities.items import Bomb, Heart, Item -from squirrelbattle.entities.monsters import Beaver, Hedgehog, Rabbit, TeddyBear +from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, TeddyBear from squirrelbattle.entities.player import Player from squirrelbattle.interfaces import Entity, Map from squirrelbattle.resources import ResourceManager @@ -36,17 +36,17 @@ class TestEntities(unittest.TestCase): """ Test some random stuff with fighting entities. """ - entity = Beaver() + entity = Tiger() self.map.add_entity(entity) self.assertEqual(entity.maxhealth, 20) self.assertEqual(entity.maxhealth, entity.health) self.assertEqual(entity.strength, 2) for _ in range(9): self.assertEqual(entity.hit(entity), - "beaver hits beaver. beaver takes 2 damage.") + "tiger hits tiger. tiger takes 2 damage.") self.assertFalse(entity.dead) - self.assertEqual(entity.hit(entity), "beaver hits beaver. " - + "beaver takes 2 damage. beaver dies.") + self.assertEqual(entity.hit(entity), "tiger hits tiger. " + + "tiger takes 2 damage. tiger dies.") self.assertTrue(entity.dead) entity = Rabbit() From 700ba161868987e85f99c0866439cf095a347745 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 20 Nov 2020 18:04:44 +0100 Subject: [PATCH 096/171] Don't need to install the most recent version of the noto emojis since beaver is finally a tiger --- docs/install.rst | 12 +----------- docs/troubleshooting.rst | 19 +++++-------------- 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/docs/install.rst b/docs/install.rst index d73b5a9..26f8b42 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -67,17 +67,7 @@ Un paquet_ est généré par l'intégration continue de Gitlab à chaque commit. Ils sont également attachés à chaque nouvelle release. Il dépend du paquet ``fonts-noto-color-emoji``, permettant d'afficher les émojis -dans le terminal. Il peut être installé via APT normalement sur une distribution -récente, toutefois sur les versions les plus vieilles, incluant Debian Buster, -certains émojis n'apparaissent pas. Il est essentiel de maintenir ce paquet à -jour. Pour installer manuellement la dernière version de ce paquet, -il suffit d'exécuter : - -.. code:: bash - - wget http://ftp.fr.debian.org/debian/pool/main/f/fonts-noto-color-emoji/fonts-noto-color-emoji_0~20200916-1_all.deb - dpkg -i fonts-noto-color-emoji_0~20200916-1_all.deb - rm fonts-noto-color-emoji_0~20200916-1_all.deb +dans le terminal. Il peut être installé via APT. Pour installer ce paquet, il suffit de le télécharger et d'appeler ``dpkg`` : diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index 8a26c19..6e400f6 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -24,21 +24,12 @@ Sous Ubuntu/Debian ^^^^^^^^^^^^^^^^^^ À nouveau, le terminal `xfce4-terminal` est recommandé. Le paquet -`fonts-noto-color-emoji`. Toutefois, le rythme de mise à jour de Debian étant -lent, le paquet le plus récent ne contient pas tous les émojis. Sur Debian, -il faudra donc installer le paquet le plus récent, ce qui fonctionne sans -dépendance supplémentaire : +`fonts-noto-color-emoji`. -.. code:: bash - - wget http://ftp.fr.debian.org/debian/pool/main/f/fonts-noto-color-emoji/fonts-noto-color-emoji_0~20200916-1_all.deb - dpkg -i fonts-noto-color-emoji_0~20200916-1_all.deb - rm fonts-noto-color-emoji_0~20200916-1_all.deb - -Il reste le problème de l'écureuil. Sous Ubuntu et Debian, le caractère écureuil -existe déjà, mais ne s'affiche pas proprement. On peut appliquer un patch qui -permet d'afficher les émojis correctement dans son terminal. Pour cela, il -suffit de faire : +Toutefois, un problème reste avec l'écureuil. Sous Ubuntu et Debian, le +caractère écureuil existe déjà, mais ne s'affiche pas proprement. On peut +appliquer un patch qui permet d'afficher les émojis correctement dans son +terminal. Pour cela, il suffit de faire : .. code:: bash From 9ca6561bc3e83f27203e28981c6eb63c936a1b8e Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 20 Nov 2020 18:06:43 +0100 Subject: [PATCH 097/171] Added a box element --- squirrelbattle/display/display.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index eba86b9..d1ead44 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -33,7 +33,7 @@ class Display: self.width = width self.height = height if hasattr(self, "pad") and resize_pad: - self.pad.resize(self.height, self.width) + self.pad.resize(self.height + 1, self.width + 1) def refresh(self, *args, resize_pad: bool = True) -> None: if len(args) == 4: @@ -69,7 +69,7 @@ class VerticalSplit(Display): def display(self) -> None: for i in range(self.height): self.pad.addstr(i, 0, "┃") - self.pad.refresh(0, 0, self.y, self.x, self.y + self.height, self.x) + self.pad.refresh(0, 0, self.y, self.x, self.y + self.height - 1, self.x) class HorizontalSplit(Display): @@ -89,4 +89,19 @@ class HorizontalSplit(Display): def display(self) -> None: for i in range(self.width): self.pad.addstr(0, i, "━") - self.pad.refresh(0, 0, self.y, self.x, self.y, self.x + self.width) + self.pad.refresh(0, 0, self.y, self.x, self.y, self.x + self.width - 1) + +class Box(Display): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.pad = self.newpad(self.rows, self.cols) + + def display(self) -> None: + self.pad.addstr(0, 0, "┏" + "━" * (self.width - 2) + "┓") + for i in range(1, self.height - 1): + self.pad.addstr(i, 0, "┃") + self.pad.addstr(i, self.width - 1, "┃") + self.pad.addstr(self.height - 1, 0, + "┗" + "━" * (self.width - 2) + "┛") + self.pad.refresh(0, 0, self.y, self.x, self.y + self.height - 1, self.x + self.width - 1) From ca57fae19d39b1aeb32dbe36abe6ea9abd6f9a66 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 20 Nov 2020 18:07:09 +0100 Subject: [PATCH 098/171] Reshaped the game layout using new lines and boxes --- squirrelbattle/display/display_manager.py | 17 +++++++++++------ squirrelbattle/display/logsdisplay.py | 4 ++-- squirrelbattle/display/menudisplay.py | 21 ++++++--------------- squirrelbattle/display/statsdisplay.py | 18 +++++++----------- 4 files changed, 26 insertions(+), 34 deletions(-) diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index a4636eb..d50dfa1 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -1,4 +1,5 @@ import curses +from squirrelbattle.display.display import VerticalSplit, HorizontalSplit from squirrelbattle.display.mapdisplay import MapDisplay from squirrelbattle.display.statsdisplay import StatsDisplay from squirrelbattle.display.menudisplay import SettingsMenuDisplay, \ @@ -22,6 +23,8 @@ class DisplayManager: screen, pack) self.settingsmenudisplay = SettingsMenuDisplay(screen, pack) self.logsdisplay = LogsDisplay(screen, pack) + self.hbar = HorizontalSplit(screen, pack) + self.vbar = VerticalSplit(screen, pack) self.displays = [self.statsdisplay, self.mapdisplay, self.mainmenudisplay, self.settingsmenudisplay, self.logsdisplay] @@ -44,12 +47,14 @@ class DisplayManager: def refresh(self) -> None: if self.game.state == GameMode.PLAY: # The map pad has already the good size - self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, self.cols, - resize_pad=False) - self.statsdisplay.refresh(self.rows * 4 // 5, 0, - self.rows // 10, self.cols) - self.logsdisplay.refresh(self.rows * 9 // 10, 0, - self.rows // 10, self.cols) + self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, + self.cols * 4 // 5, resize_pad=False) + self.statsdisplay.refresh(0, self.cols * 4 // 5 + 1, + self.rows, self.cols // 5 - 1) + self.logsdisplay.refresh(self.rows * 4 // 5 + 1, 0, + self.rows // 5 - 1, self.cols * 4 // 5) + self.hbar.refresh(self.rows * 4 // 5, 0, 1, self.cols * 4 // 5) + self.vbar.refresh(0, self.cols * 4 // 5, self.rows, 1) if self.game.state == GameMode.MAINMENU: self.mainmenudisplay.refresh(0, 0, self.rows, self.cols) if self.game.state == GameMode.SETTINGS: diff --git a/squirrelbattle/display/logsdisplay.py b/squirrelbattle/display/logsdisplay.py index 0d95915..368c036 100644 --- a/squirrelbattle/display/logsdisplay.py +++ b/squirrelbattle/display/logsdisplay.py @@ -19,5 +19,5 @@ class LogsDisplay(Display): for i in range(min(self.height, len(messages))): self.pad.addstr(self.height - i - 1, self.x, messages[i][:self.width]) - self.pad.refresh(0, 0, self.y, self.x, self.y + self.height, - self.x + self.width) + self.pad.refresh(0, 0, self.y, self.x, self.y + self.height - 1, + self.x + self.width - 1) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 082772b..443722f 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -1,16 +1,16 @@ from typing import List from squirrelbattle.menus import Menu, MainMenu -from .display import Display +from .display import Display, Box from ..resources import ResourceManager class MenuDisplay(Display): position: int - def __init__(self, *args): - super().__init__(*args) - self.menubox = self.newpad(self.rows, self.cols) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.menubox = Box(*args, **kwargs) def update_menu(self, menu: Menu) -> None: self.menu = menu @@ -34,16 +34,7 @@ class MenuDisplay(Display): if self.height - 2 >= self.trueheight - self.menu.position else 0 # Menu box - self.menubox.clear() - self.menubox.addstr(0, 0, "┏" + "━" * (self.width - 2) + "┓") - for i in range(1, self.height - 1): - self.menubox.addstr(i, 0, "┃" + " " * (self.width - 2) + "┃") - self.menubox.addstr(self.height - 1, 0, - "┗" + "━" * (self.width - 2) + "┛") - - self.menubox.refresh(0, 0, self.y, self.x, - self.height + self.y, - self.width + self.x) + self.menubox.refresh(self.y, self.x, self.height, self.width) self.pad.clear() self.update_pad() self.pad.refresh(cornery, 0, self.y + 1, self.x + 2, @@ -90,7 +81,7 @@ class MainMenuDisplay(Display): for i in range(len(self.title)): self.pad.addstr(4 + i, max(self.width // 2 - len(self.title[0]) // 2 - 1, 0), self.title[i]) - self.pad.refresh(0, 0, self.y, self.x, self.height, self.width) + self.pad.refresh(0, 0, self.y, self.x, self.height + self.y - 1, self.width + self.x - 1) menuwidth = min(self.menudisplay.preferred_width, self.width) menuy, menux = len(self.title) + 8, self.width // 2 - menuwidth // 2 - 1 self.menudisplay.refresh( diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index 77ddb30..bc7a864 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -17,31 +17,27 @@ class StatsDisplay(Display): self.player = p def update_pad(self) -> None: - string = "" - for _ in range(self.width - 1): - string = string + "-" - self.pad.addstr(0, 0, string) - string2 = "Player -- LVL {} EXP {}/{} HP {}/{}"\ + string2 = "Player -- LVL {}\nEXP {}/{}\nHP {}/{}"\ .format(self.player.level, self.player.current_xp, self.player.max_xp, self.player.health, self.player.maxhealth) for _ in range(self.width - len(string2) - 1): string2 = string2 + " " - self.pad.addstr(1, 0, string2) - string3 = "Stats : STR {} INT {} CHR {} DEX {} CON {}"\ + self.pad.addstr(0, 0, string2) + string3 = "STR {}\nINT {}\nCHR {}\nDEX {}\nCON {}"\ .format(self.player.strength, self.player.intelligence, self.player.charisma, self.player.dexterity, self.player.constitution) for _ in range(self.width - len(string3) - 1): string3 = string3 + " " - self.pad.addstr(2, 0, string3) + self.pad.addstr(3, 0, string3) inventory_str = "Inventaire : " + "".join( self.pack[item.name.upper()] for item in self.player.inventory) - self.pad.addstr(3, 0, inventory_str) + self.pad.addstr(8, 0, inventory_str) if self.player.dead: - self.pad.addstr(4, 0, "VOUS ÊTES MORT", + self.pad.addstr(10, 0, "VOUS ÊTES MORT", curses.A_BOLD | curses.A_BLINK | curses.A_STANDOUT | self.color_pair(3)) @@ -49,4 +45,4 @@ class StatsDisplay(Display): self.pad.clear() self.update_pad() self.pad.refresh(0, 0, self.y, self.x, - 4 + self.y, self.width + self.x) + self.y + self.height - 1, self.width + self.x - 1) From 223c20e792e61fcfc16b65738bb2bf1f513599bf Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 20 Nov 2020 18:09:39 +0100 Subject: [PATCH 099/171] Linting --- squirrelbattle/display/display.py | 9 +++++---- squirrelbattle/display/menudisplay.py | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index d1ead44..0cdba98 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -91,17 +91,18 @@ class HorizontalSplit(Display): self.pad.addstr(0, i, "━") self.pad.refresh(0, 0, self.y, self.x, self.y, self.x + self.width - 1) + class Box(Display): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.pad = self.newpad(self.rows, self.cols) - + def display(self) -> None: self.pad.addstr(0, 0, "┏" + "━" * (self.width - 2) + "┓") for i in range(1, self.height - 1): self.pad.addstr(i, 0, "┃") self.pad.addstr(i, self.width - 1, "┃") - self.pad.addstr(self.height - 1, 0, - "┗" + "━" * (self.width - 2) + "┛") - self.pad.refresh(0, 0, self.y, self.x, self.y + self.height - 1, self.x + self.width - 1) + self.pad.addstr(self.height - 1, 0, "┗" + "━" * (self.width - 2) + "┛") + self.pad.refresh(0, 0, self.y, self.x, self.y + self.height - 1, + self.x + self.width - 1) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 443722f..3c4e5a1 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -81,7 +81,8 @@ class MainMenuDisplay(Display): for i in range(len(self.title)): self.pad.addstr(4 + i, max(self.width // 2 - len(self.title[0]) // 2 - 1, 0), self.title[i]) - self.pad.refresh(0, 0, self.y, self.x, self.height + self.y - 1, self.width + self.x - 1) + self.pad.refresh(0, 0, self.y, self.x, self.height + self.y - 1, + self.width + self.x - 1) menuwidth = min(self.menudisplay.preferred_width, self.width) menuy, menux = len(self.title) + 8, self.width // 2 - menuwidth // 2 - 1 self.menudisplay.refresh( From b3df257103f619686c9c8ffc20db8e7bb3ca4aa9 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 20 Nov 2020 18:12:30 +0100 Subject: [PATCH 100/171] Removed useless code --- squirrelbattle/display/statsdisplay.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index bc7a864..f47862e 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -21,15 +21,11 @@ class StatsDisplay(Display): .format(self.player.level, self.player.current_xp, self.player.max_xp, self.player.health, self.player.maxhealth) - for _ in range(self.width - len(string2) - 1): - string2 = string2 + " " self.pad.addstr(0, 0, string2) string3 = "STR {}\nINT {}\nCHR {}\nDEX {}\nCON {}"\ .format(self.player.strength, self.player.intelligence, self.player.charisma, self.player.dexterity, self.player.constitution) - for _ in range(self.width - len(string3) - 1): - string3 = string3 + " " self.pad.addstr(3, 0, string3) inventory_str = "Inventaire : " + "".join( From 8b187ec2b39f8f3723f2ec979981bdf55d86869d Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 26 Nov 2020 12:35:52 +0100 Subject: [PATCH 101/171] Use custom function to render a string message onto a pad --- squirrelbattle/display/display.py | 16 ++++++++++------ squirrelbattle/display/logsdisplay.py | 4 ++-- squirrelbattle/display/mapdisplay.py | 8 ++++---- squirrelbattle/display/menudisplay.py | 10 +++++----- squirrelbattle/display/statsdisplay.py | 12 ++++++------ 5 files changed, 27 insertions(+), 23 deletions(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 0cdba98..ecfb2ec 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -19,6 +19,9 @@ class Display: def newpad(self, height: int, width: int) -> Union[FakePad, Any]: return curses.newpad(height, width) if self.screen else FakePad() + def addstr(self, pad: Any, y: int, x: int, msg: str, *options) -> None: + return pad.addstr(y, x, msg, *options) + def init_pair(self, number: int, foreground: int, background: int) -> None: return curses.init_pair(number, foreground, background) \ if self.screen else None @@ -68,7 +71,7 @@ class VerticalSplit(Display): def display(self) -> None: for i in range(self.height): - self.pad.addstr(i, 0, "┃") + self.addstr(self.pad, i, 0, "┃") self.pad.refresh(0, 0, self.y, self.x, self.y + self.height - 1, self.x) @@ -88,7 +91,7 @@ class HorizontalSplit(Display): def display(self) -> None: for i in range(self.width): - self.pad.addstr(0, i, "━") + self.addstr(self.pad, 0, i, "━") self.pad.refresh(0, 0, self.y, self.x, self.y, self.x + self.width - 1) @@ -99,10 +102,11 @@ class Box(Display): self.pad = self.newpad(self.rows, self.cols) def display(self) -> None: - self.pad.addstr(0, 0, "┏" + "━" * (self.width - 2) + "┓") + self.addstr(self.pad, 0, 0, "┏" + "━" * (self.width - 2) + "┓") for i in range(1, self.height - 1): - self.pad.addstr(i, 0, "┃") - self.pad.addstr(i, self.width - 1, "┃") - self.pad.addstr(self.height - 1, 0, "┗" + "━" * (self.width - 2) + "┛") + self.addstr(self.pad, i, 0, "┃") + self.addstr(self.pad, i, self.width - 1, "┃") + self.addstr(self.pad, self.height - 1, 0, + "┗" + "━" * (self.width - 2) + "┛") self.pad.refresh(0, 0, self.y, self.x, self.y + self.height - 1, self.x + self.width - 1) diff --git a/squirrelbattle/display/logsdisplay.py b/squirrelbattle/display/logsdisplay.py index 368c036..304cf7b 100644 --- a/squirrelbattle/display/logsdisplay.py +++ b/squirrelbattle/display/logsdisplay.py @@ -17,7 +17,7 @@ class LogsDisplay(Display): messages = messages[::-1] self.pad.clear() for i in range(min(self.height, len(messages))): - self.pad.addstr(self.height - i - 1, self.x, - messages[i][:self.width]) + self.addstr(self.pad, self.height - i - 1, self.x, + messages[i][:self.width]) self.pad.refresh(0, 0, self.y, self.x, self.y + self.height - 1, self.x + self.width - 1) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index e6cc278..571c527 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -15,11 +15,11 @@ class MapDisplay(Display): def update_pad(self) -> None: self.init_pair(1, self.pack.tile_fg_color, self.pack.tile_bg_color) self.init_pair(2, self.pack.entity_fg_color, self.pack.entity_bg_color) - self.pad.addstr(0, 0, self.map.draw_string(self.pack), - self.color_pair(1)) + self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), + self.color_pair(1)) for e in self.map.entities: - self.pad.addstr(e.y, self.pack.tile_width * e.x, - self.pack[e.name.upper()], self.color_pair(2)) + self.addstr(self.pad, e.y, self.pack.tile_width * e.x, + self.pack[e.name.upper()], self.color_pair(2)) def display(self) -> None: y, x = self.map.currenty, self.pack.tile_width * self.map.currentx diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 3c4e5a1..8d03017 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -20,13 +20,13 @@ class MenuDisplay(Display): # Menu values are printed in pad self.pad = self.newpad(self.trueheight, self.truewidth + 2) for i in range(self.trueheight): - self.pad.addstr(i, 0, " " + self.values[i]) + self.addstr(self.pad, i, 0, " " + self.values[i]) def update_pad(self) -> None: for i in range(self.trueheight): - self.pad.addstr(i, 0, " " + self.values[i]) + self.addstr(self.pad, i, 0, " " + self.values[i]) # set a marker on the selected line - self.pad.addstr(self.menu.position, 0, ">") + self.addstr(self.pad, self.menu.position, 0, ">") def display(self) -> None: cornery = 0 if self.height - 2 >= self.menu.position - 1 \ @@ -79,8 +79,8 @@ class MainMenuDisplay(Display): def display(self) -> None: for i in range(len(self.title)): - self.pad.addstr(4 + i, max(self.width // 2 - - len(self.title[0]) // 2 - 1, 0), self.title[i]) + self.addstr(self.pad, 4 + i, max(self.width // 2 + - len(self.title[0]) // 2 - 1, 0), self.title[i]) self.pad.refresh(0, 0, self.y, self.x, self.height + self.y - 1, self.width + self.x - 1) menuwidth = min(self.menudisplay.preferred_width, self.width) diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index f47862e..bd20ede 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -21,21 +21,21 @@ class StatsDisplay(Display): .format(self.player.level, self.player.current_xp, self.player.max_xp, self.player.health, self.player.maxhealth) - self.pad.addstr(0, 0, string2) + self.addstr(self.pad, 0, 0, string2) string3 = "STR {}\nINT {}\nCHR {}\nDEX {}\nCON {}"\ .format(self.player.strength, self.player.intelligence, self.player.charisma, self.player.dexterity, self.player.constitution) - self.pad.addstr(3, 0, string3) + self.addstr(self.pad, 3, 0, string3) inventory_str = "Inventaire : " + "".join( self.pack[item.name.upper()] for item in self.player.inventory) - self.pad.addstr(8, 0, inventory_str) + self.addstr(self.pad, 8, 0, inventory_str) if self.player.dead: - self.pad.addstr(10, 0, "VOUS ÊTES MORT", - curses.A_BOLD | curses.A_BLINK | curses.A_STANDOUT - | self.color_pair(3)) + self.addstr(self.pad, 10, 0, "VOUS ÊTES MORT", + curses.A_BOLD | curses.A_BLINK | curses.A_STANDOUT + | self.color_pair(3)) def display(self) -> None: self.pad.clear() From f2318ed30863ae88fe85e8abcfd21ec7f782be74 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 26 Nov 2020 20:04:54 +0100 Subject: [PATCH 102/171] Truncate messages if they are too large --- squirrelbattle/display/display.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index ecfb2ec..0a06c65 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -19,8 +19,21 @@ class Display: def newpad(self, height: int, width: int) -> Union[FakePad, Any]: return curses.newpad(height, width) if self.screen else FakePad() + def truncate(self, msg: str, height: int, width: int) -> str: + lines = msg.split("\n") + lines = lines[:height] + lines = [line[:width] for line in lines] + return "\n".join(lines) + def addstr(self, pad: Any, y: int, x: int, msg: str, *options) -> None: - return pad.addstr(y, x, msg, *options) + """ + Display a message onto the pad. + If the message is too large, it is truncated vertically and horizontally + """ + height, width = pad.getmaxyx() + msg = self.truncate(msg, height - y, width - x) + if msg.replace("\n", ""): + return pad.addstr(y, x, msg, *options) def init_pair(self, number: int, foreground: int, background: int) -> None: return curses.init_pair(number, foreground, background) \ From ca03caf3bacaa80cfa22c97bc61525e9c752bab1 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 26 Nov 2020 20:35:10 +0100 Subject: [PATCH 103/171] Don't render message on negative indexes --- squirrelbattle/display/display.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 0a06c65..d5e3078 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -20,6 +20,8 @@ class Display: return curses.newpad(height, width) if self.screen else FakePad() def truncate(self, msg: str, height: int, width: int) -> str: + height = max(0, height) + width = max(0, width) lines = msg.split("\n") lines = lines[:height] lines = [line[:width] for line in lines] @@ -31,8 +33,8 @@ class Display: If the message is too large, it is truncated vertically and horizontally """ height, width = pad.getmaxyx() - msg = self.truncate(msg, height - y, width - x) - if msg.replace("\n", ""): + msg = self.truncate(msg, height - y, width - x - 1) + if msg.replace("\n", "") and x >= 0 and y >= 0: return pad.addstr(y, x, msg, *options) def init_pair(self, number: int, foreground: int, background: int) -> None: From f2f34bfbc66486a6efda6763dace7b8ace3d88a5 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 26 Nov 2020 20:58:46 +0100 Subject: [PATCH 104/171] Don't refresh pads with invalid coordinates. The window should be fully resizable, closes #20 --- squirrelbattle/display/display.py | 33 ++++++++++++++++++++++---- squirrelbattle/display/logsdisplay.py | 4 ++-- squirrelbattle/display/mapdisplay.py | 3 ++- squirrelbattle/display/menudisplay.py | 5 ++-- squirrelbattle/display/statsdisplay.py | 2 +- 5 files changed, 36 insertions(+), 11 deletions(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index d5e3078..1d70159 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -50,7 +50,8 @@ class Display: self.y = y self.width = width self.height = height - if hasattr(self, "pad") and resize_pad: + if hasattr(self, "pad") and resize_pad and \ + self.height >= 0 and self.width >= 0: self.pad.resize(self.height + 1, self.width + 1) def refresh(self, *args, resize_pad: bool = True) -> None: @@ -58,6 +59,26 @@ class Display: self.resize(*args, resize_pad) self.display() + def refresh_pad(self, pad: Any, top_y: int, top_x: int, + window_y: int, window_x: int, + last_y: int, last_x: int) -> None: + """ + Refresh a pad on a part of the window. + The refresh starts at coordinates (top_y, top_x) from the pad, + and is drawn from (window_y, window_x) to (last_y, last_x). + If coordinates are invalid (negative indexes/length..., then nothing + is drawn and no error is raised. + """ + top_y, top_x = max(0, top_y), max(0, top_x) + window_y, window_x = max(0, window_y), max(0, window_x) + screen_max_y, screen_max_x = self.screen.getmaxyx() + last_y, last_x = min(screen_max_y - 1, last_y), \ + min(screen_max_x - 1, last_x) + + if last_y >= window_y and last_x >= window_x: + # Refresh the pad only if coordinates are valid + pad.refresh(top_y, top_x, window_y, window_x, last_y, last_x) + def display(self) -> None: raise NotImplementedError @@ -87,7 +108,8 @@ class VerticalSplit(Display): def display(self) -> None: for i in range(self.height): self.addstr(self.pad, i, 0, "┃") - self.pad.refresh(0, 0, self.y, self.x, self.y + self.height - 1, self.x) + self.refresh_pad(self.pad, 0, 0, self.y, self.x, + self.y + self.height - 1, self.x) class HorizontalSplit(Display): @@ -107,7 +129,8 @@ class HorizontalSplit(Display): def display(self) -> None: for i in range(self.width): self.addstr(self.pad, 0, i, "━") - self.pad.refresh(0, 0, self.y, self.x, self.y, self.x + self.width - 1) + self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.y, + self.x + self.width - 1) class Box(Display): @@ -123,5 +146,5 @@ class Box(Display): self.addstr(self.pad, i, self.width - 1, "┃") self.addstr(self.pad, self.height - 1, 0, "┗" + "━" * (self.width - 2) + "┛") - self.pad.refresh(0, 0, self.y, self.x, self.y + self.height - 1, - self.x + self.width - 1) + self.refresh_pad(self.pad, 0, 0, self.y, self.x, + self.y + self.height - 1, self.x + self.width - 1) diff --git a/squirrelbattle/display/logsdisplay.py b/squirrelbattle/display/logsdisplay.py index 304cf7b..d332b69 100644 --- a/squirrelbattle/display/logsdisplay.py +++ b/squirrelbattle/display/logsdisplay.py @@ -19,5 +19,5 @@ class LogsDisplay(Display): for i in range(min(self.height, len(messages))): self.addstr(self.pad, self.height - i - 1, self.x, messages[i][:self.width]) - self.pad.refresh(0, 0, self.y, self.x, self.y + self.height - 1, - self.x + self.width - 1) + self.refresh_pad(self.pad, 0, 0, self.y, self.x, + self.y + self.height - 1, self.x + self.width - 1) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index 571c527..ce134a5 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -36,4 +36,5 @@ class MapDisplay(Display): pmincol = max(0, min(self.pack.tile_width * self.map.width, pmincol)) self.pad.clear() self.update_pad() - self.pad.refresh(pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol) + self.refresh_pad(self.pad, pminrow, pmincol, sminrow, smincol, smaxrow, + smaxcol) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 8d03017..2338e0d 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -37,7 +37,7 @@ class MenuDisplay(Display): self.menubox.refresh(self.y, self.x, self.height, self.width) self.pad.clear() self.update_pad() - self.pad.refresh(cornery, 0, self.y + 1, self.x + 2, + self.refresh_pad(self.pad, cornery, 0, self.y + 1, self.x + 2, self.height - 2 + self.y, self.width - 2 + self.x) @@ -81,7 +81,8 @@ class MainMenuDisplay(Display): for i in range(len(self.title)): self.addstr(self.pad, 4 + i, max(self.width // 2 - len(self.title[0]) // 2 - 1, 0), self.title[i]) - self.pad.refresh(0, 0, self.y, self.x, self.height + self.y - 1, + self.refresh_pad(self.pad, 0, 0, self.y, self.x, + self.height + self.y - 1, self.width + self.x - 1) menuwidth = min(self.menudisplay.preferred_width, self.width) menuy, menux = len(self.title) + 8, self.width // 2 - menuwidth // 2 - 1 diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index bd20ede..988d99c 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -40,5 +40,5 @@ class StatsDisplay(Display): def display(self) -> None: self.pad.clear() self.update_pad() - self.pad.refresh(0, 0, self.y, self.x, + self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.y + self.height - 1, self.width + self.x - 1) From 3e7dabc94e8e9beb61e2e5b16d215e3e51a95387 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 26 Nov 2020 21:59:48 +0100 Subject: [PATCH 105/171] Wrap perfectly the map on the screen, bricks won't teleport randomly anymore --- squirrelbattle/display/display_manager.py | 5 ++++- squirrelbattle/display/mapdisplay.py | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index d50dfa1..2c7baf1 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -48,7 +48,10 @@ class DisplayManager: if self.game.state == GameMode.PLAY: # The map pad has already the good size self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, - self.cols * 4 // 5, resize_pad=False) + self.mapdisplay.pack.tile_width + * (self.cols * 4 // 5 + // self.mapdisplay.pack.tile_width), + resize_pad=False) self.statsdisplay.refresh(0, self.cols * 4 // 5 + 1, self.rows, self.cols // 5 - 1) self.logsdisplay.refresh(self.rows * 4 // 5 + 1, 0, diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index ce134a5..ae4caa3 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -31,9 +31,17 @@ class MapDisplay(Display): smaxrow = min(smaxrow, self.height - 1) smaxcol = self.pack.tile_width * self.map.width - \ (x + deltax) + self.width - 1 + + # Wrap perfectly the map according to the width of the tiles + pmincol = self.pack.tile_width * (pmincol // self.pack.tile_width) + smincol = self.pack.tile_width * (smincol // self.pack.tile_width) + smaxcol = self.pack.tile_width \ + * (smaxcol // self.pack.tile_width + 1) - 1 + smaxcol = min(smaxcol, self.width - 1) pminrow = max(0, min(self.map.height, pminrow)) pmincol = max(0, min(self.pack.tile_width * self.map.width, pmincol)) + self.pad.clear() self.update_pad() self.refresh_pad(self.pad, pminrow, pmincol, sminrow, smincol, smaxrow, From 1e48bd16b37a64613394ad934d47ae6ad9e256ce Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 26 Nov 2020 22:20:14 +0100 Subject: [PATCH 106/171] Erase pads instead of clearing them, fixes #21 --- squirrelbattle/display/logsdisplay.py | 2 +- squirrelbattle/display/mapdisplay.py | 2 +- squirrelbattle/display/menudisplay.py | 2 +- squirrelbattle/display/statsdisplay.py | 2 +- squirrelbattle/game.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/squirrelbattle/display/logsdisplay.py b/squirrelbattle/display/logsdisplay.py index d332b69..43a18dc 100644 --- a/squirrelbattle/display/logsdisplay.py +++ b/squirrelbattle/display/logsdisplay.py @@ -15,7 +15,7 @@ class LogsDisplay(Display): print(type(self.logs.messages), flush=True) messages = self.logs.messages[-self.height:] messages = messages[::-1] - self.pad.clear() + self.pad.erase() for i in range(min(self.height, len(messages))): self.addstr(self.pad, self.height - i - 1, self.x, messages[i][:self.width]) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index ae4caa3..9599a54 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -42,7 +42,7 @@ class MapDisplay(Display): pminrow = max(0, min(self.map.height, pminrow)) pmincol = max(0, min(self.pack.tile_width * self.map.width, pmincol)) - self.pad.clear() + self.pad.erase() self.update_pad() self.refresh_pad(self.pad, pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 2338e0d..b04ac37 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -35,7 +35,7 @@ class MenuDisplay(Display): # Menu box self.menubox.refresh(self.y, self.x, self.height, self.width) - self.pad.clear() + self.pad.erase() self.update_pad() self.refresh_pad(self.pad, cornery, 0, self.y + 1, self.x + 2, self.height - 2 + self.y, diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index 988d99c..d33f55c 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -38,7 +38,7 @@ class StatsDisplay(Display): | self.color_pair(3)) def display(self) -> None: - self.pad.clear() + self.pad.erase() self.update_pad() self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.y + self.height - 1, self.width + self.x - 1) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 0bb3024..dce1165 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -55,7 +55,7 @@ class Game: when the given key gets pressed. """ while True: # pragma no cover - screen.clear() + screen.erase() screen.refresh() self.display_actions(DisplayActions.REFRESH) key = screen.getkey() From 0726a8db9e756aa44908d580d6fd6611b4c2ad4d Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 26 Nov 2020 22:29:10 +0100 Subject: [PATCH 107/171] A print instruction remains. It was rendered on the screen. Awkward... --- squirrelbattle/display/logsdisplay.py | 1 - 1 file changed, 1 deletion(-) diff --git a/squirrelbattle/display/logsdisplay.py b/squirrelbattle/display/logsdisplay.py index 43a18dc..7bed61e 100644 --- a/squirrelbattle/display/logsdisplay.py +++ b/squirrelbattle/display/logsdisplay.py @@ -12,7 +12,6 @@ class LogsDisplay(Display): self.logs = logs def display(self) -> None: - print(type(self.logs.messages), flush=True) messages = self.logs.messages[-self.height:] messages = messages[::-1] self.pad.erase() From 2690eb8760a570fea44ee3c03f95b4d5010903bb Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 26 Nov 2020 22:32:25 +0100 Subject: [PATCH 108/171] Update FakePad to fix tests --- squirrelbattle/display/display.py | 3 ++- squirrelbattle/tests/screen.py | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 1d70159..bd16344 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -71,7 +71,8 @@ class Display: """ top_y, top_x = max(0, top_y), max(0, top_x) window_y, window_x = max(0, window_y), max(0, window_x) - screen_max_y, screen_max_x = self.screen.getmaxyx() + screen_max_y, screen_max_x = self.screen.getmaxyx() if self.screen \ + else 42, 42 last_y, last_x = min(screen_max_y - 1, last_y), \ min(screen_max_x - 1, last_x) diff --git a/squirrelbattle/tests/screen.py b/squirrelbattle/tests/screen.py index 6eb2cd0..a6b3c9a 100644 --- a/squirrelbattle/tests/screen.py +++ b/squirrelbattle/tests/screen.py @@ -1,3 +1,6 @@ +from typing import Tuple + + class FakePad: """ In order to run tests, we simulate a fake curses pad that accepts functions @@ -10,8 +13,11 @@ class FakePad: smincol: int, smaxrow: int, smaxcol: int) -> None: pass - def clear(self) -> None: + def erase(self) -> None: pass def resize(self, height: int, width: int) -> None: pass + + def getmaxyx(self) -> Tuple[int, int]: + return 42, 42 From 0c25dd4ffea86b9f5e40b33227be94bf32dc6080 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 14:04:51 +0100 Subject: [PATCH 109/171] Display got broken --- squirrelbattle/display/display.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index bd16344..9f2f86a 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -72,7 +72,7 @@ class Display: top_y, top_x = max(0, top_y), max(0, top_x) window_y, window_x = max(0, window_y), max(0, window_x) screen_max_y, screen_max_x = self.screen.getmaxyx() if self.screen \ - else 42, 42 + else (42, 42) last_y, last_x = min(screen_max_y - 1, last_y), \ min(screen_max_x - 1, last_x) From 1782ffcffb13963d4bbc20a0656fda6a813a6ffa Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 16:16:19 +0100 Subject: [PATCH 110/171] Rename LICENSE to COPYING --- LICENSE => COPYING | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename LICENSE => COPYING (100%) diff --git a/LICENSE b/COPYING similarity index 100% rename from LICENSE rename to COPYING From 0d3e33d960b96047479c89ce6eaee525273333e2 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 16:33:17 +0100 Subject: [PATCH 111/171] Declare the license, we use GNU GPL --- COPYING | 2 +- debian/copyright | 6 +++--- main.py | 6 +++++- setup.py | 4 ++++ squirrelbattle/__init__.py | 2 ++ squirrelbattle/bootstrap.py | 3 +++ squirrelbattle/display/__init__.py | 2 ++ squirrelbattle/display/display.py | 3 +++ squirrelbattle/display/display_manager.py | 3 +++ squirrelbattle/display/logsdisplay.py | 3 +++ squirrelbattle/display/mapdisplay.py | 4 +++- squirrelbattle/display/menudisplay.py | 3 +++ squirrelbattle/display/statsdisplay.py | 3 +++ squirrelbattle/display/texturepack.py | 3 +++ squirrelbattle/entities/__init__.py | 2 ++ squirrelbattle/entities/items.py | 3 +++ squirrelbattle/entities/monsters.py | 3 +++ squirrelbattle/entities/player.py | 3 +++ squirrelbattle/enums.py | 3 +++ squirrelbattle/game.py | 3 +++ squirrelbattle/interfaces.py | 4 +++- squirrelbattle/menus.py | 3 +++ squirrelbattle/resources.py | 3 +++ squirrelbattle/settings.py | 3 +++ squirrelbattle/term_manager.py | 3 +++ squirrelbattle/tests/__init__.py | 2 ++ squirrelbattle/tests/entities_test.py | 3 +++ squirrelbattle/tests/game_test.py | 3 +++ squirrelbattle/tests/interfaces_test.py | 3 +++ squirrelbattle/tests/screen.py | 3 +++ squirrelbattle/tests/settings_test.py | 3 +++ 31 files changed, 90 insertions(+), 7 deletions(-) diff --git a/COPYING b/COPYING index 7d821ed..e3107c7 100644 --- a/COPYING +++ b/COPYING @@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - Squirrel Battle Copyright (C) 2020 ynerant + Squirrel Battle Copyright (C) 2020 ÿnérant, eichhornchen, nicomarg, charlse This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. diff --git a/debian/copyright b/debian/copyright index c616d56..6d35767 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,11 +1,11 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: Yohann D'ANELLO -Upstream-Contact: Yohann D'ANELLO +Upstream-Name: ÿnérant, eichhornchen, nicomarg, charlse +Upstream-Contact: ÿnérant, eichhornchen, nicomarg, charlse Source: https://gitlab.crans.org/ynerant/squirrel-battle Files: * -Copyright: 2020 Yohann D'ANELLO +Copyright: 2020 ÿnérant, eichhornchen, nicomarg, charlse License: GPL-3+ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public diff --git a/main.py b/main.py index 64972f1..e8c333e 100755 --- a/main.py +++ b/main.py @@ -1,4 +1,8 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 + +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + from squirrelbattle.bootstrap import Bootstrap if __name__ == "__main__": diff --git a/setup.py b/setup.py index dfb15ef..2cd5038 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,8 @@ #!/usr/bin/env python3 + +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + import os from setuptools import find_packages, setup diff --git a/squirrelbattle/__init__.py b/squirrelbattle/__init__.py index e69de29..1cc6688 100644 --- a/squirrelbattle/__init__.py +++ b/squirrelbattle/__init__.py @@ -0,0 +1,2 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/squirrelbattle/bootstrap.py b/squirrelbattle/bootstrap.py index 45c2ad1..f041aef 100644 --- a/squirrelbattle/bootstrap.py +++ b/squirrelbattle/bootstrap.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + from squirrelbattle.game import Game from squirrelbattle.display.display_manager import DisplayManager from squirrelbattle.term_manager import TermManager diff --git a/squirrelbattle/display/__init__.py b/squirrelbattle/display/__init__.py index e69de29..1cc6688 100644 --- a/squirrelbattle/display/__init__.py +++ b/squirrelbattle/display/__init__.py @@ -0,0 +1,2 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 9f2f86a..00169f5 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + import curses from typing import Any, Optional, Union diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 2c7baf1..061d8c0 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + import curses from squirrelbattle.display.display import VerticalSplit, HorizontalSplit from squirrelbattle.display.mapdisplay import MapDisplay diff --git a/squirrelbattle/display/logsdisplay.py b/squirrelbattle/display/logsdisplay.py index 7bed61e..b768a0e 100644 --- a/squirrelbattle/display/logsdisplay.py +++ b/squirrelbattle/display/logsdisplay.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + from squirrelbattle.display.display import Display from squirrelbattle.interfaces import Logs diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index 9599a54..445f736 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -1,4 +1,6 @@ -#!/usr/bin/env python +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + from squirrelbattle.interfaces import Map from .display import Display diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index b04ac37..731ecee 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + from typing import List from squirrelbattle.menus import Menu, MainMenu diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index d33f55c..b65e716 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + import curses from .display import Display diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 375172a..dfee866 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + import curses from typing import Any diff --git a/squirrelbattle/entities/__init__.py b/squirrelbattle/entities/__init__.py index e69de29..1cc6688 100644 --- a/squirrelbattle/entities/__init__.py +++ b/squirrelbattle/entities/__init__.py @@ -0,0 +1,2 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 9927ef4..147d72c 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + from typing import Optional from .player import Player diff --git a/squirrelbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py index cf4ba39..624f8a3 100644 --- a/squirrelbattle/entities/monsters.py +++ b/squirrelbattle/entities/monsters.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + from random import choice from .player import Player diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 702b055..e452c8d 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + from random import randint from typing import Dict, Tuple diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index 350c196..024f167 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + from enum import Enum, auto from typing import Optional diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index dce1165..c60d93f 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + from random import randint from typing import Any, Optional import json diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 5cbe48f..8958e7b 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -1,4 +1,6 @@ -#!/usr/bin/env python +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + from enum import Enum, auto from math import sqrt from random import choice, randint diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index cd7604a..31c50ea 100644 --- a/squirrelbattle/menus.py +++ b/squirrelbattle/menus.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + from enum import Enum from typing import Any, Optional diff --git a/squirrelbattle/resources.py b/squirrelbattle/resources.py index fc6f708..b3421db 100644 --- a/squirrelbattle/resources.py +++ b/squirrelbattle/resources.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + from pathlib import Path diff --git a/squirrelbattle/settings.py b/squirrelbattle/settings.py index 6c2e31c..9601457 100644 --- a/squirrelbattle/settings.py +++ b/squirrelbattle/settings.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + import json import os from typing import Any, Generator diff --git a/squirrelbattle/term_manager.py b/squirrelbattle/term_manager.py index b1f10b1..6284173 100644 --- a/squirrelbattle/term_manager.py +++ b/squirrelbattle/term_manager.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + import curses from types import TracebackType diff --git a/squirrelbattle/tests/__init__.py b/squirrelbattle/tests/__init__.py index e69de29..1cc6688 100644 --- a/squirrelbattle/tests/__init__.py +++ b/squirrelbattle/tests/__init__.py @@ -0,0 +1,2 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index b92a2c4..8f4e0c2 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + import unittest from squirrelbattle.entities.items import Bomb, Heart, Item diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 61c6c8e..28b354d 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + import os import unittest diff --git a/squirrelbattle/tests/interfaces_test.py b/squirrelbattle/tests/interfaces_test.py index 62f2092..c9f7253 100644 --- a/squirrelbattle/tests/interfaces_test.py +++ b/squirrelbattle/tests/interfaces_test.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + import unittest from squirrelbattle.display.texturepack import TexturePack diff --git a/squirrelbattle/tests/screen.py b/squirrelbattle/tests/screen.py index a6b3c9a..9a8afe6 100644 --- a/squirrelbattle/tests/screen.py +++ b/squirrelbattle/tests/screen.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + from typing import Tuple diff --git a/squirrelbattle/tests/settings_test.py b/squirrelbattle/tests/settings_test.py index 76bcaba..cef60c0 100644 --- a/squirrelbattle/tests/settings_test.py +++ b/squirrelbattle/tests/settings_test.py @@ -1,3 +1,6 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + import unittest from squirrelbattle.settings import Settings From f0488690b79620ccca5af2882fd624e32eb471eb Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 16:53:16 +0100 Subject: [PATCH 112/171] Forgot some author mentions in some files, closes #23 --- COPYING | 2 +- debian/README.debian | 2 +- debian/changelog | 2 +- debian/control | 2 +- docs/deployment.rst | 10 +++++----- setup.py | 6 +++--- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/COPYING b/COPYING index e3107c7..1bc08a5 100644 --- a/COPYING +++ b/COPYING @@ -632,7 +632,7 @@ state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Squirrel Battle - Copyright (C) 2020 ynerant + Copyright (C) 2020 ÿnérant, eichhornchen, nicomarg, charlse This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/debian/README.debian b/debian/README.debian index 8404efb..6961b22 100644 --- a/debian/README.debian +++ b/debian/README.debian @@ -2,4 +2,4 @@ Squirrel Battle Watch out for squirrel's knifes! - -- Yohann D'ANELLO Thu, 19 Nov 2020 03:30:42 +0100 + -- Yohann D'ANELLO Thu, 19 Nov 2020 03:30:42 +0100 diff --git a/debian/changelog b/debian/changelog index d1afdad..536fca5 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,4 +2,4 @@ python3-squirrel-battle (3.14) beta; urgency=low * Initial release. - -- Yohann D'ANELLO Thu, 19 Nov 2020 03:30:42 +0100 + -- Yohann D'ANELLO Thu, 19 Nov 2020 03:30:42 +0100 diff --git a/debian/control b/debian/control index e36e424..fc52e38 100644 --- a/debian/control +++ b/debian/control @@ -1,7 +1,7 @@ Source: python3-squirrel-battle Section: devel Priority: optional -Maintainer: ynerant +Maintainer: ynerant Build-Depends: debhelper (>=10~), dh-python, python3-all, python3-setuptools Depends: fonts-noto-color-emoji Standards-Version: 4.1.4 diff --git a/docs/deployment.rst b/docs/deployment.rst index 1a4860d..99b15f9 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -37,9 +37,9 @@ paquet ainsi que des détails à fournir à PyPI : setup( name="squirrel-battle", version="3.14", - author="ynerant", - author_email="ynerant@crans.org", - description="Watch out for squirrel's knifes!", + author="ÿnérant, eichhornchen, nicomarg, charlse", + author_email="squirrel-battle@crans.org", + description="Watch out for squirrel's knives!", long_description=long_description, long_description_content_type="text/markdown", url="https://gitlab.crans.org/ynerant/squirrel-battle", @@ -156,7 +156,7 @@ du dépôt Git. Le fichier ``PKGBUILD`` dispose de cette structure : .. code:: - # Maintainer: Yohann D'ANELLO + # Maintainer: Yohann D'ANELLO pkgbase=squirrel-battle pkgname=python-squirrel-battle-git @@ -206,7 +206,7 @@ les releases, est plus ou moins similaire : .. code:: - # Maintainer: Yohann D'ANELLO + # Maintainer: Yohann D'ANELLO pkgbase=squirrel-battle pkgname=python-squirrel-battle diff --git a/setup.py b/setup.py index 2cd5038..ef34c31 100644 --- a/setup.py +++ b/setup.py @@ -13,9 +13,9 @@ with open("README.md", "r") as f: setup( name="squirrel-battle", version="3.14", - author="ynerant", - author_email="ynerant@crans.org", - description="Watch out for squirrel's knifes!", + author="ÿnérant, eichhornchen, nicomarg, charlse", + author_email="squirrel-battle@crans.org", + description="Watch out for squirrel's knives!", long_description=long_description, long_description_content_type="text/markdown", url="https://gitlab.crans.org/ynerant/squirrel-battle", From 461d176ce42b304d8a805191e1ed5784ae0c78af Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 16:54:47 +0100 Subject: [PATCH 113/171] Build debian packages only on pipelines that run on the master branch --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6cb844c..8613258 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -45,3 +45,5 @@ build-deb: paths: - build/*.deb expire_in: 1 week + only: + - master From 5cdb12e8a83a84ed6d83cfdee1d91ae5bea6ed08 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 17:32:26 +0100 Subject: [PATCH 114/171] Display a message on a popup --- squirrelbattle/display/display_manager.py | 14 +++++++++++- squirrelbattle/display/messagedisplay.py | 26 +++++++++++++++++++++++ squirrelbattle/game.py | 1 + 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 squirrelbattle/display/messagedisplay.py diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 061d8c0..869b7d4 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -4,6 +4,7 @@ import curses from squirrelbattle.display.display import VerticalSplit, HorizontalSplit from squirrelbattle.display.mapdisplay import MapDisplay +from squirrelbattle.display.messagedisplay import MessageDisplay from squirrelbattle.display.statsdisplay import StatsDisplay from squirrelbattle.display.menudisplay import SettingsMenuDisplay, \ MainMenuDisplay @@ -26,11 +27,12 @@ class DisplayManager: screen, pack) self.settingsmenudisplay = SettingsMenuDisplay(screen, pack) self.logsdisplay = LogsDisplay(screen, pack) + self.messagedisplay = MessageDisplay(screen) self.hbar = HorizontalSplit(screen, pack) self.vbar = VerticalSplit(screen, pack) self.displays = [self.statsdisplay, self.mapdisplay, self.mainmenudisplay, self.settingsmenudisplay, - self.logsdisplay] + self.logsdisplay, self.messagedisplay] self.update_game_components() def handle_display_action(self, action: DisplayActions) -> None: @@ -46,6 +48,7 @@ class DisplayManager: self.statsdisplay.update_player(self.game.player) self.settingsmenudisplay.update_menu(self.game.settings_menu) self.logsdisplay.update_logs(self.game.logs) + self.messagedisplay.update_message(self.game.message) def refresh(self) -> None: if self.game.state == GameMode.PLAY: @@ -65,6 +68,15 @@ class DisplayManager: self.mainmenudisplay.refresh(0, 0, self.rows, self.cols) if self.game.state == GameMode.SETTINGS: self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols - 1) + + if self.game.message: + height, width = 0, 0 + for line in self.game.message.split("\n"): + height += 1 + width = max(width, len(line)) + y, x = (self.rows - height) // 2, (self.cols - width) // 2 + self.messagedisplay.refresh(y, x, height, width) + self.resize_window() def resize_window(self) -> bool: diff --git a/squirrelbattle/display/messagedisplay.py b/squirrelbattle/display/messagedisplay.py new file mode 100644 index 0000000..34c6586 --- /dev/null +++ b/squirrelbattle/display/messagedisplay.py @@ -0,0 +1,26 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + +from squirrelbattle.display.display import Box, Display + + +class MessageDisplay(Display): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.box = Box(*args, **kwargs) + self.message = "" + self.pad = self.newpad(1, 1) + + def update_message(self, msg: str) -> None: + self.message = msg + + def display(self) -> None: + self.box.refresh(self.y - 1, self.x - 2, + self.height + 2, self.width + 4) + self.box.display() + self.pad.erase() + self.addstr(self.pad, 0, 0, self.message) + self.refresh_pad(self.pad, 0, 0, self.y, self.x, + self.height + self.y - 1, + self.width + self.x - 1) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index c60d93f..4553392 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -37,6 +37,7 @@ class Game: self.settings.write_settings() self.settings_menu.update_values(self.settings) self.logs = Logs() + self.message = "Vive les écureuils" def new_game(self) -> None: """ From b7f61d9485473398cd92eced6f76d2c9631e4398 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 17:35:51 +0100 Subject: [PATCH 115/171] Close popup if there is a message --- squirrelbattle/display/messagedisplay.py | 4 ++++ squirrelbattle/game.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/squirrelbattle/display/messagedisplay.py b/squirrelbattle/display/messagedisplay.py index 34c6586..9e4ffae 100644 --- a/squirrelbattle/display/messagedisplay.py +++ b/squirrelbattle/display/messagedisplay.py @@ -5,6 +5,10 @@ from squirrelbattle.display.display import Box, Display class MessageDisplay(Display): + """ + Display a message in a popup. + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 4553392..4eb38c1 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -72,6 +72,10 @@ class Game: Indicates what should be done when the given key is pressed, according to the current game state. """ + if self.message: + self.message = None + return + if self.state == GameMode.PLAY: self.handle_key_pressed_play(key) elif self.state == GameMode.MAINMENU: From fb8d8f033b5be942c9a8bed84ff903111189201f Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 17:52:26 +0100 Subject: [PATCH 116/171] Popup border color is red --- squirrelbattle/display/display.py | 16 +++++++++++----- squirrelbattle/display/display_manager.py | 2 +- squirrelbattle/display/messagedisplay.py | 3 ++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 00169f5..9cc1456 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -139,16 +139,22 @@ class HorizontalSplit(Display): class Box(Display): - def __init__(self, *args, **kwargs): + def __init__(self, *args, fg_border_color: Optional[int] = None, **kwargs): super().__init__(*args, **kwargs) self.pad = self.newpad(self.rows, self.cols) + self.fg_border_color = fg_border_color or curses.COLOR_WHITE + + pair_number = 4 + self.fg_border_color + self.init_pair(pair_number, self.fg_border_color, curses.COLOR_BLACK) + self.pair = self.color_pair(pair_number) def display(self) -> None: - self.addstr(self.pad, 0, 0, "┏" + "━" * (self.width - 2) + "┓") + self.addstr(self.pad, 0, 0, "┏" + "━" * (self.width - 2) + "┓", + self.pair) for i in range(1, self.height - 1): - self.addstr(self.pad, i, 0, "┃") - self.addstr(self.pad, i, self.width - 1, "┃") + self.addstr(self.pad, i, 0, "┃", self.pair) + self.addstr(self.pad, i, self.width - 1, "┃", self.pair) self.addstr(self.pad, self.height - 1, 0, - "┗" + "━" * (self.width - 2) + "┛") + "┗" + "━" * (self.width - 2) + "┛", self.pair) self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.y + self.height - 1, self.x + self.width - 1) diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 869b7d4..f7a0882 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -27,7 +27,7 @@ class DisplayManager: screen, pack) self.settingsmenudisplay = SettingsMenuDisplay(screen, pack) self.logsdisplay = LogsDisplay(screen, pack) - self.messagedisplay = MessageDisplay(screen) + self.messagedisplay = MessageDisplay(screen=screen, pack=None) self.hbar = HorizontalSplit(screen, pack) self.vbar = VerticalSplit(screen, pack) self.displays = [self.statsdisplay, self.mapdisplay, diff --git a/squirrelbattle/display/messagedisplay.py b/squirrelbattle/display/messagedisplay.py index 9e4ffae..c237b6b 100644 --- a/squirrelbattle/display/messagedisplay.py +++ b/squirrelbattle/display/messagedisplay.py @@ -1,5 +1,6 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later +import curses from squirrelbattle.display.display import Box, Display @@ -12,7 +13,7 @@ class MessageDisplay(Display): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.box = Box(*args, **kwargs) + self.box = Box(fg_border_color=curses.COLOR_RED, *args, **kwargs) self.message = "" self.pad = self.newpad(1, 1) From be9c726fa02d8b014b7c765ad4303d8a1987b883 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 17:54:31 +0100 Subject: [PATCH 117/171] Display message in bold format --- squirrelbattle/display/messagedisplay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/display/messagedisplay.py b/squirrelbattle/display/messagedisplay.py index c237b6b..bcc2539 100644 --- a/squirrelbattle/display/messagedisplay.py +++ b/squirrelbattle/display/messagedisplay.py @@ -25,7 +25,7 @@ class MessageDisplay(Display): self.height + 2, self.width + 4) self.box.display() self.pad.erase() - self.addstr(self.pad, 0, 0, self.message) + self.addstr(self.pad, 0, 0, self.message, curses.A_BOLD) self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.height + self.y - 1, self.width + self.x - 1) From 25ba94b9ac5a1fcbf0007e1f5434fee28c29b15e Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 18:09:08 +0100 Subject: [PATCH 118/171] Game displays an error message when a save file could not be loaded. --- squirrelbattle/game.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 4eb38c1..28d20de 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -1,6 +1,6 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later - +from json import JSONDecodeError from random import randint from typing import Any, Optional import json @@ -37,7 +37,7 @@ class Game: self.settings.write_settings() self.settings_menu.update_values(self.settings) self.logs = Logs() - self.message = "Vive les écureuils" + self.message = None def new_game(self) -> None: """ @@ -139,8 +139,15 @@ class Game: Loads the game from a dictionary """ self.map.load_state(d) - # noinspection PyTypeChecker - self.player = self.map.find_entities(Player)[0] + players = self.map.find_entities(Player) + if not players: + self.message = "No player was found on this map!\n" \ + "Maybe you died?" + self.player.health = 0 + self.display_actions(DisplayActions.UPDATE) + return + + self.player = players[0] self.display_actions(DisplayActions.UPDATE) def load_game(self) -> None: @@ -150,7 +157,14 @@ class Game: file_path = ResourceManager.get_config_path("save.json") if os.path.isfile(file_path): with open(file_path, "r") as f: - self.load_state(json.loads(f.read())) + try: + state = json.loads(f.read()) + self.load_state(state) + except JSONDecodeError: + self.message = "The JSON file is not correct.\n" \ + "Your save seems corrupted. It got deleted." + os.unlink(file_path) + self.display_actions(DisplayActions.UPDATE) def save_game(self) -> None: """ From 5faebfe556b31131a11924ea68d37643975564d8 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 18:12:27 +0100 Subject: [PATCH 119/171] Test message display --- squirrelbattle/game.py | 1 + squirrelbattle/tests/game_test.py | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 28d20de..93af352 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -74,6 +74,7 @@ class Game: """ if self.message: self.message = None + self.display_actions(DisplayActions.REFRESH) return if self.state == GameMode.PLAY: diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 28b354d..a466fa9 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -4,6 +4,8 @@ import os import unittest +from squirrelbattle.enums import DisplayActions + from squirrelbattle.bootstrap import Bootstrap from squirrelbattle.display.display import Display from squirrelbattle.display.display_manager import DisplayManager @@ -292,3 +294,13 @@ class TestGame(unittest.TestCase): Check that some functions are not implemented, only for coverage. """ self.assertRaises(NotImplementedError, Display.display, None) + + def test_messages(self) -> None: + """ + Display error messages. + """ + self.game.message = "I am an error" + self.game.display_actions(DisplayActions.UPDATE) + self.game.display_actions(DisplayActions.REFRESH) + self.game.handle_key_pressed(None, "random key") + self.assertIsNone(self.game.message) From b0e352444bdb13cb62d846d653f17a762d8c7fff Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 18:16:54 +0100 Subject: [PATCH 120/171] Test loading wrong saves --- squirrelbattle/game.py | 10 +++++++++- squirrelbattle/tests/game_test.py | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 93af352..a64d82e 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -139,7 +139,15 @@ class Game: """ Loads the game from a dictionary """ - self.map.load_state(d) + try: + self.map.load_state(d) + except KeyError: + self.message = "Some keys are missing in your save file.\n" \ + "Your save seems to be corrupt. It got deleted." + os.unlink(ResourceManager.get_config_path("save.json")) + self.display_actions(DisplayActions.UPDATE) + return + players = self.map.find_entities(Player) if not players: self.message = "No player was found on this map!\n" \ diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index a466fa9..5081912 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -4,6 +4,8 @@ import os import unittest +from squirrelbattle.resources import ResourceManager + from squirrelbattle.enums import DisplayActions from squirrelbattle.bootstrap import Bootstrap @@ -43,6 +45,27 @@ class TestGame(unittest.TestCase): new_state = self.game.save_state() self.assertEqual(old_state, new_state) + # Error on loading save + with open(ResourceManager.get_config_path("save.json"), "w") as f: + f.write("I am not a JSON file") + self.assertIsNone(self.game.message) + self.game.load_game() + self.assertIsNotNone(self.game.message) + self.game.message = None + + with open(ResourceManager.get_config_path("save.json"), "w") as f: + f.write("{}") + self.assertIsNone(self.game.message) + self.game.load_game() + self.assertIsNotNone(self.game.message) + self.game.message = None + + # Load game with a dead player + self.game.map.remove_entity(self.game.player) + self.game.save_game() + self.game.load_game() + self.assertIsNotNone(self.game.message) + def test_bootstrap_fail(self) -> None: """ Ensure that the test can't play the game, From d85ee1758fcdfaf41625a16a180ddc372419b928 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 18:27:28 +0100 Subject: [PATCH 121/171] Release v3.14.1 --- debian/changelog | 6 ++++++ docs/deployment.rst | 16 ++++++++-------- docs/install.rst | 4 ++-- setup.py | 2 +- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/debian/changelog b/debian/changelog index 536fca5..887634f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,11 @@ python3-squirrel-battle (3.14) beta; urgency=low + * Some graphical improvements. + + -- Yohann D'ANELLO Thu, 27 Nov 2020 18:25:42 +0100 + + python3-squirrel-battle (3.14) beta; urgency=low + * Initial release. -- Yohann D'ANELLO Thu, 19 Nov 2020 03:30:42 +0100 diff --git a/docs/deployment.rst b/docs/deployment.rst index 99b15f9..a9f58ee 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -3,7 +3,7 @@ Déploiement du projet .. _PyPI: https://pypi.org/project/squirrel-battle/ .. _AUR: https://aur.archlinux.org/packages/python-squirrel-battle/ -.. _Debian: https://gitlab.crans.org/ynerant/squirrel-battle/-/jobs/artifacts/master/raw/build/python3-squirrelbattle_3.14_all.deb?job=build-deb +.. _Debian: https://gitlab.crans.org/ynerant/squirrel-battle/-/jobs/artifacts/master/raw/build/python3-squirrelbattle_3.14.1_all.deb?job=build-deb .. _installation: install.html À chaque nouvelle version du projet, il est compilé et déployé dans PyPI_, dans @@ -36,7 +36,7 @@ paquet ainsi que des détails à fournir à PyPI : setup( name="squirrel-battle", - version="3.14", + version="3.14.1", author="ÿnérant, eichhornchen, nicomarg, charlse", author_email="squirrel-battle@crans.org", description="Watch out for squirrel's knives!", @@ -160,8 +160,8 @@ du dépôt Git. Le fichier ``PKGBUILD`` dispose de cette structure : pkgbase=squirrel-battle pkgname=python-squirrel-battle-git - pkgver=3.14 - pkgrel=2 + pkgver=3.14.1 + pkgrel=1 pkgdesc="Watch out for squirrel's knives!" arch=('any') url="https://gitlab.crans.org/ynerant/squirrel-battle" @@ -210,8 +210,8 @@ les releases, est plus ou moins similaire : pkgbase=squirrel-battle pkgname=python-squirrel-battle - pkgver=3.14 - pkgrel=2 + pkgver=3.14.1 + pkgrel=1 pkgdesc="Watch out for squirrel's knives!" arch=('any') url="https://gitlab.crans.org/ynerant/squirrel-battle" @@ -220,7 +220,7 @@ les releases, est plus ou moins similaire : makedepends=('python-setuptools') depends=('noto-fonts-emoji') checkdepends=('python-tox') - source=("https://gitlab.crans.org/ynerant/squirrel-battle/-/archive/v3.14/$pkgbase-v$pkgver.tar.gz") + source=("https://gitlab.crans.org/ynerant/squirrel-battle/-/archive/v3.14.1/$pkgbase-v$pkgver.tar.gz") sha256sums=("6090534d598c0b3a8f5acdb553c12908ba8107d62d08e17747d1dbb397bddef0") build() { @@ -305,7 +305,7 @@ On peut ensuite construire le paquet : dpkg-buildpackage mkdir build && cp ../*.deb build/ -Le paquet sera installé dans ``build/python3-squirrel-battle_3.14_all.deb``. +Le paquet sera installé dans ``build/python3-squirrel-battle_3.14.1_all.deb``. Le paquet Debian_ est construit par l'intégration continue Gitlab et ajouté à chaque release. diff --git a/docs/install.rst b/docs/install.rst index 26f8b42..5cc2351 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -61,7 +61,7 @@ Le jeu peut être ensuite lancé via la commande ``squirrel-battle``. Sur Ubuntu/Debian ~~~~~~~~~~~~~~~~~ -.. _paquet: https://gitlab.crans.org/ynerant/squirrel-battle/-/jobs/artifacts/master/raw/build/python3-squirrelbattle_3.14_all.deb?job=build-deb +.. _paquet: https://gitlab.crans.org/ynerant/squirrel-battle/-/jobs/artifacts/master/raw/build/python3-squirrelbattle_3.14.1_all.deb?job=build-deb Un paquet_ est généré par l'intégration continue de Gitlab à chaque commit. Ils sont également attachés à chaque nouvelle release. @@ -73,7 +73,7 @@ Pour installer ce paquet, il suffit de le télécharger et d'appeler ``dpkg`` : .. code:: bash - dpkg -i python3-squirrelbattle_3.14_all.deb + dpkg -i python3-squirrelbattle_3.14.1_all.deb Ce paquet inclut un patch pour afficher les émojis écureuil correctement. diff --git a/setup.py b/setup.py index ef34c31..6287f7d 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ with open("README.md", "r") as f: setup( name="squirrel-battle", - version="3.14", + version="3.14.1", author="ÿnérant, eichhornchen, nicomarg, charlse", author_email="squirrel-battle@crans.org", description="Watch out for squirrel's knives!", From cb18b3881f15785c194776ee670d2f896bcf4295 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 19:42:45 +0100 Subject: [PATCH 122/171] Fix Debian package version --- debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/changelog b/debian/changelog index 887634f..2399e41 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -python3-squirrel-battle (3.14) beta; urgency=low +python3-squirrel-battle (3.14.1) beta; urgency=low * Some graphical improvements. From e3be4b4f3f62689202a627035b853be294196e3d Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 20:32:40 +0100 Subject: [PATCH 123/171] First setup for translation --- locale/en/LC_MESSAGES/squirrelbattle.mo | Bin 0 -> 371 bytes locale/en/LC_MESSAGES/squirrelbattle.po | 22 ++++++++++++++++++++++ locale/fr/LC_MESSAGES/squirrelbattle.mo | Bin 0 -> 371 bytes locale/fr/LC_MESSAGES/squirrelbattle.po | 22 ++++++++++++++++++++++ squirrelbattle/translations.py | 15 +++++++++++++++ 5 files changed, 59 insertions(+) create mode 100644 locale/en/LC_MESSAGES/squirrelbattle.mo create mode 100644 locale/en/LC_MESSAGES/squirrelbattle.po create mode 100644 locale/fr/LC_MESSAGES/squirrelbattle.mo create mode 100644 locale/fr/LC_MESSAGES/squirrelbattle.po create mode 100644 squirrelbattle/translations.py diff --git a/locale/en/LC_MESSAGES/squirrelbattle.mo b/locale/en/LC_MESSAGES/squirrelbattle.mo new file mode 100644 index 0000000000000000000000000000000000000000..ecca9e27bf3bf4b3c848e637f5bb9cf11bef0958 GIT binary patch literal 371 zcmYL@u};G<5QYOPOGXwJ2JdhMw#pP#)3_zVPC}Yig3Yuh7!|v+T?7xp>+vjH5Td{C zlYG*D^7nn%`${nT$T4z?93VraONI21d*o;@Nfv**#W&7xy4(LWF1n=h?o}@%)oN(8dZR?JVmj|k zhC+!5+mCEsaZ^v~0=vr$|8?sJ1aQGdj<%~~0INtQ3l2OY2G%>)L!UZ?e} VJ#f2pD`Q(q22UoYNuxtdegF@5)Y literal 0 HcmV?d00001 diff --git a/locale/en/LC_MESSAGES/squirrelbattle.po b/locale/en/LC_MESSAGES/squirrelbattle.po new file mode 100644 index 0000000..85d099d --- /dev/null +++ b/locale/en/LC_MESSAGES/squirrelbattle.po @@ -0,0 +1,22 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-11-27 20:06+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: squirrelbattle/translations.py:7 +msgid "Toto" +msgstr "Test" diff --git a/locale/fr/LC_MESSAGES/squirrelbattle.mo b/locale/fr/LC_MESSAGES/squirrelbattle.mo new file mode 100644 index 0000000000000000000000000000000000000000..ecca9e27bf3bf4b3c848e637f5bb9cf11bef0958 GIT binary patch literal 371 zcmYL@u};G<5QYOPOGXwJ2JdhMw#pP#)3_zVPC}Yig3Yuh7!|v+T?7xp>+vjH5Td{C zlYG*D^7nn%`${nT$T4z?93VraONI21d*o;@Nfv**#W&7xy4(LWF1n=h?o}@%)oN(8dZR?JVmj|k zhC+!5+mCEsaZ^v~0=vr$|8?sJ1aQGdj<%~~0INtQ3l2OY2G%>)L!UZ?e} VJ#f2pD`Q(q22UoYNuxtdegF@5)Y literal 0 HcmV?d00001 diff --git a/locale/fr/LC_MESSAGES/squirrelbattle.po b/locale/fr/LC_MESSAGES/squirrelbattle.po new file mode 100644 index 0000000..85d099d --- /dev/null +++ b/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -0,0 +1,22 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-11-27 20:06+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: squirrelbattle/translations.py:7 +msgid "Toto" +msgstr "Test" diff --git a/squirrelbattle/translations.py b/squirrelbattle/translations.py new file mode 100644 index 0000000..27ec288 --- /dev/null +++ b/squirrelbattle/translations.py @@ -0,0 +1,15 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + +import gettext + + +_TRANSLATORS = dict() +for language in ["en", "fr"]: + _TRANSLATORS[language] = gettext.translation("squirrelbattle", + localedir="locale", + languages=[language]) + + +def gettext(message: str) -> str: + return _TRANSLATORS.get("en", _TRANSLATORS.get("en")).gettext(message) From 2498fd2a61517aa562fdcab9e66359212cb7834c Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 20:42:19 +0100 Subject: [PATCH 124/171] Translate strings --- locale/en/LC_MESSAGES/squirrelbattle.mo | Bin 371 -> 337 bytes locale/en/LC_MESSAGES/squirrelbattle.po | 68 ++++++++++++++++++++-- locale/fr/LC_MESSAGES/squirrelbattle.mo | Bin 371 -> 1445 bytes locale/fr/LC_MESSAGES/squirrelbattle.po | 74 ++++++++++++++++++++++-- squirrelbattle/display/statsdisplay.py | 8 +-- squirrelbattle/game.py | 15 +++-- squirrelbattle/interfaces.py | 12 ++-- squirrelbattle/menus.py | 15 ++--- 8 files changed, 163 insertions(+), 29 deletions(-) diff --git a/locale/en/LC_MESSAGES/squirrelbattle.mo b/locale/en/LC_MESSAGES/squirrelbattle.mo index ecca9e27bf3bf4b3c848e637f5bb9cf11bef0958..6c5906d1cd061dff54de8b533942893de34efc9e 100644 GIT binary patch delta 65 wcmey&bdky8o)F7a1|VPrVi_P-0b*t#)&XJ=umEClprj>`2C0F8jiHi^0R0~YqyPW_ delta 100 zcmcb}^qDF2o)F7a1|VPpVi_RT0b*7lwgF-g2moRhAPxj#aYhD)FepC{$Oa-X0O^H; Sko=PTjTVxOtRbnzB@6)iEeZ($ diff --git a/locale/en/LC_MESSAGES/squirrelbattle.po b/locale/en/LC_MESSAGES/squirrelbattle.po index 85d099d..31fcdec 100644 --- a/locale/en/LC_MESSAGES/squirrelbattle.po +++ b/locale/en/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-11-27 20:06+0100\n" +"POT-Creation-Date: 2020-11-27 20:39+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,66 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/translations.py:7 -msgid "Toto" -msgstr "Test" +#: squirrelbattle/display/statsdisplay.py:34 +msgid "Inventory:" +msgstr "" + +#: squirrelbattle/display/statsdisplay.py:39 +msgid "YOU ARE DEAD" +msgstr "" + +#: squirrelbattle/interfaces.py:394 +#, python-brace-format +msgid "{name} hits {opponent}." +msgstr "" + +#: squirrelbattle/interfaces.py:405 +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "" + +#: squirrelbattle/menus.py:45 +msgid "New game" +msgstr "" + +#: squirrelbattle/menus.py:46 +msgid "Resume" +msgstr "" + +#: squirrelbattle/menus.py:47 +msgid "Save" +msgstr "" + +#: squirrelbattle/menus.py:48 +msgid "Load" +msgstr "" + +#: squirrelbattle/menus.py:49 +msgid "Settings" +msgstr "" + +#: squirrelbattle/menus.py:50 +msgid "Exit" +msgstr "" + +#: squirrelbattle/menus.py:71 +msgid "Back" +msgstr "" + +#: squirrelbattle/game.py:147 +msgid "" +"Some keys are missing in your save file.\n" +"Your save seems to be corrupt. It got deleted." +msgstr "" + +#: squirrelbattle/game.py:155 +msgid "" +"No player was found on this map!\n" +"Maybe you died?" +msgstr "" + +#: squirrelbattle/game.py:175 +msgid "" +"The JSON file is not correct.\n" +"Your save seems corrupted.It got deleted." +msgstr "" diff --git a/locale/fr/LC_MESSAGES/squirrelbattle.mo b/locale/fr/LC_MESSAGES/squirrelbattle.mo index ecca9e27bf3bf4b3c848e637f5bb9cf11bef0958..7e3ecd63e13fc07cf7b1df1e42fa1bffd4ee8666 100644 GIT binary patch literal 1445 zcmbu8yRREX6vihIUgjmyAS94ZDiCOw&_yPYY_2yKhxG$LF1mDM@9}zay)(M1 z6m*CZL{Ek3DXC0DL4zm}eM{$+Ay;haj&w9Lwa&?C@^j^=l+~q1N3JGJD^pXh zIKL(a>}s4F$|WA`z0!pfLsK%H@XC?2Or>%T%cOKTXUV~rW@^E4ox{l8SA5tD^wocs0$a3Op_Y@UNqpP3TL90^xUN5G#NylUerqHonEuke(i%^ zH*I9n`DkRNb_BP><)k{&na;={IRQm)5a=YdpFcT{-s) zWzE<|sh*k~U$;qDCdxOU2aj(m-m8`OY z+tdto=$9pJ)_$`lZ{Y_ztS%%?;mmZY(dXJno9{eA;>j*rni}H7P17|I`cS$?lA%PB zLfRbfeQ9O6`4&f)`JW1A diff --git a/locale/fr/LC_MESSAGES/squirrelbattle.po b/locale/fr/LC_MESSAGES/squirrelbattle.po index 85d099d..ab04ec8 100644 --- a/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-11-27 20:06+0100\n" +"POT-Creation-Date: 2020-11-27 20:39+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,72 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/translations.py:7 -msgid "Toto" -msgstr "Test" +#: squirrelbattle/display/statsdisplay.py:34 +msgid "Inventory:" +msgstr "Inventaire :" + +#: squirrelbattle/display/statsdisplay.py:39 +msgid "YOU ARE DEAD" +msgstr "VOUS ÊTES MORT" + +#: squirrelbattle/interfaces.py:394 +#, python-brace-format +msgid "{name} hits {opponent}." +msgstr "{name} frappe {opponent}." + +#: squirrelbattle/interfaces.py:405 +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} prend {amount} points de dégât." + +#: squirrelbattle/menus.py:45 +msgid "New game" +msgstr "Nouvelle partie" + +#: squirrelbattle/menus.py:46 +msgid "Resume" +msgstr "Continuer" + +#: squirrelbattle/menus.py:47 +msgid "Save" +msgstr "Sauvegarder" + +#: squirrelbattle/menus.py:48 +msgid "Load" +msgstr "Charger" + +#: squirrelbattle/menus.py:49 +msgid "Settings" +msgstr "Paramètres" + +#: squirrelbattle/menus.py:50 +msgid "Exit" +msgstr "Quitter" + +#: squirrelbattle/menus.py:71 +msgid "Back" +msgstr "Retour" + +#: squirrelbattle/game.py:147 +msgid "" +"Some keys are missing in your save file.\n" +"Your save seems to be corrupt. It got deleted." +msgstr "" +"Certaines clés de votre ficher de sauvegarde sont manquantes.\n" +"Votre sauvegarde semble corrompue. Elle a été supprimée." + +#: squirrelbattle/game.py:155 +msgid "" +"No player was found on this map!\n" +"Maybe you died?" +msgstr "" +"Aucun joueur n'a été trouvé sur la carte !\n" +"Peut-être êtes-vous mort ?" + +#: squirrelbattle/game.py:175 +msgid "" +"The JSON file is not correct.\n" +"Your save seems corrupted.It got deleted." +msgstr "" +"Le fichier JSON de sauvegarde est incorrect.\n" +"Votre sauvegarde semble corrompue. Elle a été supprimée." diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index b65e716..da9213f 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -3,10 +3,10 @@ import curses +from ..entities.player import Player +from ..translations import gettext as _ from .display import Display -from squirrelbattle.entities.player import Player - class StatsDisplay(Display): player: Player @@ -31,12 +31,12 @@ class StatsDisplay(Display): self.player.dexterity, self.player.constitution) self.addstr(self.pad, 3, 0, string3) - inventory_str = "Inventaire : " + "".join( + inventory_str = _("Inventory:") + " " + "".join( self.pack[item.name.upper()] for item in self.player.inventory) self.addstr(self.pad, 8, 0, inventory_str) if self.player.dead: - self.addstr(self.pad, 10, 0, "VOUS ÊTES MORT", + self.addstr(self.pad, 10, 0, _("YOU ARE DEAD"), curses.A_BOLD | curses.A_BLINK | curses.A_STANDOUT | self.color_pair(3)) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index a64d82e..a9689a5 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -1,5 +1,6 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later + from json import JSONDecodeError from random import randint from typing import Any, Optional @@ -13,6 +14,7 @@ from .interfaces import Map, Logs from .resources import ResourceManager from .settings import Settings from . import menus +from .translations import gettext as _ from typing import Callable @@ -142,16 +144,16 @@ class Game: try: self.map.load_state(d) except KeyError: - self.message = "Some keys are missing in your save file.\n" \ - "Your save seems to be corrupt. It got deleted." + self.message = _("Some keys are missing in your save file.\n" + "Your save seems to be corrupt. It got deleted.") os.unlink(ResourceManager.get_config_path("save.json")) self.display_actions(DisplayActions.UPDATE) return players = self.map.find_entities(Player) if not players: - self.message = "No player was found on this map!\n" \ - "Maybe you died?" + self.message = _("No player was found on this map!\n" + "Maybe you died?") self.player.health = 0 self.display_actions(DisplayActions.UPDATE) return @@ -170,8 +172,9 @@ class Game: state = json.loads(f.read()) self.load_state(state) except JSONDecodeError: - self.message = "The JSON file is not correct.\n" \ - "Your save seems corrupted. It got deleted." + self.message = _("The JSON file is not correct.\n" + "Your save seems corrupted." + "It got deleted.") os.unlink(file_path) self.display_actions(DisplayActions.UPDATE) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 8958e7b..0e51589 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -6,7 +6,8 @@ from math import sqrt from random import choice, randint from typing import List, Optional -from squirrelbattle.display.texturepack import TexturePack +from .display.texturepack import TexturePack +from .translations import gettext as _ class Logs: @@ -390,7 +391,8 @@ class FightingEntity(Entity): """ Deals damage to the opponent, based on the stats """ - return f"{self.name} hits {opponent.name}. "\ + return _("{name} hits {opponent}.")\ + .format(name=str(self), opponent=str(opponent)) + " "\ + opponent.take_damage(self, self.strength) def take_damage(self, attacker: "Entity", amount: int) -> str: @@ -400,8 +402,10 @@ class FightingEntity(Entity): self.health -= amount if self.health <= 0: self.die() - return f"{self.name} takes {amount} damage."\ - + (f" {self.name} dies." if self.health <= 0 else "") + return _("{name} takes {amount} damage.")\ + .format(name=str(self), amount=str(amount)) \ + + (" " + "{name} dies.".format(name=str(self)) + if self.health <= 0 else "") def die(self) -> None: """ diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index 31c50ea..3c2d9e8 100644 --- a/squirrelbattle/menus.py +++ b/squirrelbattle/menus.py @@ -7,6 +7,7 @@ from typing import Any, Optional from .display.texturepack import TexturePack from .enums import GameMode, KeyValues, DisplayActions from .settings import Settings +from .translations import gettext as _ class Menu: @@ -41,12 +42,12 @@ class MainMenuValues(Enum): """ Values of the main menu """ - START = 'Nouvelle partie' - RESUME = 'Continuer' - SAVE = 'Sauvegarder' - LOAD = 'Charger' - SETTINGS = 'Paramètres' - EXIT = 'Quitter' + START = _("New game") + RESUME = _("Resume") + SAVE = _("Save") + LOAD = _("Load") + SETTINGS = _("Settings") + EXIT = _("Exit") def __str__(self): return self.value @@ -67,7 +68,7 @@ class SettingsMenu(Menu): def update_values(self, settings: Settings) -> None: self.values = list(settings.__dict__.items()) - self.values.append(("RETURN", ["", "Retour"])) + self.values.append(("RETURN", ["", _("Back")])) def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str, game: Any) -> None: From 4287b4f045f91139b0c4d9c45965d2cab0adba60 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 20:53:24 +0100 Subject: [PATCH 125/171] Add possibility to change the language --- squirrelbattle/game.py | 3 ++- squirrelbattle/interfaces.py | 8 ++++---- squirrelbattle/menus.py | 8 +++++++- squirrelbattle/settings.py | 2 ++ squirrelbattle/translations.py | 17 +++++++++++++++-- 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index a9689a5..be1e01a 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -14,7 +14,7 @@ from .interfaces import Map, Logs from .resources import ResourceManager from .settings import Settings from . import menus -from .translations import gettext as _ +from .translations import gettext as _, setlocale from typing import Callable @@ -38,6 +38,7 @@ class Game: self.settings.load_settings() self.settings.write_settings() self.settings_menu.update_values(self.settings) + setlocale(self.settings.LOCALE) self.logs = Logs() self.message = None diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 0e51589..845e3bd 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -129,7 +129,7 @@ class Map: """ Put randomly {count} hedgehogs on the map, where it is available. """ - for _ in range(count): + for ignored in range(count): y, x = 0, 0 while True: y, x = randint(0, self.height - 1), randint(0, self.width - 1) @@ -392,7 +392,7 @@ class FightingEntity(Entity): Deals damage to the opponent, based on the stats """ return _("{name} hits {opponent}.")\ - .format(name=str(self), opponent=str(opponent)) + " "\ + .format(name=self.name, opponent=opponent.name) + " "\ + opponent.take_damage(self, self.strength) def take_damage(self, attacker: "Entity", amount: int) -> str: @@ -403,8 +403,8 @@ class FightingEntity(Entity): if self.health <= 0: self.die() return _("{name} takes {amount} damage.")\ - .format(name=str(self), amount=str(amount)) \ - + (" " + "{name} dies.".format(name=str(self)) + .format(name=self.name, amount=str(amount)) \ + + (" " + "{name} dies.".format(name=self.name) if self.health <= 0 else "") def die(self) -> None: diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index 3c2d9e8..56a7db2 100644 --- a/squirrelbattle/menus.py +++ b/squirrelbattle/menus.py @@ -7,7 +7,7 @@ from typing import Any, Optional from .display.texturepack import TexturePack from .enums import GameMode, KeyValues, DisplayActions from .settings import Settings -from .translations import gettext as _ +from .translations import gettext as _, setlocale class Menu: @@ -96,6 +96,12 @@ class SettingsMenu(Menu): game.settings.TEXTURE_PACK) game.settings.write_settings() self.update_values(game.settings) + elif option == "LOCALE": + game.settings.LOCALE = 'fr' if game.settings.LOCALE == 'en'\ + else 'en' + setlocale(game.settings.LOCALE) + game.settings.write_settings() + self.update_values(game.settings) else: self.waiting_for_key = True self.update_values(game.settings) diff --git a/squirrelbattle/settings.py b/squirrelbattle/settings.py index 9601457..726d96c 100644 --- a/squirrelbattle/settings.py +++ b/squirrelbattle/settings.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import json +import locale import os from typing import Any, Generator @@ -35,6 +36,7 @@ class Settings: self.KEY_ENTER = \ ['\n', 'Touche pour valider un menu'] self.TEXTURE_PACK = ['ascii', 'Pack de textures utilisé'] + self.LOCALE = [locale.getlocale()[0][:2], 'Langue'] def __getattribute__(self, item: str) -> Any: superattribute = super().__getattribute__(item) diff --git a/squirrelbattle/translations.py b/squirrelbattle/translations.py index 27ec288..fa8196d 100644 --- a/squirrelbattle/translations.py +++ b/squirrelbattle/translations.py @@ -4,12 +4,25 @@ import gettext +SUPPORTED_LOCALES = ["en", "fr"] +DEFAULT_LOCALE = "en" + +_current_locale = DEFAULT_LOCALE + _TRANSLATORS = dict() -for language in ["en", "fr"]: +for language in SUPPORTED_LOCALES: _TRANSLATORS[language] = gettext.translation("squirrelbattle", localedir="locale", languages=[language]) def gettext(message: str) -> str: - return _TRANSLATORS.get("en", _TRANSLATORS.get("en")).gettext(message) + return _TRANSLATORS.get(_current_locale, + _TRANSLATORS.get("en")).gettext(message) + + +def setlocale(lang: str) -> None: + global _current_locale + lang = lang[:2] + if lang in SUPPORTED_LOCALES: + _current_locale = lang From c151e0f65620d4e0854b6b5bc7eb59b83fc7eb8e Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 21:44:17 +0100 Subject: [PATCH 126/171] Menu items are translated --- locale/en/LC_MESSAGES/squirrelbattle.po | 20 ++++++++++---------- locale/fr/LC_MESSAGES/squirrelbattle.po | 20 ++++++++++---------- squirrelbattle/display/menudisplay.py | 2 +- squirrelbattle/menus.py | 14 +++++++------- squirrelbattle/tests/translations_test.py | 19 +++++++++++++++++++ 5 files changed, 47 insertions(+), 28 deletions(-) create mode 100644 squirrelbattle/tests/translations_test.py diff --git a/locale/en/LC_MESSAGES/squirrelbattle.po b/locale/en/LC_MESSAGES/squirrelbattle.po index 31fcdec..fb84fe2 100644 --- a/locale/en/LC_MESSAGES/squirrelbattle.po +++ b/locale/en/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-11-27 20:39+0100\n" +"POT-Creation-Date: 2020-11-27 21:43+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -35,27 +35,27 @@ msgstr "" msgid "{name} takes {amount} damage." msgstr "" -#: squirrelbattle/menus.py:45 +#: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:14 msgid "New game" msgstr "" -#: squirrelbattle/menus.py:46 +#: squirrelbattle/menus.py:46 squirrelbattle/tests/translations_test.py:15 msgid "Resume" msgstr "" -#: squirrelbattle/menus.py:47 +#: squirrelbattle/menus.py:47 squirrelbattle/tests/translations_test.py:17 msgid "Save" msgstr "" -#: squirrelbattle/menus.py:48 +#: squirrelbattle/menus.py:48 squirrelbattle/tests/translations_test.py:16 msgid "Load" msgstr "" -#: squirrelbattle/menus.py:49 +#: squirrelbattle/menus.py:49 squirrelbattle/tests/translations_test.py:18 msgid "Settings" msgstr "" -#: squirrelbattle/menus.py:50 +#: squirrelbattle/menus.py:50 squirrelbattle/tests/translations_test.py:19 msgid "Exit" msgstr "" @@ -63,19 +63,19 @@ msgstr "" msgid "Back" msgstr "" -#: squirrelbattle/game.py:147 +#: squirrelbattle/game.py:147 squirrelbattle/game.py:148 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." msgstr "" -#: squirrelbattle/game.py:155 +#: squirrelbattle/game.py:155 squirrelbattle/game.py:156 msgid "" "No player was found on this map!\n" "Maybe you died?" msgstr "" -#: squirrelbattle/game.py:175 +#: squirrelbattle/game.py:175 squirrelbattle/game.py:176 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted.It got deleted." diff --git a/locale/fr/LC_MESSAGES/squirrelbattle.po b/locale/fr/LC_MESSAGES/squirrelbattle.po index ab04ec8..0068f30 100644 --- a/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-11-27 20:39+0100\n" +"POT-Creation-Date: 2020-11-27 21:43+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -35,27 +35,27 @@ msgstr "{name} frappe {opponent}." msgid "{name} takes {amount} damage." msgstr "{name} prend {amount} points de dégât." -#: squirrelbattle/menus.py:45 +#: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:14 msgid "New game" msgstr "Nouvelle partie" -#: squirrelbattle/menus.py:46 +#: squirrelbattle/menus.py:46 squirrelbattle/tests/translations_test.py:15 msgid "Resume" msgstr "Continuer" -#: squirrelbattle/menus.py:47 +#: squirrelbattle/menus.py:47 squirrelbattle/tests/translations_test.py:17 msgid "Save" msgstr "Sauvegarder" -#: squirrelbattle/menus.py:48 +#: squirrelbattle/menus.py:48 squirrelbattle/tests/translations_test.py:16 msgid "Load" msgstr "Charger" -#: squirrelbattle/menus.py:49 +#: squirrelbattle/menus.py:49 squirrelbattle/tests/translations_test.py:18 msgid "Settings" msgstr "Paramètres" -#: squirrelbattle/menus.py:50 +#: squirrelbattle/menus.py:50 squirrelbattle/tests/translations_test.py:19 msgid "Exit" msgstr "Quitter" @@ -63,7 +63,7 @@ msgstr "Quitter" msgid "Back" msgstr "Retour" -#: squirrelbattle/game.py:147 +#: squirrelbattle/game.py:147 squirrelbattle/game.py:148 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -71,7 +71,7 @@ msgstr "" "Certaines clés de votre ficher de sauvegarde sont manquantes.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/game.py:155 +#: squirrelbattle/game.py:155 squirrelbattle/game.py:156 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -79,7 +79,7 @@ msgstr "" "Aucun joueur n'a été trouvé sur la carte !\n" "Peut-être êtes-vous mort ?" -#: squirrelbattle/game.py:175 +#: squirrelbattle/game.py:175 squirrelbattle/game.py:176 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted.It got deleted." diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 731ecee..e388a83 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -18,7 +18,7 @@ class MenuDisplay(Display): def update_menu(self, menu: Menu) -> None: self.menu = menu self.trueheight = len(self.values) - self.truewidth = max([len(a) for a in self.values]) + self.truewidth = max([len(str(a)) for a in self.values]) # Menu values are printed in pad self.pad = self.newpad(self.trueheight, self.truewidth + 2) diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index 56a7db2..3f6f7a0 100644 --- a/squirrelbattle/menus.py +++ b/squirrelbattle/menus.py @@ -42,15 +42,15 @@ class MainMenuValues(Enum): """ Values of the main menu """ - START = _("New game") - RESUME = _("Resume") - SAVE = _("Save") - LOAD = _("Load") - SETTINGS = _("Settings") - EXIT = _("Exit") + START = "New game" + RESUME = "Resume" + SAVE = "Save" + LOAD = "Load" + SETTINGS = "Settings" + EXIT = "Exit" def __str__(self): - return self.value + return _(self.value) class MainMenu(Menu): diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py new file mode 100644 index 0000000..de93eb4 --- /dev/null +++ b/squirrelbattle/tests/translations_test.py @@ -0,0 +1,19 @@ +import unittest + +from squirrelbattle.translations import gettext as _, setlocale + + +class TestTranslations(unittest.TestCase): + def setUp(self) -> None: + setlocale("fr") + + def test_translations(self): + """ + Ensure that some strings are well-translated. + """ + self.assertEqual(_("New game"), "Nouvelle partie") + self.assertEqual(_("Resume"), "Continuer") + self.assertEqual(_("Load"), "Charger") + self.assertEqual(_("Save"), "Sauvegarder") + self.assertEqual(_("Settings"), "Paramètres") + self.assertEqual(_("Exit"), "Quitter") From 31b7ece449ed66187a8cfb61d3d143d1956d56c5 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 21:51:54 +0100 Subject: [PATCH 127/171] Main menu width must be updated when the language got changed --- squirrelbattle/display/menudisplay.py | 10 ++++++++-- squirrelbattle/game.py | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index e388a83..e6bb00d 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -17,8 +17,6 @@ class MenuDisplay(Display): def update_menu(self, menu: Menu) -> None: self.menu = menu - self.trueheight = len(self.values) - self.truewidth = max([len(str(a)) for a in self.values]) # Menu values are printed in pad self.pad = self.newpad(self.trueheight, self.truewidth + 2) @@ -44,6 +42,14 @@ class MenuDisplay(Display): self.height - 2 + self.y, self.width - 2 + self.x) + @property + def truewidth(self) -> int: + return max([len(str(a)) for a in self.values]) + + @property + def trueheight(self) -> int: + return len(self.values) + @property def preferred_width(self) -> int: return self.truewidth + 6 diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index be1e01a..ad2f1cc 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -37,8 +37,8 @@ class Game: self.settings = Settings() self.settings.load_settings() self.settings.write_settings() - self.settings_menu.update_values(self.settings) setlocale(self.settings.LOCALE) + self.settings_menu.update_values(self.settings) self.logs = Logs() self.message = None From f07324662a594ac9b7a2a765147e55e090908b8e Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 21:56:25 +0100 Subject: [PATCH 128/171] Cover the language change --- squirrelbattle/tests/game_test.py | 15 +++++++++++---- squirrelbattle/tests/translations_test.py | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 5081912..8f5b1c1 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -15,6 +15,7 @@ from squirrelbattle.entities.player import Player from squirrelbattle.game import Game, KeyValues, GameMode from squirrelbattle.menus import MainMenuValues from squirrelbattle.settings import Settings +from squirrelbattle.translations import gettext as _ class TestGame(unittest.TestCase): @@ -275,12 +276,18 @@ class TestGame(unittest.TestCase): self.game.handle_key_pressed(KeyValues.ENTER) self.assertEqual(self.game.settings.TEXTURE_PACK, "ascii") + # Change language + self.game.settings.LOCALE = "en" + self.game.handle_key_pressed(KeyValues.DOWN) + self.game.handle_key_pressed(KeyValues.ENTER) + self.assertEqual(self.game.settings.LOCALE, "fr") + self.assertEqual(_("New game"), "Nouvelle partie") + self.game.handle_key_pressed(KeyValues.ENTER) + self.assertEqual(self.game.settings.LOCALE, "en") + self.assertEqual(_("New game"), "New game") + # Navigate to "back" button 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.DOWN) - self.game.handle_key_pressed(KeyValues.DOWN) self.game.handle_key_pressed(KeyValues.ENTER) self.assertEqual(self.game.state, GameMode.MAINMENU) diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index de93eb4..e51cb8a 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -7,7 +7,7 @@ class TestTranslations(unittest.TestCase): def setUp(self) -> None: setlocale("fr") - def test_translations(self): + def test_translations(self) -> None: """ Ensure that some strings are well-translated. """ From d2d74c97a4d5fe26145d4aaf7b1e84d9fecdd30c Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 22:19:41 +0100 Subject: [PATCH 129/171] Settings menu was not translated --- locale/en/LC_MESSAGES/squirrelbattle.po | 47 +++++++++++++++++++++- locale/fr/LC_MESSAGES/squirrelbattle.mo | Bin 1445 -> 2310 bytes locale/fr/LC_MESSAGES/squirrelbattle.po | 47 +++++++++++++++++++++- squirrelbattle/display/menudisplay.py | 6 ++- squirrelbattle/game.py | 4 +- squirrelbattle/settings.py | 36 +++++++---------- squirrelbattle/tests/settings_test.py | 2 +- squirrelbattle/tests/translations_test.py | 21 ++++++++++ 8 files changed, 134 insertions(+), 29 deletions(-) diff --git a/locale/en/LC_MESSAGES/squirrelbattle.po b/locale/en/LC_MESSAGES/squirrelbattle.po index fb84fe2..9ca5419 100644 --- a/locale/en/LC_MESSAGES/squirrelbattle.po +++ b/locale/en/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-11-27 21:43+0100\n" +"POT-Creation-Date: 2020-11-27 22:05+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -36,6 +36,7 @@ msgid "{name} takes {amount} damage." msgstr "" #: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:14 +#: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287 msgid "New game" msgstr "" @@ -80,3 +81,47 @@ msgid "" "The JSON file is not correct.\n" "Your save seems corrupted.It got deleted." msgstr "" + +#: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:21 +msgid "Main key to move up" +msgstr "" + +#: squirrelbattle/settings.py:22 squirrelbattle/tests/translations_test.py:23 +msgid "Secondary key to move up" +msgstr "" + +#: squirrelbattle/settings.py:23 squirrelbattle/tests/translations_test.py:25 +msgid "Main key to move down" +msgstr "" + +#: squirrelbattle/settings.py:24 squirrelbattle/tests/translations_test.py:27 +msgid "Secondary key to move down" +msgstr "" + +#: squirrelbattle/settings.py:25 squirrelbattle/tests/translations_test.py:29 +msgid "Main key to move left" +msgstr "" + +#: squirrelbattle/settings.py:26 squirrelbattle/tests/translations_test.py:31 +msgid "Secondary key to move left" +msgstr "" + +#: squirrelbattle/settings.py:27 squirrelbattle/tests/translations_test.py:33 +msgid "Main key to move right" +msgstr "" + +#: squirrelbattle/settings.py:29 squirrelbattle/tests/translations_test.py:35 +msgid "Secondary key to move right" +msgstr "" + +#: squirrelbattle/settings.py:30 squirrelbattle/tests/translations_test.py:37 +msgid "Key to validate a menu" +msgstr "" + +#: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:39 +msgid "Texture pack" +msgstr "" + +#: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40 +msgid "Language" +msgstr "" diff --git a/locale/fr/LC_MESSAGES/squirrelbattle.mo b/locale/fr/LC_MESSAGES/squirrelbattle.mo index 7e3ecd63e13fc07cf7b1df1e42fa1bffd4ee8666..bc9146e56582ef46663f44ca84d8b24c1f344325 100644 GIT binary patch literal 2310 zcmbu9%WoVt9LHTK6qa`>?;`ywgb1o;3AeHonr(I(w%td%y9o*)BxlFF>t@CtY|p0K zDp!Pn8-f!T5JCiQ^w0}JNPFim01j|07bI?6_G~hw48}LWli)Eddd`6_f-(3I_!)Q} z{2II${0qDf{0H>=k3gB(M&L#$JuCxSIJr7<4~L+jj9PiHD3t;h|e zrG%#^3UxkO6*STrMN&j(SWDOp``UbnkLYR@aWmR&;oeKRnR_oyY~6be2DgQCQuQrs z>qK~Umbej=NNX`o2og**WMLNdWGsq>mF=+=BC$TyaHOk^$x>IOs-wPk6p2_k5fxcS zTy_~wOns_413~B8%|?C{AruOteyixZ-6v1a#3{EQy3$;tax0`tSgx?E3IX4sfpnIx z>NM2~m9SCVns&=N!G_XHJGScz7`ax17egG!m+XqM{LlW#Q}VWGyY z3p$1?8*`LqiOwz6YSbv#LpmMS%hlRkxl(C`?e^;sofK!#5j-hTt=w2x@|K*g)#hSZ z)TX~90}JZadbnfu%f;EkyjBiw2c1z0dmMuP66#N?SE)PT#tQe^Qs-Q7YDdnS)e|NN zRaZwyb%{=`N>@M~IwY7~%%pR|Oz#z!$Y6Za=@*gUlLv2RP% zNY#}okEgqM9AnQA0VhnYa(fU#jw|IAA51nfTDk+t3L5at;i_gs0lQG5#!11-Sr<-S z(wTsp>IF{YO*h^oXCMb!%*LF$5EgWzuqZMYjK6gV5Wd0&L!IG@qi*O7^I4H9Lp<6X zO8VSHJF&5oTkb=fNAy2qv!CGYvDsht*4XSPJB`gEH#`}C1Me)G7sjEYgr#mg-n6*l zG}PYjopoRsgcQe z(_dCR9{zk^*LX}Ut4*{@sLkJ#TXhTT@N#XxEB{CHa&xIo;}1Jwo9fM0XX{bv8J?zM cujeK;0#E(+bCYVR@Gil=XuR1Uf9ZTyp*O-h1s`J1e~vl5HtSg(#9&X@FUd^n# zM2{&?v4sWfIQuxE9$|!g)aPzbM9MN0>2p(M;R)$$LmQrxoV88uA{UO>p^mjS)X+b0 y4Cb+aNQbx}`3@L|JZ diff --git a/locale/fr/LC_MESSAGES/squirrelbattle.po b/locale/fr/LC_MESSAGES/squirrelbattle.po index 0068f30..9dac712 100644 --- a/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-11-27 21:43+0100\n" +"POT-Creation-Date: 2020-11-27 22:05+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -36,6 +36,7 @@ msgid "{name} takes {amount} damage." msgstr "{name} prend {amount} points de dégât." #: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:14 +#: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287 msgid "New game" msgstr "Nouvelle partie" @@ -86,3 +87,47 @@ msgid "" msgstr "" "Le fichier JSON de sauvegarde est incorrect.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." + +#: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:21 +msgid "Main key to move up" +msgstr "Touche principale pour aller vers le haut" + +#: squirrelbattle/settings.py:22 squirrelbattle/tests/translations_test.py:23 +msgid "Secondary key to move up" +msgstr "Touche secondaire pour aller vers le haut" + +#: squirrelbattle/settings.py:23 squirrelbattle/tests/translations_test.py:25 +msgid "Main key to move down" +msgstr "Touche principale pour aller vers le bas" + +#: squirrelbattle/settings.py:24 squirrelbattle/tests/translations_test.py:27 +msgid "Secondary key to move down" +msgstr "Touche secondaire pour aller vers le bas" + +#: squirrelbattle/settings.py:25 squirrelbattle/tests/translations_test.py:29 +msgid "Main key to move left" +msgstr "Touche principale pour aller vers la gauche" + +#: squirrelbattle/settings.py:26 squirrelbattle/tests/translations_test.py:31 +msgid "Secondary key to move left" +msgstr "Touche secondaire pour aller vers la gauche" + +#: squirrelbattle/settings.py:27 squirrelbattle/tests/translations_test.py:33 +msgid "Main key to move right" +msgstr "Touche principale pour aller vers la droite" + +#: squirrelbattle/settings.py:29 squirrelbattle/tests/translations_test.py:35 +msgid "Secondary key to move right" +msgstr "Touche secondaire pour aller vers la droite" + +#: squirrelbattle/settings.py:30 squirrelbattle/tests/translations_test.py:37 +msgid "Key to validate a menu" +msgstr "Touche pour valider un menu" + +#: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:39 +msgid "Texture pack" +msgstr "Pack de textures" + +#: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40 +msgid "Language" +msgstr "Langue" diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index e6bb00d..b3036a0 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -6,6 +6,7 @@ from typing import List from squirrelbattle.menus import Menu, MainMenu from .display import Display, Box from ..resources import ResourceManager +from ..translations import gettext as _ class MenuDisplay(Display): @@ -66,9 +67,10 @@ class MenuDisplay(Display): class SettingsMenuDisplay(MenuDisplay): @property def values(self) -> List[str]: - return [a[1][1] + (" : " + return [_(a[1][1]) + (" : " + ("?" if self.menu.waiting_for_key - and a == self.menu.validate() else a[1][0]) + and a == self.menu.validate() else a[1][0] + .replace("\n", "\\n")) if a[1][0] else "") for a in self.menu.values] diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index ad2f1cc..7851aee 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -32,12 +32,12 @@ class Game: Init the game. """ self.state = GameMode.MAINMENU - self.main_menu = menus.MainMenu() - self.settings_menu = menus.SettingsMenu() self.settings = Settings() self.settings.load_settings() self.settings.write_settings() setlocale(self.settings.LOCALE) + self.main_menu = menus.MainMenu() + self.settings_menu = menus.SettingsMenu() self.settings_menu.update_values(self.settings) self.logs = Logs() self.message = None diff --git a/squirrelbattle/settings.py b/squirrelbattle/settings.py index 726d96c..3090679 100644 --- a/squirrelbattle/settings.py +++ b/squirrelbattle/settings.py @@ -7,6 +7,7 @@ import os from typing import Any, Generator from .resources import ResourceManager +from .translations import gettext as _ class Settings: @@ -17,26 +18,17 @@ class Settings: We can define the setting by simply use settings.TEXTURE_PACK = 'new_key' """ def __init__(self): - self.KEY_UP_PRIMARY = \ - ['z', 'Touche principale pour aller vers le haut'] - self.KEY_UP_SECONDARY = \ - ['KEY_UP', 'Touche secondaire pour aller vers le haut'] - self.KEY_DOWN_PRIMARY = \ - ['s', 'Touche principale pour aller vers le bas'] - self.KEY_DOWN_SECONDARY = \ - ['KEY_DOWN', 'Touche secondaire pour aller vers le bas'] - self.KEY_LEFT_PRIMARY = \ - ['q', 'Touche principale pour aller vers la gauche'] - self.KEY_LEFT_SECONDARY = \ - ['KEY_LEFT', 'Touche secondaire pour aller vers la gauche'] - self.KEY_RIGHT_PRIMARY = \ - ['d', 'Touche principale pour aller vers la droite'] - self.KEY_RIGHT_SECONDARY = \ - ['KEY_RIGHT', 'Touche secondaire pour aller vers la droite'] - self.KEY_ENTER = \ - ['\n', 'Touche pour valider un menu'] - self.TEXTURE_PACK = ['ascii', 'Pack de textures utilisé'] - self.LOCALE = [locale.getlocale()[0][:2], 'Langue'] + self.KEY_UP_PRIMARY = ['z', 'Main key to move up'] + self.KEY_UP_SECONDARY = ['KEY_UP', 'Secondary key to move up'] + self.KEY_DOWN_PRIMARY = ['s', 'Main key to move down'] + self.KEY_DOWN_SECONDARY = ['KEY_DOWN', 'Secondary key to move down'] + self.KEY_LEFT_PRIMARY = ['q', 'Main key to move left'] + self.KEY_LEFT_SECONDARY = ['KEY_LEFT', 'Secondary key to move left'] + self.KEY_RIGHT_PRIMARY = ['d', 'Main key to move right'] + self.KEY_RIGHT_SECONDARY = ['KEY_RIGHT', 'Secondary key to move right'] + self.KEY_ENTER = ['\n', 'Key to validate a menu'] + self.TEXTURE_PACK = ['ascii', 'Texture pack'] + self.LOCALE = [locale.getlocale()[0][:2], 'Language'] def __getattribute__(self, item: str) -> Any: superattribute = super().__getattribute__(item) @@ -55,10 +47,10 @@ class Settings: Retrieve the comment of a setting. """ if item in self.settings_keys: - return object.__getattribute__(self, item)[1] + return _(object.__getattribute__(self, item)[1]) for key in self.settings_keys: if getattr(self, key) == item: - return object.__getattribute__(self, key)[1] + return _(object.__getattribute__(self, key)[1]) @property def settings_keys(self) -> Generator[str, Any, None]: diff --git a/squirrelbattle/tests/settings_test.py b/squirrelbattle/tests/settings_test.py index cef60c0..b0d9739 100644 --- a/squirrelbattle/tests/settings_test.py +++ b/squirrelbattle/tests/settings_test.py @@ -24,7 +24,7 @@ class TestSettings(unittest.TestCase): self.assertEqual(settings.get_comment(settings.TEXTURE_PACK), settings.get_comment('TEXTURE_PACK')) self.assertEqual(settings.get_comment(settings.TEXTURE_PACK), - 'Pack de textures utilisé') + 'Texture pack') settings.TEXTURE_PACK = 'squirrel' self.assertEqual(settings.TEXTURE_PACK, 'squirrel') diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index e51cb8a..0d190d6 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -17,3 +17,24 @@ class TestTranslations(unittest.TestCase): self.assertEqual(_("Save"), "Sauvegarder") self.assertEqual(_("Settings"), "Paramètres") self.assertEqual(_("Exit"), "Quitter") + + self.assertEqual(_("Main key to move up"), + "Touche principale pour aller vers le haut") + self.assertEqual(_("Secondary key to move up"), + "Touche secondaire pour aller vers le haut") + self.assertEqual(_("Main key to move down"), + "Touche principale pour aller vers le bas") + self.assertEqual(_("Secondary key to move down"), + "Touche secondaire pour aller vers le bas") + self.assertEqual(_("Main key to move left"), + "Touche principale pour aller vers la gauche") + self.assertEqual(_("Secondary key to move left"), + "Touche secondaire pour aller vers la gauche") + self.assertEqual(_("Main key to move right"), + "Touche principale pour aller vers la droite") + self.assertEqual(_("Secondary key to move right"), + "Touche secondaire pour aller vers la droite") + self.assertEqual(_("Key to validate a menu"), + "Touche pour valider un menu") + self.assertEqual(_("Texture pack"), "Pack de textures") + self.assertEqual(_("Language"), "Langue") From 8f85093eb8b0796859be289ad23eb999c0591a25 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 22:21:16 +0100 Subject: [PATCH 130/171] One string was missing --- locale/en/LC_MESSAGES/squirrelbattle.po | 9 +++++++-- locale/fr/LC_MESSAGES/squirrelbattle.po | 9 +++++++-- squirrelbattle/game.py | 2 +- squirrelbattle/interfaces.py | 2 +- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/locale/en/LC_MESSAGES/squirrelbattle.po b/locale/en/LC_MESSAGES/squirrelbattle.po index 9ca5419..a027782 100644 --- a/locale/en/LC_MESSAGES/squirrelbattle.po +++ b/locale/en/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-11-27 22:05+0100\n" +"POT-Creation-Date: 2020-11-27 22:20+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -79,7 +79,7 @@ msgstr "" #: squirrelbattle/game.py:175 squirrelbattle/game.py:176 msgid "" "The JSON file is not correct.\n" -"Your save seems corrupted.It got deleted." +"Your save seems corrupted. It got deleted." msgstr "" #: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:21 @@ -125,3 +125,8 @@ msgstr "" #: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40 msgid "Language" msgstr "" + +#: squirrelbattle/interfaces.py:407 +#, python-brace-format +msgid "{name} dies." +msgstr "" diff --git a/locale/fr/LC_MESSAGES/squirrelbattle.po b/locale/fr/LC_MESSAGES/squirrelbattle.po index 9dac712..c47f2ca 100644 --- a/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-11-27 22:05+0100\n" +"POT-Creation-Date: 2020-11-27 22:20+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -83,7 +83,7 @@ msgstr "" #: squirrelbattle/game.py:175 squirrelbattle/game.py:176 msgid "" "The JSON file is not correct.\n" -"Your save seems corrupted.It got deleted." +"Your save seems corrupted. It got deleted." msgstr "" "Le fichier JSON de sauvegarde est incorrect.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." @@ -131,3 +131,8 @@ msgstr "Pack de textures" #: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40 msgid "Language" msgstr "Langue" + +#: squirrelbattle/interfaces.py:407 +#, python-brace-format +msgid "{name} dies." +msgstr "{name} meurt." diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 7851aee..09f1328 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -174,7 +174,7 @@ class Game: self.load_state(state) except JSONDecodeError: self.message = _("The JSON file is not correct.\n" - "Your save seems corrupted." + "Your save seems corrupted. " "It got deleted.") os.unlink(file_path) self.display_actions(DisplayActions.UPDATE) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 845e3bd..78c1a15 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -404,7 +404,7 @@ class FightingEntity(Entity): self.die() return _("{name} takes {amount} damage.")\ .format(name=self.name, amount=str(amount)) \ - + (" " + "{name} dies.".format(name=self.name) + + (" " + _("{name} dies.").format(name=self.name) if self.health <= 0 else "") def die(self) -> None: From 70ae60b9a428c129355361aa6f71175155bd16bc Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 27 Nov 2020 22:33:58 +0100 Subject: [PATCH 131/171] Translate entities --- locale/en/LC_MESSAGES/squirrelbattle.po | 47 ++++++++++++++++++++-- locale/fr/LC_MESSAGES/squirrelbattle.mo | Bin 2310 -> 2607 bytes locale/fr/LC_MESSAGES/squirrelbattle.po | 47 ++++++++++++++++++++-- squirrelbattle/interfaces.py | 14 +++++-- squirrelbattle/tests/translations_test.py | 19 ++++++++- 5 files changed, 113 insertions(+), 14 deletions(-) diff --git a/locale/en/LC_MESSAGES/squirrelbattle.po b/locale/en/LC_MESSAGES/squirrelbattle.po index a027782..d77e823 100644 --- a/locale/en/LC_MESSAGES/squirrelbattle.po +++ b/locale/en/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-11-27 22:20+0100\n" +"POT-Creation-Date: 2020-11-27 22:31+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -25,12 +25,12 @@ msgstr "" msgid "YOU ARE DEAD" msgstr "" -#: squirrelbattle/interfaces.py:394 +#: squirrelbattle/interfaces.py:394 squirrelbattle/interfaces.py:398 #, python-brace-format msgid "{name} hits {opponent}." msgstr "" -#: squirrelbattle/interfaces.py:405 +#: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "" @@ -83,50 +83,89 @@ msgid "" msgstr "" #: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:21 +#: squirrelbattle/tests/translations_test.py:25 msgid "Main key to move up" msgstr "" #: squirrelbattle/settings.py:22 squirrelbattle/tests/translations_test.py:23 +#: squirrelbattle/tests/translations_test.py:27 msgid "Secondary key to move up" msgstr "" #: squirrelbattle/settings.py:23 squirrelbattle/tests/translations_test.py:25 +#: squirrelbattle/tests/translations_test.py:29 msgid "Main key to move down" msgstr "" #: squirrelbattle/settings.py:24 squirrelbattle/tests/translations_test.py:27 +#: squirrelbattle/tests/translations_test.py:31 msgid "Secondary key to move down" msgstr "" #: squirrelbattle/settings.py:25 squirrelbattle/tests/translations_test.py:29 +#: squirrelbattle/tests/translations_test.py:33 msgid "Main key to move left" msgstr "" #: squirrelbattle/settings.py:26 squirrelbattle/tests/translations_test.py:31 +#: squirrelbattle/tests/translations_test.py:35 msgid "Secondary key to move left" msgstr "" #: squirrelbattle/settings.py:27 squirrelbattle/tests/translations_test.py:33 +#: squirrelbattle/tests/translations_test.py:37 msgid "Main key to move right" msgstr "" #: squirrelbattle/settings.py:29 squirrelbattle/tests/translations_test.py:35 +#: squirrelbattle/tests/translations_test.py:39 msgid "Secondary key to move right" msgstr "" #: squirrelbattle/settings.py:30 squirrelbattle/tests/translations_test.py:37 +#: squirrelbattle/tests/translations_test.py:41 msgid "Key to validate a menu" msgstr "" #: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:39 +#: squirrelbattle/tests/translations_test.py:43 msgid "Texture pack" msgstr "" #: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40 +#: squirrelbattle/tests/translations_test.py:44 msgid "Language" msgstr "" -#: squirrelbattle/interfaces.py:407 +#: squirrelbattle/interfaces.py:407 squirrelbattle/interfaces.py:412 #, python-brace-format msgid "{name} dies." msgstr "" + +#: squirrelbattle/tests/translations_test.py:47 +msgid "player" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:49 +msgid "tiger" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:50 +msgid "hedgehog" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:51 +msgid "rabbit" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:52 +msgid "teddy bear" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:54 +msgid "bomb" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:55 +msgid "heart" +msgstr "" diff --git a/locale/fr/LC_MESSAGES/squirrelbattle.mo b/locale/fr/LC_MESSAGES/squirrelbattle.mo index bc9146e56582ef46663f44ca84d8b24c1f344325..12e6fe990a48a5dcee90cc5a5049f39393074960 100644 GIT binary patch delta 938 zcmXxiKWGzS7{~D^?U~l9Rnz#VYBdcM8Z1%lBnkylP{Bng9p%oP$w`xomqe%{w>b#n za5~vt0)k){XSIVU=-?m@oeB;*h?5Tf`TpYF!@ck4-rSq#{d>3YtQc<;oJU5KDTgR? zIkRJUdWeeni4Ois{exq)hlkDf;y8}t460uh_uzc$1ysK)IDt1&{qNy%v)B?|^8D}` zmEbKNzz?Zk@gVIyANOM!mEaUE;aS{{t9Ty!Y5NEA*k7tG=uqi@W2o^a2F47=i&iv^ zJT^zAgePzxF5na{sm<>6Pzml+jo?EZOaL{}KJwT)mA=2jS^S7c@po#4j}^wZMP7Dd zj2H0%ZpF{I4L4BTpkh+? zGX3xd$zku)_9xsydjlu&8_r^$ZDT_QlPx0i8w|{e4R%gfNIRRwVP1Aqv`e}pWu1Q( zO03YpS?C^U#yWS!^na{?C5l$9&;`>G=)84QigAi2tzmTC6U+omsm)^d*-o zB`2M!1utxR*6`h^OI@q`jj(R*Wp~AoEOI?B=vvpW)mBO#U920_`TcgwZTfdgwZQLG z*A|_Yb4f{jySkqAB5S8gf5hy{pwnU;5x|Bgi6ir$UVvCayZW7ccHMIoM(h&59 zmWZIezn~gw)Fr5?rSG}k3+H|Aeeb?^-Z}R!(g@c-!p^xcrim!gCgzDp9~Z{DU&O(@ zwSc{>cW@l{u?LUs^9GKwKDAz=i3yRqw3u{rBihwTG>7^#F>4BESnuH^ z)=)pt#4WtUe*D2z^zpa4&f)|Xa26}Nrl>R2eAfYyAo=B%iN4^WCVEEQ@MboMd|`}r z3l}iLLv;n?SjHi|z#QIS2*0rp|4`o#20QO1th*Q|znn19jrUl?CmhBl$~?pzY61_Z z@C9S|j`ozOq*E=$nf8MjG}nAfJ4%C0VdeoBJ;uDAi4Oe%9hyLsg$ZppIb?*;iu4>K zgmy~@4KZVo>wuk$_v>%_qS>l@a8z}RrE;lSDlW?Q+NPV_$hhfDGVLt~Do(Q\n" "Language-Team: LANGUAGE \n" @@ -25,12 +25,12 @@ msgstr "Inventaire :" msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" -#: squirrelbattle/interfaces.py:394 +#: squirrelbattle/interfaces.py:394 squirrelbattle/interfaces.py:398 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} frappe {opponent}." -#: squirrelbattle/interfaces.py:405 +#: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} prend {amount} points de dégât." @@ -89,50 +89,89 @@ msgstr "" "Votre sauvegarde semble corrompue. Elle a été supprimée." #: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:21 +#: squirrelbattle/tests/translations_test.py:25 msgid "Main key to move up" msgstr "Touche principale pour aller vers le haut" #: squirrelbattle/settings.py:22 squirrelbattle/tests/translations_test.py:23 +#: squirrelbattle/tests/translations_test.py:27 msgid "Secondary key to move up" msgstr "Touche secondaire pour aller vers le haut" #: squirrelbattle/settings.py:23 squirrelbattle/tests/translations_test.py:25 +#: squirrelbattle/tests/translations_test.py:29 msgid "Main key to move down" msgstr "Touche principale pour aller vers le bas" #: squirrelbattle/settings.py:24 squirrelbattle/tests/translations_test.py:27 +#: squirrelbattle/tests/translations_test.py:31 msgid "Secondary key to move down" msgstr "Touche secondaire pour aller vers le bas" #: squirrelbattle/settings.py:25 squirrelbattle/tests/translations_test.py:29 +#: squirrelbattle/tests/translations_test.py:33 msgid "Main key to move left" msgstr "Touche principale pour aller vers la gauche" #: squirrelbattle/settings.py:26 squirrelbattle/tests/translations_test.py:31 +#: squirrelbattle/tests/translations_test.py:35 msgid "Secondary key to move left" msgstr "Touche secondaire pour aller vers la gauche" #: squirrelbattle/settings.py:27 squirrelbattle/tests/translations_test.py:33 +#: squirrelbattle/tests/translations_test.py:37 msgid "Main key to move right" msgstr "Touche principale pour aller vers la droite" #: squirrelbattle/settings.py:29 squirrelbattle/tests/translations_test.py:35 +#: squirrelbattle/tests/translations_test.py:39 msgid "Secondary key to move right" msgstr "Touche secondaire pour aller vers la droite" #: squirrelbattle/settings.py:30 squirrelbattle/tests/translations_test.py:37 +#: squirrelbattle/tests/translations_test.py:41 msgid "Key to validate a menu" msgstr "Touche pour valider un menu" #: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:39 +#: squirrelbattle/tests/translations_test.py:43 msgid "Texture pack" msgstr "Pack de textures" #: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40 +#: squirrelbattle/tests/translations_test.py:44 msgid "Language" msgstr "Langue" -#: squirrelbattle/interfaces.py:407 +#: squirrelbattle/interfaces.py:407 squirrelbattle/interfaces.py:412 #, python-brace-format msgid "{name} dies." msgstr "{name} meurt." + +#: squirrelbattle/tests/translations_test.py:47 +msgid "player" +msgstr "joueur" + +#: squirrelbattle/tests/translations_test.py:49 +msgid "tiger" +msgstr "tigre" + +#: squirrelbattle/tests/translations_test.py:50 +msgid "hedgehog" +msgstr "hérisson" + +#: squirrelbattle/tests/translations_test.py:51 +msgid "rabbit" +msgstr "lapin" + +#: squirrelbattle/tests/translations_test.py:52 +msgid "teddy bear" +msgstr "nounours" + +#: squirrelbattle/tests/translations_test.py:54 +msgid "bomb" +msgstr "bombe" + +#: squirrelbattle/tests/translations_test.py:55 +msgid "heart" +msgstr "cœur" diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 78c1a15..90e5d69 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -315,6 +315,10 @@ class Entity: from squirrelbattle.entities.items import Item return isinstance(self, Item) + @property + def translated_name(self) -> str: + return _(self.name.replace("_", " ")) + @staticmethod def get_all_entity_classes(): """ @@ -392,8 +396,9 @@ class FightingEntity(Entity): Deals damage to the opponent, based on the stats """ return _("{name} hits {opponent}.")\ - .format(name=self.name, opponent=opponent.name) + " "\ - + opponent.take_damage(self, self.strength) + .format(name=_(self.translated_name.capitalize()), + opponent=_(opponent.translated_name)) + " " + \ + opponent.take_damage(self, self.strength) def take_damage(self, attacker: "Entity", amount: int) -> str: """ @@ -403,8 +408,9 @@ class FightingEntity(Entity): if self.health <= 0: self.die() return _("{name} takes {amount} damage.")\ - .format(name=self.name, amount=str(amount)) \ - + (" " + _("{name} dies.").format(name=self.name) + .format(name=self.translated_name.capitalize(), amount=str(amount))\ + + (" " + _("{name} dies.") + .format(name=self.translated_name.capitalize()) if self.health <= 0 else "") def die(self) -> None: diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index 0d190d6..1931224 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -7,9 +7,9 @@ class TestTranslations(unittest.TestCase): def setUp(self) -> None: setlocale("fr") - def test_translations(self) -> None: + def test_main_menu_translation(self) -> None: """ - Ensure that some strings are well-translated. + Ensure that the main menu is translated. """ self.assertEqual(_("New game"), "Nouvelle partie") self.assertEqual(_("Resume"), "Continuer") @@ -18,6 +18,10 @@ class TestTranslations(unittest.TestCase): self.assertEqual(_("Settings"), "Paramètres") self.assertEqual(_("Exit"), "Quitter") + def test_settings_menu_translation(self) -> None: + """ + Ensure that the settings menu is translated. + """ self.assertEqual(_("Main key to move up"), "Touche principale pour aller vers le haut") self.assertEqual(_("Secondary key to move up"), @@ -38,3 +42,14 @@ class TestTranslations(unittest.TestCase): "Touche pour valider un menu") self.assertEqual(_("Texture pack"), "Pack de textures") self.assertEqual(_("Language"), "Langue") + + def test_entities_translation(self) -> None: + self.assertEqual(_("player"), "joueur") + + self.assertEqual(_("tiger"), "tigre") + self.assertEqual(_("hedgehog"), "hérisson") + self.assertEqual(_("rabbit"), "lapin") + self.assertEqual(_("teddy bear"), "nounours") + + self.assertEqual(_("bomb"), "bombe") + self.assertEqual(_("heart"), "cœur") From 138b2c6d54c8a4df83f56d4057238730427878b3 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 28 Nov 2020 01:25:35 +0100 Subject: [PATCH 132/171] Logs are capitalized --- squirrelbattle/tests/entities_test.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 8f4e0c2..371bfc7 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -46,10 +46,10 @@ class TestEntities(unittest.TestCase): self.assertEqual(entity.strength, 2) for _ in range(9): self.assertEqual(entity.hit(entity), - "tiger hits tiger. tiger takes 2 damage.") + "Tiger hits tiger. Tiger takes 2 damage.") self.assertFalse(entity.dead) - self.assertEqual(entity.hit(entity), "tiger hits tiger. " - + "tiger takes 2 damage. tiger dies.") + self.assertEqual(entity.hit(entity), "Tiger hits tiger. " + + "Tiger takes 2 damage. Tiger dies.") self.assertTrue(entity.dead) entity = Rabbit() @@ -70,8 +70,8 @@ class TestEntities(unittest.TestCase): self.assertTrue(entity.y == 2 and entity.x == 6) self.assertEqual(old_health - entity.strength, self.player.health) self.assertEqual(self.map.logs.messages[-1], - f"{entity.name} hits {self.player.name}. \ -{self.player.name} takes {entity.strength} damage.") + f"{entity.name.capitalize()} hits {self.player.name}. \ +{self.player.name.capitalize()} takes {entity.strength} damage.") # Fight the rabbit old_health = entity.health From 7d026044071ce214bb50d1648ca981fb4327a85e Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 28 Nov 2020 01:59:52 +0100 Subject: [PATCH 133/171] Clean the translation module --- squirrelbattle/game.py | 4 +- squirrelbattle/menus.py | 4 +- squirrelbattle/tests/translations_test.py | 4 +- squirrelbattle/translations.py | 52 +++++++++++++++-------- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 09f1328..44ad349 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -14,7 +14,7 @@ from .interfaces import Map, Logs from .resources import ResourceManager from .settings import Settings from . import menus -from .translations import gettext as _, setlocale +from .translations import gettext as _, Translator from typing import Callable @@ -35,7 +35,7 @@ class Game: self.settings = Settings() self.settings.load_settings() self.settings.write_settings() - setlocale(self.settings.LOCALE) + Translator.setlocale(self.settings.LOCALE) self.main_menu = menus.MainMenu() self.settings_menu = menus.SettingsMenu() self.settings_menu.update_values(self.settings) diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index 3f6f7a0..1fba7ea 100644 --- a/squirrelbattle/menus.py +++ b/squirrelbattle/menus.py @@ -7,7 +7,7 @@ from typing import Any, Optional from .display.texturepack import TexturePack from .enums import GameMode, KeyValues, DisplayActions from .settings import Settings -from .translations import gettext as _, setlocale +from .translations import gettext as _, Translator class Menu: @@ -99,7 +99,7 @@ class SettingsMenu(Menu): elif option == "LOCALE": game.settings.LOCALE = 'fr' if game.settings.LOCALE == 'en'\ else 'en' - setlocale(game.settings.LOCALE) + Translator.setlocale(game.settings.LOCALE) game.settings.write_settings() self.update_values(game.settings) else: diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index 1931224..742edea 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -1,11 +1,11 @@ import unittest -from squirrelbattle.translations import gettext as _, setlocale +from squirrelbattle.translations import gettext as _, Translator class TestTranslations(unittest.TestCase): def setUp(self) -> None: - setlocale("fr") + Translator.setlocale("fr") def test_main_menu_translation(self) -> None: """ diff --git a/squirrelbattle/translations.py b/squirrelbattle/translations.py index fa8196d..47ec9fb 100644 --- a/squirrelbattle/translations.py +++ b/squirrelbattle/translations.py @@ -1,28 +1,44 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later -import gettext +import gettext as gt +from typing import Any, List -SUPPORTED_LOCALES = ["en", "fr"] -DEFAULT_LOCALE = "en" +class Translator: + """ + This module uses gettext to translate strings. + Translator.setlocale defines the language of the strings, + then gettext() translates the message. + """ + SUPPORTED_LOCALES: List[str] = ["en", "fr"] + locale: str = "en" + translators: dict = {} -_current_locale = DEFAULT_LOCALE + for language in SUPPORTED_LOCALES: + translators[language] = gt.translation( + "squirrelbattle", + localedir="locale", + languages=[language], + ) -_TRANSLATORS = dict() -for language in SUPPORTED_LOCALES: - _TRANSLATORS[language] = gettext.translation("squirrelbattle", - localedir="locale", - languages=[language]) + @classmethod + def setlocale(cls, lang: str) -> None: + """ + Define the language used to translate the game. + The language must be supported, otherwise nothing is done. + """ + lang = lang[:2] + if lang in cls.SUPPORTED_LOCALES: + cls.locale = lang + + @classmethod + def get_translator(cls) -> Any: + return cls.translators.get(cls.locale) def gettext(message: str) -> str: - return _TRANSLATORS.get(_current_locale, - _TRANSLATORS.get("en")).gettext(message) - - -def setlocale(lang: str) -> None: - global _current_locale - lang = lang[:2] - if lang in SUPPORTED_LOCALES: - _current_locale = lang + """ + Translate a message. + """ + return Translator.get_translator().gettext(message) From ffc8b904417fdb23def673a097ade06fea0003d8 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 28 Nov 2020 02:54:04 +0100 Subject: [PATCH 134/171] Create functions that call xgettext or msgfmt --- locale/en/LC_MESSAGES/squirrelbattle.po | 10 ++++----- locale/fr/LC_MESSAGES/squirrelbattle.po | 10 ++++----- squirrelbattle/translations.py | 27 +++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/locale/en/LC_MESSAGES/squirrelbattle.po b/locale/en/LC_MESSAGES/squirrelbattle.po index d77e823..21e45e6 100644 --- a/locale/en/LC_MESSAGES/squirrelbattle.po +++ b/locale/en/LC_MESSAGES/squirrelbattle.po @@ -1,14 +1,14 @@ # SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. +# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse +# This file is distributed under the same license as the squirrelbattle package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-11-27 22:31+0100\n" +"Project-Id-Version: squirrelbattle 3.14.1\n" +"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" +"POT-Creation-Date: 2020-11-28 02:50+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/locale/fr/LC_MESSAGES/squirrelbattle.po b/locale/fr/LC_MESSAGES/squirrelbattle.po index fa4e1b0..b14ef50 100644 --- a/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -1,14 +1,14 @@ # SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. +# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse +# This file is distributed under the same license as the squirrelbattle package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-11-27 22:31+0100\n" +"Project-Id-Version: squirrelbattle 3.14.1\n" +"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" +"POT-Creation-Date: 2020-11-28 02:50+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/squirrelbattle/translations.py b/squirrelbattle/translations.py index 47ec9fb..1cee88b 100644 --- a/squirrelbattle/translations.py +++ b/squirrelbattle/translations.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import gettext as gt +import subprocess from typing import Any, List @@ -36,6 +37,32 @@ class Translator: def get_translator(cls) -> Any: return cls.translators.get(cls.locale) + @classmethod + def makemessages(cls) -> None: + for language in cls.SUPPORTED_LOCALES: + args = ["find", "squirrelbattle/", "-iname", "*.py"] + find = subprocess.Popen(args, stdout=subprocess.PIPE) + args = ["xargs", "xgettext", "--from-code", "utf-8", + "--join-existing", + "--add-comments", + "--package-name=squirrelbattle", + "--package-version=3.14.1", + "--copyright-holder=ÿnérant, eichhornchen, " + "nicomarg, charlse", + "--msgid-bugs-address=squirrel-battle@crans.org", + "-o", f"locale/{language}/LC_MESSAGES/squirrelbattle.po"] + print(f"Make {language} messages...") + subprocess.Popen(args, stdin=find.stdout) + + @classmethod + def compilemessages(cls) -> None: + for language in cls.SUPPORTED_LOCALES: + args = ["msgfmt", "--check-format", + "-o", f"locale/{language}/LC_MESSAGES/squirrelbattle.mo", + f"locale/{language}/LC_MESSAGES/squirrelbattle.po"] + print(f"Compiling {language} messages...") + subprocess.Popen(args) + def gettext(message: str) -> str: """ From 8aad15f07b6821e024665323049ac5d49c92688d Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 28 Nov 2020 03:04:28 +0100 Subject: [PATCH 135/171] Move translation files in the module --- .../locale}/en/LC_MESSAGES/squirrelbattle.mo | Bin 337 -> 368 bytes .../locale}/en/LC_MESSAGES/squirrelbattle.po | 2 +- .../locale}/fr/LC_MESSAGES/squirrelbattle.mo | Bin 2607 -> 2638 bytes .../locale}/fr/LC_MESSAGES/squirrelbattle.po | 0 squirrelbattle/translations.py | 21 +++++++++++------- 5 files changed, 14 insertions(+), 9 deletions(-) rename {locale => squirrelbattle/locale}/en/LC_MESSAGES/squirrelbattle.mo (59%) rename {locale => squirrelbattle/locale}/en/LC_MESSAGES/squirrelbattle.po (99%) rename {locale => squirrelbattle/locale}/fr/LC_MESSAGES/squirrelbattle.mo (84%) rename {locale => squirrelbattle/locale}/fr/LC_MESSAGES/squirrelbattle.po (100%) diff --git a/locale/en/LC_MESSAGES/squirrelbattle.mo b/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.mo similarity index 59% rename from locale/en/LC_MESSAGES/squirrelbattle.mo rename to squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.mo index 6c5906d1cd061dff54de8b533942893de34efc9e..14164ec0ffb6694d6c55f833f37252bf09f9cc75 100644 GIT binary patch delta 84 zcmcb}^nq!D3ZvOXRSnnT!qUv5qSTzE#FCPnR0U%_LlZqiuAtO{{Gt+F-{SPl6kVs% d^kUtRd@BW*T3v`@hvcHfykfolqV$Qy_W;)<9*O_} delta 53 zcmeysbdhO-3ZwW$RSmTOM`v$GcUOfl*Pviee?P9E)Pnq?5?$Zo^vo1pr_%Id-H?1M Jg^82y0RXnd5qJOq diff --git a/locale/en/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po similarity index 99% rename from locale/en/LC_MESSAGES/squirrelbattle.po rename to squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po index 21e45e6..9600c79 100644 --- a/locale/en/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-11-28 02:50+0100\n" +"POT-Creation-Date: 2020-11-28 03:01+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/locale/fr/LC_MESSAGES/squirrelbattle.mo b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.mo similarity index 84% rename from locale/fr/LC_MESSAGES/squirrelbattle.mo rename to squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.mo index 12e6fe990a48a5dcee90cc5a5049f39393074960..eb2e033a3be0be6f3613020253fa56fa6f3334af 100644 GIT binary patch delta 359 zcmZ24a!zD}4r9F;BLhPzD+2=?0|UbvAi)l#Hv?%NAiWz%O9SZ>KpKb`o&X7u{A(bs z0i-_zX>A}a0~7?&9za?XNS6RBYJs`Bn-rwYm_+4#`D{dBuA9Md_Q@vo^2*03~%f A8vpr)_4~CeqbJTp}`{xN(d=oMIFg$dCg??Vm7#S4`o@GRbC= zzKH=ev4lg^@s3`6qPqWfi}?$Pc>X~Ks(=ovgA?rG9D^7qrHvU>4@{B2%hGa#4B1im zlkBbaBN8DOtG}V2{-Ki+Ve(=jfv#=VUULL$xqLZSFk&sE-Y8ZpqnQ@op0&R73xSj+ AjQ{`u diff --git a/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po similarity index 100% rename from locale/fr/LC_MESSAGES/squirrelbattle.po rename to squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po diff --git a/squirrelbattle/translations.py b/squirrelbattle/translations.py index 1cee88b..7a0e524 100644 --- a/squirrelbattle/translations.py +++ b/squirrelbattle/translations.py @@ -3,6 +3,7 @@ import gettext as gt import subprocess +from pathlib import Path from typing import Any, List @@ -19,7 +20,7 @@ class Translator: for language in SUPPORTED_LOCALES: translators[language] = gt.translation( "squirrelbattle", - localedir="locale", + localedir=Path(__file__).parent / "locale", languages=[language], ) @@ -38,10 +39,11 @@ class Translator: return cls.translators.get(cls.locale) @classmethod - def makemessages(cls) -> None: + def makemessages(cls) -> None: # pragma: no cover for language in cls.SUPPORTED_LOCALES: - args = ["find", "squirrelbattle/", "-iname", "*.py"] - find = subprocess.Popen(args, stdout=subprocess.PIPE) + args = ["find", "squirrelbattle", "-iname", "*.py"] + find = subprocess.Popen(args, cwd=Path(__file__).parent.parent, + stdout=subprocess.PIPE) args = ["xargs", "xgettext", "--from-code", "utf-8", "--join-existing", "--add-comments", @@ -50,16 +52,19 @@ class Translator: "--copyright-holder=ÿnérant, eichhornchen, " "nicomarg, charlse", "--msgid-bugs-address=squirrel-battle@crans.org", - "-o", f"locale/{language}/LC_MESSAGES/squirrelbattle.po"] + "-o", Path(__file__).parent / "locale" / language + / "LC_MESSAGES" / "squirrelbattle.po"] print(f"Make {language} messages...") subprocess.Popen(args, stdin=find.stdout) @classmethod - def compilemessages(cls) -> None: + def compilemessages(cls) -> None: # pragma: no cover for language in cls.SUPPORTED_LOCALES: args = ["msgfmt", "--check-format", - "-o", f"locale/{language}/LC_MESSAGES/squirrelbattle.mo", - f"locale/{language}/LC_MESSAGES/squirrelbattle.po"] + "-o", Path(__file__).parent / "locale" / language + / "LC_MESSAGES" / "squirrelbattle.mo", + Path(__file__).parent / "locale" / language + / "LC_MESSAGES" / "squirrelbattle.po"] print(f"Compiling {language} messages...") subprocess.Popen(args) From 7c0cf3e029d408e857e7172a0567af387216eed2 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 28 Nov 2020 03:21:20 +0100 Subject: [PATCH 136/171] CLI to manage messages --- main.py | 18 +++++++++++++++++- squirrelbattle/translations.py | 22 ++++++++++++++-------- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/main.py b/main.py index e8c333e..fbbbb35 100755 --- a/main.py +++ b/main.py @@ -2,8 +2,24 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later +import argparse +import sys from squirrelbattle.bootstrap import Bootstrap +from squirrelbattle.translations import Translator if __name__ == "__main__": - Bootstrap.run_game() + parser = argparse.ArgumentParser() + + parser.add_argument("--makemessages", "-mm", action="store_true", + help="Extract translatable strings") + parser.add_argument("--compilemessages", "-cm", action="store_true", + help="Compile translatable strings") + + args = parser.parse_args(sys.argv[1:]) + if args.makemessages: + Translator.makemessages() + elif args.compilemessages: + Translator.compilemessages() + else: + Bootstrap.run_game() diff --git a/squirrelbattle/translations.py b/squirrelbattle/translations.py index 7a0e524..dfd3cf3 100644 --- a/squirrelbattle/translations.py +++ b/squirrelbattle/translations.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import gettext as gt +import os import subprocess from pathlib import Path from typing import Any, List @@ -18,11 +19,14 @@ class Translator: translators: dict = {} for language in SUPPORTED_LOCALES: - translators[language] = gt.translation( - "squirrelbattle", - localedir=Path(__file__).parent / "locale", - languages=[language], - ) + dir = Path(__file__).parent / "locale" / language / "LC_MESSAGES" + dir.mkdir(parents=True) if not dir.is_dir() else None + if os.path.isfile(dir / "squirrelbattle.mo"): + translators[language] = gt.translation( + "squirrelbattle", + localedir=Path(__file__).parent / "locale", + languages=[language], + ) @classmethod def setlocale(cls, lang: str) -> None: @@ -41,19 +45,21 @@ class Translator: @classmethod def makemessages(cls) -> None: # pragma: no cover for language in cls.SUPPORTED_LOCALES: + file_name = Path(__file__).parent / "locale" / language \ + / "LC_MESSAGES" / "squirrelbattle.po" args = ["find", "squirrelbattle", "-iname", "*.py"] find = subprocess.Popen(args, cwd=Path(__file__).parent.parent, stdout=subprocess.PIPE) args = ["xargs", "xgettext", "--from-code", "utf-8", - "--join-existing", "--add-comments", "--package-name=squirrelbattle", "--package-version=3.14.1", "--copyright-holder=ÿnérant, eichhornchen, " "nicomarg, charlse", "--msgid-bugs-address=squirrel-battle@crans.org", - "-o", Path(__file__).parent / "locale" / language - / "LC_MESSAGES" / "squirrelbattle.po"] + "-o", file_name] + if file_name.is_file(): + args.append("--join-existing") print(f"Make {language} messages...") subprocess.Popen(args, stdin=find.stdout) From 5ce62c15f7a4f771b094cb420cf0dc5ab9dc77af Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 28 Nov 2020 03:23:04 +0100 Subject: [PATCH 137/171] Include locale files in Python setup script --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6287f7d..a9b8379 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ setup( ], python_requires='>=3.6', include_package_data=True, - package_data={"squirrelbattle": ["assets/*"]}, + package_data={"squirrelbattle": ["assets/*", "locale/*"]}, entry_points={ "console_scripts": [ "squirrel-battle = squirrelbattle.bootstrap:Bootstrap.run_game", From a34dae2ad0fe0db769bb08605976b46819af8b11 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 28 Nov 2020 13:49:28 +0100 Subject: [PATCH 138/171] Compile messages on setup --- .gitignore | 3 +++ setup.py | 14 ++++++++++++-- .../locale/en/LC_MESSAGES/squirrelbattle.mo | Bin 368 -> 0 bytes .../locale/fr/LC_MESSAGES/squirrelbattle.mo | Bin 2638 -> 0 bytes 4 files changed, 15 insertions(+), 2 deletions(-) delete mode 100644 squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.mo delete mode 100644 squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.mo diff --git a/.gitignore b/.gitignore index f30aa49..8499d7c 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ save.json # Don't commit docs output docs/_build + +# Don't commit compiled messages +*.mo diff --git a/setup.py b/setup.py index a9b8379..00bd56b 100644 --- a/setup.py +++ b/setup.py @@ -3,13 +3,23 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later -import os +import subprocess from setuptools import find_packages, setup with open("README.md", "r") as f: long_description = f.read() +# Compile messages +for language in ["en", "fr"]: + args = ["msgfmt", "--check-format", + "-o", f"squirrelbattle/locale/{language}/LC_MESSAGES" + "/squirrelbattle.mo", + f"squirrelbattle/locale/{language}/LC_MESSAGES" + "/squirrelbattle.po"] + print(f"Compiling {language} messages...") + subprocess.Popen(args) + setup( name="squirrel-battle", version="3.14.1", @@ -36,7 +46,7 @@ setup( ], python_requires='>=3.6', include_package_data=True, - package_data={"squirrelbattle": ["assets/*", "locale/*"]}, + package_data={"squirrelbattle": ["assets/*", "locale/*/*/*"]}, entry_points={ "console_scripts": [ "squirrel-battle = squirrelbattle.bootstrap:Bootstrap.run_game", diff --git a/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.mo b/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.mo deleted file mode 100644 index 14164ec0ffb6694d6c55f833f37252bf09f9cc75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 368 zcmYL^zfQw25Qjl9Wn^aXzy=qnK&Y5Msv&NPWH(isBEjaSCKv^~*e-$x;q`bHE63^1&Ug2-xBIA*gv=7EvO2Yfbc(2=~ zim}z?G##9!gQV1J=Of6_m=)Y^ngEsSUHFqnwO2Nz&Ns=dfKq>$?n}s2)Ra7OR)Q=b zW8}V|GCzDOraTcUM5wy9Rz>G2883t&QzkjN;*#ZJ#Img9^Z5;`w%MpgL#5RlC4x;S z3pPPdh*4|0sl7uYBy-R4x diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.mo b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.mo deleted file mode 100644 index eb2e033a3be0be6f3613020253fa56fa6f3334af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2638 zcmbuA&yQ3^5XW0V)ImYPpNQH^Ld3uuSTTkSC=AR1gEKqp%q(QRn4WpPZyMg~esuR6 z7zzIXCllh$c<|&Hd ze*^Jj|6q6s+=zkp_hejD5a{toU1cVdxv8hjP>;8yTs@C^6`_$YV>+zf7j(X@XnNc#>= z7=pC_1CZpt0^bL}0iOViP|^h(AnCsW!gO|P!tX)+*bf-U-k&DxzkpD|ex1z!03XNv zFYtMAGn};t+zrAVBOOnHFn4q=!Zq?6r4RXa+FrsRPok5LC_i?gPaEl_M(0i&4sNH%A98jHTP{E~2d$z(d@Mswd)jwd3W$<;)^EqUK$JXA6~${NyT*w++2 z46ErnYs3-v8`)=gTYPdz0#3mO_NAE&T(NSPn2_5=5P{BwA{fgzNBKA z&n+&`os@|qSK}fbV{MlDJg;~j8P7u*OE1GbYst$#gT|CX)#^%qrdgjGsp4>jhA3^7 z9lu)hL?^VY(z;k*;KfFnm&(NwYn!Cax>8t=F^pu_L~QJQW<|S=?0_X6yo#h{ zS2Tijm0a)g(@9r($FG<)H5#dOb$Q*33lbkhf*^YA5ec#=&*rT;53_?x7@U>XDWePA zUCb0N6Ssx;vE&Eyv#;l8a}Ajq>w}t$R2Ur3A{VsGifk|zKGv~9yS%YcZoVEgWKWUG zpo9!A@Wpbm5!C8IsmM>)3$@DLck6TITva$9v}ju_kopCFa-mw~bH!SjA1T+0mFlr# zsnjSpn{PpMocuvcilo4+#ko@p#Z!>0R*%JsPF;~H@La7@E3e#YHh&;@!e|e#1g(Aw zdpv@=A1+qnfYM&nTLZgN*DA@h7y8=;zt=pMd=LwXbKz>2^+5X#*M28xpsZutS9(i->fft zuW#0qo%m*6Sdk1r#qAtBA+1M7Nyj_!aNtlvc+b$imlT3yY3`P9$sMkaN~$kr0-5RZ zxw9kLyCr1OhS#Zzm?X_)o|h>KQ-8ZGO|43X1A5CSPNbjm8pX=7>iATJV@LJs-m|6i zsEo^X>;Ig5w!YBh!)vW_lh^8v)~J?A*7^E6-bi;iu&BdEvvIPpSfolbjXH!E;;58a pIjvp^vdJ&ktR*YbRIXLcQY-PVXt|oDMro8i+%FssqTwe{@h?*k&%yuz From f78c73a7035c61bfc279ec451101e426e613413a Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 28 Nov 2020 14:02:23 +0100 Subject: [PATCH 139/171] Tests and the CI are compiling messages --- .gitlab-ci.yml | 6 +++- squirrelbattle/tests/game_test.py | 24 +++++++------- squirrelbattle/tests/translations_test.py | 2 ++ squirrelbattle/translations.py | 40 +++++++++++++++-------- 4 files changed, 46 insertions(+), 26 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8613258..867562b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,6 +7,7 @@ py37: stage: test image: python:3.7-alpine before_script: + - apk add --no-cache gettext - pip install tox script: tox -e py3 @@ -14,6 +15,7 @@ py38: stage: test image: python:3.8-alpine before_script: + - apk add --no-cache gettext - pip install tox script: tox -e py3 @@ -22,6 +24,7 @@ py39: stage: test image: python:3.9-alpine before_script: + - apk add --no-cache gettext - pip install tox script: tox -e py3 @@ -29,6 +32,7 @@ linters: stage: quality-assurance image: python:3-alpine before_script: + - apk add --no-cache gettext - pip install tox script: tox -e linters allow_failure: true @@ -37,7 +41,7 @@ build-deb: image: debian:buster-slim stage: build before_script: - - apt-get update && apt-get -y --no-install-recommends install build-essential debmake dh-python debhelper python3-all python3-setuptools + - apt-get update && apt-get -y --no-install-recommends install build-essential debmake dh-python debhelper gettext python3-all python3-setuptools script: - dpkg-buildpackage - mkdir build && cp ../*.deb build/ diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 8f5b1c1..9c950d6 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -4,18 +4,16 @@ import os import unittest -from squirrelbattle.resources import ResourceManager - -from squirrelbattle.enums import DisplayActions - -from squirrelbattle.bootstrap import Bootstrap -from squirrelbattle.display.display import Display -from squirrelbattle.display.display_manager import DisplayManager -from squirrelbattle.entities.player import Player -from squirrelbattle.game import Game, KeyValues, GameMode -from squirrelbattle.menus import MainMenuValues -from squirrelbattle.settings import Settings -from squirrelbattle.translations import gettext as _ +from ..bootstrap import Bootstrap +from ..display.display import Display +from ..display.display_manager import DisplayManager +from ..entities.player import Player +from ..enums import DisplayActions +from ..game import Game, KeyValues, GameMode +from ..menus import MainMenuValues +from ..resources import ResourceManager +from ..settings import Settings +from ..translations import gettext as _, Translator class TestGame(unittest.TestCase): @@ -277,6 +275,8 @@ class TestGame(unittest.TestCase): self.assertEqual(self.game.settings.TEXTURE_PACK, "ascii") # Change language + Translator.compilemessages() + Translator.refresh_translations() self.game.settings.LOCALE = "en" self.game.handle_key_pressed(KeyValues.DOWN) self.game.handle_key_pressed(KeyValues.ENTER) diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index 742edea..6c18840 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -5,6 +5,8 @@ from squirrelbattle.translations import gettext as _, Translator class TestTranslations(unittest.TestCase): def setUp(self) -> None: + Translator.compilemessages() + Translator.refresh_translations() Translator.setlocale("fr") def test_main_menu_translation(self) -> None: diff --git a/squirrelbattle/translations.py b/squirrelbattle/translations.py index dfd3cf3..cd5b7fc 100644 --- a/squirrelbattle/translations.py +++ b/squirrelbattle/translations.py @@ -18,15 +18,20 @@ class Translator: locale: str = "en" translators: dict = {} - for language in SUPPORTED_LOCALES: - dir = Path(__file__).parent / "locale" / language / "LC_MESSAGES" - dir.mkdir(parents=True) if not dir.is_dir() else None - if os.path.isfile(dir / "squirrelbattle.mo"): - translators[language] = gt.translation( - "squirrelbattle", - localedir=Path(__file__).parent / "locale", - languages=[language], - ) + @classmethod + def refresh_translations(cls) -> None: + """ + Load compiled translations. + """ + for language in cls.SUPPORTED_LOCALES: + rep = Path(__file__).parent / "locale" / language / "LC_MESSAGES" + rep.mkdir(parents=True) if not rep.is_dir() else None + if os.path.isfile(rep / "squirrelbattle.mo"): + cls.translators[language] = gt.translation( + "squirrelbattle", + localedir=Path(__file__).parent / "locale", + languages=[language], + ) @classmethod def setlocale(cls, lang: str) -> None: @@ -40,10 +45,13 @@ class Translator: @classmethod def get_translator(cls) -> Any: - return cls.translators.get(cls.locale) + return cls.translators.get(cls.locale, gt.NullTranslations()) @classmethod def makemessages(cls) -> None: # pragma: no cover + """ + Analyse all strings in the project and extract them. + """ for language in cls.SUPPORTED_LOCALES: file_name = Path(__file__).parent / "locale" / language \ / "LC_MESSAGES" / "squirrelbattle.po" @@ -61,10 +69,13 @@ class Translator: if file_name.is_file(): args.append("--join-existing") print(f"Make {language} messages...") - subprocess.Popen(args, stdin=find.stdout) + subprocess.Popen(args, stdin=find.stdout).wait() @classmethod - def compilemessages(cls) -> None: # pragma: no cover + def compilemessages(cls) -> None: + """ + Compile translation messages from source files. + """ for language in cls.SUPPORTED_LOCALES: args = ["msgfmt", "--check-format", "-o", Path(__file__).parent / "locale" / language @@ -72,7 +83,7 @@ class Translator: Path(__file__).parent / "locale" / language / "LC_MESSAGES" / "squirrelbattle.po"] print(f"Compiling {language} messages...") - subprocess.Popen(args) + subprocess.Popen(args).wait() def gettext(message: str) -> str: @@ -80,3 +91,6 @@ def gettext(message: str) -> str: Translate a message. """ return Translator.get_translator().gettext(message) + + +Translator.refresh_translations() From aade89de7b944b98671de16175cc1a3112904a70 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 28 Nov 2020 14:10:31 +0100 Subject: [PATCH 140/171] Tests and the CI are compiling messages --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 867562b..ff5c142 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,7 +32,6 @@ linters: stage: quality-assurance image: python:3-alpine before_script: - - apk add --no-cache gettext - pip install tox script: tox -e linters allow_failure: true From 6b09d488b63f978e77df9f5f53fb89a33df321ed Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 28 Nov 2020 16:00:17 +0100 Subject: [PATCH 141/171] Documentation on translation --- debian/control | 2 +- docs/deployment.rst | 20 ++++++-- docs/index.rst | 1 + docs/install-dev.rst | 21 +++++--- docs/translation.rst | 120 +++++++++++++++++++++++++++++++++++++++++++ setup.py | 2 +- 6 files changed, 154 insertions(+), 12 deletions(-) create mode 100644 docs/translation.rst diff --git a/debian/control b/debian/control index fc52e38..b59997d 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: python3-squirrel-battle Section: devel Priority: optional Maintainer: ynerant -Build-Depends: debhelper (>=10~), dh-python, python3-all, python3-setuptools +Build-Depends: debhelper (>=10~), dh-python, gettext, python3-all, python3-setuptools Depends: fonts-noto-color-emoji Standards-Version: 4.1.4 Homepage: https://gitlab.crans.org/ynerant/squirrel-battle diff --git a/docs/deployment.rst b/docs/deployment.rst index a9f58ee..6a57b58 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -34,6 +34,16 @@ paquet ainsi que des détails à fournir à PyPI : with open("README.md", "r") as f: long_description = f.read() + # Compile messages + for language in ["en", "fr"]: + args = ["msgfmt", "--check-format", + "-o", f"squirrelbattle/locale/{language}/LC_MESSAGES" + "/squirrelbattle.mo", + f"squirrelbattle/locale/{language}/LC_MESSAGES" + "/squirrelbattle.po"] + print(f"Compiling {language} messages...") + subprocess.Popen(args) + setup( name="squirrel-battle", version="3.14.1", @@ -60,7 +70,7 @@ paquet ainsi que des détails à fournir à PyPI : ], python_requires='>=3.6', include_package_data=True, - package_data={"squirrelbattle": ["assets/*"]}, + package_data={"squirrelbattle": ["assets/*", "locale/*/*/*.mo"]}, entry_points={ "console_scripts": [ "squirrel-battle = squirrelbattle.bootstrap:Bootstrap.run_game", @@ -72,6 +82,8 @@ Ce fichier contient le nom du paquet, sa version, l'auteur et son contact, sa description en une ligne et sa description longue, le lien d'accueil du projet, sa licence, ses classificateurs et son exécutable. +Il commence tout d'abord par compiler les fichiers de `traduction `_. + Le paramètre ``entry_points`` définit un exécutable nommé ``squirrel-battle``, qui permet de lancer le jeu. @@ -167,7 +179,7 @@ du dépôt Git. Le fichier ``PKGBUILD`` dispose de cette structure : url="https://gitlab.crans.org/ynerant/squirrel-battle" license=('GPLv3') depends=('python') - makedepends=('python-setuptools') + makedepends=('gettext' 'python-setuptools') depends=('noto-fonts-emoji') checkdepends=('python-tox') ssource=("git+https://gitlab.crans.org/ynerant/squirrel-battle.git") @@ -217,7 +229,7 @@ les releases, est plus ou moins similaire : url="https://gitlab.crans.org/ynerant/squirrel-battle" license=('GPLv3') depends=('python') - makedepends=('python-setuptools') + makedepends=('gettext' 'python-setuptools') depends=('noto-fonts-emoji') checkdepends=('python-tox') source=("https://gitlab.crans.org/ynerant/squirrel-battle/-/archive/v3.14.1/$pkgbase-v$pkgver.tar.gz") @@ -296,7 +308,7 @@ D'abord on installe les paquets nécessaires : .. code:: apt update - apt --no-install-recommends install build-essential debmake dh-python debhelper python3-all python3-setuptools + apt --no-install-recommends install build-essential debmake dh-python debhelper gettext python3-all python3-setuptools On peut ensuite construire le paquet : diff --git a/docs/index.rst b/docs/index.rst index ff6bcf3..1cb7d83 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -37,6 +37,7 @@ Bienvenue dans la documentation de Squirrel Battle ! install-dev tests display/index + translation deployment documentation diff --git a/docs/install-dev.rst b/docs/install-dev.rst index db611e0..973c0e0 100644 --- a/docs/install-dev.rst +++ b/docs/install-dev.rst @@ -1,16 +1,19 @@ Installation d'un environnement de développement ================================================ -Il est toujours préférable de travailler dans un environnement Python isolé du reste de son instalation. +Il est toujours préférable de travailler dans un environnement Python isolé du +reste de son instalation. 1. **Installation des dépendances de la distribution.** - Vous devez déjà installer Python et le module qui permet de créer des environnements virtuels. - On donne ci-dessous l'exemple pour une distribution basée sur Debian, mais vous pouvez facilement adapter pour ArchLinux ou autre. + Vous devez déjà installer Python et le module qui permet de créer des + environnements virtuels. + On donne ci-dessous l'exemple pour une distribution basée sur Debian, + mais vous pouvez facilement adapter pour ArchLinux ou autre. .. code:: bash $ sudo apt update - $ sudo apt install --no-install-recommends -y python3-setuptools python3-venv python3-dev git + $ sudo apt install --no-install-recommends -y python3-setuptools python3-venv python3-dev gettext git 2. **Clonage du dépot** là où vous voulez : @@ -25,7 +28,13 @@ Il est toujours préférable de travailler dans un environnement Python isolé d $ python3 -m venv env $ source env/bin/activate # entrer dans l'environnement - (env)$ pip3 install -r requirements.txt - (env)$ deactivate # sortir de l'environnement + (env) $ pip3 install -r requirements.txt + (env) $ deactivate # sortir de l'environnement + +4. **Compilation des messages de traduction.** + +.. code:: bash + + (env) $ python3 main.py --compilemessages Le lancement du jeu se fait en lançant la commande ``python3 main.py``. \ No newline at end of file diff --git a/docs/translation.rst b/docs/translation.rst new file mode 100644 index 0000000..ab3da46 --- /dev/null +++ b/docs/translation.rst @@ -0,0 +1,120 @@ +Traduction +========== + +Le jeu Squirrel Battle est entièrement traduit en anglais et en français. +La langue se choisit dans les `paramètres `_. + + +Utitisation +----------- + +Les traductions sont gérées grâce au module natif ``gettext``. Le module +``squirrelbattle.translations`` s'occupe d'installer les traductions, et de +donner les chaînes traduites. + +Pour choisir la langue, il faut appeler ``Translator.setlocale(language: str)``, +où ``language`` correspond au code à 2 lettres de la langue. + +Enfin, le module expose une fonction ``gettext(str) -> str`` qui permet de +traduire les chaînes. + +Il est courant et recommandé d'importer cette fonction sous l'alias ``_``, +afin de limiter la verbositer et de permettre de rendre facilement une chaîne +traduisible. + +.. code:: python + + from squirrelbattle.translations import gettext as _, Translator + + Translator.setlocale("fr") + print(_("I am a translatable string")) + print("I am not translatable") + +Si les traductions sont bien faites (voir ci-dessous), cela donnera : + +.. code:: + + Je suis une chaîne traduisible + I am not translatable + +À noter que si la chaîne n'est pas traduite, alors par défaut on renvoie la +chaîne elle-même. + + +Extraction des chaînes à traduire +--------------------------------- + +L'appel à ``gettext`` ne fait pas que traduire les chaînes : il est possible +également d'extraire toutes les chaînes à traduire. + +Il est nécessaire d'installer le paquet Linux ``gettext`` pour cela. + +L'utilitaire ``xgettext`` s'occupe de cette extraction. Il s'utilise de la façon +suivante : + +.. code:: bash + + xgettext --from-code utf-8 -o output_file.po source_1.py ... source_n.py + +Afin de ne pas avoir à sélectionner manuellement chaque fichier, il est possible +d'appeler directement ``python3 main.py --makemessages``. Cela a pour effet +d'exécuter pour chaque langue ```` : + +.. code:: bash + + find squirrelbattle -iname '*.py' | xargs xgettext --from-code utf-8 + --add-comments + --package-name=squirrelbattle + --package-version=3.14.1 + "--copyright-holder=ÿnérant, eichhornchen, nicomarg, charlse" + --msgid-bugs-address=squirrel-battle@crans.org + -o squirrelbattle/locale//LC_MESSAGES/squirrelbattle.po + +Les fichiers de traductions se trouvent alors dans +``squirrelbattle/locale//LC_MESSAGES/squirrelbattle.po``. + + +Traduire les chaînes +-------------------- + +Après extraction des chaînes, les chaînes à traduire se trouvent dans +``squirrelbattle/locale//LC_MESSAGES/squirrelbattle.po``, comme indiqué +ci-dessus. + +Ce fichier peut-être édité avec un utilitaire tel que ``poedit``, sur +l'interface Web sur ``_, +mais surtout manuellement avec un éditeur de texte. + +Dans ce fichier, on obtient pour chaque chaîne à traduire un paragraphe de la +forme : + +.. code:: po + + #: main.py:4 + msgid "I am a translatable string" + msgstr "Je suis une chaîne traduisible" + +Il sufift de remplir les champs ``msgstr``. + + +Compilation des chaînes +----------------------- + +Pour gagner en efficacité, les chaînes sont compilées dans un fichier avec +l'extension ``.mo``. Ce sont ces fichiers qui sont lus par le module de traduction. + +Pour compiler les traductions, c'est l'utilitaire ``msgfmt`` fourni toujours par +le paquet Linux ``gettext`` que nous utilisons. Il s'utilise assez simplement : + +.. code:: bash + + msgfmt po_file.po -o mo_file.mo + +À nouveau, il est possible de compiler automatiquement les messages en exécutant +``python3 main.py --compilemessages``. + +.. warning:: + + On ne partagera pas dans le dépôt Git les fichiers compilé. En développement, + on compilera soi-même les messages, et en production, la construction des + paquets se charge de compiler automatiquement les traductions. diff --git a/setup.py b/setup.py index 00bd56b..b9e493a 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ setup( ], python_requires='>=3.6', include_package_data=True, - package_data={"squirrelbattle": ["assets/*", "locale/*/*/*"]}, + package_data={"squirrelbattle": ["assets/*", "locale/*/*/*.mo"]}, entry_points={ "console_scripts": [ "squirrel-battle = squirrelbattle.bootstrap:Bootstrap.run_game", From 9f0a29302dd84691938642dd030a65a4db6eebd6 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 28 Nov 2020 16:22:06 +0100 Subject: [PATCH 142/171] Add german translation --- docs/deployment.rst | 2 +- docs/translation.rst | 2 +- setup.py | 2 +- .../locale/de/LC_MESSAGES/squirrelbattle.po | 166 ++++++++++++++++++ .../locale/en/LC_MESSAGES/squirrelbattle.po | 26 ++- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 26 ++- squirrelbattle/menus.py | 2 +- squirrelbattle/tests/game_test.py | 3 + squirrelbattle/translations.py | 2 +- 9 files changed, 224 insertions(+), 7 deletions(-) create mode 100644 squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po diff --git a/docs/deployment.rst b/docs/deployment.rst index 6a57b58..9477a10 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -35,7 +35,7 @@ paquet ainsi que des détails à fournir à PyPI : long_description = f.read() # Compile messages - for language in ["en", "fr"]: + for language in ["de", "en", "fr"]: args = ["msgfmt", "--check-format", "-o", f"squirrelbattle/locale/{language}/LC_MESSAGES" "/squirrelbattle.mo", diff --git a/docs/translation.rst b/docs/translation.rst index ab3da46..f3d2584 100644 --- a/docs/translation.rst +++ b/docs/translation.rst @@ -1,7 +1,7 @@ Traduction ========== -Le jeu Squirrel Battle est entièrement traduit en anglais et en français. +Le jeu Squirrel Battle est entièrement traduit en anglais, en français et en allement. La langue se choisit dans les `paramètres `_. diff --git a/setup.py b/setup.py index b9e493a..f051bbb 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ with open("README.md", "r") as f: long_description = f.read() # Compile messages -for language in ["en", "fr"]: +for language in ["de", "en", "fr"]: args = ["msgfmt", "--check-format", "-o", f"squirrelbattle/locale/{language}/LC_MESSAGES" "/squirrelbattle.mo", diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po new file mode 100644 index 0000000..dfd3365 --- /dev/null +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -0,0 +1,166 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse +# This file is distributed under the same license as the squirrelbattle package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: squirrelbattle 3.14.1\n" +"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" +"POT-Creation-Date: 2020-11-28 16:03+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287 +#: squirrelbattle/tests/translations_test.py:16 +msgid "New game" +msgstr "Neu Spiel" + +#: squirrelbattle/tests/translations_test.py:17 +msgid "Resume" +msgstr "Weitergehen" + +#: squirrelbattle/tests/translations_test.py:18 +msgid "Load" +msgstr "Laden" + +#: squirrelbattle/tests/translations_test.py:19 +msgid "Save" +msgstr "Speichern" + +#: squirrelbattle/tests/translations_test.py:20 +msgid "Settings" +msgstr "Einstellungen" + +#: squirrelbattle/tests/translations_test.py:21 +msgid "Exit" +msgstr "Verlassen" + +#: squirrelbattle/tests/translations_test.py:27 +msgid "Main key to move up" +msgstr "Haupttaste zum Obengehen" + +#: squirrelbattle/tests/translations_test.py:29 +msgid "Secondary key to move up" +msgstr "Sekundärtaste zum Obengehen" + +#: squirrelbattle/tests/translations_test.py:31 +msgid "Main key to move down" +msgstr "Haupttaste zum Untergehen" + +#: squirrelbattle/tests/translations_test.py:33 +msgid "Secondary key to move down" +msgstr "Sekundärtaste zum Untergehen" + +#: squirrelbattle/tests/translations_test.py:35 +msgid "Main key to move left" +msgstr "Haupttaste zum Linksgehen" + +#: squirrelbattle/tests/translations_test.py:37 +msgid "Secondary key to move left" +msgstr "Sekundärtaste zum Linksgehen" + +#: squirrelbattle/tests/translations_test.py:39 +msgid "Main key to move right" +msgstr "Haupttaste zum Rechtsgehen" + +#: squirrelbattle/tests/translations_test.py:41 +msgid "Secondary key to move right" +msgstr "Sekundärtaste zum Rechtsgehen" + +#: squirrelbattle/tests/translations_test.py:43 +msgid "Key to validate a menu" +msgstr "Menütaste" + +#: squirrelbattle/tests/translations_test.py:45 +msgid "Texture pack" +msgstr "Textur-Packung" + +#: squirrelbattle/tests/translations_test.py:46 +msgid "Language" +msgstr "Sprache" + +#: squirrelbattle/tests/translations_test.py:49 +msgid "player" +msgstr "Spieler" + +#: squirrelbattle/tests/translations_test.py:51 +msgid "tiger" +msgstr "Tiger" + +#: squirrelbattle/tests/translations_test.py:52 +msgid "hedgehog" +msgstr "Igel" + +#: squirrelbattle/tests/translations_test.py:53 +msgid "rabbit" +msgstr "Kanninchen" + +#: squirrelbattle/tests/translations_test.py:54 +msgid "teddy bear" +msgstr "Teddybär" + +#: squirrelbattle/tests/translations_test.py:56 +msgid "bomb" +msgstr "Bombe" + +#: squirrelbattle/tests/translations_test.py:57 +msgid "heart" +msgstr "Herz" + +#: squirrelbattle/display/statsdisplay.py:34 +msgid "Inventory:" +msgstr "Bestand:" + +#: squirrelbattle/display/statsdisplay.py:39 +msgid "YOU ARE DEAD" +msgstr "SIE WURDEN GESTORBEN" + +#: squirrelbattle/interfaces.py:398 +#, python-brace-format +msgid "{name} hits {opponent}." +msgstr "{name} schlägt {opponent}." + +#: squirrelbattle/interfaces.py:410 +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} nimmt {amount} Schadenspunkte." + +#: squirrelbattle/interfaces.py:412 +#, python-brace-format +msgid "{name} dies." +msgstr "{name} stirbt." + +#: squirrelbattle/menus.py:71 +msgid "Back" +msgstr "Zurück" + +#: squirrelbattle/game.py:148 +msgid "" +"Some keys are missing in your save file.\n" +"Your save seems to be corrupt. It got deleted." +msgstr "" +"In Ihrer Speicherdatei fehlen einige Schlüssel.\n" +"Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht." + +#: squirrelbattle/game.py:156 +msgid "" +"No player was found on this map!\n" +"Maybe you died?" +msgstr "" +"Auf dieser Karte wurde kein Spieler gefunden!\n" +"Vielleicht sind Sie gestorben?" + +#: squirrelbattle/game.py:176 +msgid "" +"The JSON file is not correct.\n" +"Your save seems corrupted. It got deleted." +msgstr "" +"Die JSON-Datei ist nicht korrekt.\n" +"Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." diff --git a/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po index 9600c79..3f563fa 100644 --- a/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-11-28 03:01+0100\n" +"POT-Creation-Date: 2020-11-28 16:03+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -37,26 +37,32 @@ msgstr "" #: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:14 #: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287 +#: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "" #: squirrelbattle/menus.py:46 squirrelbattle/tests/translations_test.py:15 +#: squirrelbattle/tests/translations_test.py:17 msgid "Resume" msgstr "" #: squirrelbattle/menus.py:47 squirrelbattle/tests/translations_test.py:17 +#: squirrelbattle/tests/translations_test.py:19 msgid "Save" msgstr "" #: squirrelbattle/menus.py:48 squirrelbattle/tests/translations_test.py:16 +#: squirrelbattle/tests/translations_test.py:18 msgid "Load" msgstr "" #: squirrelbattle/menus.py:49 squirrelbattle/tests/translations_test.py:18 +#: squirrelbattle/tests/translations_test.py:20 msgid "Settings" msgstr "" #: squirrelbattle/menus.py:50 squirrelbattle/tests/translations_test.py:19 +#: squirrelbattle/tests/translations_test.py:21 msgid "Exit" msgstr "" @@ -84,56 +90,67 @@ msgstr "" #: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:21 #: squirrelbattle/tests/translations_test.py:25 +#: squirrelbattle/tests/translations_test.py:27 msgid "Main key to move up" msgstr "" #: squirrelbattle/settings.py:22 squirrelbattle/tests/translations_test.py:23 #: squirrelbattle/tests/translations_test.py:27 +#: squirrelbattle/tests/translations_test.py:29 msgid "Secondary key to move up" msgstr "" #: squirrelbattle/settings.py:23 squirrelbattle/tests/translations_test.py:25 #: squirrelbattle/tests/translations_test.py:29 +#: squirrelbattle/tests/translations_test.py:31 msgid "Main key to move down" msgstr "" #: squirrelbattle/settings.py:24 squirrelbattle/tests/translations_test.py:27 #: squirrelbattle/tests/translations_test.py:31 +#: squirrelbattle/tests/translations_test.py:33 msgid "Secondary key to move down" msgstr "" #: squirrelbattle/settings.py:25 squirrelbattle/tests/translations_test.py:29 #: squirrelbattle/tests/translations_test.py:33 +#: squirrelbattle/tests/translations_test.py:35 msgid "Main key to move left" msgstr "" #: squirrelbattle/settings.py:26 squirrelbattle/tests/translations_test.py:31 #: squirrelbattle/tests/translations_test.py:35 +#: squirrelbattle/tests/translations_test.py:37 msgid "Secondary key to move left" msgstr "" #: squirrelbattle/settings.py:27 squirrelbattle/tests/translations_test.py:33 #: squirrelbattle/tests/translations_test.py:37 +#: squirrelbattle/tests/translations_test.py:39 msgid "Main key to move right" msgstr "" #: squirrelbattle/settings.py:29 squirrelbattle/tests/translations_test.py:35 #: squirrelbattle/tests/translations_test.py:39 +#: squirrelbattle/tests/translations_test.py:41 msgid "Secondary key to move right" msgstr "" #: squirrelbattle/settings.py:30 squirrelbattle/tests/translations_test.py:37 #: squirrelbattle/tests/translations_test.py:41 +#: squirrelbattle/tests/translations_test.py:43 msgid "Key to validate a menu" msgstr "" #: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:39 #: squirrelbattle/tests/translations_test.py:43 +#: squirrelbattle/tests/translations_test.py:45 msgid "Texture pack" msgstr "" #: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40 #: squirrelbattle/tests/translations_test.py:44 +#: squirrelbattle/tests/translations_test.py:46 msgid "Language" msgstr "" @@ -143,29 +160,36 @@ msgid "{name} dies." msgstr "" #: squirrelbattle/tests/translations_test.py:47 +#: squirrelbattle/tests/translations_test.py:49 msgid "player" msgstr "" #: squirrelbattle/tests/translations_test.py:49 +#: squirrelbattle/tests/translations_test.py:51 msgid "tiger" msgstr "" #: squirrelbattle/tests/translations_test.py:50 +#: squirrelbattle/tests/translations_test.py:52 msgid "hedgehog" msgstr "" #: squirrelbattle/tests/translations_test.py:51 +#: squirrelbattle/tests/translations_test.py:53 msgid "rabbit" msgstr "" #: squirrelbattle/tests/translations_test.py:52 +#: squirrelbattle/tests/translations_test.py:54 msgid "teddy bear" msgstr "" #: squirrelbattle/tests/translations_test.py:54 +#: squirrelbattle/tests/translations_test.py:56 msgid "bomb" msgstr "" #: squirrelbattle/tests/translations_test.py:55 +#: squirrelbattle/tests/translations_test.py:57 msgid "heart" msgstr "" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index b14ef50..d46cee6 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-11-28 02:50+0100\n" +"POT-Creation-Date: 2020-11-28 16:03+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -37,26 +37,32 @@ msgstr "{name} prend {amount} points de dégât." #: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:14 #: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287 +#: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Nouvelle partie" #: squirrelbattle/menus.py:46 squirrelbattle/tests/translations_test.py:15 +#: squirrelbattle/tests/translations_test.py:17 msgid "Resume" msgstr "Continuer" #: squirrelbattle/menus.py:47 squirrelbattle/tests/translations_test.py:17 +#: squirrelbattle/tests/translations_test.py:19 msgid "Save" msgstr "Sauvegarder" #: squirrelbattle/menus.py:48 squirrelbattle/tests/translations_test.py:16 +#: squirrelbattle/tests/translations_test.py:18 msgid "Load" msgstr "Charger" #: squirrelbattle/menus.py:49 squirrelbattle/tests/translations_test.py:18 +#: squirrelbattle/tests/translations_test.py:20 msgid "Settings" msgstr "Paramètres" #: squirrelbattle/menus.py:50 squirrelbattle/tests/translations_test.py:19 +#: squirrelbattle/tests/translations_test.py:21 msgid "Exit" msgstr "Quitter" @@ -90,56 +96,67 @@ msgstr "" #: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:21 #: squirrelbattle/tests/translations_test.py:25 +#: squirrelbattle/tests/translations_test.py:27 msgid "Main key to move up" msgstr "Touche principale pour aller vers le haut" #: squirrelbattle/settings.py:22 squirrelbattle/tests/translations_test.py:23 #: squirrelbattle/tests/translations_test.py:27 +#: squirrelbattle/tests/translations_test.py:29 msgid "Secondary key to move up" msgstr "Touche secondaire pour aller vers le haut" #: squirrelbattle/settings.py:23 squirrelbattle/tests/translations_test.py:25 #: squirrelbattle/tests/translations_test.py:29 +#: squirrelbattle/tests/translations_test.py:31 msgid "Main key to move down" msgstr "Touche principale pour aller vers le bas" #: squirrelbattle/settings.py:24 squirrelbattle/tests/translations_test.py:27 #: squirrelbattle/tests/translations_test.py:31 +#: squirrelbattle/tests/translations_test.py:33 msgid "Secondary key to move down" msgstr "Touche secondaire pour aller vers le bas" #: squirrelbattle/settings.py:25 squirrelbattle/tests/translations_test.py:29 #: squirrelbattle/tests/translations_test.py:33 +#: squirrelbattle/tests/translations_test.py:35 msgid "Main key to move left" msgstr "Touche principale pour aller vers la gauche" #: squirrelbattle/settings.py:26 squirrelbattle/tests/translations_test.py:31 #: squirrelbattle/tests/translations_test.py:35 +#: squirrelbattle/tests/translations_test.py:37 msgid "Secondary key to move left" msgstr "Touche secondaire pour aller vers la gauche" #: squirrelbattle/settings.py:27 squirrelbattle/tests/translations_test.py:33 #: squirrelbattle/tests/translations_test.py:37 +#: squirrelbattle/tests/translations_test.py:39 msgid "Main key to move right" msgstr "Touche principale pour aller vers la droite" #: squirrelbattle/settings.py:29 squirrelbattle/tests/translations_test.py:35 #: squirrelbattle/tests/translations_test.py:39 +#: squirrelbattle/tests/translations_test.py:41 msgid "Secondary key to move right" msgstr "Touche secondaire pour aller vers la droite" #: squirrelbattle/settings.py:30 squirrelbattle/tests/translations_test.py:37 #: squirrelbattle/tests/translations_test.py:41 +#: squirrelbattle/tests/translations_test.py:43 msgid "Key to validate a menu" msgstr "Touche pour valider un menu" #: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:39 #: squirrelbattle/tests/translations_test.py:43 +#: squirrelbattle/tests/translations_test.py:45 msgid "Texture pack" msgstr "Pack de textures" #: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40 #: squirrelbattle/tests/translations_test.py:44 +#: squirrelbattle/tests/translations_test.py:46 msgid "Language" msgstr "Langue" @@ -149,29 +166,36 @@ msgid "{name} dies." msgstr "{name} meurt." #: squirrelbattle/tests/translations_test.py:47 +#: squirrelbattle/tests/translations_test.py:49 msgid "player" msgstr "joueur" #: squirrelbattle/tests/translations_test.py:49 +#: squirrelbattle/tests/translations_test.py:51 msgid "tiger" msgstr "tigre" #: squirrelbattle/tests/translations_test.py:50 +#: squirrelbattle/tests/translations_test.py:52 msgid "hedgehog" msgstr "hérisson" #: squirrelbattle/tests/translations_test.py:51 +#: squirrelbattle/tests/translations_test.py:53 msgid "rabbit" msgstr "lapin" #: squirrelbattle/tests/translations_test.py:52 +#: squirrelbattle/tests/translations_test.py:54 msgid "teddy bear" msgstr "nounours" #: squirrelbattle/tests/translations_test.py:54 +#: squirrelbattle/tests/translations_test.py:56 msgid "bomb" msgstr "bombe" #: squirrelbattle/tests/translations_test.py:55 +#: squirrelbattle/tests/translations_test.py:57 msgid "heart" msgstr "cœur" diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index 1fba7ea..4fcfabe 100644 --- a/squirrelbattle/menus.py +++ b/squirrelbattle/menus.py @@ -98,7 +98,7 @@ class SettingsMenu(Menu): self.update_values(game.settings) elif option == "LOCALE": game.settings.LOCALE = 'fr' if game.settings.LOCALE == 'en'\ - else 'en' + else 'de' if game.settings.LOCALE == 'fr' else 'en' Translator.setlocale(game.settings.LOCALE) game.settings.write_settings() self.update_values(game.settings) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 9c950d6..a23b6f9 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -283,6 +283,9 @@ class TestGame(unittest.TestCase): self.assertEqual(self.game.settings.LOCALE, "fr") self.assertEqual(_("New game"), "Nouvelle partie") self.game.handle_key_pressed(KeyValues.ENTER) + self.assertEqual(self.game.settings.LOCALE, "de") + self.assertEqual(_("New game"), "Neu Spiel") + self.game.handle_key_pressed(KeyValues.ENTER) self.assertEqual(self.game.settings.LOCALE, "en") self.assertEqual(_("New game"), "New game") diff --git a/squirrelbattle/translations.py b/squirrelbattle/translations.py index cd5b7fc..f532bb0 100644 --- a/squirrelbattle/translations.py +++ b/squirrelbattle/translations.py @@ -14,7 +14,7 @@ class Translator: Translator.setlocale defines the language of the strings, then gettext() translates the message. """ - SUPPORTED_LOCALES: List[str] = ["en", "fr"] + SUPPORTED_LOCALES: List[str] = ["de", "en", "fr"] locale: str = "en" translators: dict = {} From da0d7e7055db7bee7c6ac41427d0d342e5212ea5 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 2 Dec 2020 16:04:43 +0100 Subject: [PATCH 143/171] Fix lag when monsters try to move in a random direction --- squirrelbattle/entities/monsters.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/squirrelbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py index 624f8a3..9c151ad 100644 --- a/squirrelbattle/entities/monsters.py +++ b/squirrelbattle/entities/monsters.py @@ -1,7 +1,7 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later -from random import choice +from random import shuffle from .player import Player from ..interfaces import FightingEntity, Map @@ -49,9 +49,13 @@ class Monster(FightingEntity): if not moved and self.distance_squared(target) <= 1: self.map.logs.add_message(self.hit(target)) else: - for _ in range(100): - if choice([self.move_up, self.move_down, - self.move_left, self.move_right])(): + # Move in a random direction + # If the direction is not available, try another one + moves = [self.move_up, self.move_down, + self.move_left, self.move_right] + shuffle(moves) + for move in moves: + if move(): break From 39af79101207c0a031f993e46310a3a7c4744296 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 4 Dec 2020 14:41:59 +0100 Subject: [PATCH 144/171] We can open an empty inventory! --- squirrelbattle/display/display_manager.py | 16 ++-- squirrelbattle/display/menudisplay.py | 5 ++ squirrelbattle/enums.py | 3 + squirrelbattle/game.py | 3 + .../locale/de/LC_MESSAGES/squirrelbattle.po | 24 +++--- .../locale/en/LC_MESSAGES/squirrelbattle.po | 16 +++- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 86 ++++++++----------- squirrelbattle/menus.py | 5 ++ squirrelbattle/settings.py | 1 + squirrelbattle/tests/game_test.py | 1 + squirrelbattle/tests/settings_test.py | 4 + squirrelbattle/tests/translations_test.py | 2 + 12 files changed, 99 insertions(+), 67 deletions(-) diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index f7a0882..20cdb3c 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -6,8 +6,8 @@ from squirrelbattle.display.display import VerticalSplit, HorizontalSplit from squirrelbattle.display.mapdisplay import MapDisplay from squirrelbattle.display.messagedisplay import MessageDisplay from squirrelbattle.display.statsdisplay import StatsDisplay -from squirrelbattle.display.menudisplay import SettingsMenuDisplay, \ - MainMenuDisplay +from squirrelbattle.display.menudisplay import MainMenuDisplay, \ + InventoryDisplay, SettingsMenuDisplay from squirrelbattle.display.logsdisplay import LogsDisplay from squirrelbattle.display.texturepack import TexturePack from typing import Any @@ -23,10 +23,11 @@ class DisplayManager: pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK) self.mapdisplay = MapDisplay(screen, pack) self.statsdisplay = StatsDisplay(screen, pack) + self.logsdisplay = LogsDisplay(screen, pack) + self.inventorydisplay = InventoryDisplay(screen, pack) self.mainmenudisplay = MainMenuDisplay(self.game.main_menu, screen, pack) self.settingsmenudisplay = SettingsMenuDisplay(screen, pack) - self.logsdisplay = LogsDisplay(screen, pack) self.messagedisplay = MessageDisplay(screen=screen, pack=None) self.hbar = HorizontalSplit(screen, pack) self.vbar = VerticalSplit(screen, pack) @@ -46,6 +47,7 @@ class DisplayManager: d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK) self.mapdisplay.update_map(self.game.map) self.statsdisplay.update_player(self.game.player) + self.inventorydisplay.update_menu(self.game.inventory_menu) self.settingsmenudisplay.update_menu(self.game.settings_menu) self.logsdisplay.update_logs(self.game.logs) self.messagedisplay.update_message(self.game.message) @@ -64,10 +66,12 @@ class DisplayManager: self.rows // 5 - 1, self.cols * 4 // 5) self.hbar.refresh(self.rows * 4 // 5, 0, 1, self.cols * 4 // 5) self.vbar.refresh(0, self.cols * 4 // 5, self.rows, 1) - if self.game.state == GameMode.MAINMENU: + elif self.game.state == GameMode.INVENTORY: + self.inventorydisplay.refresh(0, 0, self.rows, self.cols) + elif self.game.state == GameMode.MAINMENU: self.mainmenudisplay.refresh(0, 0, self.rows, self.cols) - if self.game.state == GameMode.SETTINGS: - self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols - 1) + elif self.game.state == GameMode.SETTINGS: + self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols) if self.game.message: height, width = 0, 0 diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index b3036a0..5720faa 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -100,3 +100,8 @@ class MainMenuDisplay(Display): self.menudisplay.refresh( menuy, menux, min(self.menudisplay.preferred_height, self.height - menuy), menuwidth) + + +class InventoryDisplay(MenuDisplay): + def update_pad(self) -> None: + pass diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index 024f167..cd88e66 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -37,6 +37,7 @@ class KeyValues(Enum): LEFT = auto() RIGHT = auto() ENTER = auto() + INVENTORY = auto() SPACE = auto() @staticmethod @@ -58,6 +59,8 @@ class KeyValues(Enum): return KeyValues.UP elif key == settings.KEY_ENTER: return KeyValues.ENTER + elif key == settings.KEY_INVENTORY: + return KeyValues.INVENTORY elif key == ' ': return KeyValues.SPACE return None diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 44ad349..d6045ce 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -39,6 +39,7 @@ class Game: self.main_menu = menus.MainMenu() self.settings_menu = menus.SettingsMenu() self.settings_menu.update_values(self.settings) + self.inventory_menu = menus.InventoryMenu() self.logs = Logs() self.message = None @@ -104,6 +105,8 @@ class Game: elif key == KeyValues.RIGHT: if self.player.move_right(): self.map.tick() + elif key == KeyValues.INVENTORY: + self.state = GameMode.INVENTORY elif key == KeyValues.SPACE: self.state = GameMode.MAINMENU diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index dfd3365..55e3f1f 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-11-28 16:03+0100\n" +"POT-Creation-Date: 2020-12-04 14:18+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,8 +17,8 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287 #: squirrelbattle/tests/translations_test.py:16 +#: squirrelbattle/tests/game_test.py:290 msgid "New game" msgstr "Neu Spiel" @@ -79,38 +79,42 @@ msgid "Key to validate a menu" msgstr "Menütaste" #: squirrelbattle/tests/translations_test.py:45 +msgid "Key used to open the inventory" +msgstr "Bestandtaste" + +#: squirrelbattle/tests/translations_test.py:47 msgid "Texture pack" msgstr "Textur-Packung" -#: squirrelbattle/tests/translations_test.py:46 +#: squirrelbattle/tests/translations_test.py:48 msgid "Language" msgstr "Sprache" -#: squirrelbattle/tests/translations_test.py:49 +#: squirrelbattle/tests/translations_test.py:51 msgid "player" msgstr "Spieler" -#: squirrelbattle/tests/translations_test.py:51 +#: squirrelbattle/tests/translations_test.py:53 msgid "tiger" msgstr "Tiger" -#: squirrelbattle/tests/translations_test.py:52 +#: squirrelbattle/tests/translations_test.py:54 msgid "hedgehog" msgstr "Igel" -#: squirrelbattle/tests/translations_test.py:53 +#: squirrelbattle/tests/translations_test.py:55 msgid "rabbit" msgstr "Kanninchen" -#: squirrelbattle/tests/translations_test.py:54 +#: squirrelbattle/tests/translations_test.py:56 msgid "teddy bear" msgstr "Teddybär" -#: squirrelbattle/tests/translations_test.py:56 +#: squirrelbattle/tests/translations_test.py:58 msgid "bomb" msgstr "Bombe" -#: squirrelbattle/tests/translations_test.py:57 +#: squirrelbattle/tests/translations_test.py:59 msgid "heart" msgstr "Herz" diff --git a/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po index 3f563fa..972d853 100644 --- a/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-11-28 16:03+0100\n" +"POT-Creation-Date: 2020-12-04 14:18+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -38,6 +38,7 @@ msgstr "" #: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:14 #: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287 #: squirrelbattle/tests/translations_test.py:16 +#: squirrelbattle/tests/game_test.py:290 msgid "New game" msgstr "" @@ -145,12 +146,14 @@ msgstr "" #: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:39 #: squirrelbattle/tests/translations_test.py:43 #: squirrelbattle/tests/translations_test.py:45 +#: squirrelbattle/tests/translations_test.py:47 msgid "Texture pack" msgstr "" #: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40 #: squirrelbattle/tests/translations_test.py:44 #: squirrelbattle/tests/translations_test.py:46 +#: squirrelbattle/tests/translations_test.py:48 msgid "Language" msgstr "" @@ -161,35 +164,46 @@ msgstr "" #: squirrelbattle/tests/translations_test.py:47 #: squirrelbattle/tests/translations_test.py:49 +#: squirrelbattle/tests/translations_test.py:51 msgid "player" msgstr "" #: squirrelbattle/tests/translations_test.py:49 #: squirrelbattle/tests/translations_test.py:51 +#: squirrelbattle/tests/translations_test.py:53 msgid "tiger" msgstr "" #: squirrelbattle/tests/translations_test.py:50 #: squirrelbattle/tests/translations_test.py:52 +#: squirrelbattle/tests/translations_test.py:54 msgid "hedgehog" msgstr "" #: squirrelbattle/tests/translations_test.py:51 #: squirrelbattle/tests/translations_test.py:53 +#: squirrelbattle/tests/translations_test.py:55 msgid "rabbit" msgstr "" #: squirrelbattle/tests/translations_test.py:52 #: squirrelbattle/tests/translations_test.py:54 +#: squirrelbattle/tests/translations_test.py:56 msgid "teddy bear" msgstr "" #: squirrelbattle/tests/translations_test.py:54 #: squirrelbattle/tests/translations_test.py:56 +#: squirrelbattle/tests/translations_test.py:58 msgid "bomb" msgstr "" #: squirrelbattle/tests/translations_test.py:55 #: squirrelbattle/tests/translations_test.py:57 +#: squirrelbattle/tests/translations_test.py:59 msgid "heart" msgstr "" + +#: squirrelbattle/tests/translations_test.py:45 +msgid "Key used to open the inventory" +msgstr "" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index d46cee6..647ab97 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-11-28 16:03+0100\n" +"POT-Creation-Date: 2020-12-04 14:18+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -25,43 +25,43 @@ msgstr "Inventaire :" msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" -#: squirrelbattle/interfaces.py:394 squirrelbattle/interfaces.py:398 +#: squirrelbattle/interfaces.py:398 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} frappe {opponent}." -#: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410 +#: squirrelbattle/interfaces.py:410 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} prend {amount} points de dégât." -#: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:14 -#: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287 +#: squirrelbattle/menus.py:45 #: squirrelbattle/tests/translations_test.py:16 +#: squirrelbattle/tests/game_test.py:290 msgid "New game" msgstr "Nouvelle partie" -#: squirrelbattle/menus.py:46 squirrelbattle/tests/translations_test.py:15 +#: squirrelbattle/menus.py:46 #: squirrelbattle/tests/translations_test.py:17 msgid "Resume" msgstr "Continuer" -#: squirrelbattle/menus.py:47 squirrelbattle/tests/translations_test.py:17 +#: squirrelbattle/menus.py:47 #: squirrelbattle/tests/translations_test.py:19 msgid "Save" msgstr "Sauvegarder" -#: squirrelbattle/menus.py:48 squirrelbattle/tests/translations_test.py:16 +#: squirrelbattle/menus.py:48 #: squirrelbattle/tests/translations_test.py:18 msgid "Load" msgstr "Charger" -#: squirrelbattle/menus.py:49 squirrelbattle/tests/translations_test.py:18 +#: squirrelbattle/menus.py:49 #: squirrelbattle/tests/translations_test.py:20 msgid "Settings" msgstr "Paramètres" -#: squirrelbattle/menus.py:50 squirrelbattle/tests/translations_test.py:19 +#: squirrelbattle/menus.py:50 #: squirrelbattle/tests/translations_test.py:21 msgid "Exit" msgstr "Quitter" @@ -78,7 +78,7 @@ msgstr "" "Certaines clés de votre ficher de sauvegarde sont manquantes.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/game.py:155 squirrelbattle/game.py:156 +#: squirrelbattle/game.py:156 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -86,7 +86,7 @@ msgstr "" "Aucun joueur n'a été trouvé sur la carte !\n" "Peut-être êtes-vous mort ?" -#: squirrelbattle/game.py:175 squirrelbattle/game.py:176 +#: squirrelbattle/game.py:176 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -94,108 +94,94 @@ msgstr "" "Le fichier JSON de sauvegarde est incorrect.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:21 -#: squirrelbattle/tests/translations_test.py:25 +#: squirrelbattle/settings.py:21 #: squirrelbattle/tests/translations_test.py:27 msgid "Main key to move up" msgstr "Touche principale pour aller vers le haut" -#: squirrelbattle/settings.py:22 squirrelbattle/tests/translations_test.py:23 -#: squirrelbattle/tests/translations_test.py:27 +#: squirrelbattle/settings.py:22 #: squirrelbattle/tests/translations_test.py:29 msgid "Secondary key to move up" msgstr "Touche secondaire pour aller vers le haut" -#: squirrelbattle/settings.py:23 squirrelbattle/tests/translations_test.py:25 -#: squirrelbattle/tests/translations_test.py:29 +#: squirrelbattle/settings.py:23 #: squirrelbattle/tests/translations_test.py:31 msgid "Main key to move down" msgstr "Touche principale pour aller vers le bas" -#: squirrelbattle/settings.py:24 squirrelbattle/tests/translations_test.py:27 -#: squirrelbattle/tests/translations_test.py:31 +#: squirrelbattle/settings.py:24 #: squirrelbattle/tests/translations_test.py:33 msgid "Secondary key to move down" msgstr "Touche secondaire pour aller vers le bas" -#: squirrelbattle/settings.py:25 squirrelbattle/tests/translations_test.py:29 -#: squirrelbattle/tests/translations_test.py:33 +#: squirrelbattle/settings.py:25 #: squirrelbattle/tests/translations_test.py:35 msgid "Main key to move left" msgstr "Touche principale pour aller vers la gauche" -#: squirrelbattle/settings.py:26 squirrelbattle/tests/translations_test.py:31 -#: squirrelbattle/tests/translations_test.py:35 +#: squirrelbattle/settings.py:26 #: squirrelbattle/tests/translations_test.py:37 msgid "Secondary key to move left" msgstr "Touche secondaire pour aller vers la gauche" -#: squirrelbattle/settings.py:27 squirrelbattle/tests/translations_test.py:33 -#: squirrelbattle/tests/translations_test.py:37 +#: squirrelbattle/settings.py:27 #: squirrelbattle/tests/translations_test.py:39 msgid "Main key to move right" msgstr "Touche principale pour aller vers la droite" -#: squirrelbattle/settings.py:29 squirrelbattle/tests/translations_test.py:35 -#: squirrelbattle/tests/translations_test.py:39 +#: squirrelbattle/settings.py:29 #: squirrelbattle/tests/translations_test.py:41 msgid "Secondary key to move right" msgstr "Touche secondaire pour aller vers la droite" -#: squirrelbattle/settings.py:30 squirrelbattle/tests/translations_test.py:37 -#: squirrelbattle/tests/translations_test.py:41 +#: squirrelbattle/settings.py:30 #: squirrelbattle/tests/translations_test.py:43 msgid "Key to validate a menu" msgstr "Touche pour valider un menu" -#: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:39 -#: squirrelbattle/tests/translations_test.py:43 #: squirrelbattle/tests/translations_test.py:45 +msgid "Key used to open the inventory" +msgstr "Touche utilisée pour ouvrir l'inventaire" + +#: squirrelbattle/settings.py:31 +#: squirrelbattle/tests/translations_test.py:47 msgid "Texture pack" msgstr "Pack de textures" -#: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40 -#: squirrelbattle/tests/translations_test.py:44 -#: squirrelbattle/tests/translations_test.py:46 +#: squirrelbattle/settings.py:32 +#: squirrelbattle/tests/translations_test.py:48 msgid "Language" msgstr "Langue" -#: squirrelbattle/interfaces.py:407 squirrelbattle/interfaces.py:412 +#: squirrelbattle/interfaces.py:412 #, python-brace-format msgid "{name} dies." msgstr "{name} meurt." -#: squirrelbattle/tests/translations_test.py:47 -#: squirrelbattle/tests/translations_test.py:49 +#: squirrelbattle/tests/translations_test.py:51 msgid "player" msgstr "joueur" -#: squirrelbattle/tests/translations_test.py:49 -#: squirrelbattle/tests/translations_test.py:51 +#: squirrelbattle/tests/translations_test.py:53 msgid "tiger" msgstr "tigre" -#: squirrelbattle/tests/translations_test.py:50 -#: squirrelbattle/tests/translations_test.py:52 +#: squirrelbattle/tests/translations_test.py:54 msgid "hedgehog" msgstr "hérisson" -#: squirrelbattle/tests/translations_test.py:51 -#: squirrelbattle/tests/translations_test.py:53 +#: squirrelbattle/tests/translations_test.py:55 msgid "rabbit" msgstr "lapin" -#: squirrelbattle/tests/translations_test.py:52 -#: squirrelbattle/tests/translations_test.py:54 +#: squirrelbattle/tests/translations_test.py:56 msgid "teddy bear" msgstr "nounours" -#: squirrelbattle/tests/translations_test.py:54 -#: squirrelbattle/tests/translations_test.py:56 +#: squirrelbattle/tests/translations_test.py:58 msgid "bomb" msgstr "bombe" -#: squirrelbattle/tests/translations_test.py:55 -#: squirrelbattle/tests/translations_test.py:57 +#: squirrelbattle/tests/translations_test.py:59 msgid "heart" msgstr "cœur" diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index 4fcfabe..68c8c3e 100644 --- a/squirrelbattle/menus.py +++ b/squirrelbattle/menus.py @@ -115,3 +115,8 @@ class SettingsMenu(Menu): game.settings.write_settings() self.waiting_for_key = False self.update_values(game.settings) + + +class InventoryMenu(Menu): + # FIXME Use real menu + values = ["bomb"] diff --git a/squirrelbattle/settings.py b/squirrelbattle/settings.py index 3090679..a816d67 100644 --- a/squirrelbattle/settings.py +++ b/squirrelbattle/settings.py @@ -27,6 +27,7 @@ class Settings: self.KEY_RIGHT_PRIMARY = ['d', 'Main key to move right'] self.KEY_RIGHT_SECONDARY = ['KEY_RIGHT', 'Secondary key to move right'] self.KEY_ENTER = ['\n', 'Key to validate a menu'] + self.KEY_INVENTORY = ['i', 'Key used to open the inventory'] self.TEXTURE_PACK = ['ascii', 'Texture pack'] self.LOCALE = [locale.getlocale()[0][:2], 'Language'] diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index a23b6f9..42886e5 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -266,6 +266,7 @@ class TestGame(unittest.TestCase): 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.DOWN) # Change texture pack self.assertEqual(self.game.settings.TEXTURE_PACK, "ascii") diff --git a/squirrelbattle/tests/settings_test.py b/squirrelbattle/tests/settings_test.py index b0d9739..06225b2 100644 --- a/squirrelbattle/tests/settings_test.py +++ b/squirrelbattle/tests/settings_test.py @@ -4,9 +4,13 @@ import unittest from squirrelbattle.settings import Settings +from squirrelbattle.translations import Translator class TestSettings(unittest.TestCase): + def setUp(self) -> None: + Translator.setlocale("en") + def test_settings(self) -> None: """ Ensure that settings are well loaded. diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index 6c18840..6d3a0e4 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -42,6 +42,8 @@ class TestTranslations(unittest.TestCase): "Touche secondaire pour aller vers la droite") self.assertEqual(_("Key to validate a menu"), "Touche pour valider un menu") + self.assertEqual(_("Key used to open the inventory"), + "Touche utilisée pour ouvrir l'inventaire") self.assertEqual(_("Texture pack"), "Pack de textures") self.assertEqual(_("Language"), "Langue") From 067570fd1abaeb3bda8d36af071227a7aab1772b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 4 Dec 2020 14:51:41 +0100 Subject: [PATCH 145/171] The inventory is a popup --- squirrelbattle/display/display_manager.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 20cdb3c..0e9cf04 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -53,7 +53,8 @@ class DisplayManager: self.messagedisplay.update_message(self.game.message) def refresh(self) -> None: - if self.game.state == GameMode.PLAY: + if self.game.state == GameMode.PLAY \ + or self.game.state == GameMode.INVENTORY: # The map pad has already the good size self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, self.mapdisplay.pack.tile_width @@ -66,8 +67,11 @@ class DisplayManager: self.rows // 5 - 1, self.cols * 4 // 5) self.hbar.refresh(self.rows * 4 // 5, 0, 1, self.cols * 4 // 5) self.vbar.refresh(0, self.cols * 4 // 5, self.rows, 1) - elif self.game.state == GameMode.INVENTORY: - self.inventorydisplay.refresh(0, 0, self.rows, self.cols) + if self.game.state == GameMode.INVENTORY: + self.inventorydisplay.refresh(self.rows // 10, + self.cols // 2, + 8 * self.rows // 10, + 2 * self.cols // 5) elif self.game.state == GameMode.MAINMENU: self.mainmenudisplay.refresh(0, 0, self.rows, self.cols) elif self.game.state == GameMode.SETTINGS: From cdd527a7fd47e6b398b69cd717410c553b61474d Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 4 Dec 2020 14:57:53 +0100 Subject: [PATCH 146/171] Close the inventory using the same key --- squirrelbattle/game.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index d6045ce..b4b3147 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -83,6 +83,8 @@ class Game: if self.state == GameMode.PLAY: self.handle_key_pressed_play(key) + elif self.state == GameMode.INVENTORY: + self.handle_key_pressed_inventory(key) elif self.state == GameMode.MAINMENU: self.handle_key_pressed_main_menu(key) elif self.state == GameMode.SETTINGS: @@ -110,6 +112,13 @@ class Game: elif key == KeyValues.SPACE: self.state = GameMode.MAINMENU + def handle_key_pressed_inventory(self, key: KeyValues) -> None: + """ + In the inventory menu, we can interact with items or close the menu. + """ + if key == KeyValues.SPACE or key == KeyValues.INVENTORY: + self.state = GameMode.PLAY + def handle_key_pressed_main_menu(self, key: KeyValues) -> None: """ In the main menu, we can navigate through options. From fbfcd5dae07eb4357957242571df340cfbbdab93 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 4 Dec 2020 16:02:03 +0100 Subject: [PATCH 147/171] Inventory title --- squirrelbattle/display/menudisplay.py | 6 +- .../locale/de/LC_MESSAGES/squirrelbattle.po | 15 +++-- .../locale/en/LC_MESSAGES/squirrelbattle.po | 12 +++- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 65 ++++++++----------- 4 files changed, 51 insertions(+), 47 deletions(-) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 5720faa..61ac155 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -1,6 +1,6 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later - +import curses from typing import List from squirrelbattle.menus import Menu, MainMenu @@ -104,4 +104,6 @@ class MainMenuDisplay(Display): class InventoryDisplay(MenuDisplay): def update_pad(self) -> None: - pass + message = _("== INVENTORY ==") + self.addstr(self.pad, 0, (self.width - len(message)) // 2, message, + curses.A_BOLD | curses.A_ITALIC) diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 55e3f1f..01cb4aa 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-12-04 14:18+0100\n" +"POT-Creation-Date: 2020-12-04 15:59+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,7 +18,8 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" #: squirrelbattle/tests/translations_test.py:16 -#: squirrelbattle/tests/game_test.py:290 +#: squirrelbattle/tests/game_test.py:290 squirrelbattle/tests/game_test.py:285 +#: squirrelbattle/tests/game_test.py:288 squirrelbattle/tests/game_test.py:291 msgid "New game" msgstr "Neu Spiel" @@ -145,7 +146,7 @@ msgstr "{name} stirbt." msgid "Back" msgstr "Zurück" -#: squirrelbattle/game.py:148 +#: squirrelbattle/game.py:148 squirrelbattle/game.py:160 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -153,7 +154,7 @@ msgstr "" "In Ihrer Speicherdatei fehlen einige Schlüssel.\n" "Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht." -#: squirrelbattle/game.py:156 +#: squirrelbattle/game.py:156 squirrelbattle/game.py:168 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -161,10 +162,14 @@ msgstr "" "Auf dieser Karte wurde kein Spieler gefunden!\n" "Vielleicht sind Sie gestorben?" -#: squirrelbattle/game.py:176 +#: squirrelbattle/game.py:176 squirrelbattle/game.py:188 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." msgstr "" "Die JSON-Datei ist nicht korrekt.\n" "Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." + +#: squirrelbattle/display/menudisplay.py:107 +msgid "== INVENTORY ==" +msgstr "== BESTAND ==" diff --git a/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po index 972d853..2081f69 100644 --- a/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-12-04 14:18+0100\n" +"POT-Creation-Date: 2020-12-04 15:59+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -38,7 +38,8 @@ msgstr "" #: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:14 #: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287 #: squirrelbattle/tests/translations_test.py:16 -#: squirrelbattle/tests/game_test.py:290 +#: squirrelbattle/tests/game_test.py:290 squirrelbattle/tests/game_test.py:285 +#: squirrelbattle/tests/game_test.py:288 squirrelbattle/tests/game_test.py:291 msgid "New game" msgstr "" @@ -72,18 +73,21 @@ msgid "Back" msgstr "" #: squirrelbattle/game.py:147 squirrelbattle/game.py:148 +#: squirrelbattle/game.py:160 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." msgstr "" #: squirrelbattle/game.py:155 squirrelbattle/game.py:156 +#: squirrelbattle/game.py:168 msgid "" "No player was found on this map!\n" "Maybe you died?" msgstr "" #: squirrelbattle/game.py:175 squirrelbattle/game.py:176 +#: squirrelbattle/game.py:188 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -207,3 +211,7 @@ msgstr "" #: squirrelbattle/tests/translations_test.py:45 msgid "Key used to open the inventory" msgstr "" + +#: squirrelbattle/display/menudisplay.py:107 +msgid "== INVENTORY ==" +msgstr "" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 647ab97..99abe43 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-12-04 14:18+0100\n" +"POT-Creation-Date: 2020-12-04 15:59+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -35,34 +35,29 @@ msgstr "{name} frappe {opponent}." msgid "{name} takes {amount} damage." msgstr "{name} prend {amount} points de dégât." -#: squirrelbattle/menus.py:45 -#: squirrelbattle/tests/translations_test.py:16 -#: squirrelbattle/tests/game_test.py:290 +#: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:16 +#: squirrelbattle/tests/game_test.py:290 squirrelbattle/tests/game_test.py:285 +#: squirrelbattle/tests/game_test.py:288 squirrelbattle/tests/game_test.py:291 msgid "New game" msgstr "Nouvelle partie" -#: squirrelbattle/menus.py:46 -#: squirrelbattle/tests/translations_test.py:17 +#: squirrelbattle/menus.py:46 squirrelbattle/tests/translations_test.py:17 msgid "Resume" msgstr "Continuer" -#: squirrelbattle/menus.py:47 -#: squirrelbattle/tests/translations_test.py:19 +#: squirrelbattle/menus.py:47 squirrelbattle/tests/translations_test.py:19 msgid "Save" msgstr "Sauvegarder" -#: squirrelbattle/menus.py:48 -#: squirrelbattle/tests/translations_test.py:18 +#: squirrelbattle/menus.py:48 squirrelbattle/tests/translations_test.py:18 msgid "Load" msgstr "Charger" -#: squirrelbattle/menus.py:49 -#: squirrelbattle/tests/translations_test.py:20 +#: squirrelbattle/menus.py:49 squirrelbattle/tests/translations_test.py:20 msgid "Settings" msgstr "Paramètres" -#: squirrelbattle/menus.py:50 -#: squirrelbattle/tests/translations_test.py:21 +#: squirrelbattle/menus.py:50 squirrelbattle/tests/translations_test.py:21 msgid "Exit" msgstr "Quitter" @@ -71,6 +66,7 @@ msgid "Back" msgstr "Retour" #: squirrelbattle/game.py:147 squirrelbattle/game.py:148 +#: squirrelbattle/game.py:160 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -78,7 +74,7 @@ msgstr "" "Certaines clés de votre ficher de sauvegarde sont manquantes.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/game.py:156 +#: squirrelbattle/game.py:156 squirrelbattle/game.py:168 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -86,7 +82,7 @@ msgstr "" "Aucun joueur n'a été trouvé sur la carte !\n" "Peut-être êtes-vous mort ?" -#: squirrelbattle/game.py:176 +#: squirrelbattle/game.py:176 squirrelbattle/game.py:188 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -94,48 +90,39 @@ msgstr "" "Le fichier JSON de sauvegarde est incorrect.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/settings.py:21 -#: squirrelbattle/tests/translations_test.py:27 +#: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:27 msgid "Main key to move up" msgstr "Touche principale pour aller vers le haut" -#: squirrelbattle/settings.py:22 -#: squirrelbattle/tests/translations_test.py:29 +#: squirrelbattle/settings.py:22 squirrelbattle/tests/translations_test.py:29 msgid "Secondary key to move up" msgstr "Touche secondaire pour aller vers le haut" -#: squirrelbattle/settings.py:23 -#: squirrelbattle/tests/translations_test.py:31 +#: squirrelbattle/settings.py:23 squirrelbattle/tests/translations_test.py:31 msgid "Main key to move down" msgstr "Touche principale pour aller vers le bas" -#: squirrelbattle/settings.py:24 -#: squirrelbattle/tests/translations_test.py:33 +#: squirrelbattle/settings.py:24 squirrelbattle/tests/translations_test.py:33 msgid "Secondary key to move down" msgstr "Touche secondaire pour aller vers le bas" -#: squirrelbattle/settings.py:25 -#: squirrelbattle/tests/translations_test.py:35 +#: squirrelbattle/settings.py:25 squirrelbattle/tests/translations_test.py:35 msgid "Main key to move left" msgstr "Touche principale pour aller vers la gauche" -#: squirrelbattle/settings.py:26 -#: squirrelbattle/tests/translations_test.py:37 +#: squirrelbattle/settings.py:26 squirrelbattle/tests/translations_test.py:37 msgid "Secondary key to move left" msgstr "Touche secondaire pour aller vers la gauche" -#: squirrelbattle/settings.py:27 -#: squirrelbattle/tests/translations_test.py:39 +#: squirrelbattle/settings.py:27 squirrelbattle/tests/translations_test.py:39 msgid "Main key to move right" msgstr "Touche principale pour aller vers la droite" -#: squirrelbattle/settings.py:29 -#: squirrelbattle/tests/translations_test.py:41 +#: squirrelbattle/settings.py:29 squirrelbattle/tests/translations_test.py:41 msgid "Secondary key to move right" msgstr "Touche secondaire pour aller vers la droite" -#: squirrelbattle/settings.py:30 -#: squirrelbattle/tests/translations_test.py:43 +#: squirrelbattle/settings.py:30 squirrelbattle/tests/translations_test.py:43 msgid "Key to validate a menu" msgstr "Touche pour valider un menu" @@ -143,13 +130,11 @@ msgstr "Touche pour valider un menu" msgid "Key used to open the inventory" msgstr "Touche utilisée pour ouvrir l'inventaire" -#: squirrelbattle/settings.py:31 -#: squirrelbattle/tests/translations_test.py:47 +#: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:47 msgid "Texture pack" msgstr "Pack de textures" -#: squirrelbattle/settings.py:32 -#: squirrelbattle/tests/translations_test.py:48 +#: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:48 msgid "Language" msgstr "Langue" @@ -185,3 +170,7 @@ msgstr "bombe" #: squirrelbattle/tests/translations_test.py:59 msgid "heart" msgstr "cœur" + +#: squirrelbattle/display/menudisplay.py:107 +msgid "== INVENTORY ==" +msgstr "== INVENTAIRE ==" From a68b3a6d0899a7d3481af7369e9a6607a53f8ab0 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 4 Dec 2020 16:28:37 +0100 Subject: [PATCH 148/171] Display full inventory --- squirrelbattle/display/menudisplay.py | 15 +++++++++++++-- squirrelbattle/game.py | 1 + squirrelbattle/menus.py | 11 +++++++++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 61ac155..285b6db 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -21,8 +21,6 @@ class MenuDisplay(Display): # Menu values are printed in pad self.pad = self.newpad(self.trueheight, self.truewidth + 2) - for i in range(self.trueheight): - self.addstr(self.pad, i, 0, " " + self.values[i]) def update_pad(self) -> None: for i in range(self.trueheight): @@ -107,3 +105,16 @@ class InventoryDisplay(MenuDisplay): message = _("== INVENTORY ==") self.addstr(self.pad, 0, (self.width - len(message)) // 2, message, curses.A_BOLD | curses.A_ITALIC) + for i, item in enumerate(self.menu.values): + rep = self.pack[item.name.upper()] + selection = f"[{rep}]" if i == self.menu.position else f" {rep} " + self.addstr(self.pad, 2 + i, 0, selection + + " " + _(item.name).capitalize()) + + @property + def truewidth(self) -> int: + return max(1, self.height if hasattr(self, "height") else 10) + + @property + def trueheight(self) -> int: + return 2 + super().trueheight diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index b4b3147..f1171fa 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -55,6 +55,7 @@ class Game: self.map.add_entity(self.player) self.player.move(self.map.start_y, self.map.start_x) self.map.spawn_random_entities(randint(3, 10)) + self.inventory_menu.update_player(self.player) def run(self, screen: Any) -> None: """ diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index 68c8c3e..d6946d0 100644 --- a/squirrelbattle/menus.py +++ b/squirrelbattle/menus.py @@ -5,6 +5,7 @@ from enum import Enum from typing import Any, Optional from .display.texturepack import TexturePack +from .entities.player import Player from .enums import GameMode, KeyValues, DisplayActions from .settings import Settings from .translations import gettext as _, Translator @@ -118,5 +119,11 @@ class SettingsMenu(Menu): class InventoryMenu(Menu): - # FIXME Use real menu - values = ["bomb"] + player: Player + + def update_player(self, player: Player) -> None: + self.player = player + + @property + def values(self) -> list: + return self.player.inventory From 0da74867508898638938279ce00ad4387bcbfc9f Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 4 Dec 2020 16:31:15 +0100 Subject: [PATCH 149/171] Navigate through inventory menu --- squirrelbattle/game.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index f1171fa..71ff9dc 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -119,6 +119,10 @@ class Game: """ if key == KeyValues.SPACE or key == KeyValues.INVENTORY: self.state = GameMode.PLAY + elif key == KeyValues.UP: + self.inventory_menu.go_up() + elif key == KeyValues.DOWN: + self.inventory_menu.go_down() def handle_key_pressed_main_menu(self, key: KeyValues) -> None: """ From c7545e53f75ca32d22b06a9840f7da670fa399d9 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 4 Dec 2020 16:53:27 +0100 Subject: [PATCH 150/171] Items can be dropped/equipped/used --- squirrelbattle/entities/items.py | 40 ++++++++++++++++++++++++-------- squirrelbattle/enums.py | 9 +++++++ squirrelbattle/game.py | 6 +++++ squirrelbattle/settings.py | 3 +++ 4 files changed, 48 insertions(+), 10 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 147d72c..6bd1912 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -20,16 +20,26 @@ class Item(Entity): self.held = held self.held_by = held_by - def drop(self, y: int, x: int) -> None: + def drop(self) -> None: """ The item is dropped from the inventory onto the floor """ if self.held: self.held_by.inventory.remove(self) + self.map.add_entity(self) + self.move(self.held_by.y, self.held_by.x) self.held = False self.held_by = None - self.map.add_entity(self) - self.move(y, x) + + def use(self) -> None: + """ + Indicates what should be done when the item is used. + """ + + def equip(self) -> None: + """ + Indicates what should be done when the item is equipped. + """ def hold(self, player: "Player") -> None: """ @@ -80,6 +90,7 @@ class Bomb(Item): A bomb item intended to deal damage to enemies at long range """ damage: int = 5 + tick: int exploding: bool def __init__(self, damage: int = 5, exploding: bool = False, @@ -87,20 +98,29 @@ class Bomb(Item): super().__init__(name="bomb", *args, **kwargs) self.damage = damage self.exploding = exploding + self.tick = 5 - def drop(self, x: int, y: int) -> None: - super().drop(x, y) - self.exploding = True + def use(self) -> None: + """ + When the bomb is used, throw it and explodes it. + """ + if self.held: + super().drop() + self.exploding = True def act(self, m: Map) -> None: """ Special exploding action of the bomb """ if self.exploding: - 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) + if self.tick > 0: + self.tick -= 1 + else: + 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) + m.entities.remove(self) def save_state(self) -> dict: """ diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index cd88e66..84eb498 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -38,6 +38,9 @@ class KeyValues(Enum): RIGHT = auto() ENTER = auto() INVENTORY = auto() + USE = auto() + EQUIP = auto() + DROP = auto() SPACE = auto() @staticmethod @@ -61,6 +64,12 @@ class KeyValues(Enum): return KeyValues.ENTER elif key == settings.KEY_INVENTORY: return KeyValues.INVENTORY + elif key == settings.KEY_USE: + return KeyValues.USE + elif key == settings.KEY_EQUIP: + return KeyValues.EQUIP + elif key == settings.KEY_DROP: + return KeyValues.DROP elif key == ' ': return KeyValues.SPACE return None diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 71ff9dc..f9f8881 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -123,6 +123,12 @@ class Game: self.inventory_menu.go_up() elif key == KeyValues.DOWN: self.inventory_menu.go_down() + elif key == KeyValues.USE: + self.inventory_menu.validate().use() + elif key == KeyValues.EQUIP: + self.inventory_menu.validate().equip() + elif key == KeyValues.DROP: + self.inventory_menu.validate().use() def handle_key_pressed_main_menu(self, key: KeyValues) -> None: """ diff --git a/squirrelbattle/settings.py b/squirrelbattle/settings.py index a816d67..7460b98 100644 --- a/squirrelbattle/settings.py +++ b/squirrelbattle/settings.py @@ -28,6 +28,9 @@ class Settings: self.KEY_RIGHT_SECONDARY = ['KEY_RIGHT', 'Secondary key to move right'] self.KEY_ENTER = ['\n', 'Key to validate a menu'] self.KEY_INVENTORY = ['i', 'Key used to open the inventory'] + self.KEY_USE = ['u', 'Key used to use an item in the inventory'] + self.KEY_EQUIP = ['e', 'Key used to equip an item in the inventory'] + self.KEY_DROP = ['d', 'Key used to drop an item in the inventory'] self.TEXTURE_PACK = ['ascii', 'Texture pack'] self.LOCALE = [locale.getlocale()[0][:2], 'Language'] From 056ca5cca8cb1ec17a9083348bd62d2d3450e35f Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 4 Dec 2020 17:01:00 +0100 Subject: [PATCH 151/171] Ensure that the inventory is not empty before interacting with an item --- squirrelbattle/game.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index f9f8881..ed576ed 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -123,12 +123,18 @@ class Game: self.inventory_menu.go_up() elif key == KeyValues.DOWN: self.inventory_menu.go_down() - elif key == KeyValues.USE: - self.inventory_menu.validate().use() - elif key == KeyValues.EQUIP: - self.inventory_menu.validate().equip() - elif key == KeyValues.DROP: - self.inventory_menu.validate().use() + if self.inventory_menu.values: + if key == KeyValues.USE: + self.inventory_menu.validate().use() + elif key == KeyValues.EQUIP: + self.inventory_menu.validate().equip() + elif key == KeyValues.DROP: + self.inventory_menu.validate().use() + + # Ensure that the cursor has a good position + self.inventory_menu.position = min(self.inventory_menu.position, + len(self.inventory_menu.values) + - 1) def handle_key_pressed_main_menu(self, key: KeyValues) -> None: """ From 27fd73c96ba9818cab8697d919135e6e2f4ba0bb Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 4 Dec 2020 17:10:23 +0100 Subject: [PATCH 152/171] Add log messages when a bomb is exploding --- squirrelbattle/entities/items.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 6bd1912..c95ab9d 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -1,10 +1,12 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later +from random import randint from typing import Optional from .player import Player from ..interfaces import Entity, FightingEntity, Map +from ..translations import gettext as _ class Item(Entity): @@ -90,6 +92,7 @@ class Bomb(Item): A bomb item intended to deal damage to enemies at long range """ damage: int = 5 + owner: "Player" tick: int exploding: bool @@ -98,13 +101,14 @@ class Bomb(Item): super().__init__(name="bomb", *args, **kwargs) self.damage = damage self.exploding = exploding - self.tick = 5 + self.tick = 4 def use(self) -> None: """ When the bomb is used, throw it and explodes it. """ if self.held: + self.owner = self.held_by super().drop() self.exploding = True @@ -114,12 +118,20 @@ class Bomb(Item): """ if self.exploding: if self.tick > 0: + # The bomb will explode in moves self.tick -= 1 else: + # The bomb is exploding. + # Each entity that is close to the bomb takes damages. + # The player earn XP if the entity was killed. + log_message = _("Bomb is exploding.") for e in m.entities.copy(): - if abs(e.x - self.x) + abs(e.y - self.y) <= 1 and \ + if abs(e.x - self.x) + abs(e.y - self.y) <= 3 and \ isinstance(e, FightingEntity): - e.take_damage(self, self.damage) + log_message += " " + e.take_damage(self, self.damage) + if e.dead: + self.owner.add_xp(randint(3, 7)) + m.logs.add_message(log_message) m.entities.remove(self) def save_state(self) -> dict: From 5b4dc601bc83e44f1ea63e8ff6bd10ba94806394 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 4 Dec 2020 17:15:41 +0100 Subject: [PATCH 153/171] Fix broken tests --- squirrelbattle/tests/entities_test.py | 15 ++++++++++----- squirrelbattle/tests/game_test.py | 8 ++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 371bfc7..efd3397 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -97,12 +97,13 @@ class TestEntities(unittest.TestCase): self.assertFalse(item.held) item.hold(self.player) self.assertTrue(item.held) - item.drop(2, 6) - self.assertEqual(item.y, 2) + item.drop() + self.assertEqual(item.y, 1) self.assertEqual(item.x, 6) # Pick up item - self.player.move_down() + self.player.move_left() + self.player.move_right() self.assertTrue(item.held) self.assertEqual(item.held_by, self.player) self.assertIn(item, self.player.inventory) @@ -125,10 +126,14 @@ class TestEntities(unittest.TestCase): item.act(self.map) self.assertFalse(hedgehog.dead) self.assertFalse(teddy_bear.dead) - item.drop(42, 42) + self.player.move(42, 42) + item.hold(self.player) + item.use() self.assertEqual(item.y, 42) self.assertEqual(item.x, 42) - item.act(self.map) + # Wait for the explosion + for ignored in range(5): + item.act(self.map) self.assertTrue(hedgehog.dead) self.assertTrue(teddy_bear.dead) bomb_state = item.save_state() diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 42886e5..fd3ec3e 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -261,12 +261,8 @@ class TestGame(unittest.TestCase): self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a') # Navigate to "texture pack" - 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.DOWN) - self.game.handle_key_pressed(KeyValues.DOWN) - self.game.handle_key_pressed(KeyValues.DOWN) + for ignored in range(9): + self.game.handle_key_pressed(KeyValues.DOWN) # Change texture pack self.assertEqual(self.game.settings.TEXTURE_PACK, "ascii") From 91a4514649bbf856b8261d453616ead67085ad45 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 4 Dec 2020 17:19:06 +0100 Subject: [PATCH 154/171] 'D' is not an intelligent key to drop an item --- squirrelbattle/game.py | 2 +- squirrelbattle/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index ed576ed..ff5d465 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -129,7 +129,7 @@ class Game: elif key == KeyValues.EQUIP: self.inventory_menu.validate().equip() elif key == KeyValues.DROP: - self.inventory_menu.validate().use() + self.inventory_menu.validate().drop() # Ensure that the cursor has a good position self.inventory_menu.position = min(self.inventory_menu.position, diff --git a/squirrelbattle/settings.py b/squirrelbattle/settings.py index 7460b98..4004645 100644 --- a/squirrelbattle/settings.py +++ b/squirrelbattle/settings.py @@ -30,7 +30,7 @@ class Settings: self.KEY_INVENTORY = ['i', 'Key used to open the inventory'] self.KEY_USE = ['u', 'Key used to use an item in the inventory'] self.KEY_EQUIP = ['e', 'Key used to equip an item in the inventory'] - self.KEY_DROP = ['d', 'Key used to drop an item in the inventory'] + self.KEY_DROP = ['r', 'Key used to drop an item in the inventory'] self.TEXTURE_PACK = ['ascii', 'Texture pack'] self.LOCALE = [locale.getlocale()[0][:2], 'Language'] From 4b9399c687fb95ee7efacbdd9b3b47ca27552beb Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 4 Dec 2020 18:16:46 +0100 Subject: [PATCH 155/171] Test inventory --- squirrelbattle/entities/items.py | 5 ++- squirrelbattle/tests/game_test.py | 74 +++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index c95ab9d..4800a58 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -92,9 +92,9 @@ class Bomb(Item): A bomb item intended to deal damage to enemies at long range """ damage: int = 5 - owner: "Player" - tick: int exploding: bool + owner: Optional["Player"] + tick: int def __init__(self, damage: int = 5, exploding: bool = False, *args, **kwargs): @@ -102,6 +102,7 @@ class Bomb(Item): self.damage = damage self.exploding = exploding self.tick = 4 + self.owner = None def use(self) -> None: """ diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index fd3ec3e..f0120eb 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -7,6 +7,7 @@ import unittest from ..bootstrap import Bootstrap from ..display.display import Display from ..display.display_manager import DisplayManager +from ..entities.items import Bomb from ..entities.player import Player from ..enums import DisplayActions from ..game import Game, KeyValues, GameMode @@ -107,6 +108,18 @@ class TestGame(unittest.TestCase): self.assertEqual(KeyValues.translate_key( self.game.settings.KEY_ENTER, self.game.settings), KeyValues.ENTER) + self.assertEqual(KeyValues.translate_key( + self.game.settings.KEY_INVENTORY, self.game.settings), + KeyValues.INVENTORY) + self.assertEqual(KeyValues.translate_key( + self.game.settings.KEY_USE, self.game.settings), + KeyValues.USE) + self.assertEqual(KeyValues.translate_key( + self.game.settings.KEY_EQUIP, self.game.settings), + KeyValues.EQUIP) + self.assertEqual(KeyValues.translate_key( + self.game.settings.KEY_DROP, self.game.settings), + KeyValues.DROP) self.assertEqual(KeyValues.translate_key(' ', self.game.settings), KeyValues.SPACE) self.assertEqual(KeyValues.translate_key('plop', self.game.settings), @@ -334,3 +347,64 @@ class TestGame(unittest.TestCase): self.game.display_actions(DisplayActions.REFRESH) self.game.handle_key_pressed(None, "random key") self.assertIsNone(self.game.message) + + def test_inventory_menu(self) -> None: + """ + Open the inventory menu and interact with items. + """ + self.game.state = GameMode.PLAY + # Open and close the inventory + self.game.handle_key_pressed(KeyValues.INVENTORY) + self.assertEqual(self.game.state, GameMode.INVENTORY) + self.game.handle_key_pressed(KeyValues.SPACE) + self.assertEqual(self.game.state, GameMode.PLAY) + + # Add five bombs in the inventory + for ignored in range(5): + bomb = Bomb() + bomb.map = self.game.map + bomb.map.add_entity(bomb) + bomb.hold(self.game.player) + + self.game.handle_key_pressed(KeyValues.INVENTORY) + self.assertEqual(self.game.state, GameMode.INVENTORY) + + # Navigate in the menu + self.game.handle_key_pressed(KeyValues.DOWN) + self.game.handle_key_pressed(KeyValues.DOWN) + self.game.handle_key_pressed(KeyValues.DOWN) + self.assertEqual(self.game.inventory_menu.position, 3) + self.game.handle_key_pressed(KeyValues.DOWN) + self.game.handle_key_pressed(KeyValues.DOWN) + self.game.handle_key_pressed(KeyValues.UP) + self.game.handle_key_pressed(KeyValues.DOWN) + self.assertEqual(self.game.inventory_menu.position, 4) + + # Equip key does nothing + self.game.handle_key_pressed(KeyValues.EQUIP) + + # Drop an item + bomb = self.game.player.inventory[-1] + self.assertEqual(self.game.inventory_menu.validate(), bomb) + self.assertTrue(bomb.held) + self.assertEqual(bomb.held_by, self.game.player) + self.game.handle_key_pressed(KeyValues.DROP) + self.assertFalse(bomb.held) + self.assertIsNone(bomb.held_by) + self.assertIsNone(bomb.owner) + self.assertFalse(bomb.exploding) + self.assertEqual(bomb.y, self.game.player.y) + self.assertEqual(bomb.x, self.game.player.x) + + # Use the bomb + bomb = self.game.player.inventory[-1] + self.assertEqual(self.game.inventory_menu.validate(), bomb) + self.assertTrue(bomb.held) + self.assertEqual(bomb.held_by, self.game.player) + self.game.handle_key_pressed(KeyValues.USE) + self.assertFalse(bomb.held) + self.assertIsNone(bomb.held_by) + self.assertEqual(bomb.owner, self.game.player) + self.assertTrue(bomb.exploding) + self.assertEqual(bomb.y, self.game.player.y) + self.assertEqual(bomb.x, self.game.player.x) From 25c42ea9e8be051c7f457fca2279146dd0fd3bda Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 5 Dec 2020 13:07:37 +0100 Subject: [PATCH 156/171] Don't translate in english since it is the main language --- setup.py | 2 +- .../locale/en/LC_MESSAGES/squirrelbattle.po | 217 ------------------ squirrelbattle/translations.py | 3 + 3 files changed, 4 insertions(+), 218 deletions(-) delete mode 100644 squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po diff --git a/setup.py b/setup.py index f051bbb..573eea7 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ with open("README.md", "r") as f: long_description = f.read() # Compile messages -for language in ["de", "en", "fr"]: +for language in ["de", "fr"]: args = ["msgfmt", "--check-format", "-o", f"squirrelbattle/locale/{language}/LC_MESSAGES" "/squirrelbattle.mo", diff --git a/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po deleted file mode 100644 index 2081f69..0000000 --- a/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po +++ /dev/null @@ -1,217 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse -# This file is distributed under the same license as the squirrelbattle package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: squirrelbattle 3.14.1\n" -"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-12-04 15:59+0100\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#: squirrelbattle/display/statsdisplay.py:34 -msgid "Inventory:" -msgstr "" - -#: squirrelbattle/display/statsdisplay.py:39 -msgid "YOU ARE DEAD" -msgstr "" - -#: squirrelbattle/interfaces.py:394 squirrelbattle/interfaces.py:398 -#, python-brace-format -msgid "{name} hits {opponent}." -msgstr "" - -#: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410 -#, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "" - -#: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:14 -#: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287 -#: squirrelbattle/tests/translations_test.py:16 -#: squirrelbattle/tests/game_test.py:290 squirrelbattle/tests/game_test.py:285 -#: squirrelbattle/tests/game_test.py:288 squirrelbattle/tests/game_test.py:291 -msgid "New game" -msgstr "" - -#: squirrelbattle/menus.py:46 squirrelbattle/tests/translations_test.py:15 -#: squirrelbattle/tests/translations_test.py:17 -msgid "Resume" -msgstr "" - -#: squirrelbattle/menus.py:47 squirrelbattle/tests/translations_test.py:17 -#: squirrelbattle/tests/translations_test.py:19 -msgid "Save" -msgstr "" - -#: squirrelbattle/menus.py:48 squirrelbattle/tests/translations_test.py:16 -#: squirrelbattle/tests/translations_test.py:18 -msgid "Load" -msgstr "" - -#: squirrelbattle/menus.py:49 squirrelbattle/tests/translations_test.py:18 -#: squirrelbattle/tests/translations_test.py:20 -msgid "Settings" -msgstr "" - -#: squirrelbattle/menus.py:50 squirrelbattle/tests/translations_test.py:19 -#: squirrelbattle/tests/translations_test.py:21 -msgid "Exit" -msgstr "" - -#: squirrelbattle/menus.py:71 -msgid "Back" -msgstr "" - -#: squirrelbattle/game.py:147 squirrelbattle/game.py:148 -#: squirrelbattle/game.py:160 -msgid "" -"Some keys are missing in your save file.\n" -"Your save seems to be corrupt. It got deleted." -msgstr "" - -#: squirrelbattle/game.py:155 squirrelbattle/game.py:156 -#: squirrelbattle/game.py:168 -msgid "" -"No player was found on this map!\n" -"Maybe you died?" -msgstr "" - -#: squirrelbattle/game.py:175 squirrelbattle/game.py:176 -#: squirrelbattle/game.py:188 -msgid "" -"The JSON file is not correct.\n" -"Your save seems corrupted. It got deleted." -msgstr "" - -#: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:21 -#: squirrelbattle/tests/translations_test.py:25 -#: squirrelbattle/tests/translations_test.py:27 -msgid "Main key to move up" -msgstr "" - -#: squirrelbattle/settings.py:22 squirrelbattle/tests/translations_test.py:23 -#: squirrelbattle/tests/translations_test.py:27 -#: squirrelbattle/tests/translations_test.py:29 -msgid "Secondary key to move up" -msgstr "" - -#: squirrelbattle/settings.py:23 squirrelbattle/tests/translations_test.py:25 -#: squirrelbattle/tests/translations_test.py:29 -#: squirrelbattle/tests/translations_test.py:31 -msgid "Main key to move down" -msgstr "" - -#: squirrelbattle/settings.py:24 squirrelbattle/tests/translations_test.py:27 -#: squirrelbattle/tests/translations_test.py:31 -#: squirrelbattle/tests/translations_test.py:33 -msgid "Secondary key to move down" -msgstr "" - -#: squirrelbattle/settings.py:25 squirrelbattle/tests/translations_test.py:29 -#: squirrelbattle/tests/translations_test.py:33 -#: squirrelbattle/tests/translations_test.py:35 -msgid "Main key to move left" -msgstr "" - -#: squirrelbattle/settings.py:26 squirrelbattle/tests/translations_test.py:31 -#: squirrelbattle/tests/translations_test.py:35 -#: squirrelbattle/tests/translations_test.py:37 -msgid "Secondary key to move left" -msgstr "" - -#: squirrelbattle/settings.py:27 squirrelbattle/tests/translations_test.py:33 -#: squirrelbattle/tests/translations_test.py:37 -#: squirrelbattle/tests/translations_test.py:39 -msgid "Main key to move right" -msgstr "" - -#: squirrelbattle/settings.py:29 squirrelbattle/tests/translations_test.py:35 -#: squirrelbattle/tests/translations_test.py:39 -#: squirrelbattle/tests/translations_test.py:41 -msgid "Secondary key to move right" -msgstr "" - -#: squirrelbattle/settings.py:30 squirrelbattle/tests/translations_test.py:37 -#: squirrelbattle/tests/translations_test.py:41 -#: squirrelbattle/tests/translations_test.py:43 -msgid "Key to validate a menu" -msgstr "" - -#: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:39 -#: squirrelbattle/tests/translations_test.py:43 -#: squirrelbattle/tests/translations_test.py:45 -#: squirrelbattle/tests/translations_test.py:47 -msgid "Texture pack" -msgstr "" - -#: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40 -#: squirrelbattle/tests/translations_test.py:44 -#: squirrelbattle/tests/translations_test.py:46 -#: squirrelbattle/tests/translations_test.py:48 -msgid "Language" -msgstr "" - -#: squirrelbattle/interfaces.py:407 squirrelbattle/interfaces.py:412 -#, python-brace-format -msgid "{name} dies." -msgstr "" - -#: squirrelbattle/tests/translations_test.py:47 -#: squirrelbattle/tests/translations_test.py:49 -#: squirrelbattle/tests/translations_test.py:51 -msgid "player" -msgstr "" - -#: squirrelbattle/tests/translations_test.py:49 -#: squirrelbattle/tests/translations_test.py:51 -#: squirrelbattle/tests/translations_test.py:53 -msgid "tiger" -msgstr "" - -#: squirrelbattle/tests/translations_test.py:50 -#: squirrelbattle/tests/translations_test.py:52 -#: squirrelbattle/tests/translations_test.py:54 -msgid "hedgehog" -msgstr "" - -#: squirrelbattle/tests/translations_test.py:51 -#: squirrelbattle/tests/translations_test.py:53 -#: squirrelbattle/tests/translations_test.py:55 -msgid "rabbit" -msgstr "" - -#: squirrelbattle/tests/translations_test.py:52 -#: squirrelbattle/tests/translations_test.py:54 -#: squirrelbattle/tests/translations_test.py:56 -msgid "teddy bear" -msgstr "" - -#: squirrelbattle/tests/translations_test.py:54 -#: squirrelbattle/tests/translations_test.py:56 -#: squirrelbattle/tests/translations_test.py:58 -msgid "bomb" -msgstr "" - -#: squirrelbattle/tests/translations_test.py:55 -#: squirrelbattle/tests/translations_test.py:57 -#: squirrelbattle/tests/translations_test.py:59 -msgid "heart" -msgstr "" - -#: squirrelbattle/tests/translations_test.py:45 -msgid "Key used to open the inventory" -msgstr "" - -#: squirrelbattle/display/menudisplay.py:107 -msgid "== INVENTORY ==" -msgstr "" diff --git a/squirrelbattle/translations.py b/squirrelbattle/translations.py index f532bb0..5ce8986 100644 --- a/squirrelbattle/translations.py +++ b/squirrelbattle/translations.py @@ -53,6 +53,9 @@ class Translator: Analyse all strings in the project and extract them. """ for language in cls.SUPPORTED_LOCALES: + if language == "en": + # Don't translate the main language + continue file_name = Path(__file__).parent / "locale" / language \ / "LC_MESSAGES" / "squirrelbattle.po" args = ["find", "squirrelbattle", "-iname", "*.py"] From 013e81b3c0cce15f00b9ae305641e4acbe7efcee Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 5 Dec 2020 13:13:04 +0100 Subject: [PATCH 157/171] Better sort in translation files, remove unwanted headers --- squirrelbattle/translations.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/squirrelbattle/translations.py b/squirrelbattle/translations.py index 5ce8986..d5ac3e2 100644 --- a/squirrelbattle/translations.py +++ b/squirrelbattle/translations.py @@ -3,6 +3,7 @@ import gettext as gt import os +import re import subprocess from pathlib import Path from typing import Any, List @@ -68,9 +69,14 @@ class Translator: "--copyright-holder=ÿnérant, eichhornchen, " "nicomarg, charlse", "--msgid-bugs-address=squirrel-battle@crans.org", + "--sort-by-file", "-o", file_name] if file_name.is_file(): args.append("--join-existing") + with open(file_name, "r") as f: + content = f.read() + with open(file_name, "w") as f: + f.write(re.sub("#:.*\n", "", content)) print(f"Make {language} messages...") subprocess.Popen(args, stdin=find.stdout).wait() From bc40f8d0e667a49301c80bba458bc4550d62868f Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 5 Dec 2020 13:19:04 +0100 Subject: [PATCH 158/171] Some translations were missing in the settings menu --- .../locale/de/LC_MESSAGES/squirrelbattle.po | 151 +++++++++------- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 170 ++++++++++-------- squirrelbattle/tests/translations_test.py | 6 + squirrelbattle/translations.py | 2 + 4 files changed, 187 insertions(+), 142 deletions(-) diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 01cb4aa..0d20e90 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-12-04 15:59+0100\n" +"POT-Creation-Date: 2020-12-05 13:11+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,9 +17,71 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +#: squirrelbattle/display/menudisplay.py:105 +msgid "== INVENTORY ==" +msgstr "== BESTAND ==" + +#: squirrelbattle/display/statsdisplay.py:34 +msgid "Inventory:" +msgstr "Bestand:" + +#: squirrelbattle/display/statsdisplay.py:39 +msgid "YOU ARE DEAD" +msgstr "SIE WURDEN GESTORBEN" + +#. The bomb is exploding. +#. Each entity that is close to the bomb takes damages. +#. The player earn XP if the entity was killed. +#: squirrelbattle/entities/items.py:129 +msgid "Bomb is exploding." +msgstr "" + +#: squirrelbattle/game.py:177 +msgid "" +"Some keys are missing in your save file.\n" +"Your save seems to be corrupt. It got deleted." +msgstr "" +"In Ihrer Speicherdatei fehlen einige Schlüssel.\n" +"Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht." + +#: squirrelbattle/game.py:185 +msgid "" +"No player was found on this map!\n" +"Maybe you died?" +msgstr "" +"Auf dieser Karte wurde kein Spieler gefunden!\n" +"Vielleicht sind Sie gestorben?" + +#: squirrelbattle/game.py:205 +msgid "" +"The JSON file is not correct.\n" +"Your save seems corrupted. It got deleted." +msgstr "" +"Die JSON-Datei ist nicht korrekt.\n" +"Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." + +#: squirrelbattle/interfaces.py:398 +#, python-brace-format +msgid "{name} hits {opponent}." +msgstr "{name} schlägt {opponent}." + +#: squirrelbattle/interfaces.py:410 +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} nimmt {amount} Schadenspunkte." + +#: squirrelbattle/interfaces.py:412 +#, python-brace-format +msgid "{name} dies." +msgstr "{name} stirbt." + +#: squirrelbattle/menus.py:72 +msgid "Back" +msgstr "Zurück" + +#: squirrelbattle/tests/game_test.py:294 squirrelbattle/tests/game_test.py:297 +#: squirrelbattle/tests/game_test.py:300 #: squirrelbattle/tests/translations_test.py:16 -#: squirrelbattle/tests/game_test.py:290 squirrelbattle/tests/game_test.py:285 -#: squirrelbattle/tests/game_test.py:288 squirrelbattle/tests/game_test.py:291 msgid "New game" msgstr "Neu Spiel" @@ -84,92 +146,49 @@ msgid "Key used to open the inventory" msgstr "Bestandtaste" #: squirrelbattle/tests/translations_test.py:47 +msgid "Key used to use an item in the inventory" +msgstr "Taste um eines Objekts im Bestand zu verwenden" + +#: squirrelbattle/tests/translations_test.py:49 +msgid "Key used to equip an item in the inventory" +msgstr "Taste um eines Objekts im Bestand auszurüsten" + +#: squirrelbattle/tests/translations_test.py:51 +msgid "Key used to drop an item in the inventory" +msgstr "Taste um eines Objekts im Bestand zu werfen" + +#: squirrelbattle/tests/translations_test.py:53 msgid "Texture pack" msgstr "Textur-Packung" -#: squirrelbattle/tests/translations_test.py:48 +#: squirrelbattle/tests/translations_test.py:54 msgid "Language" msgstr "Sprache" -#: squirrelbattle/tests/translations_test.py:51 +#: squirrelbattle/tests/translations_test.py:57 msgid "player" msgstr "Spieler" -#: squirrelbattle/tests/translations_test.py:53 +#: squirrelbattle/tests/translations_test.py:59 msgid "tiger" msgstr "Tiger" -#: squirrelbattle/tests/translations_test.py:54 +#: squirrelbattle/tests/translations_test.py:60 msgid "hedgehog" msgstr "Igel" -#: squirrelbattle/tests/translations_test.py:55 +#: squirrelbattle/tests/translations_test.py:61 msgid "rabbit" msgstr "Kanninchen" -#: squirrelbattle/tests/translations_test.py:56 +#: squirrelbattle/tests/translations_test.py:62 msgid "teddy bear" msgstr "Teddybär" -#: squirrelbattle/tests/translations_test.py:58 +#: squirrelbattle/tests/translations_test.py:64 msgid "bomb" msgstr "Bombe" -#: squirrelbattle/tests/translations_test.py:59 +#: squirrelbattle/tests/translations_test.py:65 msgid "heart" msgstr "Herz" - -#: squirrelbattle/display/statsdisplay.py:34 -msgid "Inventory:" -msgstr "Bestand:" - -#: squirrelbattle/display/statsdisplay.py:39 -msgid "YOU ARE DEAD" -msgstr "SIE WURDEN GESTORBEN" - -#: squirrelbattle/interfaces.py:398 -#, python-brace-format -msgid "{name} hits {opponent}." -msgstr "{name} schlägt {opponent}." - -#: squirrelbattle/interfaces.py:410 -#, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} nimmt {amount} Schadenspunkte." - -#: squirrelbattle/interfaces.py:412 -#, python-brace-format -msgid "{name} dies." -msgstr "{name} stirbt." - -#: squirrelbattle/menus.py:71 -msgid "Back" -msgstr "Zurück" - -#: squirrelbattle/game.py:148 squirrelbattle/game.py:160 -msgid "" -"Some keys are missing in your save file.\n" -"Your save seems to be corrupt. It got deleted." -msgstr "" -"In Ihrer Speicherdatei fehlen einige Schlüssel.\n" -"Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht." - -#: squirrelbattle/game.py:156 squirrelbattle/game.py:168 -msgid "" -"No player was found on this map!\n" -"Maybe you died?" -msgstr "" -"Auf dieser Karte wurde kein Spieler gefunden!\n" -"Vielleicht sind Sie gestorben?" - -#: squirrelbattle/game.py:176 squirrelbattle/game.py:188 -msgid "" -"The JSON file is not correct.\n" -"Your save seems corrupted. It got deleted." -msgstr "" -"Die JSON-Datei ist nicht korrekt.\n" -"Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." - -#: squirrelbattle/display/menudisplay.py:107 -msgid "== INVENTORY ==" -msgstr "== BESTAND ==" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 99abe43..33ab440 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-12-04 15:59+0100\n" +"POT-Creation-Date: 2020-12-05 13:11+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,10 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +#: squirrelbattle/display/menudisplay.py:105 +msgid "== INVENTORY ==" +msgstr "== INVENTAIRE ==" + #: squirrelbattle/display/statsdisplay.py:34 msgid "Inventory:" msgstr "Inventaire :" @@ -25,6 +29,37 @@ msgstr "Inventaire :" msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" +#. The bomb is exploding. +#. Each entity that is close to the bomb takes damages. +#. The player earn XP if the entity was killed. +#: squirrelbattle/entities/items.py:129 +msgid "Bomb is exploding." +msgstr "La bombe explose." + +#: squirrelbattle/game.py:177 +msgid "" +"Some keys are missing in your save file.\n" +"Your save seems to be corrupt. It got deleted." +msgstr "" +"Certaines clés de votre ficher de sauvegarde sont manquantes.\n" +"Votre sauvegarde semble corrompue. Elle a été supprimée." + +#: squirrelbattle/game.py:185 +msgid "" +"No player was found on this map!\n" +"Maybe you died?" +msgstr "" +"Aucun joueur n'a été trouvé sur la carte !\n" +"Peut-être êtes-vous mort ?" + +#: squirrelbattle/game.py:205 +msgid "" +"The JSON file is not correct.\n" +"Your save seems corrupted. It got deleted." +msgstr "" +"Le fichier JSON de sauvegarde est incorrect.\n" +"Votre sauvegarde semble corrompue. Elle a été supprimée." + #: squirrelbattle/interfaces.py:398 #, python-brace-format msgid "{name} hits {opponent}." @@ -35,94 +70,74 @@ msgstr "{name} frappe {opponent}." msgid "{name} takes {amount} damage." msgstr "{name} prend {amount} points de dégât." -#: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:16 -#: squirrelbattle/tests/game_test.py:290 squirrelbattle/tests/game_test.py:285 -#: squirrelbattle/tests/game_test.py:288 squirrelbattle/tests/game_test.py:291 -msgid "New game" -msgstr "Nouvelle partie" +#: squirrelbattle/interfaces.py:412 +#, python-brace-format +msgid "{name} dies." +msgstr "{name} meurt." -#: squirrelbattle/menus.py:46 squirrelbattle/tests/translations_test.py:17 -msgid "Resume" -msgstr "Continuer" - -#: squirrelbattle/menus.py:47 squirrelbattle/tests/translations_test.py:19 -msgid "Save" -msgstr "Sauvegarder" - -#: squirrelbattle/menus.py:48 squirrelbattle/tests/translations_test.py:18 -msgid "Load" -msgstr "Charger" - -#: squirrelbattle/menus.py:49 squirrelbattle/tests/translations_test.py:20 -msgid "Settings" -msgstr "Paramètres" - -#: squirrelbattle/menus.py:50 squirrelbattle/tests/translations_test.py:21 -msgid "Exit" -msgstr "Quitter" - -#: squirrelbattle/menus.py:71 +#: squirrelbattle/menus.py:72 msgid "Back" msgstr "Retour" -#: squirrelbattle/game.py:147 squirrelbattle/game.py:148 -#: squirrelbattle/game.py:160 -msgid "" -"Some keys are missing in your save file.\n" -"Your save seems to be corrupt. It got deleted." -msgstr "" -"Certaines clés de votre ficher de sauvegarde sont manquantes.\n" -"Votre sauvegarde semble corrompue. Elle a été supprimée." +#: squirrelbattle/tests/game_test.py:294 squirrelbattle/tests/game_test.py:297 +#: squirrelbattle/tests/game_test.py:300 +#: squirrelbattle/tests/translations_test.py:16 +msgid "New game" +msgstr "Nouvelle partie" -#: squirrelbattle/game.py:156 squirrelbattle/game.py:168 -msgid "" -"No player was found on this map!\n" -"Maybe you died?" -msgstr "" -"Aucun joueur n'a été trouvé sur la carte !\n" -"Peut-être êtes-vous mort ?" +#: squirrelbattle/tests/translations_test.py:17 +msgid "Resume" +msgstr "Continuer" -#: squirrelbattle/game.py:176 squirrelbattle/game.py:188 -msgid "" -"The JSON file is not correct.\n" -"Your save seems corrupted. It got deleted." -msgstr "" -"Le fichier JSON de sauvegarde est incorrect.\n" -"Votre sauvegarde semble corrompue. Elle a été supprimée." +#: squirrelbattle/tests/translations_test.py:18 +msgid "Load" +msgstr "Charger" -#: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:27 +#: squirrelbattle/tests/translations_test.py:19 +msgid "Save" +msgstr "Sauvegarder" + +#: squirrelbattle/tests/translations_test.py:20 +msgid "Settings" +msgstr "Paramètres" + +#: squirrelbattle/tests/translations_test.py:21 +msgid "Exit" +msgstr "Quitter" + +#: squirrelbattle/tests/translations_test.py:27 msgid "Main key to move up" msgstr "Touche principale pour aller vers le haut" -#: squirrelbattle/settings.py:22 squirrelbattle/tests/translations_test.py:29 +#: squirrelbattle/tests/translations_test.py:29 msgid "Secondary key to move up" msgstr "Touche secondaire pour aller vers le haut" -#: squirrelbattle/settings.py:23 squirrelbattle/tests/translations_test.py:31 +#: squirrelbattle/tests/translations_test.py:31 msgid "Main key to move down" msgstr "Touche principale pour aller vers le bas" -#: squirrelbattle/settings.py:24 squirrelbattle/tests/translations_test.py:33 +#: squirrelbattle/tests/translations_test.py:33 msgid "Secondary key to move down" msgstr "Touche secondaire pour aller vers le bas" -#: squirrelbattle/settings.py:25 squirrelbattle/tests/translations_test.py:35 +#: squirrelbattle/tests/translations_test.py:35 msgid "Main key to move left" msgstr "Touche principale pour aller vers la gauche" -#: squirrelbattle/settings.py:26 squirrelbattle/tests/translations_test.py:37 +#: squirrelbattle/tests/translations_test.py:37 msgid "Secondary key to move left" msgstr "Touche secondaire pour aller vers la gauche" -#: squirrelbattle/settings.py:27 squirrelbattle/tests/translations_test.py:39 +#: squirrelbattle/tests/translations_test.py:39 msgid "Main key to move right" msgstr "Touche principale pour aller vers la droite" -#: squirrelbattle/settings.py:29 squirrelbattle/tests/translations_test.py:41 +#: squirrelbattle/tests/translations_test.py:41 msgid "Secondary key to move right" msgstr "Touche secondaire pour aller vers la droite" -#: squirrelbattle/settings.py:30 squirrelbattle/tests/translations_test.py:43 +#: squirrelbattle/tests/translations_test.py:43 msgid "Key to validate a menu" msgstr "Touche pour valider un menu" @@ -130,47 +145,50 @@ msgstr "Touche pour valider un menu" msgid "Key used to open the inventory" msgstr "Touche utilisée pour ouvrir l'inventaire" -#: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:47 +#: squirrelbattle/tests/translations_test.py:47 +msgid "Key used to use an item in the inventory" +msgstr "Touche pour utiliser un objet de l'inventaire" + +#: squirrelbattle/tests/translations_test.py:49 +msgid "Key used to equip an item in the inventory" +msgstr "Touche pour équiper un objet de l'inventaire" + +#: squirrelbattle/tests/translations_test.py:51 +msgid "Key used to drop an item in the inventory" +msgstr "Touche pour jeter un objet de l'inventaire" + +#: squirrelbattle/tests/translations_test.py:53 msgid "Texture pack" msgstr "Pack de textures" -#: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:48 +#: squirrelbattle/tests/translations_test.py:54 msgid "Language" msgstr "Langue" -#: squirrelbattle/interfaces.py:412 -#, python-brace-format -msgid "{name} dies." -msgstr "{name} meurt." - -#: squirrelbattle/tests/translations_test.py:51 +#: squirrelbattle/tests/translations_test.py:57 msgid "player" msgstr "joueur" -#: squirrelbattle/tests/translations_test.py:53 +#: squirrelbattle/tests/translations_test.py:59 msgid "tiger" msgstr "tigre" -#: squirrelbattle/tests/translations_test.py:54 +#: squirrelbattle/tests/translations_test.py:60 msgid "hedgehog" msgstr "hérisson" -#: squirrelbattle/tests/translations_test.py:55 +#: squirrelbattle/tests/translations_test.py:61 msgid "rabbit" msgstr "lapin" -#: squirrelbattle/tests/translations_test.py:56 +#: squirrelbattle/tests/translations_test.py:62 msgid "teddy bear" msgstr "nounours" -#: squirrelbattle/tests/translations_test.py:58 +#: squirrelbattle/tests/translations_test.py:64 msgid "bomb" msgstr "bombe" -#: squirrelbattle/tests/translations_test.py:59 +#: squirrelbattle/tests/translations_test.py:65 msgid "heart" msgstr "cœur" - -#: squirrelbattle/display/menudisplay.py:107 -msgid "== INVENTORY ==" -msgstr "== INVENTAIRE ==" diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index 6d3a0e4..ef7369f 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -44,6 +44,12 @@ class TestTranslations(unittest.TestCase): "Touche pour valider un menu") self.assertEqual(_("Key used to open the inventory"), "Touche utilisée pour ouvrir l'inventaire") + self.assertEqual(_("Key used to use an item in the inventory"), + "Touche pour utiliser un objet de l'inventaire") + self.assertEqual(_("Key used to equip an item in the inventory"), + "Touche pour équiper un objet de l'inventaire") + self.assertEqual(_("Key used to drop an item in the inventory"), + "Touche pour jeter un objet de l'inventaire") self.assertEqual(_("Texture pack"), "Pack de textures") self.assertEqual(_("Language"), "Langue") diff --git a/squirrelbattle/translations.py b/squirrelbattle/translations.py index d5ac3e2..1e97df6 100644 --- a/squirrelbattle/translations.py +++ b/squirrelbattle/translations.py @@ -86,6 +86,8 @@ class Translator: Compile translation messages from source files. """ for language in cls.SUPPORTED_LOCALES: + if language == "en": + continue args = ["msgfmt", "--check-format", "-o", Path(__file__).parent / "locale" / language / "LC_MESSAGES" / "squirrelbattle.mo", From fb8b2aff0142298fd9476a7aef3999ccc60d041f Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 5 Dec 2020 13:20:52 +0100 Subject: [PATCH 159/171] The player must be alive to interact with the inventory --- squirrelbattle/game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index ff5d465..b9439b5 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -123,7 +123,7 @@ class Game: self.inventory_menu.go_up() elif key == KeyValues.DOWN: self.inventory_menu.go_down() - if self.inventory_menu.values: + if self.inventory_menu.values and not self.player.dead: if key == KeyValues.USE: self.inventory_menu.validate().use() elif key == KeyValues.EQUIP: From ca2ae15117ef3db12d78bc2e1fad25660fb11de0 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 5 Dec 2020 13:42:33 +0100 Subject: [PATCH 160/171] Stack items in the inventory, closes #29 --- squirrelbattle/display/statsdisplay.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index da9213f..ac1a89c 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -31,8 +31,19 @@ class StatsDisplay(Display): self.player.dexterity, self.player.constitution) self.addstr(self.pad, 3, 0, string3) - inventory_str = _("Inventory:") + " " + "".join( - self.pack[item.name.upper()] for item in self.player.inventory) + inventory_str = _("Inventory:") + " " + # Stack items by type instead of displaying each item + item_types = [item.name for item in self.player.inventory] + item_types.sort(key=item_types.count, reverse=True) + printed_items = [] + for item in item_types: + if item in printed_items: + continue + count = item_types.count(item) + inventory_str += self.pack[item.upper()] + if count > 1: + inventory_str += f"x{count} " + printed_items.append(item) self.addstr(self.pad, 8, 0, inventory_str) if self.player.dead: From ea672272f57cbd29b516ef41a6d7084bc888ccec Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 5 Dec 2020 14:20:58 +0100 Subject: [PATCH 161/171] Add body snatch potion --- squirrelbattle/display/menudisplay.py | 2 +- squirrelbattle/display/texturepack.py | 2 ++ squirrelbattle/entities/items.py | 28 ++++++++++++++++++- squirrelbattle/game.py | 2 +- squirrelbattle/interfaces.py | 10 ++++--- .../locale/de/LC_MESSAGES/squirrelbattle.po | 18 +++++++----- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 18 +++++++----- squirrelbattle/tests/translations_test.py | 1 + 8 files changed, 60 insertions(+), 21 deletions(-) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 285b6db..d040d81 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -109,7 +109,7 @@ class InventoryDisplay(MenuDisplay): rep = self.pack[item.name.upper()] selection = f"[{rep}]" if i == self.menu.position else f" {rep} " self.addstr(self.pad, 2 + i, 0, selection - + " " + _(item.name).capitalize()) + + " " + item.translated_name.capitalize()) @property def truewidth(self) -> int: diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index dfee866..7fc4a9a 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -56,6 +56,7 @@ TexturePack.ASCII_PACK = TexturePack( RABBIT='Y', TIGER='n', TEDDY_BEAR='8', + BODY_SNATCH_POTION='S', ) TexturePack.SQUIRREL_PACK = TexturePack( @@ -75,4 +76,5 @@ TexturePack.SQUIRREL_PACK = TexturePack( RABBIT='🐇', TIGER='🐅', TEDDY_BEAR='🧸', + BODY_SNATCH_POTION='🔀', ) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 4800a58..8953b39 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -1,7 +1,7 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later -from random import randint +from random import choice, randint from typing import Optional from .player import Player @@ -143,3 +143,29 @@ class Bomb(Item): d["exploding"] = self.exploding d["damage"] = self.damage return d + + +class BodySnatchPotion(Item): + """ + The body-snatch potion allows to exchange all characteristics with a random + other entity. + """ + + def __init__(self, *args, **kwargs): + super().__init__(name="body_snatch_potion", *args, **kwargs) + + def use(self) -> None: + """ + Find a valid random entity, then exchange characteristics. + """ + valid_entities = self.held_by.map.find_entities(FightingEntity) + valid_entities.remove(self.held_by) + entity = choice(valid_entities) + entity_state = entity.save_state() + player_state = self.held_by.save_state() + self.held_by.__dict__.update(entity_state) + entity.__dict__.update(player_state) + self.held_by.map.currenty, self.held_by.map.currentx = self.held_by.y,\ + self.held_by.x + + self.held_by.inventory.remove(self) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index b9439b5..6d9e9e7 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -48,7 +48,7 @@ class Game: Create a new game on the screen. """ # TODO generate a new map procedurally - self.map = Map.load(ResourceManager.get_asset_path("example_map_2.txt")) + self.map = Map.load(ResourceManager.get_asset_path("example_map.txt")) self.map.logs = self.logs self.logs.clear() self.player = Player() diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 90e5d69..3567ea0 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -324,10 +324,11 @@ class Entity: """ Returns all entities subclasses """ - from squirrelbattle.entities.items import Heart, Bomb + from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart from squirrelbattle.entities.monsters import Tiger, Hedgehog, \ Rabbit, TeddyBear - return [Tiger, Bomb, Heart, Hedgehog, Rabbit, TeddyBear] + return [BodySnatchPotion, Bomb, Heart, Hedgehog, + Rabbit, TeddyBear, Tiger] @staticmethod def get_all_entity_classes_in_a_dict() -> dict: @@ -337,11 +338,12 @@ class Entity: from squirrelbattle.entities.player import Player from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \ TeddyBear - from squirrelbattle.entities.items import Bomb, Heart + from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart return { "Tiger": Tiger, "Bomb": Bomb, "Heart": Heart, + "BodySnatchPotion": BodySnatchPotion, "Hedgehog": Hedgehog, "Rabbit": Rabbit, "TeddyBear": TeddyBear, @@ -423,7 +425,7 @@ class FightingEntity(Entity): """ Returns a fighting entities specific attributes """ - return ["maxhealth", "health", "level", "strength", + return ["name", "maxhealth", "health", "level", "strength", "intelligence", "charisma", "dexterity", "constitution"] def save_state(self) -> dict: diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 0d20e90..505d2d8 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-12-05 13:11+0100\n" +"POT-Creation-Date: 2020-12-05 14:13+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -25,14 +25,14 @@ msgstr "== BESTAND ==" msgid "Inventory:" msgstr "Bestand:" -#: squirrelbattle/display/statsdisplay.py:39 +#: squirrelbattle/display/statsdisplay.py:50 msgid "YOU ARE DEAD" msgstr "SIE WURDEN GESTORBEN" #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. -#: squirrelbattle/entities/items.py:129 +#: squirrelbattle/entities/items.py:128 msgid "Bomb is exploding." msgstr "" @@ -60,17 +60,17 @@ msgstr "" "Die JSON-Datei ist nicht korrekt.\n" "Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." -#: squirrelbattle/interfaces.py:398 +#: squirrelbattle/interfaces.py:400 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} schlägt {opponent}." -#: squirrelbattle/interfaces.py:410 +#: squirrelbattle/interfaces.py:412 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} nimmt {amount} Schadenspunkte." -#: squirrelbattle/interfaces.py:412 +#: squirrelbattle/interfaces.py:414 #, python-brace-format msgid "{name} dies." msgstr "{name} stirbt." @@ -186,9 +186,13 @@ msgid "teddy bear" msgstr "Teddybär" #: squirrelbattle/tests/translations_test.py:64 +msgid "body snatch potion" +msgstr "Leichenfleddererzaubertrank" + +#: squirrelbattle/tests/translations_test.py:65 msgid "bomb" msgstr "Bombe" -#: squirrelbattle/tests/translations_test.py:65 +#: squirrelbattle/tests/translations_test.py:66 msgid "heart" msgstr "Herz" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 33ab440..1c17ccf 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-12-05 13:11+0100\n" +"POT-Creation-Date: 2020-12-05 14:13+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -25,14 +25,14 @@ msgstr "== INVENTAIRE ==" msgid "Inventory:" msgstr "Inventaire :" -#: squirrelbattle/display/statsdisplay.py:39 +#: squirrelbattle/display/statsdisplay.py:50 msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. -#: squirrelbattle/entities/items.py:129 +#: squirrelbattle/entities/items.py:128 msgid "Bomb is exploding." msgstr "La bombe explose." @@ -60,17 +60,17 @@ msgstr "" "Le fichier JSON de sauvegarde est incorrect.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/interfaces.py:398 +#: squirrelbattle/interfaces.py:400 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} frappe {opponent}." -#: squirrelbattle/interfaces.py:410 +#: squirrelbattle/interfaces.py:412 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} prend {amount} points de dégât." -#: squirrelbattle/interfaces.py:412 +#: squirrelbattle/interfaces.py:414 #, python-brace-format msgid "{name} dies." msgstr "{name} meurt." @@ -186,9 +186,13 @@ msgid "teddy bear" msgstr "nounours" #: squirrelbattle/tests/translations_test.py:64 +msgid "body snatch potion" +msgstr "potion d'arrachage de corps" + +#: squirrelbattle/tests/translations_test.py:65 msgid "bomb" msgstr "bombe" -#: squirrelbattle/tests/translations_test.py:65 +#: squirrelbattle/tests/translations_test.py:66 msgid "heart" msgstr "cœur" diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index ef7369f..0cb39c5 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -61,5 +61,6 @@ class TestTranslations(unittest.TestCase): self.assertEqual(_("rabbit"), "lapin") self.assertEqual(_("teddy bear"), "nounours") + self.assertEqual(_("body snatch potion"), "potion d'arrachage de corps") self.assertEqual(_("bomb"), "bombe") self.assertEqual(_("heart"), "cœur") From f887a1f0aacbf5410073007d79111c92cdd5289e Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 5 Dec 2020 14:25:22 +0100 Subject: [PATCH 162/171] Entity name is a parameter since it can be changed through body snatch potion --- squirrelbattle/entities/items.py | 14 +++++++------- squirrelbattle/entities/monsters.py | 24 ++++++++++++------------ squirrelbattle/entities/player.py | 11 ++++++----- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 8953b39..943b0c8 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -67,8 +67,8 @@ class Heart(Item): """ healing: int - def __init__(self, healing: int = 5, *args, **kwargs): - super().__init__(name="heart", *args, **kwargs) + def __init__(self, name: str = "heart", healing: int = 5, *args, **kwargs): + super().__init__(name=name, *args, **kwargs) self.healing = healing def hold(self, player: "Player") -> None: @@ -96,9 +96,9 @@ class Bomb(Item): owner: Optional["Player"] tick: int - def __init__(self, damage: int = 5, exploding: bool = False, - *args, **kwargs): - super().__init__(name="bomb", *args, **kwargs) + def __init__(self, name: str = "bomb", damage: int = 5, + exploding: bool = False, *args, **kwargs): + super().__init__(name=name, *args, **kwargs) self.damage = damage self.exploding = exploding self.tick = 4 @@ -151,8 +151,8 @@ class BodySnatchPotion(Item): other entity. """ - def __init__(self, *args, **kwargs): - super().__init__(name="body_snatch_potion", *args, **kwargs) + def __init__(self, name: str = "body_snatch_potion", *args, **kwargs): + super().__init__(name=name, *args, **kwargs) def use(self) -> None: """ diff --git a/squirrelbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py index 9c151ad..feff81a 100644 --- a/squirrelbattle/entities/monsters.py +++ b/squirrelbattle/entities/monsters.py @@ -63,9 +63,9 @@ class Tiger(Monster): """ A tiger monster """ - def __init__(self, strength: int = 2, maxhealth: int = 20, - *args, **kwargs) -> None: - super().__init__(name="tiger", strength=strength, + def __init__(self, name: str = "tiger", strength: int = 2, + maxhealth: int = 20, *args, **kwargs) -> None: + super().__init__(name=name, strength=strength, maxhealth=maxhealth, *args, **kwargs) @@ -73,9 +73,9 @@ class Hedgehog(Monster): """ A really mean hedgehog monster """ - def __init__(self, strength: int = 3, maxhealth: int = 10, - *args, **kwargs) -> None: - super().__init__(name="hedgehog", strength=strength, + def __init__(self, name: str = "hedgehog", strength: int = 3, + maxhealth: int = 10, *args, **kwargs) -> None: + super().__init__(name=name, strength=strength, maxhealth=maxhealth, *args, **kwargs) @@ -83,9 +83,9 @@ class Rabbit(Monster): """ A rabbit monster """ - def __init__(self, strength: int = 1, maxhealth: int = 15, - *args, **kwargs) -> None: - super().__init__(name="rabbit", strength=strength, + def __init__(self, name: str = "rabbit", strength: int = 1, + maxhealth: int = 15, *args, **kwargs) -> None: + super().__init__(name=name, strength=strength, maxhealth=maxhealth, *args, **kwargs) @@ -93,7 +93,7 @@ class TeddyBear(Monster): """ A cute teddybear monster """ - def __init__(self, strength: int = 0, maxhealth: int = 50, - *args, **kwargs) -> None: - super().__init__(name="teddy_bear", strength=strength, + def __init__(self, name: str = "teddy_bear", strength: int = 0, + maxhealth: int = 50, *args, **kwargs) -> None: + super().__init__(name=name, strength=strength, maxhealth=maxhealth, *args, **kwargs) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index e452c8d..02a6d8c 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -16,11 +16,12 @@ 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, + def __init__(self, name: str = "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, *args, **kwargs) \ + -> None: + super().__init__(name=name, maxhealth=maxhealth, strength=strength, intelligence=intelligence, charisma=charisma, dexterity=dexterity, constitution=constitution, level=level, *args, **kwargs) From c38f8cdc53d663003d171a40100e1b8cc39d7638 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 5 Dec 2020 14:35:59 +0100 Subject: [PATCH 163/171] Save the inventory of the player when saving the game, fixes #33 --- squirrelbattle/entities/player.py | 11 +++++++++-- squirrelbattle/tests/game_test.py | 6 ++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 02a6d8c..3f1d941 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -19,7 +19,8 @@ class Player(FightingEntity): def __init__(self, name: str = "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, *args, **kwargs) \ + current_xp: int = 0, max_xp: int = 10, inventory: list = None, + *args, **kwargs) \ -> None: super().__init__(name=name, maxhealth=maxhealth, strength=strength, intelligence=intelligence, charisma=charisma, @@ -27,7 +28,12 @@ class Player(FightingEntity): level=level, *args, **kwargs) self.current_xp = current_xp self.max_xp = max_xp - self.inventory = list() + self.inventory = inventory if inventory else list() + for i in range(len(self.inventory)): + if isinstance(self.inventory[i], dict): + entity_classes = self.get_all_entity_classes_in_a_dict() + item_class = entity_classes[self.inventory[i]["type"]] + self.inventory[i] = item_class(**self.inventory[i]) self.paths = dict() def move(self, y: int, x: int) -> None: @@ -118,4 +124,5 @@ class Player(FightingEntity): d = super().save_state() d["current_xp"] = self.current_xp d["max_xp"] = self.max_xp + d["inventory"] = [item.save_state() for item in self.inventory] return d diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index f0120eb..887835b 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -32,6 +32,9 @@ class TestGame(unittest.TestCase): """ Save a game and reload it. """ + bomb = Bomb() + self.game.map.add_entity(bomb) + bomb.hold(self.game.player) old_state = self.game.save_state() self.game.handle_key_pressed(KeyValues.DOWN) @@ -45,6 +48,9 @@ class TestGame(unittest.TestCase): new_state = self.game.save_state() self.assertEqual(old_state, new_state) + # Ensure that the bomb is loaded + self.assertTrue(self.game.player.inventory) + # Error on loading save with open(ResourceManager.get_config_path("save.json"), "w") as f: f.write("I am not a JSON file") From f39113fd0bb554797692e1f8c4334c8ad41d5213 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 5 Dec 2020 14:39:33 +0100 Subject: [PATCH 164/171] Test body snatch potion, fixes #34 --- squirrelbattle/tests/entities_test.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index efd3397..2c72abd 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -3,7 +3,7 @@ import unittest -from squirrelbattle.entities.items import Bomb, Heart, Item +from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart, Item from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, TeddyBear from squirrelbattle.entities.player import Player from squirrelbattle.interfaces import Entity, Map @@ -154,6 +154,24 @@ class TestEntities(unittest.TestCase): heart_state = item.save_state() self.assertEqual(heart_state["healing"], item.healing) + def test_body_snatch_potion(self) -> None: + """ + Test some random stuff with body snatch potions. + """ + item = BodySnatchPotion() + self.map.add_entity(item) + item.hold(self.player) + + tiger = Tiger(y=42, x=42) + self.map.add_entity(tiger) + + # The player becomes a tiger, and the tiger becomes a squirrel + item.use() + self.assertEqual(self.player.name, "tiger") + self.assertEqual(tiger.name, "player") + self.assertEqual(self.player.y, 42) + self.assertEqual(self.player.x, 42) + def test_players(self) -> None: """ Test some random stuff with players. From 7ad2cad77c582952254de2079a7fcb5c7f2a5897 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 5 Dec 2020 14:47:54 +0100 Subject: [PATCH 165/171] Add a message in the logs when a body-snap potion is used --- squirrelbattle/entities/items.py | 5 +++++ .../locale/de/LC_MESSAGES/squirrelbattle.po | 13 +++++++++---- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 11 ++++++++--- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 943b0c8..2405a7f 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -168,4 +168,9 @@ class BodySnatchPotion(Item): self.held_by.map.currenty, self.held_by.map.currentx = self.held_by.y,\ self.held_by.x + self.held_by.map.logs.add_message( + _("{player} exchanged its body with {entity}.").format( + player=self.held_by.translated_name.capitalize(), + entity=entity.translated_name)) + self.held_by.inventory.remove(self) diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 505d2d8..28307eb 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-12-05 14:13+0100\n" +"POT-Creation-Date: 2020-12-05 14:46+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -34,7 +34,12 @@ msgstr "SIE WURDEN GESTORBEN" #. The player earn XP if the entity was killed. #: squirrelbattle/entities/items.py:128 msgid "Bomb is exploding." -msgstr "" +msgstr "Die Bombe explodiert." + +#: squirrelbattle/entities/items.py:172 +#, python-brace-format +msgid "{player} exchanged its body with {entity}." +msgstr "{player} täuscht seinem Körper mit {entity} aus." #: squirrelbattle/game.py:177 msgid "" @@ -79,8 +84,8 @@ msgstr "{name} stirbt." msgid "Back" msgstr "Zurück" -#: squirrelbattle/tests/game_test.py:294 squirrelbattle/tests/game_test.py:297 -#: squirrelbattle/tests/game_test.py:300 +#: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303 +#: squirrelbattle/tests/game_test.py:306 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Neu Spiel" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 1c17ccf..652871d 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-12-05 14:13+0100\n" +"POT-Creation-Date: 2020-12-05 14:46+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -36,6 +36,11 @@ msgstr "VOUS ÊTES MORT" msgid "Bomb is exploding." msgstr "La bombe explose." +#: squirrelbattle/entities/items.py:172 +#, python-brace-format +msgid "{player} exchanged its body with {entity}." +msgstr "{player} a échangé son corps avec {entity}." + #: squirrelbattle/game.py:177 msgid "" "Some keys are missing in your save file.\n" @@ -79,8 +84,8 @@ msgstr "{name} meurt." msgid "Back" msgstr "Retour" -#: squirrelbattle/tests/game_test.py:294 squirrelbattle/tests/game_test.py:297 -#: squirrelbattle/tests/game_test.py:300 +#: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303 +#: squirrelbattle/tests/game_test.py:306 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Nouvelle partie" From f8ca5b2cc5f72e5775a13ceaf7a47508326a503c Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 5 Dec 2020 14:48:43 +0100 Subject: [PATCH 166/171] Recalculate the path finder when the player exchanges its body --- squirrelbattle/entities/items.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 2405a7f..e90ec32 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -173,4 +173,6 @@ class BodySnatchPotion(Item): player=self.held_by.translated_name.capitalize(), entity=entity.translated_name)) + self.held_by.recalculate_paths() + self.held_by.inventory.remove(self) From fdc2bcab8dc037c43d49481fe0f17e8ca7950923 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 7 Dec 2020 01:01:39 +0100 Subject: [PATCH 167/171] Add spanish translation --- docs/deployment.rst | 2 +- setup.py | 2 +- .../locale/de/LC_MESSAGES/squirrelbattle.po | 4 +- .../locale/es/LC_MESSAGES/squirrelbattle.po | 206 ++++++++++++++++++ .../locale/fr/LC_MESSAGES/squirrelbattle.po | 5 +- squirrelbattle/menus.py | 3 +- squirrelbattle/tests/game_test.py | 3 + squirrelbattle/translations.py | 4 +- 8 files changed, 218 insertions(+), 11 deletions(-) create mode 100644 squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po diff --git a/docs/deployment.rst b/docs/deployment.rst index 9477a10..6bde6f0 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -35,7 +35,7 @@ paquet ainsi que des détails à fournir à PyPI : long_description = f.read() # Compile messages - for language in ["de", "en", "fr"]: + for language in ["de", "es", "fr"]: args = ["msgfmt", "--check-format", "-o", f"squirrelbattle/locale/{language}/LC_MESSAGES" "/squirrelbattle.mo", diff --git a/setup.py b/setup.py index 573eea7..7f39d83 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ with open("README.md", "r") as f: long_description = f.read() # Compile messages -for language in ["de", "fr"]: +for language in ["de", "es", "fr"]: args = ["msgfmt", "--check-format", "-o", f"squirrelbattle/locale/{language}/LC_MESSAGES" "/squirrelbattle.mo", diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 28307eb..38d16a6 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -1,9 +1,7 @@ -# SOME DESCRIPTIVE TITLE. +# German translation of Squirrel Battle # Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse # This file is distributed under the same license as the squirrelbattle package. -# FIRST AUTHOR , YEAR. # -#, fuzzy msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po new file mode 100644 index 0000000..acbfb5a --- /dev/null +++ b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po @@ -0,0 +1,206 @@ +# Spanish translation of Squirrel Battle +# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse +# This file is distributed under the same license as the squirrelbattle package. +# Translation by ifugaao +# +msgid "" +msgstr "" +"Project-Id-Version: squirrelbattle 3.14.1\n" +"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" +"POT-Creation-Date: 2020-12-05 14:46+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: ifugao\n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +# Suggested in Weblate: == INVENTORIO == +#: squirrelbattle/display/menudisplay.py:105 +msgid "== INVENTORY ==" +msgstr "== INVENTORIO ==" + +# Suggested in Weblate: Inventorio : +#: squirrelbattle/display/statsdisplay.py:34 +msgid "Inventory:" +msgstr "Inventorio :" + +# Suggested in Weblate: ERES MUERTO +#: squirrelbattle/display/statsdisplay.py:50 +msgid "YOU ARE DEAD" +msgstr "ERES MUERTO" + +#. The bomb is exploding. +#. Each entity that is close to the bomb takes damages. +#. The player earn XP if the entity was killed. +#: squirrelbattle/entities/items.py:128 +msgid "Bomb is exploding." +msgstr "La bomba está explotando." + +#: squirrelbattle/entities/items.py:172 +#, python-brace-format +msgid "{player} exchanged its body with {entity}." +msgstr "{player} intercambió su cuerpo con {entity}." + +#: squirrelbattle/game.py:177 +msgid "" +"Some keys are missing in your save file.\n" +"Your save seems to be corrupt. It got deleted." +msgstr "" +"Algunas claves faltan en su archivo de guarda.\n" +"Su guarda parece a ser corruptido. Fue eliminado." + +#: squirrelbattle/game.py:185 +msgid "" +"No player was found on this map!\n" +"Maybe you died?" +msgstr "" +"No jugador encontrado sobre la carta !\n" +"¿ Quizas murió ?" + +#: squirrelbattle/game.py:205 +msgid "" +"The JSON file is not correct.\n" +"Your save seems corrupted. It got deleted." +msgstr "" +"El JSON archivo no es correcto.\n" +"Su guarda parece corrupta. Fue eliminada." + +#: squirrelbattle/interfaces.py:400 +#, python-brace-format +msgid "{name} hits {opponent}." +msgstr "{name} golpea a {opponent}." + +#: squirrelbattle/interfaces.py:412 +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} recibe {amount} daño." + +#: squirrelbattle/interfaces.py:414 +#, python-brace-format +msgid "{name} dies." +msgstr "{name} se muere." + +#: squirrelbattle/menus.py:72 +msgid "Back" +msgstr "Volver" + +#: squirrelbattle/tests/game_test.py:300, +#: squirrelbattle/tests/game_test.py:303, +#: squirrelbattle/tests/game_test.py:306, +#: squirrelbattle/tests/translations_test.py:16 +msgid "New game" +msgstr "Nuevo partido" + +#: squirrelbattle/tests/translations_test.py:17 +msgid "Resume" +msgstr "Resumir" + +#: squirrelbattle/tests/translations_test.py:18 +msgid "Load" +msgstr "Cargar" + +#: squirrelbattle/tests/translations_test.py:19 +msgid "Save" +msgstr "Guardar" + +#: squirrelbattle/tests/translations_test.py:20 +msgid "Settings" +msgstr "Parametros" + +#: squirrelbattle/tests/translations_test.py:21 +msgid "Exit" +msgstr "Salir" + +#: squirrelbattle/tests/translations_test.py:27 +msgid "Main key to move up" +msgstr "Primera tecla para subir" + +#: squirrelbattle/tests/translations_test.py:29 +msgid "Secondary key to move up" +msgstr "Segunda tecla para subir" + +#: squirrelbattle/tests/translations_test.py:31 +msgid "Main key to move down" +msgstr "Primera tecla para bajar" + +#: squirrelbattle/tests/translations_test.py:33 +msgid "Secondary key to move down" +msgstr "Segunda tecla para bajar" + +#: squirrelbattle/tests/translations_test.py:35 +msgid "Main key to move left" +msgstr "Primera tecla para moverse a la izquierda" + +#: squirrelbattle/tests/translations_test.py:37 +msgid "Secondary key to move left" +msgstr "Segunda tecla para moverse a la izquierda" + +#: squirrelbattle/tests/translations_test.py:39 +msgid "Main key to move right" +msgstr "Primera tecla para moverse a la derecha" + +#: squirrelbattle/tests/translations_test.py:41 +msgid "Secondary key to move right" +msgstr "Segunda tecla para moverse a la derecha" + +#: squirrelbattle/tests/translations_test.py:43 +msgid "Key to validate a menu" +msgstr "Tecla para validar un menú" + +#: squirrelbattle/tests/translations_test.py:45 +msgid "Key used to open the inventory" +msgstr "Tecla para abrir el inventorio" + +#: squirrelbattle/tests/translations_test.py:47 +msgid "Key used to use an item in the inventory" +msgstr "Tecla para utilizar un objeto del inventorio" + +#: squirrelbattle/tests/translations_test.py:49 +msgid "Key used to equip an item in the inventory" +msgstr "Tecla para equipar un objeto del inventorio" + +#: squirrelbattle/tests/translations_test.py:51 +msgid "Key used to drop an item in the inventory" +msgstr "Tecla para dejar un objeto del inventorio" + +#: squirrelbattle/tests/translations_test.py:53 +msgid "Texture pack" +msgstr "Paquete de texturas" + +#: squirrelbattle/tests/translations_test.py:54 +msgid "Language" +msgstr "Languaje" + +#: squirrelbattle/tests/translations_test.py:57 +msgid "player" +msgstr "jugador" + +#: squirrelbattle/tests/translations_test.py:59 +msgid "tiger" +msgstr "tigre" + +#: squirrelbattle/tests/translations_test.py:60 +msgid "hedgehog" +msgstr "erizo" + +#: squirrelbattle/tests/translations_test.py:61 +msgid "rabbit" +msgstr "conejo" + +#: squirrelbattle/tests/translations_test.py:62 +msgid "teddy bear" +msgstr "osito de peluche" + +#: squirrelbattle/tests/translations_test.py:64 +msgid "body snatch potion" +msgstr "poción de intercambio" + +#: squirrelbattle/tests/translations_test.py:65 +msgid "bomb" +msgstr "bomba" + +#: squirrelbattle/tests/translations_test.py:66 +msgid "heart" +msgstr "corazón" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 652871d..85bd728 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -1,7 +1,6 @@ -# SOME DESCRIPTIVE TITLE. +# French translation of Squirrel Battle # Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse # This file is distributed under the same license as the squirrelbattle package. -# FIRST AUTHOR , YEAR. # #, fuzzy msgid "" @@ -12,7 +11,7 @@ msgstr "" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" -"Language: \n" +"Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index d6946d0..3a536f3 100644 --- a/squirrelbattle/menus.py +++ b/squirrelbattle/menus.py @@ -99,7 +99,8 @@ class SettingsMenu(Menu): self.update_values(game.settings) elif option == "LOCALE": game.settings.LOCALE = 'fr' if game.settings.LOCALE == 'en'\ - else 'de' if game.settings.LOCALE == 'fr' else 'en' + else 'de' if game.settings.LOCALE == 'fr' else 'es' \ + if game.settings.LOCALE == 'de' else 'en' Translator.setlocale(game.settings.LOCALE) game.settings.write_settings() self.update_values(game.settings) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 887835b..3a32c95 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -302,6 +302,9 @@ class TestGame(unittest.TestCase): self.assertEqual(self.game.settings.LOCALE, "de") self.assertEqual(_("New game"), "Neu Spiel") self.game.handle_key_pressed(KeyValues.ENTER) + self.assertEqual(self.game.settings.LOCALE, "es") + self.assertEqual(_("New game"), "Nuevo partido") + self.game.handle_key_pressed(KeyValues.ENTER) self.assertEqual(self.game.settings.LOCALE, "en") self.assertEqual(_("New game"), "New game") diff --git a/squirrelbattle/translations.py b/squirrelbattle/translations.py index 1e97df6..08d40d1 100644 --- a/squirrelbattle/translations.py +++ b/squirrelbattle/translations.py @@ -15,7 +15,7 @@ class Translator: Translator.setlocale defines the language of the strings, then gettext() translates the message. """ - SUPPORTED_LOCALES: List[str] = ["de", "en", "fr"] + SUPPORTED_LOCALES: List[str] = ["de", "en", "es", "fr"] locale: str = "en" translators: dict = {} @@ -67,7 +67,7 @@ class Translator: "--package-name=squirrelbattle", "--package-version=3.14.1", "--copyright-holder=ÿnérant, eichhornchen, " - "nicomarg, charlse", + "nicomarg, charlse, ifugao", "--msgid-bugs-address=squirrel-battle@crans.org", "--sort-by-file", "-o", file_name] From 7823a422b9a7f35dcefa85e1ec218b9eddd3a1fe Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Tue, 8 Dec 2020 00:59:19 +0100 Subject: [PATCH 168/171] Start f new pathfinding, not working --- squirrelbattle/display/mapdisplay.py | 19 +++++++++++++++++++ squirrelbattle/entities/player.py | 25 ++++++++++++++----------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index 445f736..93be162 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -3,6 +3,7 @@ from squirrelbattle.interfaces import Map from .display import Display +from squirrelbattle.entities.player import Player class MapDisplay(Display): @@ -22,6 +23,24 @@ class MapDisplay(Display): for e in self.map.entities: self.addstr(self.pad, e.y, self.pack.tile_width * e.x, self.pack[e.name.upper()], self.color_pair(2)) + players = [ p for p in self.map.entities if isinstance(p,Player) ] + player = players[0] if len(players) > 0 else None + if player: + for x in range(self.map.width): + for y in range(self.map.height): + if (y,x) in player.paths: + deltay, deltax = (y - player.path[(y, x)][0], + x - player.path[(y, x)][1]) + if (deltay, deltax) == (-1, 0): + character = '╹' + elif (deltay, deltax) == (1, 0): + character = '╻' + elif (deltay, deltax) == (0, -1): + character = '╺' + else: + character = '╸' + self.addstr(self.pad, y, self.pack.tile_width * x, + character, self.color_pair(1)) def display(self) -> None: y, x = self.map.currenty, self.pack.tile_width * self.map.currentx diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 3f1d941..0875d3a 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -3,6 +3,7 @@ from random import randint from typing import Dict, Tuple +from queue import PriorityQueue from ..interfaces import FightingEntity @@ -95,26 +96,28 @@ class Player(FightingEntity): Use Dijkstra algorithm to calculate best paths for monsters to go to the player. """ - queue = [(self.y, self.x)] + queue = PriorityQueue() + queue.put((0, (self.y, self.x))) visited = [] distances = {(self.y, self.x): 0} predecessors = {} - while queue: - y, x = queue.pop(0) - visited.append((y, x)) - if distances[(y, x)] >= max_distance: + while not queue.empty: + dist, (y, x) = queue.get() + if dist >= max_distance or (y,x) in visited: continue + visited.append((y, x)) for diff_y, diff_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]: new_y, new_x = y + diff_y, x + diff_x if not 0 <= new_y < self.map.height or \ not 0 <= new_x < self.map.width or \ - not self.map.tiles[y][x].can_walk() or \ - (new_y, new_x) in visited or \ - (new_y, new_x) in queue: + not self.map.tiles[y][x].can_walk(): continue - predecessors[(new_y, new_x)] = (y, x) - distances[(new_y, new_x)] = distances[(y, x)] + 1 - queue.append((new_y, new_x)) + new_distance = dist + 1 + if not (new_y, new_x) in distances or \ + distances[(new_y, new_x)] > new_distance: + predecessors[(new_y, new_x)] = (y, x) + distances[(new_y, new_x)] = new_distance + queue.put(new_distance, (new_y, new_x)) self.paths = predecessors def save_state(self) -> dict: From 50d806cdcf7e4d34225f7a2dcff0e039c2c6933f Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Tue, 8 Dec 2020 22:22:20 +0100 Subject: [PATCH 169/171] Working Dijkstra --- squirrelbattle/display/mapdisplay.py | 12 ++++++------ squirrelbattle/entities/player.py | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index 93be162..1ac99bb 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -29,16 +29,16 @@ class MapDisplay(Display): for x in range(self.map.width): for y in range(self.map.height): if (y,x) in player.paths: - deltay, deltax = (y - player.path[(y, x)][0], - x - player.path[(y, x)][1]) + deltay, deltax = (y - player.paths[(y, x)][0], + x - player.paths[(y, x)][1]) if (deltay, deltax) == (-1, 0): - character = '╹' + character = '↓' elif (deltay, deltax) == (1, 0): - character = '╻' + character = '↑' elif (deltay, deltax) == (0, -1): - character = '╺' + character = '→' else: - character = '╸' + character = '←' self.addstr(self.pad, y, self.pack.tile_width * x, character, self.color_pair(1)) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 0875d3a..b814d88 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -101,7 +101,7 @@ class Player(FightingEntity): visited = [] distances = {(self.y, self.x): 0} predecessors = {} - while not queue.empty: + while not queue.empty(): dist, (y, x) = queue.get() if dist >= max_distance or (y,x) in visited: continue @@ -110,14 +110,14 @@ class Player(FightingEntity): new_y, new_x = y + diff_y, x + diff_x if not 0 <= new_y < self.map.height or \ not 0 <= new_x < self.map.width or \ - not self.map.tiles[y][x].can_walk(): + not self.map.tiles[new_y][new_x].can_walk(): continue new_distance = dist + 1 if not (new_y, new_x) in distances or \ distances[(new_y, new_x)] > new_distance: predecessors[(new_y, new_x)] = (y, x) distances[(new_y, new_x)] = new_distance - queue.put(new_distance, (new_y, new_x)) + queue.put((new_distance, (new_y, new_x))) self.paths = predecessors def save_state(self) -> dict: From cc6033e8e437587e236debe32efb4073cfe90b5a Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Thu, 10 Dec 2020 22:21:09 +0100 Subject: [PATCH 170/171] New pathfinding that avoids most of the mobs getting stuck, closes #35 --- squirrelbattle/display/mapdisplay.py | 40 ++++++++-------- squirrelbattle/entities/monsters.py | 13 ++++-- squirrelbattle/entities/player.py | 69 ++++++++++++++++++---------- 3 files changed, 74 insertions(+), 48 deletions(-) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index 1ac99bb..d403f7f 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -3,7 +3,6 @@ from squirrelbattle.interfaces import Map from .display import Display -from squirrelbattle.entities.player import Player class MapDisplay(Display): @@ -23,24 +22,27 @@ class MapDisplay(Display): for e in self.map.entities: self.addstr(self.pad, e.y, self.pack.tile_width * e.x, self.pack[e.name.upper()], self.color_pair(2)) - players = [ p for p in self.map.entities if isinstance(p,Player) ] - player = players[0] if len(players) > 0 else None - if player: - for x in range(self.map.width): - for y in range(self.map.height): - if (y,x) in player.paths: - deltay, deltax = (y - player.paths[(y, x)][0], - x - player.paths[(y, x)][1]) - if (deltay, deltax) == (-1, 0): - character = '↓' - elif (deltay, deltax) == (1, 0): - character = '↑' - elif (deltay, deltax) == (0, -1): - character = '→' - else: - character = '←' - self.addstr(self.pad, y, self.pack.tile_width * x, - character, self.color_pair(1)) + + # Display Path map for deubg purposes + # from squirrelbattle.entities.player import Player + # players = [ p for p in self.map.entities if isinstance(p,Player) ] + # player = players[0] if len(players) > 0 else None + # if player: + # for x in range(self.map.width): + # for y in range(self.map.height): + # if (y,x) in player.paths: + # deltay, deltax = (y - player.paths[(y, x)][0], + # x - player.paths[(y, x)][1]) + # if (deltay, deltax) == (-1, 0): + # character = '↓' + # elif (deltay, deltax) == (1, 0): + # character = '↑' + # elif (deltay, deltax) == (0, -1): + # character = '→' + # else: + # character = '←' + # self.addstr(self.pad, y, self.pack.tile_width * x, + # character, self.color_pair(1)) def display(self) -> None: y, x = self.map.currenty, self.pack.tile_width * self.map.currentx diff --git a/squirrelbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py index feff81a..34cd4bf 100644 --- a/squirrelbattle/entities/monsters.py +++ b/squirrelbattle/entities/monsters.py @@ -43,11 +43,14 @@ class Monster(FightingEntity): # If they can't move and they are already close to the player, # They hit. if target and (self.y, self.x) in target.paths: - # Move to target player - next_y, next_x = target.paths[(self.y, self.x)] - moved = self.check_move(next_y, next_x, True) - if not moved and self.distance_squared(target) <= 1: - self.map.logs.add_message(self.hit(target)) + # Move to target player by choosing the best avaliable path + for next_y, next_x in target.paths[(self.y, self.x)]: + moved = self.check_move(next_y, next_x, True) + if moved: + break + if self.distance_squared(target) <= 1: + self.map.logs.add_message(self.hit(target)) + break else: # Move in a random direction # If the direction is not available, try another one diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index b814d88..dd4d6f0 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -1,9 +1,10 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later +from functools import reduce +from queue import PriorityQueue from random import randint from typing import Dict, Tuple -from queue import PriorityQueue from ..interfaces import FightingEntity @@ -93,32 +94,52 @@ class Player(FightingEntity): def recalculate_paths(self, max_distance: int = 8) -> None: """ - Use Dijkstra algorithm to calculate best paths - for monsters to go to the player. + Use Dijkstra algorithm to calculate best paths for monsters to go to + the player. Actually, the paths are computed for each tile adjacent to + the player then for each step the monsters use the best path avaliable. """ - queue = PriorityQueue() - queue.put((0, (self.y, self.x))) - visited = [] - distances = {(self.y, self.x): 0} - predecessors = {} - while not queue.empty(): - dist, (y, x) = queue.get() - if dist >= max_distance or (y,x) in visited: + distances = [] + predecessors = [] + # four Dijkstras, one for each adjacent tile + for dir_y, dir_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]: + queue = PriorityQueue() + new_y, new_x = self.y + dir_y, self.x + dir_x + if not 0 <= new_y < self.map.height or \ + not 0 <= new_x < self.map.width or \ + not self.map.tiles[new_y][new_x].can_walk(): continue - visited.append((y, x)) - for diff_y, diff_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]: - new_y, new_x = y + diff_y, x + diff_x - if not 0 <= new_y < self.map.height or \ - not 0 <= new_x < self.map.width or \ - not self.map.tiles[new_y][new_x].can_walk(): + queue.put(((1, 0), (new_y, new_x))) + visited = [(self.y, self.x)] + distances.append({(self.y, self.x): (0, 0), (new_y, new_x): (1, 0)}) + predecessors.append({(new_y, new_x): (self.y, self.x)}) + while not queue.empty(): + dist, (y, x) = queue.get() + if dist[0] >= max_distance or (y, x) in visited: continue - new_distance = dist + 1 - if not (new_y, new_x) in distances or \ - distances[(new_y, new_x)] > new_distance: - predecessors[(new_y, new_x)] = (y, x) - distances[(new_y, new_x)] = new_distance - queue.put((new_distance, (new_y, new_x))) - self.paths = predecessors + visited.append((y, x)) + for diff_y, diff_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]: + new_y, new_x = y + diff_y, x + diff_x + if not 0 <= new_y < self.map.height or \ + not 0 <= new_x < self.map.width or \ + not self.map.tiles[new_y][new_x].can_walk(): + continue + new_distance = (dist[0] + 1, + dist[1] + (not self.map.is_free(y, x))) + if not (new_y, new_x) in distances[-1] or \ + distances[-1][(new_y, new_x)] > new_distance: + predecessors[-1][(new_y, new_x)] = (y, x) + distances[-1][(new_y, new_x)] = new_distance + queue.put((new_distance, (new_y, new_x))) + # For each tile that is reached by at least one Dijkstra, sort the + # different paths by distance to the player. For the technical bits : + # The reduce function is a fold starting on the first element of the + # iterable, and we associate the points to their distance, sort + # along the distance, then only keep the points. + self.paths = {} + for y, x in reduce(set.union, [set(p.keys()) for p in predecessors]): + self.paths[(y, x)] = [p for d, p in sorted( + [(distances[i][(y, x)], predecessors[i][(y, x)]) + for i in range(len(distances)) if (y, x) in predecessors[i]])] def save_state(self) -> dict: """ From 01cc77e1463623a6e78490849c0f25ba2731d724 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Thu, 10 Dec 2020 22:28:12 +0100 Subject: [PATCH 171/171] Fixed a bug when trying to pathfind when player is surrounded by inaccessible tiles --- squirrelbattle/entities/player.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index dd4d6f0..45e2bdf 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -133,10 +133,11 @@ class Player(FightingEntity): # For each tile that is reached by at least one Dijkstra, sort the # different paths by distance to the player. For the technical bits : # The reduce function is a fold starting on the first element of the - # iterable, and we associate the points to their distance, sort + # iterable, and we associate the points to their distance, sort # along the distance, then only keep the points. self.paths = {} - for y, x in reduce(set.union, [set(p.keys()) for p in predecessors]): + for y, x in reduce(set.union, + [set(p.keys()) for p in predecessors], set()): self.paths[(y, x)] = [p for d, p in sorted( [(distances[i][(y, x)], predecessors[i][(y, x)]) for i in range(len(distances)) if (y, x) in predecessors[i]])]