From 777f155d777576871157c0580be6c81dcfd1a873 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 20 Nov 2020 15:26:02 +0100 Subject: [PATCH 01/46] 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 6b72f4b284f77517f32399a6e32042c38e87d523 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 20 Nov 2020 17:42:56 +0100 Subject: [PATCH 02/46] Added a friendly entity class. --- squirrelbattle/interfaces.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index d1baa2a..7fc4171 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -422,3 +422,17 @@ class FightingEntity(Entity): for name in self.keys(): d[name] = getattr(self, name) return d + +class FriendlyEntity(Entity): + """ + Friendly entities are living entities which do not attack the player + """ + maxhealth: int + health: int #Friendly entities can be killed + + def __init__(self, maxhealth: int = 0, health: Optional[int] = None, + *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.maxhealth = maxhealth + self.health = maxhealth if health is None else health + From 76bbee7e6db23677c2b3c2119edc53db1cf02d96 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 27 Nov 2020 16:56:22 +0100 Subject: [PATCH 03/46] Added a merchant class and a talk_to method to interact with friendly entities --- squirrelbattle/entities/friendly.py | 20 ++++++++++++++++++++ squirrelbattle/interfaces.py | 3 +++ 2 files changed, 23 insertions(+) create mode 100644 squirrelbattle/entities/friendly.py diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py new file mode 100644 index 0000000..7cbd03a --- /dev/null +++ b/squirrelbattle/entities/friendly.py @@ -0,0 +1,20 @@ +from ..interfaces import FriendlyEntity, Map + +class Merchant(FriendlyEntity) : + """ + The class for merchants in the dungeon + """ + + inventory = list + + def __init__(self, inventory : list): + super().__init__() + self.inventory = inventory + + def talk_to(self, player : Player) -> None: + """ + This function is used to open the merchant's inventory in a menu, + and allow the player to buy/sell objects + """ + #TODO + diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 7fc4171..b629b96 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -435,4 +435,7 @@ class FriendlyEntity(Entity): super().__init__(*args, **kwargs) self.maxhealth = maxhealth self.health = maxhealth if health is None else health + + def talk_to(self, player : Player) -> None: + #TODO From 4fdf08cab901b8e687f246965d3764231c52f1e0 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 27 Nov 2020 17:11:59 +0100 Subject: [PATCH 04/46] Added a currency : the hazel (for hazelnuts). The player and the merchants possess a certain quantity of hazel --- squirrelbattle/entities/player.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 702b055..502f200 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -12,11 +12,12 @@ class Player(FightingEntity): max_xp: int = 10 inventory: list paths: Dict[Tuple[int, int], Tuple[int, int]] + hazel: int #It is the currency of this game 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: + max_xp: int = 10, hazel: int = 42, *args, **kwargs) -> None: super().__init__(name="player", maxhealth=maxhealth, strength=strength, intelligence=intelligence, charisma=charisma, dexterity=dexterity, constitution=constitution, @@ -25,6 +26,7 @@ class Player(FightingEntity): self.max_xp = max_xp self.inventory = list() self.paths = dict() + self.hazel = hazel def move(self, y: int, x: int) -> None: """ From bad56ba442750ed9adeb3ad95489b0b0189ee73e Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 27 Nov 2020 17:54:03 +0100 Subject: [PATCH 05/46] Added the merchants in the texturepack --- squirrelbattle/display/texturepack.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 4296393..e09f277 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -53,6 +53,7 @@ TexturePack.ASCII_PACK = TexturePack( RABBIT='Y', BEAVER='_', TEDDY_BEAR='8', + MERCHANT='M', ) TexturePack.SQUIRREL_PACK = TexturePack( @@ -65,11 +66,12 @@ TexturePack.SQUIRREL_PACK = TexturePack( EMPTY=' ', WALL='🧱', FLOOR='██', - PLAYER='🐿️ ️', + PLAYER='🐿️️', HEDGEHOG='🦔', HEART='💜', BOMB='💣', RABBIT='🐇', BEAVER='🦫', TEDDY_BEAR='🧸', + MERCHANT='🦜', ) From 3f301423fb8886c3b7e73d0dee93fbba473309db Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 27 Nov 2020 17:54:55 +0100 Subject: [PATCH 06/46] Added a menudisplay class for menus with a value such as the menu of the merchants --- squirrelbattle/display/menudisplay.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index fca1ddf..f8df5ee 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -94,3 +94,10 @@ class MainMenuDisplay(Display): self.menudisplay.refresh( menuy, menux, min(self.menudisplay.preferred_height, self.height - menuy), menuwidth) + +class VariableMenuDisplay(MenuDisplay): + @property + def values(self) -> List[str]: + return [a[1][1] + (" : " + + (a[1][0]) + if a[1][0] else "" for a in self.menu.values] From ca86572677829b433dd4a325c27a0434f5635f5f Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 27 Nov 2020 17:56:01 +0100 Subject: [PATCH 07/46] Added a weapon class and a sword subclass --- squirrelbattle/entities/items.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 9927ef4..f5da235 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -107,3 +107,30 @@ class Bomb(Item): d["exploding"] = self.exploding d["damage"] = self.damage return d + +class Weapon(Item): + """ + Non-throwable items that improve player damage + """ + damage: int + + def __init__(self, damage: int = 3, *args, **kwargs): + super().__init__(*args, **kwargs) + self.damage = damage + + def save_state(self) -> dict: + """ + Saves the state of the weapon into a dictionary + """ + d = super().save_state() + d["damage"] = self.damage + return d + +class Sword(Weapon) : + """ + A basic weapon + """ + def __init__(self, name: int, *args, **kwargs): + super().__init__(*args, **kwargs) + self.name = "sword" + From d987e6000661e027bec5845cf8afb61b9b4d40fc Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 27 Nov 2020 18:00:54 +0100 Subject: [PATCH 08/46] Added some documentation for menudisplay --- squirrelbattle/display/menudisplay.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index f8df5ee..a9d0d2d 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -6,6 +6,9 @@ from ..resources import ResourceManager class MenuDisplay(Display): + """ + A class to display the menu objects + """ position: int def __init__(self, *args): @@ -62,6 +65,9 @@ class MenuDisplay(Display): class SettingsMenuDisplay(MenuDisplay): + """ + A class to display specifically a settingsmenu object + """ @property def values(self) -> List[str]: return [a[1][1] + (" : " @@ -71,6 +77,9 @@ class SettingsMenuDisplay(MenuDisplay): class MainMenuDisplay(Display): + """ + A class to display specifically a mainmenu object + """ def __init__(self, menu: MainMenu, *args): super().__init__(*args) self.menu = menu @@ -96,6 +105,9 @@ class MainMenuDisplay(Display): self.height - menuy), menuwidth) class VariableMenuDisplay(MenuDisplay): + """ + A class to display a menu in which each value is associated to a parameter + """ @property def values(self) -> List[str]: return [a[1][1] + (" : " From fbd9f0045bb2a6b489b03f575c3228cbb9610ba7 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 27 Nov 2020 18:35:52 +0100 Subject: [PATCH 09/46] Friendly entities can now talk to the player, a sunflower entity was added to test this new feature. Related to issue #22 --- squirrelbattle/display/menudisplay.py | 2 +- squirrelbattle/display/texturepack.py | 2 ++ squirrelbattle/entities/friendly.py | 19 ++++++++++++++++--- squirrelbattle/entities/player.py | 2 ++ squirrelbattle/interfaces.py | 22 ++++++++++++++++++---- 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index a9d0d2d..6a0c45f 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -112,4 +112,4 @@ class VariableMenuDisplay(MenuDisplay): def values(self) -> List[str]: return [a[1][1] + (" : " + (a[1][0]) - if a[1][0] else "" for a in self.menu.values] + if a[1][0] else "") for a in self.menu.values] diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index e09f277..71b07a6 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -54,6 +54,7 @@ TexturePack.ASCII_PACK = TexturePack( BEAVER='_', TEDDY_BEAR='8', MERCHANT='M', + SUNFLOWER='I', ) TexturePack.SQUIRREL_PACK = TexturePack( @@ -74,4 +75,5 @@ TexturePack.SQUIRREL_PACK = TexturePack( BEAVER='🦫', TEDDY_BEAR='🧸', MERCHANT='🦜', + SUNFLOWER='🌻', ) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 7cbd03a..6804a83 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -1,15 +1,18 @@ from ..interfaces import FriendlyEntity, Map +from .player import Player class Merchant(FriendlyEntity) : """ The class for merchants in the dungeon """ - + name = "Merchant" inventory = list + hazel = int - def __init__(self, inventory : list): + def __init__(self, inventory : list, hazel : int = 75): super().__init__() self.inventory = inventory + self.hazel = hazel def talk_to(self, player : Player) -> None: """ @@ -17,4 +20,14 @@ class Merchant(FriendlyEntity) : and allow the player to buy/sell objects """ #TODO - + +class Sunflower(FriendlyEntity) : + """ + A friendly sunflower + """ + dialogue_option = ["Flower power!!", "The sun is warm today"] + + def __init__(self, maxhealth: int = 15, + *args, **kwargs) -> None: + super().__init__(name="sunflower", + maxhealth=maxhealth, *args, **kwargs) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 502f200..c5cd9e3 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -80,6 +80,8 @@ class Player(FightingEntity): return True elif entity.is_item(): entity.hold(self) + elif entity.is_friendly(): + self.map.logs.add_message(entity.talk_to(self)) return super().check_move(y, x, move_if_possible) def recalculate_paths(self, max_distance: int = 8) -> None: diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index b629b96..0db0fc2 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -1,8 +1,9 @@ #!/usr/bin/env python +from random import randint from enum import Enum, auto from math import sqrt from random import choice, randint -from typing import List, Optional +from typing import List, Optional, Any from squirrelbattle.display.texturepack import TexturePack @@ -312,6 +313,12 @@ class Entity: from squirrelbattle.entities.items import Item return isinstance(self, Item) + def is_friendly(self) -> bool: + """ + Is this entity a friendly entity? + """ + return isinstance(self, FriendlyEntity) + @staticmethod def get_all_entity_classes(): """ @@ -320,7 +327,8 @@ class Entity: from squirrelbattle.entities.items import Heart, Bomb from squirrelbattle.entities.monsters import Beaver, Hedgehog, \ Rabbit, TeddyBear - return [Beaver, Bomb, Heart, Hedgehog, Rabbit, TeddyBear] + from squirrelbattle.entities.friendly import Merchant,Sunflower + return [Beaver, Bomb, Heart, Hedgehog, Rabbit, TeddyBear,Sunflower] @staticmethod def get_all_entity_classes_in_a_dict() -> dict: @@ -331,6 +339,7 @@ class Entity: from squirrelbattle.entities.monsters import Beaver, Hedgehog, Rabbit, \ TeddyBear from squirrelbattle.entities.items import Bomb, Heart + from squirrelbattle.entities.friendly import Merchant,Sunflower return { "Beaver": Beaver, "Bomb": Bomb, @@ -339,6 +348,8 @@ class Entity: "Rabbit": Rabbit, "TeddyBear": TeddyBear, "Player": Player, + "Merchant": Merchant, + "Sunflower": Sunflower, } def save_state(self) -> dict: @@ -429,6 +440,7 @@ class FriendlyEntity(Entity): """ maxhealth: int health: int #Friendly entities can be killed + dialogue_option : list def __init__(self, maxhealth: int = 0, health: Optional[int] = None, *args, **kwargs) -> None: @@ -436,6 +448,8 @@ class FriendlyEntity(Entity): self.maxhealth = maxhealth self.health = maxhealth if health is None else health - def talk_to(self, player : Player) -> None: - #TODO + def talk_to(self, player : Any) -> str: + a = randint(0,len(self.dialogue_option)-1) + return "The sunflower said : "+self.dialogue_option[a] + From 654bab7c1dc8a6ab0a2de31e7c1570523dcd11c5 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Tue, 1 Dec 2020 17:12:22 +0100 Subject: [PATCH 10/46] Translate sun flower messages --- squirrelbattle/entities/friendly.py | 9 ++++++--- .../locale/de/LC_MESSAGES/squirrelbattle.po | 17 +++++++++++++---- .../locale/en/LC_MESSAGES/squirrelbattle.po | 14 +++++++++++++- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 14 +++++++++++++- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 6804a83..88281a6 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -1,6 +1,8 @@ -from ..interfaces import FriendlyEntity, Map +from ..interfaces import FriendlyEntity +from ..translations import gettext as _ from .player import Player + class Merchant(FriendlyEntity) : """ The class for merchants in the dungeon @@ -19,13 +21,14 @@ class Merchant(FriendlyEntity) : This function is used to open the merchant's inventory in a menu, and allow the player to buy/sell objects """ - #TODO + # TODO + class Sunflower(FriendlyEntity) : """ A friendly sunflower """ - dialogue_option = ["Flower power!!", "The sun is warm today"] + dialogue_option = [_("Flower power!!"), _("The sun is warm today")] def __init__(self, maxhealth: int = 15, *args, **kwargs) -> None: diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index dfd3365..0416b84 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-01 17:10+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -19,6 +19,7 @@ msgstr "" #: 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" @@ -122,17 +123,17 @@ msgstr "Bestand:" msgid "YOU ARE DEAD" msgstr "SIE WURDEN GESTORBEN" -#: squirrelbattle/interfaces.py:398 +#: squirrelbattle/interfaces.py:398 squirrelbattle/interfaces.py:408 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} schlägt {opponent}." -#: squirrelbattle/interfaces.py:410 +#: squirrelbattle/interfaces.py:410 squirrelbattle/interfaces.py:420 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} nimmt {amount} Schadenspunkte." -#: squirrelbattle/interfaces.py:412 +#: squirrelbattle/interfaces.py:412 squirrelbattle/interfaces.py:422 #, python-brace-format msgid "{name} dies." msgstr "{name} stirbt." @@ -164,3 +165,11 @@ msgid "" msgstr "" "Die JSON-Datei ist nicht korrekt.\n" "Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." + +#: squirrelbattle/entities/friendly.py:31 +msgid "Flower power!!" +msgstr "Blumenmacht!!" + +#: squirrelbattle/entities/friendly.py:31 +msgid "The sun is warm today" +msgstr "Die Sonne ist warm heute" diff --git a/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po index 3f563fa..c45e893 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-01 17:10+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -26,11 +26,13 @@ msgid "YOU ARE DEAD" msgstr "" #: squirrelbattle/interfaces.py:394 squirrelbattle/interfaces.py:398 +#: squirrelbattle/interfaces.py:408 #, python-brace-format msgid "{name} hits {opponent}." msgstr "" #: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410 +#: squirrelbattle/interfaces.py:420 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "" @@ -38,6 +40,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 "" @@ -155,6 +158,7 @@ msgid "Language" msgstr "" #: squirrelbattle/interfaces.py:407 squirrelbattle/interfaces.py:412 +#: squirrelbattle/interfaces.py:422 #, python-brace-format msgid "{name} dies." msgstr "" @@ -193,3 +197,11 @@ msgstr "" #: squirrelbattle/tests/translations_test.py:57 msgid "heart" msgstr "" + +#: squirrelbattle/entities/friendly.py:31 +msgid "Flower power!!" +msgstr "" + +#: squirrelbattle/entities/friendly.py:31 +msgid "The sun is warm today" +msgstr "" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index d46cee6..fb9024e 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-01 17:10+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -26,11 +26,13 @@ msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" #: squirrelbattle/interfaces.py:394 squirrelbattle/interfaces.py:398 +#: squirrelbattle/interfaces.py:408 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} frappe {opponent}." #: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410 +#: squirrelbattle/interfaces.py:420 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} prend {amount} points de dégât." @@ -38,6 +40,7 @@ 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 +#: squirrelbattle/tests/game_test.py:290 msgid "New game" msgstr "Nouvelle partie" @@ -161,6 +164,7 @@ msgid "Language" msgstr "Langue" #: squirrelbattle/interfaces.py:407 squirrelbattle/interfaces.py:412 +#: squirrelbattle/interfaces.py:422 #, python-brace-format msgid "{name} dies." msgstr "{name} meurt." @@ -199,3 +203,11 @@ msgstr "bombe" #: squirrelbattle/tests/translations_test.py:57 msgid "heart" msgstr "cœur" + +#: squirrelbattle/entities/friendly.py:31 +msgid "Flower power!!" +msgstr "Pouvoir des fleurs !!" + +#: squirrelbattle/entities/friendly.py:31 +msgid "The sun is warm today" +msgstr "Le soleil est chaud aujourd'hui" From 3886bee1baa70486a2ea69eb32f3562771066688 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 4 Dec 2020 00:27:25 +0100 Subject: [PATCH 11/46] Friendly entities are now a subclass of fighting entities, and can die. The T key is now used to talk to friendly entities --- squirrelbattle/entities/friendly.py | 9 +++++++-- squirrelbattle/entities/player.py | 5 ++++- squirrelbattle/enums.py | 3 +++ squirrelbattle/game.py | 26 +++++++++++++++++++++++++ squirrelbattle/interfaces.py | 30 ++++++++++++++++------------- 5 files changed, 57 insertions(+), 16 deletions(-) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 88281a6..365bdbc 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -10,6 +10,12 @@ class Merchant(FriendlyEntity) : name = "Merchant" inventory = list hazel = int + + def keys(self) -> list : + """ + Returns a friendly entitie's specific attributes + """ + return ["maxhealth", "health", "inventory", "hazel"] def __init__(self, inventory : list, hazel : int = 75): super().__init__() @@ -22,8 +28,7 @@ class Merchant(FriendlyEntity) : and allow the player to buy/sell objects """ # TODO - - + class Sunflower(FriendlyEntity) : """ A friendly sunflower diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 171f019..900a3bd 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -84,7 +84,10 @@ class Player(FightingEntity): elif entity.is_item(): entity.hold(self) elif entity.is_friendly(): - self.map.logs.add_message(entity.talk_to(self)) +# self.map.logs.add_message(entity.talk_to(self)) + self.map.logs.add_message(self.hit(entity)) + if entity.dead: + self.add_xp(randint(3, 7)) return super().check_move(y, x, move_if_possible) def recalculate_paths(self, max_distance: int = 8) -> None: diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index 024f167..17a4393 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -38,6 +38,7 @@ class KeyValues(Enum): RIGHT = auto() ENTER = auto() SPACE = auto() + T = auto() @staticmethod def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]: @@ -60,4 +61,6 @@ class KeyValues(Enum): return KeyValues.ENTER elif key == ' ': return KeyValues.SPACE + elif key == 't': + return KeyValues.T return None diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 44ad349..62ec243 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -24,6 +24,7 @@ class Game: """ map: Map player: Player + screen: Any # display_actions is a display interface set by the bootstrapper display_actions: Callable[[DisplayActions], None] @@ -61,6 +62,7 @@ class Game: We wait for the player's action, then we do what that should be done when the given key gets pressed. """ + self.screen = screen while True: # pragma no cover screen.erase() screen.refresh() @@ -106,6 +108,30 @@ class Game: self.map.tick() elif key == KeyValues.SPACE: self.state = GameMode.MAINMENU + elif key == KeyValues.T : + keykey = self.screen.getkey() + keykey = KeyValues.translate_key(keykey, self.settings) + if keykey == KeyValues.UP: + xp = self.player.x + yp = self.player.y+1 + elif keykey == KeyValues.DOWN: + xp = self.player.x + yp = self.player.y-1 + elif keykey == KeyValues.LEFT: + xp = self.player.x-1 + yp = self.player.y + elif keykey == KeyValues.RIGHT: + xp = self.player.x+1 + yp = self.player.y + else : + raise Exception(keykey) + if self.map.entity_is_present(yp, xp) : + for entity in self.map.entities : + if entity.is_friendly() and entity.x == xp and entity.y == yp : + msg = entity.talk_to(self.player) + self.logs.add_message(msg) + + def handle_key_pressed_main_menu(self, key: KeyValues) -> None: """ diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 5c89172..db44675 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -76,11 +76,19 @@ class Map: def is_free(self, y: int, x: int) -> bool: """ - Indicates that the case at the coordinates (y, x) is empty. + Indicates that the tile at the coordinates (y, x) is empty. """ return 0 <= y < self.height and 0 <= x < self.width and \ self.tiles[y][x].can_walk() and \ not any(entity.x == x and entity.y == y for entity in self.entities) + + def entity_is_present(self, y: int, x: int) -> bool: + """ + Indicates that the tile at the coordinates (y, x) contains a killable entity + """ + return 0 <= y < self.height and 0 <= x < self.width and \ + any(entity.x == x and entity.y == y and \ + entity.is_friendly() for entity in self.entities) @staticmethod def load(filename: str) -> "Map": @@ -431,7 +439,7 @@ class FightingEntity(Entity): def keys(self) -> list: """ - Returns a fighting entities specific attributes + Returns a fighting entity's specific attributes """ return ["maxhealth", "health", "level", "strength", "intelligence", "charisma", "dexterity", "constitution"] @@ -445,22 +453,18 @@ class FightingEntity(Entity): d[name] = getattr(self, name) return d -class FriendlyEntity(Entity): +class FriendlyEntity(FightingEntity): """ Friendly entities are living entities which do not attack the player """ - maxhealth: int - health: int #Friendly entities can be killed dialogue_option : list - def __init__(self, maxhealth: int = 0, health: Optional[int] = None, - *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - self.maxhealth = maxhealth - self.health = maxhealth if health is None else health - def talk_to(self, player : Any) -> str: a = randint(0,len(self.dialogue_option)-1) - return "The sunflower said : "+self.dialogue_option[a] - + return "The "+self.name+" said : "+self.dialogue_option[a] + def keys(self) -> list : + """ + Returns a friendly entity's specific attributes + """ + return ["maxhealth", "health", "dialogue_option"] From ba68e6858457651f630d88ca9daf0936af725e8b Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sat, 5 Dec 2020 21:43:13 +0100 Subject: [PATCH 12/46] Added a Gamemode for selling interfaces, as well as the base of the player/merchant interaction, related to issue #18 --- squirrelbattle/entities/friendly.py | 4 ++-- squirrelbattle/entities/player.py | 6 ++++++ squirrelbattle/enums.py | 1 + squirrelbattle/game.py | 3 ++- squirrelbattle/interfaces.py | 8 +++++++- 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 365bdbc..9bdb3d0 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -11,7 +11,7 @@ class Merchant(FriendlyEntity) : inventory = list hazel = int - def keys(self) -> list : + def keys(self) -> list: """ Returns a friendly entitie's specific attributes """ @@ -22,7 +22,7 @@ class Merchant(FriendlyEntity) : self.inventory = inventory self.hazel = hazel - def talk_to(self, player : Player) -> None: + def talk_to(self, player : Player) -> str: """ This function is used to open the merchant's inventory in a menu, and allow the player to buy/sell objects diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 900a3bd..46baacd 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -117,6 +117,12 @@ class Player(FightingEntity): queue.append((new_y, new_x)) self.paths = predecessors + def add_to_inventory(self, obj : Item) -> None : + """ + Adds an object to inventory + """ + self.inventory.append(obj) + def save_state(self) -> dict: """ Saves the state of the entity into a dictionary diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index 17a4393..e563600 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -26,6 +26,7 @@ class GameMode(Enum): PLAY = auto() SETTINGS = auto() INVENTORY = auto() + STORE = auto() class KeyValues(Enum): diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 62ec243..ec54dae 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -130,7 +130,8 @@ class Game: if entity.is_friendly() and entity.x == xp and entity.y == yp : msg = entity.talk_to(self.player) self.logs.add_message(msg) - + if entity.is_merchant() : + self.state = GameMode.STORE def handle_key_pressed_main_menu(self, key: KeyValues) -> None: diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index db44675..c1f3078 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -329,6 +329,12 @@ class Entity: """ return isinstance(self, FriendlyEntity) + def is_merchant(self) -> bool: + """ + Is this entity a merchant? + """ + return isinstance(self, Merchant) + @property def translated_name(self) -> str: return _(self.name.replace("_", " ")) @@ -459,7 +465,7 @@ class FriendlyEntity(FightingEntity): """ dialogue_option : list - def talk_to(self, player : Any) -> str: + def talk_to(self, player : Any) -> str : a = randint(0,len(self.dialogue_option)-1) return "The "+self.name+" said : "+self.dialogue_option[a] From 38842cee686df199df90d47109cfb8032112b1ef Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sat, 5 Dec 2020 21:50:02 +0100 Subject: [PATCH 13/46] There was a import error --- squirrelbattle/entities/player.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 46baacd..6b7f084 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from random import randint -from typing import Dict, Tuple +from typing import Dict, Tuple, Any from ..interfaces import FightingEntity @@ -117,7 +117,7 @@ class Player(FightingEntity): queue.append((new_y, new_x)) self.paths = predecessors - def add_to_inventory(self, obj : Item) -> None : + def add_to_inventory(self, obj : Any) -> None : """ Adds an object to inventory """ From 866af98fe423847e2e90c27c0de3a9e25eec2992 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 6 Dec 2020 11:43:48 +0100 Subject: [PATCH 14/46] Merging master into village, conflicts were solved --- setup.py | 2 +- squirrelbattle/display/display_manager.py | 22 +- squirrelbattle/display/menudisplay.py | 30 ++- squirrelbattle/display/statsdisplay.py | 15 +- squirrelbattle/display/texturepack.py | 2 + squirrelbattle/entities/items.py | 98 +++++++-- squirrelbattle/entities/monsters.py | 36 ++-- squirrelbattle/entities/player.py | 20 +- squirrelbattle/enums.py | 12 ++ squirrelbattle/game.py | 31 ++- squirrelbattle/interfaces.py | 9 +- .../locale/de/LC_MESSAGES/squirrelbattle.po | 113 ++++++++++- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 190 ++++++++++-------- squirrelbattle/menus.py | 12 ++ squirrelbattle/settings.py | 4 + squirrelbattle/tests/entities_test.py | 35 +++- squirrelbattle/tests/game_test.py | 87 +++++++- squirrelbattle/tests/settings_test.py | 4 + squirrelbattle/tests/translations_test.py | 9 + squirrelbattle/translations.py | 11 + 20 files changed, 574 insertions(+), 168 deletions(-) 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/display/display_manager.py b/squirrelbattle/display/display_manager.py index f7a0882..0e9cf04 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,12 +47,14 @@ 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) 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 @@ -64,10 +67,15 @@ 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: + 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) - 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 9cc3ad5..1dd2e4c 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 @@ -24,8 +24,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): @@ -110,12 +108,22 @@ class MainMenuDisplay(Display): menuy, menux, min(self.menudisplay.preferred_height, self.height - menuy), menuwidth) -class VariableMenuDisplay(MenuDisplay): - """ - A class to display a menu in which each value is associated to a parameter - """ + +class InventoryDisplay(MenuDisplay): + def update_pad(self) -> None: + 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.translated_name.capitalize()) + @property - def values(self) -> List[str]: - return [a[1][1] + (" : " - + (a[1][0]) - if a[1][0] else "") for a in self.menu.values] + 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/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: diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 665dd6f..fcb4ce5 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -58,6 +58,7 @@ TexturePack.ASCII_PACK = TexturePack( TEDDY_BEAR='8', MERCHANT='M', SUNFLOWER='I', + BODY_SNATCH_POTION='S', ) TexturePack.SQUIRREL_PACK = TexturePack( @@ -79,4 +80,5 @@ TexturePack.SQUIRREL_PACK = TexturePack( TEDDY_BEAR='🧸', MERCHANT='🦜', SUNFLOWER='🌻', + BODY_SNATCH_POTION='🔀', ) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index e1326b9..ea5afeb 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 choice, randint from typing import Optional from .player import Player from ..interfaces import Entity, FightingEntity, Map +from ..translations import gettext as _ class Item(Entity): @@ -20,16 +22,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: """ @@ -55,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: @@ -81,26 +93,47 @@ class Bomb(Item): """ damage: int = 5 exploding: bool + 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 + self.owner = None - 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: + self.owner = self.held_by + 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: + # 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) <= 3 and \ + isinstance(e, FightingEntity): + 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: """ @@ -110,7 +143,7 @@ class Bomb(Item): d["exploding"] = self.exploding d["damage"] = self.damage return d - + class Weapon(Item): """ Non-throwable items that improve player damage @@ -136,4 +169,35 @@ class Sword(Weapon) : def __init__(self, name: int, *args, **kwargs): super().__init__(*args, **kwargs) self.name = "sword" - + +class BodySnatchPotion(Item): + """ + The body-snatch potion allows to exchange all characteristics with a random + other entity. + """ + + def __init__(self, name: str = "body_snatch_potion", *args, **kwargs): + super().__init__(name=name, *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.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.recalculate_paths() + + self.held_by.inventory.remove(self) diff --git a/squirrelbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py index 624f8a3..feff81a 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 @@ -59,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) @@ -69,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) @@ -79,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) @@ -89,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 6b7f084..c344f15 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -17,17 +17,24 @@ class Player(FightingEntity): paths: Dict[Tuple[int, int], Tuple[int, int]] hazel: int #It is the currency of this game - 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, hazel: int = 42, *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, inventory: list = None, + hazel: int = 42, *args, **kwargs) \ + -> None: + super().__init__(name=name, 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.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() self.hazel = hazel @@ -130,4 +137,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/enums.py b/squirrelbattle/enums.py index e563600..c62d199 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -38,6 +38,10 @@ class KeyValues(Enum): LEFT = auto() RIGHT = auto() ENTER = auto() + INVENTORY = auto() + USE = auto() + EQUIP = auto() + DROP = auto() SPACE = auto() T = auto() @@ -60,6 +64,14 @@ class KeyValues(Enum): return KeyValues.UP elif key == settings.KEY_ENTER: 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 elif key == 't': diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index ec54dae..a465097 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -40,6 +40,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 @@ -48,13 +49,14 @@ 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() 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: """ @@ -84,6 +86,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: @@ -106,6 +110,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 elif key == KeyValues.T : @@ -134,6 +140,29 @@ class Game: self.state = GameMode.STORE + 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 + elif key == KeyValues.UP: + self.inventory_menu.go_up() + elif key == KeyValues.DOWN: + self.inventory_menu.go_down() + if self.inventory_menu.values and not self.player.dead: + 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().drop() + + # 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: """ In the main menu, we can navigate through options. diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index c1f3078..db56156 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -344,11 +344,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 from squirrelbattle.entities.friendly import Merchant,Sunflower - return [Tiger, Bomb, Heart, Hedgehog, Rabbit, TeddyBear,Sunflower] + return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear,Sunflower,Tiger] @staticmethod def get_all_entity_classes_in_a_dict() -> dict: @@ -358,12 +358,13 @@ 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.friendly import Merchant,Sunflower + from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart return { "Tiger": Tiger, "Bomb": Bomb, "Heart": Heart, + "BodySnatchPotion": BodySnatchPotion, "Hedgehog": Hedgehog, "Rabbit": Rabbit, "TeddyBear": TeddyBear, @@ -447,7 +448,7 @@ class FightingEntity(Entity): """ Returns a fighting entity's 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 0416b84..37ceb39 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,11 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" +<<<<<<< HEAD "POT-Creation-Date: 2020-12-01 17:10+0100\n" +======= +"POT-Creation-Date: 2020-12-05 14:46+0100\n" +>>>>>>> master "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,7 +21,75 @@ 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/display/menudisplay.py:105 +msgid "== INVENTORY ==" +msgstr "== BESTAND ==" + +#: squirrelbattle/display/statsdisplay.py:34 +msgid "Inventory:" +msgstr "Bestand:" + +#: 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:128 +msgid "Bomb is exploding." +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 "" +"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:400 +#, python-brace-format +msgid "{name} hits {opponent}." +msgstr "{name} schlägt {opponent}." + +#: squirrelbattle/interfaces.py:412 +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} nimmt {amount} Schadenspunkte." + +#: squirrelbattle/interfaces.py:414 +#, python-brace-format +msgid "{name} dies." +msgstr "{name} stirbt." + +#: squirrelbattle/menus.py:72 +msgid "Back" +msgstr "Zurück" + +#: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303 +#: squirrelbattle/tests/game_test.py:306 #: squirrelbattle/tests/translations_test.py:16 #: squirrelbattle/tests/game_test.py:290 msgid "New game" @@ -80,40 +152,61 @@ 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 "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:46 +#: squirrelbattle/tests/translations_test.py:54 msgid "Language" msgstr "Sprache" -#: squirrelbattle/tests/translations_test.py:49 +#: squirrelbattle/tests/translations_test.py:57 msgid "player" msgstr "Spieler" -#: squirrelbattle/tests/translations_test.py:51 +#: squirrelbattle/tests/translations_test.py:59 msgid "tiger" msgstr "Tiger" -#: squirrelbattle/tests/translations_test.py:52 +#: squirrelbattle/tests/translations_test.py:60 msgid "hedgehog" msgstr "Igel" -#: squirrelbattle/tests/translations_test.py:53 +#: squirrelbattle/tests/translations_test.py:61 msgid "rabbit" msgstr "Kanninchen" -#: squirrelbattle/tests/translations_test.py:54 +#: squirrelbattle/tests/translations_test.py:62 msgid "teddy bear" msgstr "Teddybär" -#: squirrelbattle/tests/translations_test.py:56 +#: 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:57 +#: squirrelbattle/tests/translations_test.py:66 msgid "heart" msgstr "Herz" +<<<<<<< HEAD #: squirrelbattle/display/statsdisplay.py:34 msgid "Inventory:" @@ -173,3 +266,5 @@ msgstr "Blumenmacht!!" #: squirrelbattle/entities/friendly.py:31 msgid "The sun is warm today" msgstr "Die Sonne ist warm heute" +======= +>>>>>>> master diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index fb9024e..6f4675e 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,11 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" +<<<<<<< HEAD "POT-Creation-Date: 2020-12-01 17:10+0100\n" +======= +"POT-Creation-Date: 2020-12-05 14:46+0100\n" +>>>>>>> master "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,63 +21,35 @@ 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 :" -#: squirrelbattle/display/statsdisplay.py:39 +#: squirrelbattle/display/statsdisplay.py:50 msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" +<<<<<<< HEAD #: squirrelbattle/interfaces.py:394 squirrelbattle/interfaces.py:398 #: squirrelbattle/interfaces.py:408 +======= +#. 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 bombe explose." + +#: squirrelbattle/entities/items.py:172 #, python-brace-format -msgid "{name} hits {opponent}." -msgstr "{name} frappe {opponent}." +msgid "{player} exchanged its body with {entity}." +msgstr "{player} a échangé son corps avec {entity}." -#: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410 -#: squirrelbattle/interfaces.py:420 -#, 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/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/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" - -#: squirrelbattle/menus.py:71 -msgid "Back" -msgstr "Retour" - -#: squirrelbattle/game.py:147 squirrelbattle/game.py:148 +#: squirrelbattle/game.py:177 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -81,7 +57,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:185 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -89,7 +65,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:205 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -97,72 +73,119 @@ 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/interfaces.py:400 +>>>>>>> master +#, python-brace-format +msgid "{name} hits {opponent}." +msgstr "{name} frappe {opponent}." + +<<<<<<< HEAD +#: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410 +#: squirrelbattle/interfaces.py:420 +======= +#: squirrelbattle/interfaces.py:412 +>>>>>>> master +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} prend {amount} points de dégât." + +#: squirrelbattle/interfaces.py:414 +#, python-brace-format +msgid "{name} dies." +msgstr "{name} meurt." + +#: squirrelbattle/menus.py:72 +msgid "Back" +msgstr "Retour" + +#: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303 +#: squirrelbattle/tests/game_test.py:306 +#: squirrelbattle/tests/translations_test.py:16 +#: squirrelbattle/tests/game_test.py:290 +msgid "New game" +msgstr "Nouvelle partie" + +#: squirrelbattle/tests/translations_test.py:17 +msgid "Resume" +msgstr "Continuer" + +#: squirrelbattle/tests/translations_test.py:18 +msgid "Load" +msgstr "Charger" + +#: 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: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 "Key used to open the inventory" +msgstr "Touche utilisée pour ouvrir l'inventaire" + +#: 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:40 -#: squirrelbattle/tests/translations_test.py:44 -#: squirrelbattle/tests/translations_test.py:46 +#: squirrelbattle/tests/translations_test.py:54 msgid "Language" msgstr "Langue" +<<<<<<< HEAD #: squirrelbattle/interfaces.py:407 squirrelbattle/interfaces.py:412 #: squirrelbattle/interfaces.py:422 #, python-brace-format @@ -171,36 +194,37 @@ msgstr "{name} meurt." #: squirrelbattle/tests/translations_test.py:47 #: squirrelbattle/tests/translations_test.py:49 +======= +#: squirrelbattle/tests/translations_test.py:57 +>>>>>>> master msgid "player" msgstr "joueur" -#: squirrelbattle/tests/translations_test.py:49 -#: squirrelbattle/tests/translations_test.py:51 +#: squirrelbattle/tests/translations_test.py:59 msgid "tiger" msgstr "tigre" -#: squirrelbattle/tests/translations_test.py:50 -#: squirrelbattle/tests/translations_test.py:52 +#: squirrelbattle/tests/translations_test.py:60 msgid "hedgehog" msgstr "hérisson" -#: squirrelbattle/tests/translations_test.py:51 -#: squirrelbattle/tests/translations_test.py:53 +#: squirrelbattle/tests/translations_test.py:61 msgid "rabbit" msgstr "lapin" -#: squirrelbattle/tests/translations_test.py:52 -#: squirrelbattle/tests/translations_test.py:54 +#: squirrelbattle/tests/translations_test.py:62 msgid "teddy bear" msgstr "nounours" -#: squirrelbattle/tests/translations_test.py:54 -#: squirrelbattle/tests/translations_test.py:56 +#: 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:55 -#: squirrelbattle/tests/translations_test.py:57 +#: squirrelbattle/tests/translations_test.py:66 msgid "heart" msgstr "cœur" diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index 4fcfabe..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 @@ -115,3 +116,14 @@ class SettingsMenu(Menu): game.settings.write_settings() self.waiting_for_key = False self.update_values(game.settings) + + +class InventoryMenu(Menu): + player: Player + + def update_player(self, player: Player) -> None: + self.player = player + + @property + def values(self) -> list: + return self.player.inventory diff --git a/squirrelbattle/settings.py b/squirrelbattle/settings.py index 3090679..4004645 100644 --- a/squirrelbattle/settings.py +++ b/squirrelbattle/settings.py @@ -27,6 +27,10 @@ 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.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 = ['r', 'Key used to drop an item in the inventory'] self.TEXTURE_PACK = ['ascii', 'Texture pack'] self.LOCALE = [locale.getlocale()[0][:2], 'Language'] diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 371bfc7..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 @@ -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() @@ -149,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. diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index a23b6f9..887835b 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 @@ -31,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) @@ -44,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") @@ -107,6 +114,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), @@ -261,11 +280,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) + for ignored in range(9): + self.game.handle_key_pressed(KeyValues.DOWN) # Change texture pack self.assertEqual(self.game.settings.TEXTURE_PACK, "ascii") @@ -337,3 +353,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) 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..0cb39c5 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -42,6 +42,14 @@ 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(_("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") @@ -53,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") diff --git a/squirrelbattle/translations.py b/squirrelbattle/translations.py index f532bb0..1e97df6 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 @@ -53,6 +54,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"] @@ -65,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() @@ -77,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 8540a8f3541f05e9c5220240cbee688c6a735e62 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Mon, 7 Dec 2020 20:54:53 +0100 Subject: [PATCH 15/46] Work in progress on printing a store menu. Its buggy though --- squirrelbattle/display/display_manager.py | 20 +++++++++++---- squirrelbattle/display/menudisplay.py | 8 +++++- squirrelbattle/entities/friendly.py | 12 ++++++--- squirrelbattle/entities/items.py | 9 ++++--- squirrelbattle/game.py | 31 +++++++++++------------ squirrelbattle/interfaces.py | 10 ++++---- squirrelbattle/menus.py | 12 +++++++++ 7 files changed, 68 insertions(+), 34 deletions(-) diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 0e9cf04..1ef0bfb 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -7,7 +7,7 @@ from squirrelbattle.display.mapdisplay import MapDisplay from squirrelbattle.display.messagedisplay import MessageDisplay from squirrelbattle.display.statsdisplay import StatsDisplay from squirrelbattle.display.menudisplay import MainMenuDisplay, \ - InventoryDisplay, SettingsMenuDisplay + PlayerInventoryDisplay, StoreInventoryDisplay, SettingsMenuDisplay from squirrelbattle.display.logsdisplay import LogsDisplay from squirrelbattle.display.texturepack import TexturePack from typing import Any @@ -24,7 +24,8 @@ class DisplayManager: self.mapdisplay = MapDisplay(screen, pack) self.statsdisplay = StatsDisplay(screen, pack) self.logsdisplay = LogsDisplay(screen, pack) - self.inventorydisplay = InventoryDisplay(screen, pack) + self.playerinventorydisplay = PlayerInventoryDisplay(screen, pack) + self.storeinventorydisplay = StoreInventoryDisplay(screen, pack) self.mainmenudisplay = MainMenuDisplay(self.game.main_menu, screen, pack) self.settingsmenudisplay = SettingsMenuDisplay(screen, pack) @@ -47,14 +48,18 @@ 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.game.inventory_menu.update_player(self.game.player) + self.game.store_menu.update_merchant(self.game.player) + self.playerinventorydisplay.update_menu(self.game.inventory_menu) + self.storeinventorydisplay.update_menu(self.game.store_menu) 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 \ - or self.game.state == GameMode.INVENTORY: + or self.game.state == GameMode.INVENTORY \ + or self.game.state == GameMode.STORE : # The map pad has already the good size self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, self.mapdisplay.pack.tile_width @@ -68,7 +73,12 @@ class DisplayManager: 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.INVENTORY: - self.inventorydisplay.refresh(self.rows // 10, + self.playerinventorydisplay.refresh(self.rows // 10, + self.cols // 2, + 8 * self.rows // 10, + 2 * self.cols // 5) + elif self.game.state == GameMode.STORE: + self.storeinventorydisplay.refresh(self.rows // 10, self.cols // 2, 8 * self.rows // 10, 2 * self.cols // 5) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 1dd2e4c..a96914c 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -110,8 +110,8 @@ class MainMenuDisplay(Display): class InventoryDisplay(MenuDisplay): + message : str def update_pad(self) -> None: - 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): @@ -127,3 +127,9 @@ class InventoryDisplay(MenuDisplay): @property def trueheight(self) -> int: return 2 + super().trueheight + +class PlayerInventoryDisplay(InventoryDisplay): + message = _("== INVENTORY ==") + +class StoreInventoryDisplay(InventoryDisplay): + message = _("== STALL ==") diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 9bdb3d0..2454909 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -1,13 +1,14 @@ from ..interfaces import FriendlyEntity from ..translations import gettext as _ from .player import Player +from .items import Item +from random import choice class Merchant(FriendlyEntity) : """ The class for merchants in the dungeon """ - name = "Merchant" inventory = list hazel = int @@ -17,10 +18,13 @@ class Merchant(FriendlyEntity) : """ return ["maxhealth", "health", "inventory", "hazel"] - def __init__(self, inventory : list, hazel : int = 75): - super().__init__() - self.inventory = inventory + def __init__(self, name : str = "merchant", hazel : int = 75): + super().__init__(name = name) self.hazel = hazel + self.name = name + self.inventory = [] + for i in range(5) : + self.inventory.append(choice(Item.get_all_items())()) def talk_to(self, player : Player) -> str: """ diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 0029355..9756fa4 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -59,6 +59,9 @@ class Item(Entity): d = super().save_state() d["held"] = self.held return d + + def get_all_items() -> list: + return [BodySnatchPotion, Bomb, Heart, Weapon, Sword] class Heart(Item): @@ -166,9 +169,9 @@ class Sword(Weapon) : """ A basic weapon """ - def __init__(self, name: int, *args, **kwargs): - super().__init__(*args, **kwargs) - self.name = "sword" + def __init__(self, name: str = "sword", *args, **kwargs): + super().__init__(name = name, *args, **kwargs) + self.name = name class BodySnatchPotion(Item): diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index b20393d..82dee4f 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -41,6 +41,7 @@ class Game: self.settings_menu = menus.SettingsMenu() self.settings_menu.update_values(self.settings) self.inventory_menu = menus.InventoryMenu() + self.store_menu = menus.StoreMenu() self.logs = Logs() self.message = None @@ -92,6 +93,8 @@ class Game: self.handle_key_pressed_main_menu(key) elif self.state == GameMode.SETTINGS: self.settings_menu.handle_key_pressed(key, raw_key, self) + elif self.state == GameMode.STORE: + self.handle_key_pressed_store(key) self.display_actions(DisplayActions.REFRESH) def handle_key_pressed_play(self, key: KeyValues) -> None: @@ -138,6 +141,7 @@ class Game: self.logs.add_message(msg) if entity.is_merchant() : self.state = GameMode.STORE + self.store_menu.update_merchant(entity) def handle_key_pressed_inventory(self, key: KeyValues) -> None: @@ -162,28 +166,23 @@ class Game: self.inventory_menu.position = min(self.inventory_menu.position, len(self.inventory_menu.values) - 1) - - def handle_key_pressed_inventory(self, key: KeyValues) -> None: + + def handle_key_pressed_store(self, key: KeyValues) -> None: """ - In the inventory menu, we can interact with items or close the menu. + In a store menu, we can buy items or close the menu. """ - if key == KeyValues.SPACE or key == KeyValues.INVENTORY: + if key == KeyValues.SPACE : self.state = GameMode.PLAY elif key == KeyValues.UP: - self.inventory_menu.go_up() + self.store_menu.go_up() elif key == KeyValues.DOWN: - self.inventory_menu.go_down() - if self.inventory_menu.values and not self.player.dead: - 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().drop() - + self.store_menu.go_down() + if self.store_menu.values and not self.player.dead: + if key == KeyValues.ENTER: + self.player.add_to_inventory(self.store_menu.validate()) # Ensure that the cursor has a good position - self.inventory_menu.position = min(self.inventory_menu.position, - len(self.inventory_menu.values) + self.store_menu.position = min(self.store_menu.position, + len(self.store_menu.values) - 1) def handle_key_pressed_main_menu(self, key: KeyValues) -> None: diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 7f3f376..1c5e8b9 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -135,7 +135,7 @@ class Map: def spawn_random_entities(self, count: int) -> None: """ - Put randomly {count} hedgehogs on the map, where it is available. + Put randomly {count} entities on the map, where it is available. """ for ignored in range(count): y, x = 0, 0 @@ -333,6 +333,7 @@ class Entity: """ Is this entity a merchant? """ + from squirrelbattle.entities.friendly import Merchant return isinstance(self, Merchant) @property @@ -340,7 +341,7 @@ class Entity: return _(self.name.replace("_", " ")) @staticmethod - def get_all_entity_classes(): + def get_all_entity_classes() -> list: """ Returns all entities subclasses """ @@ -348,9 +349,8 @@ class Entity: from squirrelbattle.entities.monsters import Tiger, Hedgehog, \ Rabbit, TeddyBear from squirrelbattle.entities.friendly import Merchant,Sunflower - return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear,Sunflower,Tiger] - return [BodySnatchPotion, Bomb, Heart, Hedgehog, - Rabbit, TeddyBear, Tiger] + return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear, \ + Sunflower,Tiger,Merchant] @staticmethod def get_all_entity_classes_in_a_dict() -> dict: diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index d6946d0..6404997 100644 --- a/squirrelbattle/menus.py +++ b/squirrelbattle/menus.py @@ -6,6 +6,7 @@ from typing import Any, Optional from .display.texturepack import TexturePack from .entities.player import Player +from .entities.friendly import Merchant from .enums import GameMode, KeyValues, DisplayActions from .settings import Settings from .translations import gettext as _, Translator @@ -127,3 +128,14 @@ class InventoryMenu(Menu): @property def values(self) -> list: return self.player.inventory + +class StoreMenu(Menu) : + merchant: Merchant + + def update_merchant(self, merchant: Merchant) -> None: + self.merchant = merchant + + @property + def values(self) -> list: + return self.merchant.inventory + From b24cc1877f607c160ba50436d2b3f8c45bf4299f Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 7 Dec 2020 21:13:55 +0100 Subject: [PATCH 16/46] Merchant inventory is working! --- squirrelbattle/display/display_manager.py | 2 +- squirrelbattle/display/menudisplay.py | 2 +- squirrelbattle/display/texturepack.py | 2 ++ squirrelbattle/entities/friendly.py | 11 ++++++----- squirrelbattle/entities/items.py | 5 +++-- squirrelbattle/game.py | 6 ++---- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 1ef0bfb..9d3b1dc 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -59,7 +59,7 @@ class DisplayManager: def refresh(self) -> None: if self.game.state == GameMode.PLAY \ or self.game.state == GameMode.INVENTORY \ - or self.game.state == GameMode.STORE : + or self.game.state == GameMode.STORE: # The map pad has already the good size self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, self.mapdisplay.pack.tile_width diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index a96914c..3efd44c 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -112,7 +112,7 @@ class MainMenuDisplay(Display): class InventoryDisplay(MenuDisplay): message : str def update_pad(self) -> None: - self.addstr(self.pad, 0, (self.width - len(message)) // 2, message, + self.addstr(self.pad, 0, (self.width - len(self.message)) // 2, self.message, curses.A_BOLD | curses.A_ITALIC) for i, item in enumerate(self.menu.values): rep = self.pack[item.name.upper()] diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index fcb4ce5..55966fc 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -59,6 +59,7 @@ TexturePack.ASCII_PACK = TexturePack( MERCHANT='M', SUNFLOWER='I', BODY_SNATCH_POTION='S', + SWORD='\u2020', ) TexturePack.SQUIRREL_PACK = TexturePack( @@ -81,4 +82,5 @@ TexturePack.SQUIRREL_PACK = TexturePack( MERCHANT='🦜', SUNFLOWER='🌻', BODY_SNATCH_POTION='🔀', + SWORD='🗡️', ) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 2454909..4c973d2 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -18,12 +18,12 @@ class Merchant(FriendlyEntity) : """ return ["maxhealth", "health", "inventory", "hazel"] - def __init__(self, name : str = "merchant", hazel : int = 75): - super().__init__(name = name) + def __init__(self, name: str = "merchant", inventory: list = None, + hazel: int = 75, *args, **kwargs): + super().__init__(name=name, *args, **kwargs) + self.inventory = inventory or [] self.hazel = hazel - self.name = name - self.inventory = [] - for i in range(5) : + for i in range(5): self.inventory.append(choice(Item.get_all_items())()) def talk_to(self, player : Player) -> str: @@ -32,6 +32,7 @@ class Merchant(FriendlyEntity) : and allow the player to buy/sell objects """ # TODO + return _("I don't sell any squirrel") class Sunflower(FriendlyEntity) : """ diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 9756fa4..f448c0d 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -59,9 +59,10 @@ class Item(Entity): d = super().save_state() d["held"] = self.held return d - + + @staticmethod def get_all_items() -> list: - return [BodySnatchPotion, Bomb, Heart, Weapon, Sword] + return [BodySnatchPotion, Bomb, Heart, Sword] class Heart(Item): diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 82dee4f..10780cf 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -122,18 +122,16 @@ class Game: keykey = KeyValues.translate_key(keykey, self.settings) if keykey == KeyValues.UP: xp = self.player.x - yp = self.player.y+1 + yp = self.player.y-1 elif keykey == KeyValues.DOWN: xp = self.player.x - yp = self.player.y-1 + yp = self.player.y+1 elif keykey == KeyValues.LEFT: xp = self.player.x-1 yp = self.player.y elif keykey == KeyValues.RIGHT: xp = self.player.x+1 yp = self.player.y - else : - raise Exception(keykey) if self.map.entity_is_present(yp, xp) : for entity in self.map.entities : if entity.is_friendly() and entity.x == xp and entity.y == yp : From 57fab7db519c31a4414d7a036eb7e6a4242f55e6 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 7 Dec 2020 21:22:06 +0100 Subject: [PATCH 17/46] Linting --- squirrelbattle/display/display_manager.py | 14 +++--- squirrelbattle/display/menudisplay.py | 9 ++-- squirrelbattle/entities/friendly.py | 16 +++--- squirrelbattle/entities/items.py | 10 ++-- squirrelbattle/entities/player.py | 8 +-- squirrelbattle/game.py | 60 ++++++++++++----------- squirrelbattle/interfaces.py | 29 ++++++----- squirrelbattle/menus.py | 4 +- 8 files changed, 80 insertions(+), 70 deletions(-) diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 9d3b1dc..6680f3f 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -73,15 +73,13 @@ class DisplayManager: 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.INVENTORY: - self.playerinventorydisplay.refresh(self.rows // 10, - self.cols // 2, - 8 * self.rows // 10, - 2 * self.cols // 5) + self.playerinventorydisplay.refresh( + self.rows // 10, self.cols // 2, + 8 * self.rows // 10, 2 * self.cols // 5) elif self.game.state == GameMode.STORE: - self.storeinventorydisplay.refresh(self.rows // 10, - self.cols // 2, - 8 * self.rows // 10, - 2 * self.cols // 5) + self.storeinventorydisplay.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: diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 3efd44c..58b7fdf 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -110,10 +110,11 @@ class MainMenuDisplay(Display): class InventoryDisplay(MenuDisplay): - message : str + message: str + def update_pad(self) -> None: - self.addstr(self.pad, 0, (self.width - len(self.message)) // 2, self.message, - curses.A_BOLD | curses.A_ITALIC) + self.addstr(self.pad, 0, (self.width - len(self.message)) // 2, + self.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} " @@ -128,8 +129,10 @@ class InventoryDisplay(MenuDisplay): def trueheight(self) -> int: return 2 + super().trueheight + class PlayerInventoryDisplay(InventoryDisplay): message = _("== INVENTORY ==") + class StoreInventoryDisplay(InventoryDisplay): message = _("== STALL ==") diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 4c973d2..221707a 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -5,13 +5,13 @@ from .items import Item from random import choice -class Merchant(FriendlyEntity) : +class Merchant(FriendlyEntity): """ The class for merchants in the dungeon """ inventory = list hazel = int - + def keys(self) -> list: """ Returns a friendly entitie's specific attributes @@ -26,21 +26,21 @@ class Merchant(FriendlyEntity) : for i in range(5): self.inventory.append(choice(Item.get_all_items())()) - def talk_to(self, player : Player) -> str: + def talk_to(self, player: Player) -> str: """ This function is used to open the merchant's inventory in a menu, and allow the player to buy/sell objects """ # TODO return _("I don't sell any squirrel") - -class Sunflower(FriendlyEntity) : + + +class Sunflower(FriendlyEntity): """ A friendly sunflower """ dialogue_option = [_("Flower power!!"), _("The sun is warm today")] - + def __init__(self, maxhealth: int = 15, *args, **kwargs) -> None: - super().__init__(name="sunflower", - maxhealth=maxhealth, *args, **kwargs) + super().__init__(name="sunflower", maxhealth=maxhealth, *args, **kwargs) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index f448c0d..1c41cf1 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -147,7 +147,8 @@ class Bomb(Item): d["exploding"] = self.exploding d["damage"] = self.damage return d - + + class Weapon(Item): """ Non-throwable items that improve player damage @@ -166,14 +167,15 @@ class Weapon(Item): d["damage"] = self.damage return d -class Sword(Weapon) : + +class Sword(Weapon): """ A basic weapon """ def __init__(self, name: str = "sword", *args, **kwargs): - super().__init__(name = name, *args, **kwargs) + super().__init__(name=name, *args, **kwargs) self.name = name - + class BodySnatchPotion(Item): """ diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index c344f15..dce6839 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -15,7 +15,7 @@ class Player(FightingEntity): max_xp: int = 10 inventory: list paths: Dict[Tuple[int, int], Tuple[int, int]] - hazel: int #It is the currency of this game + hazel: int # It is the currency of this game def __init__(self, name: str = "player", maxhealth: int = 20, strength: int = 5, intelligence: int = 1, charisma: int = 1, @@ -91,7 +91,7 @@ class Player(FightingEntity): elif entity.is_item(): entity.hold(self) elif entity.is_friendly(): -# self.map.logs.add_message(entity.talk_to(self)) + # self.map.logs.add_message(entity.talk_to(self)) self.map.logs.add_message(self.hit(entity)) if entity.dead: self.add_xp(randint(3, 7)) @@ -124,12 +124,12 @@ class Player(FightingEntity): queue.append((new_y, new_x)) self.paths = predecessors - def add_to_inventory(self, obj : Any) -> None : + def add_to_inventory(self, obj: Any) -> None: """ Adds an object to inventory """ self.inventory.append(obj) - + def save_state(self) -> dict: """ Saves the state of the entity into a dictionary diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 10780cf..39ddac1 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -117,30 +117,35 @@ class Game: self.state = GameMode.INVENTORY elif key == KeyValues.SPACE: self.state = GameMode.MAINMENU - elif key == KeyValues.T : - keykey = self.screen.getkey() - keykey = KeyValues.translate_key(keykey, self.settings) - if keykey == KeyValues.UP: - xp = self.player.x - yp = self.player.y-1 - elif keykey == KeyValues.DOWN: - xp = self.player.x - yp = self.player.y+1 - elif keykey == KeyValues.LEFT: - xp = self.player.x-1 - yp = self.player.y - elif keykey == KeyValues.RIGHT: - xp = self.player.x+1 - yp = self.player.y - if self.map.entity_is_present(yp, xp) : - for entity in self.map.entities : - if entity.is_friendly() and entity.x == xp and entity.y == yp : - msg = entity.talk_to(self.player) - self.logs.add_message(msg) - if entity.is_merchant() : - self.state = GameMode.STORE - self.store_menu.update_merchant(entity) - + elif key == KeyValues.T: + self.handle_friendly_entity_chat() + + def handle_friendly_entity_chat(self) -> None: + keykey = self.screen.getkey() + keykey = KeyValues.translate_key(keykey, self.settings) + if keykey == KeyValues.UP: + xp = self.player.x + yp = self.player.y - 1 + elif keykey == KeyValues.DOWN: + xp = self.player.x + yp = self.player.y + 1 + elif keykey == KeyValues.LEFT: + xp = self.player.x - 1 + yp = self.player.y + elif keykey == KeyValues.RIGHT: + xp = self.player.x + 1 + yp = self.player.y + else: + return + if self.map.entity_is_present(yp, xp): + for entity in self.map.entities: + if entity.is_friendly() and entity.x == xp and \ + entity.y == yp: + msg = entity.talk_to(self.player) + self.logs.add_message(msg) + if entity.is_merchant(): + self.state = GameMode.STORE + self.store_menu.update_merchant(entity) def handle_key_pressed_inventory(self, key: KeyValues) -> None: """ @@ -164,12 +169,12 @@ class Game: self.inventory_menu.position = min(self.inventory_menu.position, len(self.inventory_menu.values) - 1) - + def handle_key_pressed_store(self, key: KeyValues) -> None: """ In a store menu, we can buy items or close the menu. """ - if key == KeyValues.SPACE : + if key == KeyValues.SPACE: self.state = GameMode.PLAY elif key == KeyValues.UP: self.store_menu.go_up() @@ -180,8 +185,7 @@ class Game: self.player.add_to_inventory(self.store_menu.validate()) # Ensure that the cursor has a good position self.store_menu.position = min(self.store_menu.position, - len(self.store_menu.values) - - 1) + len(self.store_menu.values) - 1) def handle_key_pressed_main_menu(self, key: KeyValues) -> None: """ diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 1c5e8b9..1984465 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -81,14 +81,15 @@ class Map: return 0 <= y < self.height and 0 <= x < self.width and \ self.tiles[y][x].can_walk() and \ not any(entity.x == x and entity.y == y for entity in self.entities) - + def entity_is_present(self, y: int, x: int) -> bool: """ - Indicates that the tile at the coordinates (y, x) contains a killable entity + Indicates that the tile at the coordinates (y, x) contains a killable + entity """ return 0 <= y < self.height and 0 <= x < self.width and \ - any(entity.x == x and entity.y == y and \ - entity.is_friendly() for entity in self.entities) + any(entity.x == x and entity.y == y and entity.is_friendly() + for entity in self.entities) @staticmethod def load(filename: str) -> "Map": @@ -348,9 +349,9 @@ class Entity: from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart from squirrelbattle.entities.monsters import Tiger, Hedgehog, \ Rabbit, TeddyBear - from squirrelbattle.entities.friendly import Merchant,Sunflower - return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear, \ - Sunflower,Tiger,Merchant] + from squirrelbattle.entities.friendly import Merchant, Sunflower + return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear, + Sunflower, Tiger, Merchant] @staticmethod def get_all_entity_classes_in_a_dict() -> dict: @@ -360,7 +361,7 @@ class Entity: from squirrelbattle.entities.player import Player from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \ TeddyBear - from squirrelbattle.entities.friendly import Merchant,Sunflower + from squirrelbattle.entities.friendly import Merchant, Sunflower from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart return { "Tiger": Tiger, @@ -462,17 +463,19 @@ class FightingEntity(Entity): d[name] = getattr(self, name) return d + class FriendlyEntity(FightingEntity): """ Friendly entities are living entities which do not attack the player """ - dialogue_option : list + dialogue_option: list - def talk_to(self, player : Any) -> str : - a = randint(0,len(self.dialogue_option)-1) - return "The "+self.name+" said : "+self.dialogue_option[a] + def talk_to(self, player: Any) -> str: + a = randint(0, len(self.dialogue_option) - 1) + return "The " + self.translated_name \ + + " said : " + self.dialogue_option[a] - def keys(self) -> list : + def keys(self) -> list: """ Returns a friendly entity's specific attributes """ diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index 6404997..a826ecd 100644 --- a/squirrelbattle/menus.py +++ b/squirrelbattle/menus.py @@ -129,7 +129,8 @@ class InventoryMenu(Menu): def values(self) -> list: return self.player.inventory -class StoreMenu(Menu) : + +class StoreMenu(Menu): merchant: Merchant def update_merchant(self, merchant: Merchant) -> None: @@ -138,4 +139,3 @@ class StoreMenu(Menu) : @property def values(self) -> list: return self.merchant.inventory - From 099508d4c05e9effaae147e70ce5e7c2503e395c Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 7 Dec 2020 21:29:57 +0100 Subject: [PATCH 18/46] Don't call screen.getkey() at a wrong place --- squirrelbattle/game.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 39ddac1..a7ea5de 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -33,6 +33,7 @@ class Game: Init the game. """ self.state = GameMode.MAINMENU + self.waiting_for_friendly_key = False self.settings = Settings() self.settings.load_settings() self.settings.write_settings() @@ -86,7 +87,11 @@ class Game: return if self.state == GameMode.PLAY: - self.handle_key_pressed_play(key) + if self.waiting_for_friendly_key: + # The player requested to talk with a friendly entity + self.handle_friendly_entity_chat(key) + else: + self.handle_key_pressed_play(key) elif self.state == GameMode.INVENTORY: self.handle_key_pressed_inventory(key) elif self.state == GameMode.MAINMENU: @@ -118,21 +123,28 @@ class Game: elif key == KeyValues.SPACE: self.state = GameMode.MAINMENU elif key == KeyValues.T: - self.handle_friendly_entity_chat() + # Wait for the direction of the friendly entity + self.waiting_for_friendly_key = True - def handle_friendly_entity_chat(self) -> None: - keykey = self.screen.getkey() - keykey = KeyValues.translate_key(keykey, self.settings) - if keykey == KeyValues.UP: + def handle_friendly_entity_chat(self, key: KeyValues) -> None: + """ + If the player is talking to a friendly entity, we get the direction + where the entity is, then we interact with it. + """ + if not self.waiting_for_friendly_key: + return + self.waiting_for_friendly_key = False + + if key == KeyValues.UP: xp = self.player.x yp = self.player.y - 1 - elif keykey == KeyValues.DOWN: + elif key == KeyValues.DOWN: xp = self.player.x yp = self.player.y + 1 - elif keykey == KeyValues.LEFT: + elif key == KeyValues.LEFT: xp = self.player.x - 1 yp = self.player.y - elif keykey == KeyValues.RIGHT: + elif key == KeyValues.RIGHT: xp = self.player.x + 1 yp = self.player.y else: From 2bb99a707e55d265da1aa7057950574ba41084a5 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 7 Dec 2020 21:48:56 +0100 Subject: [PATCH 19/46] Merchant inventory is well-saved --- squirrelbattle/entities/friendly.py | 21 +++++++++++++++++++-- squirrelbattle/entities/player.py | 2 +- squirrelbattle/interfaces.py | 4 +++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 221707a..52d2cd2 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -23,8 +23,17 @@ class Merchant(FriendlyEntity): super().__init__(name=name, *args, **kwargs) self.inventory = inventory or [] self.hazel = hazel - for i in range(5): - self.inventory.append(choice(Item.get_all_items())()) + + entity_classes = self.get_all_entity_classes_in_a_dict() + + for i in range(len(self.inventory)): + if isinstance(self.inventory[i], dict): + item_class = entity_classes[self.inventory[i]["type"]] + self.inventory[i] = item_class(**self.inventory[i]) + + if not self.inventory: + for i in range(5): + self.inventory.append(choice(Item.get_all_items())()) def talk_to(self, player: Player) -> str: """ @@ -34,6 +43,14 @@ class Merchant(FriendlyEntity): # TODO return _("I don't sell any squirrel") + def save_state(self) -> dict: + """ + We save the inventory of the merchant formatted as JSON + """ + d = super().save_state() + d["inventory"] = [item.save_state() for item in self.inventory] + return d + class Sunflower(FriendlyEntity): """ diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index dce6839..48a5b7f 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -30,9 +30,9 @@ class Player(FightingEntity): self.current_xp = current_xp self.max_xp = max_xp self.inventory = inventory if inventory else list() + entity_classes = self.get_all_entity_classes_in_a_dict() 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() diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 1984465..15f6852 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -362,7 +362,8 @@ class Entity: from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \ TeddyBear from squirrelbattle.entities.friendly import Merchant, Sunflower - from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart + from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \ + Heart, Sword return { "Tiger": Tiger, "Bomb": Bomb, @@ -374,6 +375,7 @@ class Entity: "Player": Player, "Merchant": Merchant, "Sunflower": Sunflower, + "Sword": Sword, } def save_state(self) -> dict: From ca17bf424a8aa3a42113597aeda32f6897732383 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 7 Dec 2020 21:51:51 +0100 Subject: [PATCH 20/46] Inventory displays were not updated when the texture pack changed --- squirrelbattle/display/display_manager.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 6680f3f..38104ee 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -34,7 +34,9 @@ class DisplayManager: self.vbar = VerticalSplit(screen, pack) self.displays = [self.statsdisplay, self.mapdisplay, self.mainmenudisplay, self.settingsmenudisplay, - self.logsdisplay, self.messagedisplay] + self.logsdisplay, self.messagedisplay, + self.playerinventorydisplay, + self.storeinventorydisplay] self.update_game_components() def handle_display_action(self, action: DisplayActions) -> None: From e3ae7bcacfbff6c73f31df8fc95f7e2a340ef3cb Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 7 Dec 2020 22:00:27 +0100 Subject: [PATCH 21/46] Resolve merge conflicts on german translation file --- .../locale/de/LC_MESSAGES/squirrelbattle.po | 72 +------------------ 1 file changed, 1 insertion(+), 71 deletions(-) diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 250a036..38d16a6 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -1,22 +1,12 @@ -# 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" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -<<<<<<< HEAD -<<<<<<< HEAD -"POT-Creation-Date: 2020-12-01 17:10+0100\n" -======= "POT-Creation-Date: 2020-12-05 14:46+0100\n" ->>>>>>> master -======= -"POT-Creation-Date: 2020-12-05 14:46+0100\n" ->>>>>>> master "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -95,7 +85,6 @@ msgstr "Zurück" #: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303 #: squirrelbattle/tests/game_test.py:306 #: squirrelbattle/tests/translations_test.py:16 -#: squirrelbattle/tests/game_test.py:290 msgid "New game" msgstr "Neu Spiel" @@ -210,62 +199,3 @@ msgstr "Bombe" #: squirrelbattle/tests/translations_test.py:66 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 squirrelbattle/interfaces.py:408 -#, python-brace-format -msgid "{name} hits {opponent}." -msgstr "{name} schlägt {opponent}." - -#: squirrelbattle/interfaces.py:410 squirrelbattle/interfaces.py:420 -#, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} nimmt {amount} Schadenspunkte." - -#: squirrelbattle/interfaces.py:412 squirrelbattle/interfaces.py:422 -#, 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." - -#: squirrelbattle/entities/friendly.py:31 -msgid "Flower power!!" -msgstr "Blumenmacht!!" - -#: squirrelbattle/entities/friendly.py:31 -msgid "The sun is warm today" -msgstr "Die Sonne ist warm heute" From 7ab421327359f30528a450eb7932e3b71c897db5 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 7 Dec 2020 22:03:48 +0100 Subject: [PATCH 22/46] Fix french translation file --- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 28 ++----------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 54f14c8..85bd728 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -1,20 +1,17 @@ -# 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 "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-12-01 17:10+0100\n" -"POT-Creation-Date: 2020-12-05 14:46+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" -"Language: \n" +"Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -31,8 +28,6 @@ msgstr "Inventaire :" msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" -#: squirrelbattle/interfaces.py:394 squirrelbattle/interfaces.py:398 -#: squirrelbattle/interfaces.py:408 #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. @@ -74,8 +69,6 @@ msgstr "" msgid "{name} hits {opponent}." msgstr "{name} frappe {opponent}." -#: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410 -#: squirrelbattle/interfaces.py:420 #: squirrelbattle/interfaces.py:412 #, python-brace-format msgid "{name} takes {amount} damage." @@ -93,7 +86,6 @@ msgstr "Retour" #: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303 #: squirrelbattle/tests/game_test.py:306 #: squirrelbattle/tests/translations_test.py:16 -#: squirrelbattle/tests/game_test.py:290 msgid "New game" msgstr "Nouvelle partie" @@ -177,14 +169,6 @@ msgstr "Pack de textures" msgid "Language" msgstr "Langue" -#: squirrelbattle/interfaces.py:407 squirrelbattle/interfaces.py:412 -#: squirrelbattle/interfaces.py:422 -#, 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:57 msgid "player" msgstr "joueur" @@ -216,11 +200,3 @@ msgstr "bombe" #: squirrelbattle/tests/translations_test.py:66 msgid "heart" msgstr "cœur" - -#: squirrelbattle/entities/friendly.py:31 -msgid "Flower power!!" -msgstr "Pouvoir des fleurs !!" - -#: squirrelbattle/entities/friendly.py:31 -msgid "The sun is warm today" -msgstr "Le soleil est chaud aujourd'hui" From 39787861dcbfee57e6d04361c5be2d7e267e915c Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 9 Dec 2020 15:05:19 +0100 Subject: [PATCH 23/46] Test sunflowers interaction --- squirrelbattle/tests/game_test.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 887835b..9c14d40 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.friendly import Sunflower from ..entities.items import Bomb from ..entities.player import Player from ..enums import DisplayActions @@ -414,3 +415,32 @@ class TestGame(unittest.TestCase): self.assertTrue(bomb.exploding) self.assertEqual(bomb.y, self.game.player.y) self.assertEqual(bomb.x, self.game.player.x) + + def test_talk_to_sunflowers(self) -> None: + """ + Interact with sunflowers + """ + self.game.state = GameMode.PLAY + + sunflower = Sunflower() + sunflower.move(2, 6) + self.game.map.add_entity(sunflower) + + # Talk to sunflower... or not + self.game.handle_key_pressed(KeyValues.T) + self.assertTrue(self.game.waiting_for_friendly_key) + self.game.handle_key_pressed(KeyValues.UP) + self.assertFalse(self.game.waiting_for_friendly_key) + self.assertEqual(self.game.state, GameMode.PLAY) + self.assertFalse(len(self.game.logs.messages) > 1) + + # Talk to sunflower + self.game.handle_key_pressed(KeyValues.T) + self.assertTrue(self.game.waiting_for_friendly_key) + self.game.handle_key_pressed(KeyValues.DOWN) + self.assertFalse(self.game.waiting_for_friendly_key) + self.assertEqual(self.game.state, GameMode.PLAY) + self.assertTrue(self.game.logs.messages) + # Ensure that the message is a good message + self.assertIn(self.game.logs.messages[1][21:], + Sunflower.dialogue_option) From cea015d015b43055831b436c7b83ad0b5b3d1d3d Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 9 Dec 2020 15:09:07 +0100 Subject: [PATCH 24/46] In load&save test, force to have a merchant --- squirrelbattle/tests/game_test.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 9c14d40..4a57ba0 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -7,7 +7,7 @@ import unittest from ..bootstrap import Bootstrap from ..display.display import Display from ..display.display_manager import DisplayManager -from ..entities.friendly import Sunflower +from ..entities.friendly import Sunflower, Merchant from ..entities.items import Bomb from ..entities.player import Player from ..enums import DisplayActions @@ -35,7 +35,14 @@ class TestGame(unittest.TestCase): """ bomb = Bomb() self.game.map.add_entity(bomb) + # Add an item in the inventory to check that it is well loaded bomb.hold(self.game.player) + + # Ensure that merchants can be saved + merchant = Merchant() + merchant.move(3, 6) + self.game.map.add_entity(merchant) + old_state = self.game.save_state() self.game.handle_key_pressed(KeyValues.DOWN) From 657f4e5209cda59073eaabcb865c577127a727a3 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 9 Dec 2020 15:10:28 +0100 Subject: [PATCH 25/46] Remove dead code: friendly entities are fighting entities --- squirrelbattle/entities/player.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 48a5b7f..ce27c37 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -90,11 +90,6 @@ class Player(FightingEntity): return True elif entity.is_item(): entity.hold(self) - elif entity.is_friendly(): - # self.map.logs.add_message(entity.talk_to(self)) - self.map.logs.add_message(self.hit(entity)) - if entity.dead: - self.add_xp(randint(3, 7)) return super().check_move(y, x, move_if_possible) def recalculate_paths(self, max_distance: int = 8) -> None: From 19b82ff71a253c2d0c1de5799991f15b111e5630 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 9 Dec 2020 15:32:37 +0100 Subject: [PATCH 26/46] Test merchants --- squirrelbattle/entities/friendly.py | 2 +- squirrelbattle/entities/items.py | 2 +- squirrelbattle/enums.py | 6 +-- squirrelbattle/game.py | 3 +- squirrelbattle/interfaces.py | 2 +- squirrelbattle/settings.py | 1 + squirrelbattle/tests/game_test.py | 78 ++++++++++++++++++++++++++--- 7 files changed, 80 insertions(+), 14 deletions(-) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 52d2cd2..205e92b 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -16,7 +16,7 @@ class Merchant(FriendlyEntity): """ Returns a friendly entitie's specific attributes """ - return ["maxhealth", "health", "inventory", "hazel"] + return super().keys() + ["inventory", "hazel"] def __init__(self, name: str = "merchant", inventory: list = None, hazel: int = 75, *args, **kwargs): diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 1c41cf1..123362f 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -50,7 +50,7 @@ class Item(Entity): self.held = True self.held_by = player self.map.remove_entity(self) - player.inventory.append(self) + player.add_to_inventory(self) def save_state(self) -> dict: """ diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index c62d199..7c0798d 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -43,7 +43,7 @@ class KeyValues(Enum): EQUIP = auto() DROP = auto() SPACE = auto() - T = auto() + CHAT = auto() @staticmethod def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]: @@ -74,6 +74,6 @@ class KeyValues(Enum): return KeyValues.DROP elif key == ' ': return KeyValues.SPACE - elif key == 't': - return KeyValues.T + elif key == settings.KEY_CHAT: + return KeyValues.CHAT return None diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index a7ea5de..2aa21f5 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -66,7 +66,6 @@ class Game: We wait for the player's action, then we do what that should be done when the given key gets pressed. """ - self.screen = screen while True: # pragma no cover screen.erase() screen.refresh() @@ -122,7 +121,7 @@ class Game: self.state = GameMode.INVENTORY elif key == KeyValues.SPACE: self.state = GameMode.MAINMENU - elif key == KeyValues.T: + elif key == KeyValues.CHAT: # Wait for the direction of the friendly entity self.waiting_for_friendly_key = True diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 15f6852..5efdf7f 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -481,4 +481,4 @@ class FriendlyEntity(FightingEntity): """ Returns a friendly entity's specific attributes """ - return ["maxhealth", "health", "dialogue_option"] + return ["maxhealth", "health"] diff --git a/squirrelbattle/settings.py b/squirrelbattle/settings.py index 4004645..4e6040c 100644 --- a/squirrelbattle/settings.py +++ b/squirrelbattle/settings.py @@ -31,6 +31,7 @@ class Settings: 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 = ['r', 'Key used to drop an item in the inventory'] + self.KEY_CHAT = ['t', 'Key used to talk to a friendly entity'] 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 4a57ba0..e510012 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -7,8 +7,8 @@ import unittest from ..bootstrap import Bootstrap from ..display.display import Display from ..display.display_manager import DisplayManager -from ..entities.friendly import Sunflower, Merchant -from ..entities.items import Bomb +from ..entities.friendly import Merchant, Sunflower +from ..entities.items import Bomb, Sword from ..entities.player import Player from ..enums import DisplayActions from ..game import Game, KeyValues, GameMode @@ -35,8 +35,11 @@ class TestGame(unittest.TestCase): """ bomb = Bomb() self.game.map.add_entity(bomb) - # Add an item in the inventory to check that it is well loaded + sword = Sword() + self.game.map.add_entity(sword) + # Add items in the inventory to check that it is well loaded bomb.hold(self.game.player) + sword.hold(self.game.player) # Ensure that merchants can be saved merchant = Merchant() @@ -125,6 +128,9 @@ class TestGame(unittest.TestCase): self.assertEqual(KeyValues.translate_key( self.game.settings.KEY_INVENTORY, self.game.settings), KeyValues.INVENTORY) + self.assertEqual(KeyValues.translate_key( + self.game.settings.KEY_CHAT, self.game.settings), + KeyValues.CHAT) self.assertEqual(KeyValues.translate_key( self.game.settings.KEY_USE, self.game.settings), KeyValues.USE) @@ -288,7 +294,7 @@ class TestGame(unittest.TestCase): self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a') # Navigate to "texture pack" - for ignored in range(9): + for ignored in range(10): self.game.handle_key_pressed(KeyValues.DOWN) # Change texture pack @@ -433,8 +439,16 @@ class TestGame(unittest.TestCase): sunflower.move(2, 6) self.game.map.add_entity(sunflower) + # Does nothing + self.assertIsNone(self.game.handle_friendly_entity_chat(KeyValues.UP)) + # Talk to sunflower... or not - self.game.handle_key_pressed(KeyValues.T) + self.game.handle_key_pressed(KeyValues.CHAT) + self.assertTrue(self.game.waiting_for_friendly_key) + # Wrong key + self.game.handle_key_pressed(KeyValues.EQUIP) + self.assertFalse(self.game.waiting_for_friendly_key) + self.game.handle_key_pressed(KeyValues.CHAT) self.assertTrue(self.game.waiting_for_friendly_key) self.game.handle_key_pressed(KeyValues.UP) self.assertFalse(self.game.waiting_for_friendly_key) @@ -442,7 +456,7 @@ class TestGame(unittest.TestCase): self.assertFalse(len(self.game.logs.messages) > 1) # Talk to sunflower - self.game.handle_key_pressed(KeyValues.T) + self.game.handle_key_pressed(KeyValues.CHAT) self.assertTrue(self.game.waiting_for_friendly_key) self.game.handle_key_pressed(KeyValues.DOWN) self.assertFalse(self.game.waiting_for_friendly_key) @@ -451,3 +465,55 @@ class TestGame(unittest.TestCase): # Ensure that the message is a good message self.assertIn(self.game.logs.messages[1][21:], Sunflower.dialogue_option) + + # Test all directions to detect the friendly entity + self.game.player.move(3, 6) + self.game.handle_key_pressed(KeyValues.CHAT) + self.game.handle_key_pressed(KeyValues.UP) + self.assertEqual(len(self.game.logs.messages), 3) + self.game.player.move(2, 7) + self.game.handle_key_pressed(KeyValues.CHAT) + self.game.handle_key_pressed(KeyValues.LEFT) + self.assertEqual(len(self.game.logs.messages), 4) + self.game.player.move(2, 5) + self.game.handle_key_pressed(KeyValues.CHAT) + self.game.handle_key_pressed(KeyValues.RIGHT) + self.assertEqual(len(self.game.logs.messages), 5) + + def test_talk_to_merchant(self) -> None: + """ + Interact with merchants + """ + self.game.state = GameMode.PLAY + + merchant = Merchant() + merchant.move(2, 6) + self.game.map.add_entity(merchant) + + # Does nothing + self.assertIsNone(self.game.handle_friendly_entity_chat(KeyValues.UP)) + + # Talk to merchant + self.game.handle_key_pressed(KeyValues.CHAT) + self.assertTrue(self.game.waiting_for_friendly_key) + self.game.handle_key_pressed(KeyValues.DOWN) + self.assertFalse(self.game.waiting_for_friendly_key) + self.assertEqual(self.game.state, GameMode.STORE) + self.assertTrue(self.game.logs.messages) + + # Navigate in the menu + self.game.handle_key_pressed(KeyValues.DOWN) + self.game.handle_key_pressed(KeyValues.DOWN) + self.game.handle_key_pressed(KeyValues.UP) + self.assertEqual(self.game.store_menu.position, 1) + + # Buy the second item + item = self.game.store_menu.validate() + self.assertIn(item, merchant.inventory) + self.game.handle_key_pressed(KeyValues.ENTER) + self.assertIn(item, self.game.player.inventory) + self.assertNotIn(item, merchant.inventory) + + # Exit the menu + self.game.handle_key_pressed(KeyValues.SPACE) + self.assertEqual(self.game.state, GameMode.PLAY) From 405ee895d8fd90fd2065b2bfedfbc283b5c05ce1 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 9 Dec 2020 15:35:53 +0100 Subject: [PATCH 27/46] When an item is bought, remove it from the merchant inventory. Fixes #37 --- squirrelbattle/game.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 2aa21f5..685eb6b 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -193,7 +193,9 @@ class Game: self.store_menu.go_down() if self.store_menu.values and not self.player.dead: if key == KeyValues.ENTER: - self.player.add_to_inventory(self.store_menu.validate()) + item = self.store_menu.validate() + self.player.add_to_inventory(item) + self.store_menu.merchant.inventory.remove(item) # Ensure that the cursor has a good position self.store_menu.position = min(self.store_menu.position, len(self.store_menu.values) - 1) From 05e79c14e3dd141e4e00389c9f6c9688ee0a3327 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 9 Dec 2020 15:45:50 +0100 Subject: [PATCH 28/46] Translate the settings entry that change the chat key --- .../locale/de/LC_MESSAGES/squirrelbattle.po | 69 ++++++++++++------ .../locale/fr/LC_MESSAGES/squirrelbattle.po | 70 ++++++++++++------- squirrelbattle/tests/translations_test.py | 2 + 3 files changed, 94 insertions(+), 47 deletions(-) diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 38d16a6..3f46b32 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -1,12 +1,14 @@ -# German translation of Squirrel Battle +# 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-05 14:46+0100\n" +"POT-Creation-Date: 2020-12-09 15:40+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,10 +17,14 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/display/menudisplay.py:105 +#: squirrelbattle/display/menudisplay.py:134 msgid "== INVENTORY ==" msgstr "== BESTAND ==" +#: squirrelbattle/display/menudisplay.py:138 +msgid "== STALL ==" +msgstr "== STAND ==" + #: squirrelbattle/display/statsdisplay.py:34 msgid "Inventory:" msgstr "Bestand:" @@ -27,19 +33,32 @@ msgstr "Bestand:" msgid "YOU ARE DEAD" msgstr "SIE WURDEN GESTORBEN" +#. TODO +#: squirrelbattle/entities/friendly.py:44 +msgid "I don't sell any squirrel" +msgstr "Ich verkaufe keinen Eichhörnchen." + +#: squirrelbattle/entities/friendly.py:59 +msgid "Flower power!!" +msgstr "Blumenmacht!!" + +#: squirrelbattle/entities/friendly.py:59 +msgid "The sun is warm today" +msgstr "Die Sonne ist warm heute" + #. 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 +#: squirrelbattle/entities/items.py:132 msgid "Bomb is exploding." msgstr "Die Bombe explodiert." -#: squirrelbattle/entities/items.py:172 +#: squirrelbattle/entities/items.py:204 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} täuscht seinem Körper mit {entity} aus." -#: squirrelbattle/game.py:177 +#: squirrelbattle/game.py:241 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -47,7 +66,7 @@ msgstr "" "In Ihrer Speicherdatei fehlen einige Schlüssel.\n" "Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht." -#: squirrelbattle/game.py:185 +#: squirrelbattle/game.py:249 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -55,7 +74,7 @@ msgstr "" "Auf dieser Karte wurde kein Spieler gefunden!\n" "Vielleicht sind Sie gestorben?" -#: squirrelbattle/game.py:205 +#: squirrelbattle/game.py:269 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -63,27 +82,27 @@ msgstr "" "Die JSON-Datei ist nicht korrekt.\n" "Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." -#: squirrelbattle/interfaces.py:400 +#: squirrelbattle/interfaces.py:428 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} schlägt {opponent}." -#: squirrelbattle/interfaces.py:412 +#: squirrelbattle/interfaces.py:440 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} nimmt {amount} Schadenspunkte." -#: squirrelbattle/interfaces.py:414 +#: squirrelbattle/interfaces.py:442 #, python-brace-format msgid "{name} dies." msgstr "{name} stirbt." -#: squirrelbattle/menus.py:72 +#: squirrelbattle/menus.py:73 msgid "Back" msgstr "Zurück" -#: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303 -#: squirrelbattle/tests/game_test.py:306 +#: squirrelbattle/tests/game_test.py:314 squirrelbattle/tests/game_test.py:317 +#: squirrelbattle/tests/game_test.py:320 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Neu Spiel" @@ -161,41 +180,45 @@ 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 "Key used to talk to a friendly entity" +msgstr "Taste um mit einer friedlicher Entität zu sprechen" + +#: squirrelbattle/tests/translations_test.py:55 msgid "Texture pack" msgstr "Textur-Packung" -#: squirrelbattle/tests/translations_test.py:54 +#: squirrelbattle/tests/translations_test.py:56 msgid "Language" msgstr "Sprache" -#: squirrelbattle/tests/translations_test.py:57 +#: squirrelbattle/tests/translations_test.py:59 msgid "player" msgstr "Spieler" -#: squirrelbattle/tests/translations_test.py:59 +#: squirrelbattle/tests/translations_test.py:61 msgid "tiger" msgstr "Tiger" -#: squirrelbattle/tests/translations_test.py:60 +#: squirrelbattle/tests/translations_test.py:62 msgid "hedgehog" msgstr "Igel" -#: squirrelbattle/tests/translations_test.py:61 +#: squirrelbattle/tests/translations_test.py:63 msgid "rabbit" msgstr "Kanninchen" -#: squirrelbattle/tests/translations_test.py:62 +#: squirrelbattle/tests/translations_test.py:64 msgid "teddy bear" msgstr "Teddybär" -#: squirrelbattle/tests/translations_test.py:64 +#: squirrelbattle/tests/translations_test.py:66 msgid "body snatch potion" msgstr "Leichenfleddererzaubertrank" -#: squirrelbattle/tests/translations_test.py:65 +#: squirrelbattle/tests/translations_test.py:67 msgid "bomb" msgstr "Bombe" -#: squirrelbattle/tests/translations_test.py:66 +#: squirrelbattle/tests/translations_test.py:68 msgid "heart" msgstr "Herz" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 85bd728..d949d9a 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -1,25 +1,30 @@ -# French translation of Squirrel Battle +# 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-05 14:46+0100\n" +"POT-Creation-Date: 2020-12-09 15:40+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" -"Language: fr\n" +"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/display/menudisplay.py:105 +#: squirrelbattle/display/menudisplay.py:134 msgid "== INVENTORY ==" msgstr "== INVENTAIRE ==" +#: squirrelbattle/display/menudisplay.py:138 +msgid "== STALL ==" +msgstr "== STAND ==" + #: squirrelbattle/display/statsdisplay.py:34 msgid "Inventory:" msgstr "Inventaire :" @@ -28,19 +33,32 @@ msgstr "Inventaire :" msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" +#. TODO +#: squirrelbattle/entities/friendly.py:44 +msgid "I don't sell any squirrel" +msgstr "Je ne vends pas d'écureuil" + +#: squirrelbattle/entities/friendly.py:59 +msgid "Flower power!!" +msgstr "Pouvoir des fleurs !" + +#: squirrelbattle/entities/friendly.py:59 +msgid "The sun is warm today" +msgstr "Le soleil est chaud aujourd'hui" + #. 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 +#: squirrelbattle/entities/items.py:132 msgid "Bomb is exploding." msgstr "La bombe explose." -#: squirrelbattle/entities/items.py:172 +#: squirrelbattle/entities/items.py:204 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} a échangé son corps avec {entity}." -#: squirrelbattle/game.py:177 +#: squirrelbattle/game.py:241 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -48,7 +66,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:185 +#: squirrelbattle/game.py:249 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -56,7 +74,7 @@ msgstr "" "Aucun joueur n'a été trouvé sur la carte !\n" "Peut-être êtes-vous mort ?" -#: squirrelbattle/game.py:205 +#: squirrelbattle/game.py:269 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -64,27 +82,27 @@ msgstr "" "Le fichier JSON de sauvegarde est incorrect.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/interfaces.py:400 +#: squirrelbattle/interfaces.py:428 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} frappe {opponent}." -#: squirrelbattle/interfaces.py:412 +#: squirrelbattle/interfaces.py:440 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} prend {amount} points de dégât." -#: squirrelbattle/interfaces.py:414 +#: squirrelbattle/interfaces.py:442 #, python-brace-format msgid "{name} dies." msgstr "{name} meurt." -#: squirrelbattle/menus.py:72 +#: squirrelbattle/menus.py:73 msgid "Back" msgstr "Retour" -#: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303 -#: squirrelbattle/tests/game_test.py:306 +#: squirrelbattle/tests/game_test.py:314 squirrelbattle/tests/game_test.py:317 +#: squirrelbattle/tests/game_test.py:320 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Nouvelle partie" @@ -162,41 +180,45 @@ 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 "Key used to talk to a friendly entity" +msgstr "Touche pour parler à une entité pacifique" + +#: squirrelbattle/tests/translations_test.py:55 msgid "Texture pack" msgstr "Pack de textures" -#: squirrelbattle/tests/translations_test.py:54 +#: squirrelbattle/tests/translations_test.py:56 msgid "Language" msgstr "Langue" -#: squirrelbattle/tests/translations_test.py:57 +#: squirrelbattle/tests/translations_test.py:59 msgid "player" msgstr "joueur" -#: squirrelbattle/tests/translations_test.py:59 +#: squirrelbattle/tests/translations_test.py:61 msgid "tiger" msgstr "tigre" -#: squirrelbattle/tests/translations_test.py:60 +#: squirrelbattle/tests/translations_test.py:62 msgid "hedgehog" msgstr "hérisson" -#: squirrelbattle/tests/translations_test.py:61 +#: squirrelbattle/tests/translations_test.py:63 msgid "rabbit" msgstr "lapin" -#: squirrelbattle/tests/translations_test.py:62 +#: squirrelbattle/tests/translations_test.py:64 msgid "teddy bear" msgstr "nounours" -#: squirrelbattle/tests/translations_test.py:64 +#: squirrelbattle/tests/translations_test.py:66 msgid "body snatch potion" msgstr "potion d'arrachage de corps" -#: squirrelbattle/tests/translations_test.py:65 +#: squirrelbattle/tests/translations_test.py:67 msgid "bomb" msgstr "bombe" -#: squirrelbattle/tests/translations_test.py:66 +#: squirrelbattle/tests/translations_test.py:68 msgid "heart" msgstr "cœur" diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index 0cb39c5..876b652 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -50,6 +50,8 @@ class TestTranslations(unittest.TestCase): "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(_("Key used to talk to a friendly entity"), + "Touche pour parler à une entité pacifique") self.assertEqual(_("Texture pack"), "Pack de textures") self.assertEqual(_("Language"), "Langue") From 56627f3e845bf0663ddf89881f096324b6953bd2 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 9 Dec 2020 16:50:47 +0100 Subject: [PATCH 29/46] Reorder texture pack entries --- squirrelbattle/display/texturepack.py | 63 ++++++++++++++++----------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 55966fc..880f9c4 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -14,10 +14,21 @@ class TexturePack: tile_bg_color: int entity_fg_color: int entity_bg_color: int + + BODY_SNATCH_POTION: str + BOMB: str + HEART: str + HEDGEHOG: str EMPTY: str - WALL: str FLOOR: str + MERCHANT: str PLAYER: str + RABBIT: str + SUNFLOWER: str + SWORD: str + TEDDY_BEAR: str + TIGER: str + WALL: str ASCII_PACK: "TexturePack" SQUIRREL_PACK: "TexturePack" @@ -46,20 +57,21 @@ TexturePack.ASCII_PACK = TexturePack( tile_bg_color=curses.COLOR_BLACK, entity_fg_color=curses.COLOR_WHITE, entity_bg_color=curses.COLOR_BLACK, - EMPTY=' ', - WALL='#', - FLOOR='.', - PLAYER='@', - HEDGEHOG='*', - HEART='❤', - BOMB='o', - RABBIT='Y', - TIGER='n', - TEDDY_BEAR='8', - MERCHANT='M', - SUNFLOWER='I', + BODY_SNATCH_POTION='S', + BOMB='o', + EMPTY=' ', + FLOOR='.', + HEART='❤', + HEDGEHOG='*', + MERCHANT='M', + PLAYER='@', + RABBIT='Y', + SUNFLOWER='I', SWORD='\u2020', + TEDDY_BEAR='8', + TIGER='n', + WALL='#', ) TexturePack.SQUIRREL_PACK = TexturePack( @@ -69,18 +81,19 @@ TexturePack.SQUIRREL_PACK = TexturePack( tile_bg_color=curses.COLOR_BLACK, entity_fg_color=curses.COLOR_WHITE, entity_bg_color=curses.COLOR_WHITE, - EMPTY=' ', - WALL='🧱', - FLOOR='██', - PLAYER='🐿️ ️', - HEDGEHOG='🦔', - HEART='💜', - BOMB='💣', - RABBIT='🐇', - TIGER='🐅', - TEDDY_BEAR='🧸', - MERCHANT='🦜', - SUNFLOWER='🌻', + BODY_SNATCH_POTION='🔀', + BOMB='💣', + EMPTY=' ', + FLOOR='██', + HEART='💜', + HEDGEHOG='🦔', + PLAYER='🐿️ ️', + MERCHANT='🦜', + RABBIT='🐇', + SUNFLOWER='🌻', SWORD='🗡️', + TEDDY_BEAR='🧸', + TIGER='🐅', + WALL='🧱', ) From 2d5a2e4c873e46f7ba52d6b1b7794efd5e423289 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 9 Dec 2020 16:54:53 +0100 Subject: [PATCH 30/46] Display hazels in the StatsDisplay --- squirrelbattle/display/statsdisplay.py | 5 ++++- squirrelbattle/display/texturepack.py | 3 +++ squirrelbattle/entities/player.py | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index ac1a89c..ec0c90a 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -46,8 +46,11 @@ class StatsDisplay(Display): printed_items.append(item) self.addstr(self.pad, 8, 0, inventory_str) + self.addstr(self.pad, 9, 0, f"{self.pack.HAZELNUT} " + f"x{self.player.hazel}") + if self.player.dead: - self.addstr(self.pad, 10, 0, _("YOU ARE DEAD"), + self.addstr(self.pad, 11, 0, _("YOU ARE DEAD"), curses.A_BOLD | curses.A_BLINK | curses.A_STANDOUT | self.color_pair(3)) diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 880f9c4..93b6047 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -21,6 +21,7 @@ class TexturePack: HEDGEHOG: str EMPTY: str FLOOR: str + HAZELNUT: str MERCHANT: str PLAYER: str RABBIT: str @@ -62,6 +63,7 @@ TexturePack.ASCII_PACK = TexturePack( BOMB='o', EMPTY=' ', FLOOR='.', + HAZELNUT='', HEART='❤', HEDGEHOG='*', MERCHANT='M', @@ -86,6 +88,7 @@ TexturePack.SQUIRREL_PACK = TexturePack( BOMB='💣', EMPTY=' ', FLOOR='██', + HAZELNUT='🌰', HEART='💜', HEDGEHOG='🦔', PLAYER='🐿️ ️', diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index ce27c37..2a2a986 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -133,4 +133,5 @@ class Player(FightingEntity): d["current_xp"] = self.current_xp d["max_xp"] = self.max_xp d["inventory"] = [item.save_state() for item in self.inventory] + d["hazel"] = self.hazel return d From 4dbd4f79121492dc72536e036e55cbc12cb41dbb Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 9 Dec 2020 16:57:46 +0100 Subject: [PATCH 31/46] Don't buy hearts, use them instant instead. Fixes #38 --- squirrelbattle/entities/items.py | 4 ++-- squirrelbattle/game.py | 2 +- squirrelbattle/interfaces.py | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 123362f..4192674 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -49,7 +49,7 @@ class Item(Entity): """ self.held = True self.held_by = player - self.map.remove_entity(self) + self.held_by.map.remove_entity(self) player.add_to_inventory(self) def save_state(self) -> dict: @@ -80,7 +80,7 @@ class Heart(Item): When holding a heart, heal the player and don't put item in inventory. """ player.health = min(player.maxhealth, player.health + self.healing) - self.map.remove_entity(self) + player.map.remove_entity(self) def save_state(self) -> dict: """ diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 685eb6b..7c13e61 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -194,7 +194,7 @@ class Game: if self.store_menu.values and not self.player.dead: if key == KeyValues.ENTER: item = self.store_menu.validate() - self.player.add_to_inventory(item) + item.hold(self.player) self.store_menu.merchant.inventory.remove(item) # Ensure that the cursor has a good position self.store_menu.position = min(self.store_menu.position, diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 5efdf7f..e3e29da 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -68,7 +68,8 @@ class Map: """ Unregister an entity from the map. """ - self.entities.remove(entity) + if entity in self.entities: + self.entities.remove(entity) def find_entities(self, entity_class: type) -> list: return [entity for entity in self.entities From 6d4c0b2ca31075f5d47a1d56056dc25b2c9644a3 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 9 Dec 2020 17:04:29 +0100 Subject: [PATCH 32/46] Testing the merchant must handle two cases: the bought item is a heart or not --- squirrelbattle/tests/game_test.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index e510012..370de87 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -8,7 +8,7 @@ from ..bootstrap import Bootstrap from ..display.display import Display from ..display.display_manager import DisplayManager from ..entities.friendly import Merchant, Sunflower -from ..entities.items import Bomb, Sword +from ..entities.items import Bomb, Heart, Sword from ..entities.player import Player from ..enums import DisplayActions from ..game import Game, KeyValues, GameMode @@ -507,6 +507,8 @@ class TestGame(unittest.TestCase): self.game.handle_key_pressed(KeyValues.UP) self.assertEqual(self.game.store_menu.position, 1) + # The second item is not a heart + merchant.inventory[1] = Sword() # Buy the second item item = self.game.store_menu.validate() self.assertIn(item, merchant.inventory) @@ -514,6 +516,18 @@ class TestGame(unittest.TestCase): self.assertIn(item, self.game.player.inventory) self.assertNotIn(item, merchant.inventory) + # Buy a heart + merchant.inventory[1] = Heart() + item = self.game.store_menu.validate() + self.assertIn(item, merchant.inventory) + self.assertEqual(item, merchant.inventory[1]) + self.game.player.health = self.game.player.maxhealth - 1 - item.healing + self.game.handle_key_pressed(KeyValues.ENTER) + self.assertNotIn(item, self.game.player.inventory) + self.assertNotIn(item, merchant.inventory) + self.assertEqual(self.game.player.health, + self.game.player.maxhealth - 1) + # Exit the menu self.game.handle_key_pressed(KeyValues.SPACE) self.assertEqual(self.game.state, GameMode.PLAY) From 7ba49277a99699198b392c32b208430cc9f3139b Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 11 Dec 2020 15:52:36 +0100 Subject: [PATCH 33/46] Added a symbol for hazels in the ascii texturepack --- 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 93b6047..a6ec3af 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -63,7 +63,7 @@ TexturePack.ASCII_PACK = TexturePack( BOMB='o', EMPTY=' ', FLOOR='.', - HAZELNUT='', + HAZELNUT='¤', HEART='❤', HEDGEHOG='*', MERCHANT='M', From b9b776b7ad13e22d7c719549dac1e3a2e67287b3 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 11 Dec 2020 16:49:17 +0100 Subject: [PATCH 34/46] Player now pays for what he buys and buying a heart does not put it in the inventory. Solves #38 and #36 --- squirrelbattle/entities/friendly.py | 19 +++++++++++ squirrelbattle/entities/items.py | 49 +++++++++++++++++++++++------ squirrelbattle/entities/player.py | 13 ++++++++ squirrelbattle/game.py | 6 ++-- 4 files changed, 75 insertions(+), 12 deletions(-) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 205e92b..15ce8d1 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -3,6 +3,7 @@ from ..translations import gettext as _ from .player import Player from .items import Item from random import choice +from typing import Dict, Tuple, Any class Merchant(FriendlyEntity): @@ -50,6 +51,24 @@ class Merchant(FriendlyEntity): d = super().save_state() d["inventory"] = [item.save_state() for item in self.inventory] return d + + def add_to_inventory(self, obj: Any) -> None: + """ + Adds an object to inventory + """ + self.inventory.append(obj) + + def remove_from_inventory(self, obj: Any) -> None: + """ + Removes an object from the inventory + """ + self.inventory.remove(obj) + + def change_hazel_balance(self, hz: int) -> None: + """ + Change the number of hazel the merchant has by hz. + """ + self.hazel += hz class Sunflower(FriendlyEntity): diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 4192674..5148a5c 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from random import choice, randint -from typing import Optional +from typing import Optional, Any from .player import Player from ..interfaces import Entity, FightingEntity, Map @@ -15,12 +15,14 @@ class Item(Entity): """ held: bool held_by: Optional[Player] + price: int - def __init__(self, held: bool = False, held_by: Optional[Player] = None, + def __init__(self, held: bool = False, held_by: Optional[Player] = None, price : int=2, *args, **kwargs): super().__init__(*args, **kwargs) self.held = held self.held_by = held_by + self.price = price def drop(self) -> None: """ @@ -64,6 +66,19 @@ class Item(Entity): def get_all_items() -> list: return [BodySnatchPotion, Bomb, Heart, Sword] + def be_sold(self, buyer: Entity, seller: Entity, game : Any) -> bool: + """ + Does all necessary actions when an object is to be sold. + Is overwritten by some classes that cannot exist in the player's inventory + """ + if buyer.hazel>=self.price : + buyer.add_to_inventory(self) + seller.remove_from_inventory(self) + buyer.change_hazel_balance(-self.price) + seller.change_hazel_balance(self.price) + return True + else : + return False class Heart(Item): """ @@ -71,8 +86,8 @@ class Heart(Item): """ healing: int - def __init__(self, name: str = "heart", healing: int = 5, *args, **kwargs): - super().__init__(name=name, *args, **kwargs) + def __init__(self, name: str = "heart", healing: int = 5, price: int = 3, *args, **kwargs): + super().__init__(name=name, price=price, *args, **kwargs) self.healing = healing def hold(self, player: "Player") -> None: @@ -90,6 +105,20 @@ class Heart(Item): d["healing"] = self.healing return d + def be_sold(self, buyer: Entity, seller: Entity, game: Any) -> str: + """ + Does all necessary actions when an object is to be sold. + Is overwritten by some classes that cannot exist in the player's inventory + """ + if buyer.hazel>=self.price : + self.hold(buyer) + seller.remove_from_inventory(self) + buyer.change_hazel_balance(-self.price) + seller.change_hazel_balance(self.price) + return True + else : + return False + class Bomb(Item): """ @@ -101,8 +130,8 @@ class Bomb(Item): tick: int def __init__(self, name: str = "bomb", damage: int = 5, - exploding: bool = False, *args, **kwargs): - super().__init__(name=name, *args, **kwargs) + exploding: bool = False, price: int = 4, *args, **kwargs): + super().__init__(name=name, price=price, *args, **kwargs) self.damage = damage self.exploding = exploding self.tick = 4 @@ -172,8 +201,8 @@ class Sword(Weapon): """ A basic weapon """ - def __init__(self, name: str = "sword", *args, **kwargs): - super().__init__(name=name, *args, **kwargs) + def __init__(self, name: str = "sword", price: int = 20, *args, **kwargs): + super().__init__(name=name, price=price, *args, **kwargs) self.name = name @@ -183,8 +212,8 @@ class BodySnatchPotion(Item): other entity. """ - def __init__(self, name: str = "body_snatch_potion", *args, **kwargs): - super().__init__(name=name, *args, **kwargs) + def __init__(self, name: str = "body_snatch_potion", price: int = 14, *args, **kwargs): + super().__init__(name=name, price=price, *args, **kwargs) def use(self) -> None: """ diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 2a2a986..b14f9da 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -70,6 +70,13 @@ class Player(FightingEntity): self.current_xp += xp self.level_up() + def change_hazel_balance(self, hz: int) -> None: + """ + Change the number of hazel the player has by hz. hz is negative + when the player loses money and positive when he gains money + """ + self.hazel += hz + # noinspection PyTypeChecker,PyUnresolvedReferences def check_move(self, y: int, x: int, move_if_possible: bool = False) \ -> bool: @@ -125,6 +132,12 @@ class Player(FightingEntity): """ self.inventory.append(obj) + def remove_from_inventory(self, obj: Any) -> None: + """ + Removes an object from the inventory + """ + self.inventory.remove(obj) + def save_state(self) -> dict: """ Saves the state of the entity into a dictionary diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 7c13e61..1dc4fc0 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -194,8 +194,10 @@ class Game: if self.store_menu.values and not self.player.dead: if key == KeyValues.ENTER: item = self.store_menu.validate() - item.hold(self.player) - self.store_menu.merchant.inventory.remove(item) + flag = item.be_sold(self.player, self.store_menu.merchant, self) + if not flag : + self.message = _("You do not have enough money") + self.display_actions(DisplayActions.UPDATE) # Ensure that the cursor has a good position self.store_menu.position = min(self.store_menu.position, len(self.store_menu.values) - 1) From bbe37eab97df7d985b1561ea4d59a7b220f5bbbd Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 16:56:22 +0100 Subject: [PATCH 35/46] Listen for clicks, detect which display was clicked --- squirrelbattle/display/display_manager.py | 34 ++++++++++++++++++++--- squirrelbattle/enums.py | 8 ++++-- squirrelbattle/game.py | 9 ++++-- squirrelbattle/term_manager.py | 2 ++ 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 0e9cf04..a9429fd 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -2,7 +2,8 @@ # SPDX-License-Identifier: GPL-3.0-or-later import curses -from squirrelbattle.display.display import VerticalSplit, HorizontalSplit +from squirrelbattle.display.display import VerticalSplit, HorizontalSplit, \ + Display from squirrelbattle.display.mapdisplay import MapDisplay from squirrelbattle.display.messagedisplay import MessageDisplay from squirrelbattle.display.statsdisplay import StatsDisplay @@ -10,7 +11,7 @@ from squirrelbattle.display.menudisplay import MainMenuDisplay, \ InventoryDisplay, SettingsMenuDisplay from squirrelbattle.display.logsdisplay import LogsDisplay from squirrelbattle.display.texturepack import TexturePack -from typing import Any +from typing import Any, List from squirrelbattle.game import Game, GameMode from squirrelbattle.enums import DisplayActions @@ -36,11 +37,13 @@ class DisplayManager: self.logsdisplay, self.messagedisplay] self.update_game_components() - def handle_display_action(self, action: DisplayActions) -> None: + def handle_display_action(self, action: DisplayActions, *params) -> None: if action == DisplayActions.REFRESH: self.refresh() elif action == DisplayActions.UPDATE: self.update_game_components() + elif action == DisplayActions.MOUSE: + self.handle_mouse_click(*params) def update_game_components(self) -> None: for d in self.displays: @@ -52,7 +55,20 @@ class DisplayManager: self.logsdisplay.update_logs(self.game.logs) self.messagedisplay.update_message(self.game.message) - def refresh(self) -> None: + def handle_mouse_click(self, y: int, x: int) -> None: + displays = self.refresh() + display = None + for d in displays: + top_y, top_x, height, width = d.y, d.x, d.height, d.width + if top_y <= y < top_y + height and top_x <= x < top_x + width: + # The click coordinates correspond to the coordinates + # of that display + display = d + raise Exception(f"click at ({y}, {x})", display) + + def refresh(self) -> List[Display]: + displays = [] + if self.game.state == GameMode.PLAY \ or self.game.state == GameMode.INVENTORY: # The map pad has already the good size @@ -67,15 +83,22 @@ 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) + + displays += [self.mapdisplay, self.statsdisplay, self.logsdisplay, + self.hbar, self.vbar] + if self.game.state == GameMode.INVENTORY: self.inventorydisplay.refresh(self.rows // 10, self.cols // 2, 8 * self.rows // 10, 2 * self.cols // 5) + displays.append(self.inventorydisplay) elif self.game.state == GameMode.MAINMENU: self.mainmenudisplay.refresh(0, 0, self.rows, self.cols) + displays.append(self.mainmenudisplay) elif self.game.state == GameMode.SETTINGS: self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols) + displays.append(self.settingsmenudisplay) if self.game.message: height, width = 0, 0 @@ -84,9 +107,12 @@ class DisplayManager: width = max(width, len(line)) y, x = (self.rows - height) // 2, (self.cols - width) // 2 self.messagedisplay.refresh(y, x, height, width) + displays.append(self.messagedisplay) self.resize_window() + return displays + def resize_window(self) -> bool: """ If the window got resized, ensure that the screen size got updated. diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index 84eb498..5784568 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -16,6 +16,7 @@ class DisplayActions(Enum): """ REFRESH = auto() UPDATE = auto() + MOUSE = auto() class GameMode(Enum): @@ -32,6 +33,7 @@ class KeyValues(Enum): """ Key values options used in the game """ + MOUSE = auto() UP = auto() DOWN = auto() LEFT = auto() @@ -48,8 +50,10 @@ class KeyValues(Enum): """ Translate the raw string key into an enum value that we can use. """ - if key in (settings.KEY_DOWN_SECONDARY, - settings.KEY_DOWN_PRIMARY): + if key == "KEY_MOUSE": + return KeyValues.MOUSE + elif key in (settings.KEY_DOWN_SECONDARY, + settings.KEY_DOWN_PRIMARY): return KeyValues.DOWN elif key in (settings.KEY_LEFT_PRIMARY, settings.KEY_LEFT_SECONDARY): diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 6d9e9e7..0758e19 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -4,6 +4,7 @@ from json import JSONDecodeError from random import randint from typing import Any, Optional +import curses import json import os import sys @@ -15,7 +16,6 @@ from .resources import ResourceManager from .settings import Settings from . import menus from .translations import gettext as _, Translator -from typing import Callable class Game: @@ -25,7 +25,7 @@ class Game: map: Map player: Player # display_actions is a display interface set by the bootstrapper - display_actions: Callable[[DisplayActions], None] + display_actions: callable def __init__(self) -> None: """ @@ -82,6 +82,11 @@ class Game: self.display_actions(DisplayActions.REFRESH) return + if key == KeyValues.MOUSE: + _ignored1, x, y, _ignored2, _ignored3 = curses.getmouse() + self.display_actions(DisplayActions.MOUSE, y, x) + return + if self.state == GameMode.PLAY: self.handle_key_pressed_play(key) elif self.state == GameMode.INVENTORY: diff --git a/squirrelbattle/term_manager.py b/squirrelbattle/term_manager.py index 6284173..5a98a4a 100644 --- a/squirrelbattle/term_manager.py +++ b/squirrelbattle/term_manager.py @@ -20,6 +20,8 @@ class TermManager: # pragma: no cover curses.cbreak() # make cursor invisible curses.curs_set(False) + # Catch mouse events + curses.mousemask(True) # Enable colors curses.start_color() From 98b5dd64a86fddabc8b7a1165a2072b25555b346 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 17:06:30 +0100 Subject: [PATCH 36/46] Linting --- squirrelbattle/entities/friendly.py | 4 ++-- squirrelbattle/entities/items.py | 31 +++++++++++++++++------------ squirrelbattle/game.py | 2 +- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 15ce8d1..0e7ac1c 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -3,7 +3,7 @@ from ..translations import gettext as _ from .player import Player from .items import Item from random import choice -from typing import Dict, Tuple, Any +from typing import Any class Merchant(FriendlyEntity): @@ -51,7 +51,7 @@ class Merchant(FriendlyEntity): d = super().save_state() d["inventory"] = [item.save_state() for item in self.inventory] return d - + def add_to_inventory(self, obj: Any) -> None: """ Adds an object to inventory diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 5148a5c..87c9ba1 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -17,8 +17,8 @@ class Item(Entity): held_by: Optional[Player] price: int - def __init__(self, held: bool = False, held_by: Optional[Player] = None, price : int=2, - *args, **kwargs): + def __init__(self, held: bool = False, held_by: Optional[Player] = None, + price: int = 2, *args, **kwargs): super().__init__(*args, **kwargs) self.held = held self.held_by = held_by @@ -66,27 +66,30 @@ class Item(Entity): def get_all_items() -> list: return [BodySnatchPotion, Bomb, Heart, Sword] - def be_sold(self, buyer: Entity, seller: Entity, game : Any) -> bool: + def be_sold(self, buyer: Entity, seller: Entity, game: Any) -> bool: """ Does all necessary actions when an object is to be sold. - Is overwritten by some classes that cannot exist in the player's inventory + Is overwritten by some classes that cannot exist in the player's + inventory """ - if buyer.hazel>=self.price : + if buyer.hazel >= self.price: buyer.add_to_inventory(self) seller.remove_from_inventory(self) buyer.change_hazel_balance(-self.price) seller.change_hazel_balance(self.price) return True - else : + else: return False + class Heart(Item): """ A heart item to return health to the player """ healing: int - def __init__(self, name: str = "heart", healing: int = 5, price: int = 3, *args, **kwargs): + def __init__(self, name: str = "heart", healing: int = 5, price: int = 3, + *args, **kwargs): super().__init__(name=name, price=price, *args, **kwargs) self.healing = healing @@ -105,20 +108,21 @@ class Heart(Item): d["healing"] = self.healing return d - def be_sold(self, buyer: Entity, seller: Entity, game: Any) -> str: + def be_sold(self, buyer: Entity, seller: Entity, game: Any) -> bool: """ Does all necessary actions when an object is to be sold. - Is overwritten by some classes that cannot exist in the player's inventory + Is overwritten by some classes that cannot exist in the player's + inventory """ - if buyer.hazel>=self.price : + if buyer.hazel >= self.price: self.hold(buyer) seller.remove_from_inventory(self) buyer.change_hazel_balance(-self.price) seller.change_hazel_balance(self.price) return True - else : + else: return False - + class Bomb(Item): """ @@ -212,7 +216,8 @@ class BodySnatchPotion(Item): other entity. """ - def __init__(self, name: str = "body_snatch_potion", price: int = 14, *args, **kwargs): + def __init__(self, name: str = "body_snatch_potion", price: int = 14, + *args, **kwargs): super().__init__(name=name, price=price, *args, **kwargs) def use(self) -> None: diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 1dc4fc0..34ce960 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -195,7 +195,7 @@ class Game: if key == KeyValues.ENTER: item = self.store_menu.validate() flag = item.be_sold(self.player, self.store_menu.merchant, self) - if not flag : + if not flag: self.message = _("You do not have enough money") self.display_actions(DisplayActions.UPDATE) # Ensure that the cursor has a good position From 7179346e2b75930f372fd0830f7dcd0ba3e2efdc Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 17:20:50 +0100 Subject: [PATCH 37/46] Add a InventoryHolder superclass for player and merchants --- squirrelbattle/entities/friendly.py | 38 ++------------------- squirrelbattle/entities/items.py | 14 ++++---- squirrelbattle/entities/player.py | 36 +++----------------- squirrelbattle/interfaces.py | 53 +++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 73 deletions(-) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 0e7ac1c..3e965d5 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -1,18 +1,14 @@ -from ..interfaces import FriendlyEntity +from ..interfaces import FriendlyEntity, InventoryHolder from ..translations import gettext as _ from .player import Player from .items import Item from random import choice -from typing import Any -class Merchant(FriendlyEntity): +class Merchant(InventoryHolder, FriendlyEntity): """ The class for merchants in the dungeon """ - inventory = list - hazel = int - def keys(self) -> list: """ Returns a friendly entitie's specific attributes @@ -22,16 +18,9 @@ class Merchant(FriendlyEntity): def __init__(self, name: str = "merchant", inventory: list = None, hazel: int = 75, *args, **kwargs): super().__init__(name=name, *args, **kwargs) - self.inventory = inventory or [] + self.inventory = self.translate_inventory(inventory or []) self.hazel = hazel - entity_classes = self.get_all_entity_classes_in_a_dict() - - for i in range(len(self.inventory)): - if isinstance(self.inventory[i], dict): - item_class = entity_classes[self.inventory[i]["type"]] - self.inventory[i] = item_class(**self.inventory[i]) - if not self.inventory: for i in range(5): self.inventory.append(choice(Item.get_all_items())()) @@ -41,29 +30,8 @@ class Merchant(FriendlyEntity): This function is used to open the merchant's inventory in a menu, and allow the player to buy/sell objects """ - # TODO return _("I don't sell any squirrel") - def save_state(self) -> dict: - """ - We save the inventory of the merchant formatted as JSON - """ - d = super().save_state() - d["inventory"] = [item.save_state() for item in self.inventory] - return d - - def add_to_inventory(self, obj: Any) -> None: - """ - Adds an object to inventory - """ - self.inventory.append(obj) - - def remove_from_inventory(self, obj: Any) -> None: - """ - Removes an object from the inventory - """ - self.inventory.remove(obj) - def change_hazel_balance(self, hz: int) -> None: """ Change the number of hazel the merchant has by hz. diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 87c9ba1..76f8a37 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -5,7 +5,7 @@ from random import choice, randint from typing import Optional, Any from .player import Player -from ..interfaces import Entity, FightingEntity, Map +from ..interfaces import Entity, FightingEntity, Map, InventoryHolder from ..translations import gettext as _ @@ -66,7 +66,8 @@ class Item(Entity): def get_all_items() -> list: return [BodySnatchPotion, Bomb, Heart, Sword] - def be_sold(self, buyer: Entity, seller: Entity, game: Any) -> bool: + def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder, + game: Any) -> bool: """ Does all necessary actions when an object is to be sold. Is overwritten by some classes that cannot exist in the player's @@ -93,12 +94,12 @@ class Heart(Item): super().__init__(name=name, price=price, *args, **kwargs) self.healing = healing - def hold(self, player: "Player") -> None: + def hold(self, entity: InventoryHolder) -> None: """ When holding a heart, heal the player and don't put item in inventory. """ - player.health = min(player.maxhealth, player.health + self.healing) - player.map.remove_entity(self) + entity.health = min(entity.maxhealth, entity.health + self.healing) + entity.map.remove_entity(self) def save_state(self) -> dict: """ @@ -108,7 +109,8 @@ class Heart(Item): d["healing"] = self.healing return d - def be_sold(self, buyer: Entity, seller: Entity, game: Any) -> bool: + def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder, + game: Any) -> bool: """ Does all necessary actions when an object is to be sold. Is overwritten by some classes that cannot exist in the player's diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index b14f9da..d8bf31a 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -2,20 +2,18 @@ # SPDX-License-Identifier: GPL-3.0-or-later from random import randint -from typing import Dict, Tuple, Any +from typing import Dict, Tuple -from ..interfaces import FightingEntity +from ..interfaces import FightingEntity, InventoryHolder -class Player(FightingEntity): +class Player(InventoryHolder, FightingEntity): """ The class of the player """ current_xp: int = 0 max_xp: int = 10 - inventory: list paths: Dict[Tuple[int, int], Tuple[int, int]] - hazel: int # It is the currency of this game def __init__(self, name: str = "player", maxhealth: int = 20, strength: int = 5, intelligence: int = 1, charisma: int = 1, @@ -29,12 +27,7 @@ class Player(FightingEntity): level=level, *args, **kwargs) self.current_xp = current_xp self.max_xp = max_xp - self.inventory = inventory if inventory else list() - entity_classes = self.get_all_entity_classes_in_a_dict() - for i in range(len(self.inventory)): - if isinstance(self.inventory[i], dict): - item_class = entity_classes[self.inventory[i]["type"]] - self.inventory[i] = item_class(**self.inventory[i]) + self.inventory = self.translate_inventory(inventory or []) self.paths = dict() self.hazel = hazel @@ -70,13 +63,6 @@ class Player(FightingEntity): self.current_xp += xp self.level_up() - def change_hazel_balance(self, hz: int) -> None: - """ - Change the number of hazel the player has by hz. hz is negative - when the player loses money and positive when he gains money - """ - self.hazel += hz - # noinspection PyTypeChecker,PyUnresolvedReferences def check_move(self, y: int, x: int, move_if_possible: bool = False) \ -> bool: @@ -126,18 +112,6 @@ class Player(FightingEntity): queue.append((new_y, new_x)) self.paths = predecessors - def add_to_inventory(self, obj: Any) -> None: - """ - Adds an object to inventory - """ - self.inventory.append(obj) - - def remove_from_inventory(self, obj: Any) -> None: - """ - Removes an object from the inventory - """ - self.inventory.remove(obj) - def save_state(self) -> dict: """ Saves the state of the entity into a dictionary @@ -145,6 +119,4 @@ 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] - d["hazel"] = self.hazel return d diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index e3e29da..c035a38 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -483,3 +483,56 @@ class FriendlyEntity(FightingEntity): Returns a friendly entity's specific attributes """ return ["maxhealth", "health"] + + +class InventoryHolder(Entity): + hazel: int # Currency of the game + inventory: list + + def translate_inventory(self, inventory: list) -> list: + """ + Translate the JSON-state of the inventory into a list of the items in + the inventory. + """ + for i in range(len(inventory)): + if isinstance(inventory[i], dict): + inventory[i] = self.dict_to_inventory(inventory[i]) + return inventory + + def dict_to_inventory(self, item_dict: dict) -> Entity: + """ + Translate a dict object that contains the state of an item + into an item object. + """ + entity_classes = self.get_all_entity_classes_in_a_dict() + + item_class = entity_classes[item_dict["type"]] + return item_class(**item_dict) + + def save_state(self) -> dict: + """ + We save the inventory of the merchant formatted as JSON + """ + d = super().save_state() + d["hazel"] = self.hazel + d["inventory"] = [item.save_state() for item in self.inventory] + return d + + def add_to_inventory(self, obj: Any) -> None: + """ + Adds an object to inventory + """ + self.inventory.append(obj) + + def remove_from_inventory(self, obj: Any) -> None: + """ + Removes an object from the inventory + """ + self.inventory.remove(obj) + + def change_hazel_balance(self, hz: int) -> None: + """ + Change the number of hazel the entity has by hz. hz is negative + when the player loses money and positive when he gains money + """ + self.hazel += hz From 99352bc1d5e638a899c1be62babf9255507015a7 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 17:28:16 +0100 Subject: [PATCH 38/46] Test buying an item when we don't have enough of money --- squirrelbattle/entities/items.py | 30 +++++++----------------------- squirrelbattle/game.py | 2 +- squirrelbattle/tests/game_test.py | 9 +++++++++ 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 76f8a37..27066cb 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -14,11 +14,12 @@ class Item(Entity): A class for items """ held: bool - held_by: Optional[Player] + held_by: Optional[InventoryHolder] price: int - def __init__(self, held: bool = False, held_by: Optional[Player] = None, - price: int = 2, *args, **kwargs): + def __init__(self, held: bool = False, + held_by: Optional[InventoryHolder] = None, +price: int = 2, *args, **kwargs): super().__init__(*args, **kwargs) self.held = held self.held_by = held_by @@ -45,7 +46,7 @@ class Item(Entity): Indicates what should be done when the item is equipped. """ - def hold(self, player: "Player") -> None: + def hold(self, player: InventoryHolder) -> None: """ The item is taken from the floor and put into the inventory """ @@ -66,15 +67,14 @@ class Item(Entity): def get_all_items() -> list: return [BodySnatchPotion, Bomb, Heart, Sword] - def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder, - game: Any) -> bool: + def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder) -> bool: """ Does all necessary actions when an object is to be sold. Is overwritten by some classes that cannot exist in the player's inventory """ if buyer.hazel >= self.price: - buyer.add_to_inventory(self) + self.hold(buyer) seller.remove_from_inventory(self) buyer.change_hazel_balance(-self.price) seller.change_hazel_balance(self.price) @@ -109,22 +109,6 @@ class Heart(Item): d["healing"] = self.healing return d - def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder, - game: Any) -> bool: - """ - Does all necessary actions when an object is to be sold. - Is overwritten by some classes that cannot exist in the player's - inventory - """ - if buyer.hazel >= self.price: - self.hold(buyer) - seller.remove_from_inventory(self) - buyer.change_hazel_balance(-self.price) - seller.change_hazel_balance(self.price) - return True - else: - return False - class Bomb(Item): """ diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 34ce960..922ea0d 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -194,7 +194,7 @@ class Game: if self.store_menu.values and not self.player.dead: if key == KeyValues.ENTER: item = self.store_menu.validate() - flag = item.be_sold(self.player, self.store_menu.merchant, self) + flag = item.be_sold(self.player, self.store_menu.merchant) if not flag: self.message = _("You do not have enough money") self.display_actions(DisplayActions.UPDATE) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 370de87..ec5551d 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -528,6 +528,15 @@ class TestGame(unittest.TestCase): self.assertEqual(self.game.player.health, self.game.player.maxhealth - 1) + # We don't have enough of money + self.game.player.hazel = 0 + item = self.game.store_menu.validate() + self.game.handle_key_pressed(KeyValues.ENTER) + self.assertNotIn(item, self.game.player.inventory) + self.assertIn(item, merchant.inventory) + self.assertEqual(self.game.message, _("You do not have enough money")) + self.game.handle_key_pressed(KeyValues.ENTER) + # Exit the menu self.game.handle_key_pressed(KeyValues.SPACE) self.assertEqual(self.game.state, GameMode.PLAY) From d50f6701f4d3e35e5b6ee4564bd4c697d8797671 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 17:40:56 +0100 Subject: [PATCH 39/46] Open a menu with the mouse --- squirrelbattle/display/display.py | 7 +++++++ squirrelbattle/display/display_manager.py | 3 ++- squirrelbattle/display/menudisplay.py | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 9cc1456..ef4053f 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -86,6 +86,13 @@ class Display: def display(self) -> None: raise NotImplementedError + def handle_click(self, y: int, x: int) -> None: + """ + A mouse click was performed on the coordinates (y, x) of the pad. + Maybe it can do something. + """ + pass + @property def rows(self) -> int: return curses.LINES if self.screen else 42 diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index a9429fd..708f003 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -64,7 +64,8 @@ class DisplayManager: # The click coordinates correspond to the coordinates # of that display display = d - raise Exception(f"click at ({y}, {x})", display) + if display: + display.handle_click(y - display.y, x - display.x) def refresh(self) -> List[Display]: displays = [] diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index d040d81..a494484 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -41,6 +41,13 @@ class MenuDisplay(Display): self.height - 2 + self.y, self.width - 2 + self.x) + def handle_click(self, y: int, x: int) -> None: + """ + We can select a menu item with the mouse. + """ + self.menu.position = y - 1 + curses.ungetch('\n') + @property def truewidth(self) -> int: return max([len(str(a)) for a in self.values]) @@ -99,6 +106,14 @@ class MainMenuDisplay(Display): menuy, menux, min(self.menudisplay.preferred_height, self.height - menuy), menuwidth) + def handle_click(self, y: int, x: int) -> None: + menuwidth = min(self.menudisplay.preferred_width, self.width) + menuy, menux = len(self.title) + 8, self.width // 2 - menuwidth // 2 - 1 + menuheight = min(self.menudisplay.preferred_height, self.height - menuy) + + if menuy <= y < menuy + menuheight and menux <= x < menux + menuwidth: + self.menudisplay.handle_click(y - menuy, x - menux) + class InventoryDisplay(MenuDisplay): def update_pad(self) -> None: From 089a247b2fdf9c56a45600195fe581b5472bb040 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 17:43:46 +0100 Subject: [PATCH 40/46] Maybe mouse clicks may use the game --- squirrelbattle/display/display.py | 3 ++- squirrelbattle/display/display_manager.py | 2 +- squirrelbattle/display/menudisplay.py | 11 +++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index ef4053f..c5b1aa3 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -5,6 +5,7 @@ import curses from typing import Any, Optional, Union from squirrelbattle.display.texturepack import TexturePack +from squirrelbattle.game import Game from squirrelbattle.tests.screen import FakePad @@ -86,7 +87,7 @@ class Display: def display(self) -> None: raise NotImplementedError - def handle_click(self, y: int, x: int) -> None: + def handle_click(self, y: int, x: int, game: Game) -> None: """ A mouse click was performed on the coordinates (y, x) of the pad. Maybe it can do something. diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 708f003..743baef 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -65,7 +65,7 @@ class DisplayManager: # of that display display = d if display: - display.handle_click(y - display.y, x - display.x) + display.handle_click(y - display.y, x - display.x, self.game) def refresh(self) -> List[Display]: displays = [] diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index a494484..d745c28 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -1,10 +1,13 @@ # 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 from .display import Display, Box +from ..enums import KeyValues +from ..game import Game from ..resources import ResourceManager from ..translations import gettext as _ @@ -41,12 +44,12 @@ class MenuDisplay(Display): self.height - 2 + self.y, self.width - 2 + self.x) - def handle_click(self, y: int, x: int) -> None: + def handle_click(self, y: int, x: int, game: Game) -> None: """ We can select a menu item with the mouse. """ self.menu.position = y - 1 - curses.ungetch('\n') + game.handle_key_pressed(KeyValues.ENTER) @property def truewidth(self) -> int: @@ -106,13 +109,13 @@ class MainMenuDisplay(Display): menuy, menux, min(self.menudisplay.preferred_height, self.height - menuy), menuwidth) - def handle_click(self, y: int, x: int) -> None: + def handle_click(self, y: int, x: int, game: Game) -> None: menuwidth = min(self.menudisplay.preferred_width, self.width) menuy, menux = len(self.title) + 8, self.width // 2 - menuwidth // 2 - 1 menuheight = min(self.menudisplay.preferred_height, self.height - menuy) if menuy <= y < menuy + menuheight and menux <= x < menux + menuwidth: - self.menudisplay.handle_click(y - menuy, x - menux) + self.menudisplay.handle_click(y - menuy, x - menux, game) class InventoryDisplay(MenuDisplay): From 1afa397fec73b94d7cfb0169ed3e1b6d90060ea0 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 18:07:24 +0100 Subject: [PATCH 41/46] Better interaction with inventory menu --- squirrelbattle/display/menudisplay.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index d745c28..eee4606 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -48,7 +48,7 @@ class MenuDisplay(Display): """ We can select a menu item with the mouse. """ - self.menu.position = y - 1 + self.menu.position = max(0, min(len(self.menu.values) - 1, y - 1)) game.handle_key_pressed(KeyValues.ENTER) @property @@ -136,3 +136,10 @@ class InventoryDisplay(MenuDisplay): @property def trueheight(self) -> int: return 2 + super().trueheight + + def handle_click(self, y: int, x: int, game: Game) -> None: + """ + We can select a menu item with the mouse. + """ + self.menu.position = max(0, min(len(self.menu.values) - 1, y - 3)) + game.handle_key_pressed(KeyValues.ENTER) From e7f24c2371875414153133c72cf2438aa0a39dd3 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 11 Dec 2020 18:08:10 +0100 Subject: [PATCH 42/46] The store menu now displays the price of objects, fixes #41 --- squirrelbattle/display/menudisplay.py | 28 ++++++++++---- .../locale/de/LC_MESSAGES/squirrelbattle.po | 38 +++++++++++-------- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 38 +++++++++++-------- squirrelbattle/tests/translations_test.py | 1 + 4 files changed, 68 insertions(+), 37 deletions(-) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 58b7fdf..4d767c1 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -109,8 +109,8 @@ class MainMenuDisplay(Display): self.height - menuy), menuwidth) -class InventoryDisplay(MenuDisplay): - message: str +class PlayerInventoryDisplay(MenuDisplay): + message = _("== INVENTORY ==") def update_pad(self) -> None: self.addstr(self.pad, 0, (self.width - len(self.message)) // 2, @@ -130,9 +130,23 @@ class InventoryDisplay(MenuDisplay): return 2 + super().trueheight -class PlayerInventoryDisplay(InventoryDisplay): - message = _("== INVENTORY ==") - - -class StoreInventoryDisplay(InventoryDisplay): +class StoreInventoryDisplay(MenuDisplay): message = _("== STALL ==") + + def update_pad(self) -> None: + self.addstr(self.pad, 0, (self.width - len(self.message)) // 2, + self.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.translated_name.capitalize()\ + +": "+str(item.price)+" Hazels") + + @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/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 3f46b32..ea7883f 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-09 15:40+0100\n" +"POT-Creation-Date: 2020-12-11 18:06+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,11 +17,11 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/display/menudisplay.py:134 +#: squirrelbattle/display/menudisplay.py:113 msgid "== INVENTORY ==" msgstr "== BESTAND ==" -#: squirrelbattle/display/menudisplay.py:138 +#: squirrelbattle/display/menudisplay.py:134 msgid "== STALL ==" msgstr "== STAND ==" @@ -29,36 +29,40 @@ msgstr "== STAND ==" msgid "Inventory:" msgstr "Bestand:" -#: squirrelbattle/display/statsdisplay.py:50 +#: squirrelbattle/display/statsdisplay.py:53 msgid "YOU ARE DEAD" msgstr "SIE WURDEN GESTORBEN" #. TODO -#: squirrelbattle/entities/friendly.py:44 +#: squirrelbattle/entities/friendly.py:33 msgid "I don't sell any squirrel" msgstr "Ich verkaufe keinen Eichhörnchen." -#: squirrelbattle/entities/friendly.py:59 +#: squirrelbattle/entities/friendly.py:46 msgid "Flower power!!" msgstr "Blumenmacht!!" -#: squirrelbattle/entities/friendly.py:59 +#: squirrelbattle/entities/friendly.py:46 msgid "The sun is warm today" msgstr "Die Sonne ist warm heute" #. 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:132 +#: squirrelbattle/entities/items.py:151 msgid "Bomb is exploding." msgstr "Die Bombe explodiert." -#: squirrelbattle/entities/items.py:204 +#: squirrelbattle/entities/items.py:224 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} täuscht seinem Körper mit {entity} aus." -#: squirrelbattle/game.py:241 +#: squirrelbattle/game.py:199 squirrelbattle/tests/game_test.py:537 +msgid "You do not have enough money" +msgstr "" + +#: squirrelbattle/game.py:243 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -66,7 +70,7 @@ msgstr "" "In Ihrer Speicherdatei fehlen einige Schlüssel.\n" "Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht." -#: squirrelbattle/game.py:249 +#: squirrelbattle/game.py:251 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -74,7 +78,7 @@ msgstr "" "Auf dieser Karte wurde kein Spieler gefunden!\n" "Vielleicht sind Sie gestorben?" -#: squirrelbattle/game.py:269 +#: squirrelbattle/game.py:271 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -82,17 +86,17 @@ msgstr "" "Die JSON-Datei ist nicht korrekt.\n" "Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." -#: squirrelbattle/interfaces.py:428 +#: squirrelbattle/interfaces.py:429 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} schlägt {opponent}." -#: squirrelbattle/interfaces.py:440 +#: squirrelbattle/interfaces.py:441 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} nimmt {amount} Schadenspunkte." -#: squirrelbattle/interfaces.py:442 +#: squirrelbattle/interfaces.py:443 #, python-brace-format msgid "{name} dies." msgstr "{name} stirbt." @@ -222,3 +226,7 @@ msgstr "Bombe" #: squirrelbattle/tests/translations_test.py:68 msgid "heart" msgstr "Herz" + +#: squirrelbattle/tests/translations_test.py:69 +msgid "sword" +msgstr "schwert" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index d949d9a..f87fca1 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-09 15:40+0100\n" +"POT-Creation-Date: 2020-12-11 18:06+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,11 +17,11 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/display/menudisplay.py:134 +#: squirrelbattle/display/menudisplay.py:113 msgid "== INVENTORY ==" msgstr "== INVENTAIRE ==" -#: squirrelbattle/display/menudisplay.py:138 +#: squirrelbattle/display/menudisplay.py:134 msgid "== STALL ==" msgstr "== STAND ==" @@ -29,36 +29,40 @@ msgstr "== STAND ==" msgid "Inventory:" msgstr "Inventaire :" -#: squirrelbattle/display/statsdisplay.py:50 +#: squirrelbattle/display/statsdisplay.py:53 msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" #. TODO -#: squirrelbattle/entities/friendly.py:44 +#: squirrelbattle/entities/friendly.py:33 msgid "I don't sell any squirrel" msgstr "Je ne vends pas d'écureuil" -#: squirrelbattle/entities/friendly.py:59 +#: squirrelbattle/entities/friendly.py:46 msgid "Flower power!!" msgstr "Pouvoir des fleurs !" -#: squirrelbattle/entities/friendly.py:59 +#: squirrelbattle/entities/friendly.py:46 msgid "The sun is warm today" msgstr "Le soleil est chaud aujourd'hui" #. 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:132 +#: squirrelbattle/entities/items.py:151 msgid "Bomb is exploding." msgstr "La bombe explose." -#: squirrelbattle/entities/items.py:204 +#: squirrelbattle/entities/items.py:224 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} a échangé son corps avec {entity}." -#: squirrelbattle/game.py:241 +#: squirrelbattle/game.py:199 squirrelbattle/tests/game_test.py:537 +msgid "You do not have enough money" +msgstr "" + +#: squirrelbattle/game.py:243 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -66,7 +70,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:249 +#: squirrelbattle/game.py:251 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -74,7 +78,7 @@ msgstr "" "Aucun joueur n'a été trouvé sur la carte !\n" "Peut-être êtes-vous mort ?" -#: squirrelbattle/game.py:269 +#: squirrelbattle/game.py:271 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -82,17 +86,17 @@ msgstr "" "Le fichier JSON de sauvegarde est incorrect.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/interfaces.py:428 +#: squirrelbattle/interfaces.py:429 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} frappe {opponent}." -#: squirrelbattle/interfaces.py:440 +#: squirrelbattle/interfaces.py:441 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} prend {amount} points de dégât." -#: squirrelbattle/interfaces.py:442 +#: squirrelbattle/interfaces.py:443 #, python-brace-format msgid "{name} dies." msgstr "{name} meurt." @@ -222,3 +226,7 @@ msgstr "bombe" #: squirrelbattle/tests/translations_test.py:68 msgid "heart" msgstr "cœur" + +#: squirrelbattle/tests/translations_test.py:69 +msgid "sword" +msgstr "épée" diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index 876b652..8176fd4 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -66,3 +66,4 @@ class TestTranslations(unittest.TestCase): self.assertEqual(_("body snatch potion"), "potion d'arrachage de corps") self.assertEqual(_("bomb"), "bombe") self.assertEqual(_("heart"), "cœur") + self.assertEqual(_("sword"), "épée") From 45f420aaefa41e91781b7ad7fc875c521d3c4817 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 18:17:08 +0100 Subject: [PATCH 43/46] Linting --- squirrelbattle/display/menudisplay.py | 4 ++-- squirrelbattle/entities/items.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 4d767c1..a8d90f3 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -140,8 +140,8 @@ class StoreInventoryDisplay(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.translated_name.capitalize()\ - +": "+str(item.price)+" Hazels") + + " " + item.translated_name.capitalize() + + ": " + str(item.price) + " Hazels") @property def truewidth(self) -> int: diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 27066cb..f46cff1 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from random import choice, randint -from typing import Optional, Any +from typing import Optional from .player import Player from ..interfaces import Entity, FightingEntity, Map, InventoryHolder @@ -19,7 +19,7 @@ class Item(Entity): def __init__(self, held: bool = False, held_by: Optional[InventoryHolder] = None, -price: int = 2, *args, **kwargs): + price: int = 2, *args, **kwargs): super().__init__(*args, **kwargs) self.held = held self.held_by = held_by From d9912cacad5ec6e9f9430124aaf1e4b9a8eb5f87 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 18:17:59 +0100 Subject: [PATCH 44/46] Listen to mouse clicks in the main loop --- squirrelbattle/enums.py | 6 ++---- squirrelbattle/game.py | 13 ++++++------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index 5784568..11f5c17 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -50,10 +50,8 @@ class KeyValues(Enum): """ Translate the raw string key into an enum value that we can use. """ - if key == "KEY_MOUSE": - return KeyValues.MOUSE - elif key in (settings.KEY_DOWN_SECONDARY, - settings.KEY_DOWN_PRIMARY): + if key in (settings.KEY_DOWN_SECONDARY, + settings.KEY_DOWN_PRIMARY): return KeyValues.DOWN elif key in (settings.KEY_LEFT_PRIMARY, settings.KEY_LEFT_SECONDARY): diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 0758e19..a37f1d2 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -68,8 +68,12 @@ class Game: screen.refresh() self.display_actions(DisplayActions.REFRESH) key = screen.getkey() - self.handle_key_pressed( - KeyValues.translate_key(key, self.settings), key) + if key == "KEY_MOUSE": + _ignored1, x, y, _ignored2, _ignored3 = curses.getmouse() + self.display_actions(DisplayActions.MOUSE, y, x) + else: + self.handle_key_pressed( + KeyValues.translate_key(key, self.settings), key) def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str = '')\ -> None: @@ -82,11 +86,6 @@ class Game: self.display_actions(DisplayActions.REFRESH) return - if key == KeyValues.MOUSE: - _ignored1, x, y, _ignored2, _ignored3 = curses.getmouse() - self.display_actions(DisplayActions.MOUSE, y, x) - return - if self.state == GameMode.PLAY: self.handle_key_pressed_play(key) elif self.state == GameMode.INVENTORY: From f453b82a582efd2a9328f1d005a6d3af772707cb Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 18:33:47 +0100 Subject: [PATCH 45/46] Test clicking on the screen --- squirrelbattle/entities/items.py | 4 ++-- squirrelbattle/interfaces.py | 3 ++- squirrelbattle/tests/game_test.py | 27 +++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index e90ec32..a1f3bd4 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -28,7 +28,7 @@ class Item(Entity): """ if self.held: self.held_by.inventory.remove(self) - self.map.add_entity(self) + self.held_by.map.add_entity(self) self.move(self.held_by.y, self.held_by.x) self.held = False self.held_by = None @@ -49,7 +49,7 @@ class Item(Entity): """ self.held = True self.held_by = player - self.map.remove_entity(self) + self.held_by.map.remove_entity(self) player.inventory.append(self) def save_state(self) -> dict: diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 3567ea0..92b4498 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -68,7 +68,8 @@ class Map: """ Unregister an entity from the map. """ - self.entities.remove(entity) + if entity in self.entities: + self.entities.remove(entity) def find_entities(self, entity_class: type) -> list: return [entity for entity in self.entities diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 3a32c95..2bb84c5 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -216,6 +216,33 @@ class TestGame(unittest.TestCase): self.game.handle_key_pressed(KeyValues.SPACE) self.assertEqual(self.game.state, GameMode.MAINMENU) + def test_mouse_click(self) -> None: + """ + Simulate mouse clicks. + """ + self.game.state = GameMode.MAINMENU + + # Settings menu + self.game.display_actions(DisplayActions.MOUSE, 25, 21) + self.assertEqual(self.game.main_menu.position, 4) + self.assertEqual(self.game.state, GameMode.SETTINGS) + + bomb = Bomb() + bomb.hold(self.game.player) + bomb2 = Bomb() + bomb2.hold(self.game.player) + + self.game.state = GameMode.INVENTORY + + # Click nowhere + self.game.display_actions(DisplayActions.MOUSE, 0, 0) + self.assertEqual(self.game.state, GameMode.INVENTORY) + + # Click on the second item + self.game.display_actions(DisplayActions.MOUSE, 8, 25) + self.assertEqual(self.game.state, GameMode.INVENTORY) + self.assertEqual(self.game.inventory_menu.position, 1) + def test_new_game(self) -> None: """ Ensure that the start button starts a new game. From a4a10e340da74ead2220a00f5bbf8be73266c8d5 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 18:44:05 +0100 Subject: [PATCH 46/46] Test clicking on the merchant pad --- squirrelbattle/display/display_manager.py | 2 +- squirrelbattle/tests/game_test.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 203fc82..f9b3f01 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -99,7 +99,7 @@ class DisplayManager: self.playerinventorydisplay.refresh( self.rows // 10, self.cols // 2, 8 * self.rows // 10, 2 * self.cols // 5) - displays.append(self.inventorydisplay) + displays.append(self.playerinventorydisplay) elif self.game.state == GameMode.STORE: self.storeinventorydisplay.refresh( self.rows // 10, self.cols // 2, diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index e079035..bc3ce12 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -539,9 +539,10 @@ class TestGame(unittest.TestCase): # The second item is not a heart merchant.inventory[1] = Sword() - # Buy the second item + # Buy the second item by clicking on it item = self.game.store_menu.validate() self.assertIn(item, merchant.inventory) + self.game.display_actions(DisplayActions.MOUSE, 8, 25) self.game.handle_key_pressed(KeyValues.ENTER) self.assertIn(item, self.game.player.inventory) self.assertNotIn(item, merchant.inventory)