From 1cf5e7bd8b7329b6d3254a83685a3a3b11de0860 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 11 Dec 2020 19:23:21 +0100 Subject: [PATCH 01/40] First implementation of visibility, not tested, nor used for now --- .gitignore | 1 + squirrelbattle/interfaces.py | 148 ++++++++++++++++++++++++++++++++++- 2 files changed, 147 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8499d7c..e477e04 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ env/ venv/ +local/ .coverage .pytest_cache/ diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 3567ea0..91a2188 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -4,7 +4,7 @@ from enum import Enum, auto from math import sqrt from random import choice, randint -from typing import List, Optional +from typing import List, Optional, Union, Tuple from .display.texturepack import TexturePack from .translations import gettext as _ @@ -30,6 +30,28 @@ class Logs: self.messages = [] +class Slope(): + X: int + Y: int + + def __init__(self, y: int, x: int) -> None: + self.Y = y + self.X = x + + def compare(self, other: Union[Tuple[int, int], "Slope"]) -> int: + if isinstance(other, Slope): + y, x = other.Y, other.X + else: + y, x = other + return self.Y * x - self.X * y + + def __lt__(self, other: Union[Tuple[int, int], "Slope"]) -> bool: + return self.compare(other) < 0 + + def __eq__(self, other: Union[Tuple[int, int], "Slope"]) -> bool: + return self.compare(other) == 0 + + class Map: """ Object that represents a Map with its width, height @@ -40,6 +62,7 @@ class Map: start_y: int start_x: int tiles: List[List["Tile"]] + visibility: List[List[bool]] entities: List["Entity"] logs: Logs # coordinates of the point that should be @@ -54,6 +77,8 @@ class Map: self.start_y = start_y self.start_x = start_x self.tiles = tiles + self.visibility = [[False for _ in range(len(tiles[0]))] + for _ in range(len(tiles))] self.entities = [] self.logs = Logs() @@ -129,7 +154,7 @@ class Map: """ Put randomly {count} hedgehogs on the map, where it is available. """ - for ignored in range(count): + for _ignored in range(count): y, x = 0, 0 while True: y, x = randint(0, self.height - 1), randint(0, self.width - 1) @@ -140,6 +165,125 @@ class Map: entity.move(y, x) self.add_entity(entity) + def compute_visibility(self, y: int, x: int, max_range: int) -> None: + """ + Sets the visible tiles to be the ones visible by an entity at point + (y, x), using a twaked shadow casting algorithm + """ + + for line in self.visibility: + for i in range(len(line)): + line[i] = False + self.visibility[y][x] = True + for octant in range(8): + self.compute_visibility_octant(octant, (y, x), max_range, 1, + Slope(1, 1), Slope(0, 1)) + + def crop_top_visibility(self, octant: int, origin: Tuple[int, int], + x: int, top: Slope) -> int: + if top.X == 1: + top_y = x + else: + top_y = ((x * 2 - 1) * top.Y + top.X) / (top.X * 2) + if self.is_wall(top_y, x, octant, origin): + if top >= (top_y * 2 + 1, x * 2) and not\ + self.is_wall(top_y + 1, x, octant, origin): + top_y += 1 + else: + ax = x * 2 + if self.is_wall(top_y + 1, x + 1, octant, origin): + ax += 1 + if top > (top_y * 2 + 1, ax): + top_y += 1 + return top_y + + def crop_bottom_visibility(self, octant: int, origin: Tuple[int, int], + x: int, bottom: Slope) -> int: + if bottom.Y == 0: + bottom_y = 0 + else: + bottom_y = ((x * 2 + 1) * bottom.Y + bottom.X) /\ + (bottom.X * 2) + if bottom >= (bottom_y * 2 + 1, x * 2) and\ + self.is_wall(bottom_y, x, octant, origin) and\ + not self.is_wall(bottom_y + 1, x, octant, origin): + bottom_y += 1 + return bottom_y + + def compute_visibility_octant(self, octant: int, origin: Tuple[int, int], + max_range: int, distance: int, top: Slope, + bottom: Slope) -> None: + for x in range(distance, max_range): + top_y = self.crop_top_visibility(octant, origin, x, top) + bottom_y = self.crop_bottom_visibility(octant, origin, x, bottom) + was_opaque = -1 + for y in range(top_y, bottom_y - 1, -1): + if sqrt(x**2 + y**2) > max_range: + continue + is_opaque = self.is_wall(y, x, octant, origin) + is_visible = is_opaque\ + or ((y != top_y or top > (y * 4 - 1, x * 4 - 1)) + and (y != bottom_y or bottom < (y * 4 + 1, x * 4 + 1))) + if is_visible: + self.set_visible(y, x, octant, origin) + if x == max_range: + continue + if is_opaque and was_opaque == 0: + nx, ny = x * 2, y * 2 + 1 + if self.is_wall(y + 1, x, octant, origin): + nx -= 1 + if top > (ny, nx): + if y == bottom_y: + bottom = Slope(ny, nx) + break + else: + self.compute_visibility_octant( + octant, origin, max_range, x + 1, top, + Slope(ny, nx)) + else: + if y == bottom_y: + return + elif not is_opaque and was_opaque == 1: + nx, ny = x * 2, y * 2 + 1 + if self.is_wall(y + 1, x + 1, octant, origin): + nx += 1 + if bottom >= (ny, nx): + return + was_opaque = is_opaque + if was_opaque != 0: + break + + @staticmethod + def translate_coord(y: int, x: int, octant: int, + origin: Tuple[int, int]) -> Tuple[int, int]: + ny, nx = origin + if octant == 0: + return nx + x, ny - y + elif octant == 1: + return nx + y, ny - x + elif octant == 2: + return nx - y, ny - x + elif octant == 3: + return nx - x, ny - y + elif octant == 4: + return nx - x, ny + y + elif octant == 5: + return nx - y, ny + x + elif octant == 6: + return nx + y, ny + x + elif octant == 7: + return nx + x, ny + y + + def is_wall(self, y: int, x: int, octant: int, + origin: Tuple[int, int]) -> bool: + y, x = self.translate_coord(y, x, octant, origin) + return self.tiles[y][x].is_wall() + + def set_visible(self, y: int, x: int, octant: int, + origin: Tuple[int, int]) -> None: + y, x = self.translate_coord(y, x, octant, origin) + self.visibility[y][x] = True + def tick(self) -> None: """ Trigger all entity events. From 646e0063bea284109aeff6703e071118fa486eba Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 13 Dec 2020 21:29:25 +0100 Subject: [PATCH 02/40] Fixed grammar, unified the docstring's format and added documentation to some classes that did not have any. Closes #32. --- squirrelbattle/display/display.py | 38 ++++++-- squirrelbattle/display/display_manager.py | 22 ++++- squirrelbattle/display/logsdisplay.py | 4 +- squirrelbattle/display/mapdisplay.py | 4 +- squirrelbattle/display/menudisplay.py | 12 ++- squirrelbattle/display/messagedisplay.py | 2 +- squirrelbattle/display/statsdisplay.py | 3 + squirrelbattle/display/texturepack.py | 3 + squirrelbattle/entities/friendly.py | 9 +- squirrelbattle/entities/items.py | 30 ++++--- squirrelbattle/entities/monsters.py | 30 +++---- squirrelbattle/entities/player.py | 13 ++- squirrelbattle/enums.py | 11 +-- squirrelbattle/game.py | 25 +++--- squirrelbattle/interfaces.py | 100 ++++++++++++---------- squirrelbattle/menus.py | 34 ++++++-- squirrelbattle/settings.py | 14 +-- squirrelbattle/term_manager.py | 2 +- squirrelbattle/tests/entities_test.py | 16 ++-- squirrelbattle/tests/game_test.py | 28 +++--- squirrelbattle/tests/interfaces_test.py | 6 +- squirrelbattle/tests/settings_test.py | 2 +- squirrelbattle/tests/translations_test.py | 4 +- squirrelbattle/translations.py | 12 +-- 24 files changed, 254 insertions(+), 170 deletions(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 29295de..f343230 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -24,9 +24,16 @@ class Display: self.pack = pack or TexturePack.get_pack("ascii") def newpad(self, height: int, width: int) -> Union[FakePad, Any]: + """ + Overwrites the native curses function of the same name. + """ return curses.newpad(height, width) if self.screen else FakePad() def truncate(self, msg: str, height: int, width: int) -> str: + """ + Truncates a string into a string adapted to the width and height of + the screen. + """ height = max(0, height) width = max(0, width) lines = msg.split("\n") @@ -36,8 +43,8 @@ class Display: def translate_color(self, color: Union[int, Tuple[int, int, int]]) -> int: """ - Translate a tuple (R, G, B) into a curses color index. - If we have already a color index, then nothing is processed. + Translates a tuple (R, G, B) into a curses color index. + If we already have a color index, then nothing is processed. If this is a tuple, we construct a new color index if non-existing and we return this index. The values of R, G and B must be between 0 and 1000, and not @@ -66,9 +73,9 @@ class Display: low: bool = False, right: bool = False, top: bool = False, vertical: bool = False, chartext: bool = False) -> None: """ - Display a message onto the pad. + Displays a message onto the pad. If the message is too large, it is truncated vertically and horizontally - The text can be bold, italic, blinking, ... if the good parameters are + The text can be bold, italic, blinking, ... if the right parameters are given. These parameters are translated into curses attributes. The foreground and background colors can be given as curses constants (curses.COLOR_*), or by giving a tuple (R, G, B) that corresponds to @@ -126,6 +133,9 @@ class Display: def resize(self, y: int, x: int, height: int, width: int, resize_pad: bool = True) -> None: + """ + Resizes a pad. + """ self.x = x self.y = y self.width = width @@ -136,6 +146,9 @@ class Display: self.pad.resize(self.height + 1, self.width + 1) def refresh(self, *args, resize_pad: bool = True) -> None: + """ + Refreshes a pad + """ if len(args) == 4: self.resize(*args, resize_pad) self.display() @@ -144,10 +157,10 @@ class Display: window_y: int, window_x: int, last_y: int, last_x: int) -> None: """ - Refresh a pad on a part of the window. + Refreshes a pad on a part of the window. The refresh starts at coordinates (top_y, top_x) from the pad, and is drawn from (window_y, window_x) to (last_y, last_x). - If coordinates are invalid (negative indexes/length..., then nothing + If coordinates are invalid (negative indexes/length...), then nothing is drawn and no error is raised. """ top_y, top_x = max(0, top_y), max(0, top_x) @@ -167,7 +180,7 @@ class Display: 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. + Maybe it should do something. """ pass @@ -181,7 +194,9 @@ class Display: class VerticalSplit(Display): - + """ + A class to split the screen in two vertically with a pretty line. + """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.pad = self.newpad(self.rows, 1) @@ -202,7 +217,9 @@ class VerticalSplit(Display): class HorizontalSplit(Display): - + """ + A class to split the screen in two horizontally with a pretty line. + """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.pad = self.newpad(1, self.cols) @@ -223,6 +240,9 @@ class HorizontalSplit(Display): class Box(Display): + """ + A class for pretty boxes to print menus and other content. + """ title: str = "" def update_title(self, title: str) -> None: diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index f9b3f01..b9d819c 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -41,6 +41,9 @@ class DisplayManager: self.update_game_components() def handle_display_action(self, action: DisplayActions, *params) -> None: + """ + Handles the differents values of display action. + """ if action == DisplayActions.REFRESH: self.refresh() elif action == DisplayActions.UPDATE: @@ -49,6 +52,9 @@ class DisplayManager: self.handle_mouse_click(*params) def update_game_components(self) -> None: + """ + Updates the game components, for example when loading a game. + """ for d in self.displays: d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK) self.mapdisplay.update_map(self.game.map) @@ -62,6 +68,9 @@ class DisplayManager: self.messagedisplay.update_message(self.game.message) def handle_mouse_click(self, y: int, x: int) -> None: + """ + Handles the mouse clicks. + """ displays = self.refresh() display = None for d in displays: @@ -74,6 +83,9 @@ class DisplayManager: display.handle_click(y - display.y, x - display.x, self.game) def refresh(self) -> List[Display]: + """ + Refreshes all components on the screen. + """ displays = [] if self.game.state == GameMode.PLAY \ @@ -127,7 +139,7 @@ class DisplayManager: def resize_window(self) -> bool: """ - If the window got resized, ensure that the screen size got updated. + When the window is resized, ensures that the screen size is updated. """ y, x = self.screen.getmaxyx() if self.screen else (0, 0) if self.screen and curses.is_term_resized(self.rows, @@ -138,8 +150,16 @@ class DisplayManager: @property def rows(self) -> int: + """ + Overwrites the native curses attribute of the same name, + for testing purposes. + """ return curses.LINES if self.screen else 42 @property def cols(self) -> int: + """ + Overwrites the native curses attribute of the same name, + for testing purposes. + """ return curses.COLS if self.screen else 42 diff --git a/squirrelbattle/display/logsdisplay.py b/squirrelbattle/display/logsdisplay.py index b768a0e..0aac488 100644 --- a/squirrelbattle/display/logsdisplay.py +++ b/squirrelbattle/display/logsdisplay.py @@ -6,7 +6,9 @@ from squirrelbattle.interfaces import Logs class LogsDisplay(Display): - + """ + A class to handle the display of the logs. + """ def __init__(self, *args) -> None: super().__init__(*args) self.pad = self.newpad(self.rows, self.cols) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index 54d9432..2b04963 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -6,7 +6,9 @@ from .display import Display class MapDisplay(Display): - + """ + A class to handle the display of the map. + """ def __init__(self, *args): super().__init__(*args) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index a00d0fe..06bae1d 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -15,7 +15,7 @@ from ..translations import gettext as _ class MenuDisplay(Display): """ - A class to display the menu objects + A class to display the menu objects. """ position: int @@ -78,7 +78,7 @@ class MenuDisplay(Display): class SettingsMenuDisplay(MenuDisplay): """ - A class to display specifically a settingsmenu object + A class to display specifically a settingsmenu object. """ @property def values(self) -> List[str]: @@ -91,7 +91,7 @@ class SettingsMenuDisplay(MenuDisplay): class MainMenuDisplay(Display): """ - A class to display specifically a mainmenu object + A class to display specifically a mainmenu object. """ def __init__(self, menu: MainMenu, *args): super().__init__(*args) @@ -135,6 +135,9 @@ class MainMenuDisplay(Display): class PlayerInventoryDisplay(MenuDisplay): + """ + A class to handle the display of the player's inventory. + """ def update_pad(self) -> None: self.menubox.update_title(_("INVENTORY")) for i, item in enumerate(self.menu.values): @@ -160,6 +163,9 @@ class PlayerInventoryDisplay(MenuDisplay): class StoreInventoryDisplay(MenuDisplay): + """ + A class to handle the display of a merchant's inventory. + """ def update_pad(self) -> None: self.menubox.update_title(_("STALL")) for i, item in enumerate(self.menu.values): diff --git a/squirrelbattle/display/messagedisplay.py b/squirrelbattle/display/messagedisplay.py index 32f7139..74a98a9 100644 --- a/squirrelbattle/display/messagedisplay.py +++ b/squirrelbattle/display/messagedisplay.py @@ -7,7 +7,7 @@ from squirrelbattle.display.display import Box, Display class MessageDisplay(Display): """ - Display a message in a popup. + A class to handle the display of popup messages. """ def __init__(self, *args, **kwargs): diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index 9937c3e..a2fd5f4 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -9,6 +9,9 @@ from .display import Display class StatsDisplay(Display): + """ + A class to handle the display of the stats of the player. + """ player: Player def __init__(self, *args, **kwargs): diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index f72cd97..e4c181e 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -6,6 +6,9 @@ from typing import Any class TexturePack: + """ + A class to handle displaying several textures. + """ _packs = dict() name: str diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 6c99090..d06f35b 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -7,7 +7,7 @@ from random import choice class Merchant(InventoryHolder, FriendlyEntity): """ - The class for merchants in the dungeon + The class of merchants in the dungeon. """ def keys(self) -> list: """ @@ -28,13 +28,13 @@ class Merchant(InventoryHolder, FriendlyEntity): 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 + and allows the player to buy/sell objects. """ return _("I don't sell any squirrel") def change_hazel_balance(self, hz: int) -> None: """ - Change the number of hazel the merchant has by hz. + Changes the number of hazel the merchant has by hz. """ self.hazel += hz @@ -49,4 +49,7 @@ class Sunflower(FriendlyEntity): @property def dialogue_option(self) -> list: + """ + Lists all that a sunflower can say to the player. + """ return [_("Flower power!!"), _("The sun is warm today")] diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 865a703..0661d5d 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -11,7 +11,7 @@ from ..translations import gettext as _ class Item(Entity): """ - A class for items + A class for items. """ held: bool held_by: Optional[InventoryHolder] @@ -27,7 +27,7 @@ class Item(Entity): def drop(self) -> None: """ - The item is dropped from the inventory onto the floor + The item is dropped from the inventory onto the floor. """ if self.held: self.held_by.inventory.remove(self) @@ -48,7 +48,7 @@ class Item(Entity): def hold(self, player: InventoryHolder) -> None: """ - The item is taken from the floor and put into the inventory + The item is taken from the floor and put into the inventory. """ self.held = True self.held_by = player @@ -57,7 +57,7 @@ class Item(Entity): def save_state(self) -> dict: """ - Saves the state of the entity into a dictionary + Saves the state of the item into a dictionary. """ d = super().save_state() d["held"] = self.held @@ -65,13 +65,16 @@ class Item(Entity): @staticmethod def get_all_items() -> list: + """ + Returns the list of all item classes. + """ return [BodySnatchPotion, Bomb, Heart, Sword] 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 + inventory. """ if buyer.hazel >= self.price: self.hold(buyer) @@ -85,7 +88,7 @@ class Item(Entity): class Heart(Item): """ - A heart item to return health to the player + A heart item to return health to the player. """ healing: int @@ -96,14 +99,15 @@ class Heart(Item): def hold(self, entity: InventoryHolder) -> None: """ - When holding a heart, heal the player and don't put item in inventory. + When holding a heart, the player is healed and + the item is not put in the inventory. """ entity.health = min(entity.maxhealth, entity.health + self.healing) entity.map.remove_entity(self) def save_state(self) -> dict: """ - Saves the state of the header into a dictionary + Saves the state of the heart into a dictionary. """ d = super().save_state() d["healing"] = self.healing @@ -129,7 +133,7 @@ class Bomb(Item): def use(self) -> None: """ - When the bomb is used, throw it and explodes it. + When the bomb is used, it is thrown and then it explodes. """ if self.held: self.owner = self.held_by @@ -138,7 +142,7 @@ class Bomb(Item): def act(self, m: Map) -> None: """ - Special exploding action of the bomb + Special exploding action of the bomb. """ if self.exploding: if self.tick > 0: @@ -164,7 +168,7 @@ class Bomb(Item): def save_state(self) -> dict: """ - Saves the state of the bomb into a dictionary + Saves the state of the bomb into a dictionary. """ d = super().save_state() d["exploding"] = self.exploding @@ -181,13 +185,13 @@ class Explosion(Item): def act(self, m: Map) -> None: """ - The explosion instant dies. + The bomb disappears after exploding. """ m.remove_entity(self) def hold(self, player: InventoryHolder) -> None: """ - The player can't hold any explosion. + The player can't hold an explosion. """ pass diff --git a/squirrelbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py index 34cd4bf..5453235 100644 --- a/squirrelbattle/entities/monsters.py +++ b/squirrelbattle/entities/monsters.py @@ -10,8 +10,8 @@ from ..interfaces import FightingEntity, Map class Monster(FightingEntity): """ The class for all monsters in the dungeon. - A monster must override this class, and the parameters are given - in the __init__ function. + All specific monster classes overwrite this class, + and the parameters are given in the __init__ function. An example of the specification of a monster that has a strength of 4 and 20 max HP: @@ -21,7 +21,7 @@ class Monster(FightingEntity): super().__init__(name="my_monster", strength=strength, maxhealth=maxhealth, *args, **kwargs) - With that way, attributes can be overwritten when the entity got created. + With that way, attributes can be overwritten when the entity is created. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -29,7 +29,7 @@ class Monster(FightingEntity): def act(self, m: Map) -> None: """ By default, a monster will move randomly where it is possible - And if a player is close to the monster, the monster run on the player. + If the player is closeby, the monster runs to the player. """ target = None for entity in m.entities: @@ -38,12 +38,12 @@ class Monster(FightingEntity): target = entity break - # A Dijkstra algorithm has ran that targets the player. - # With that way, monsters can simply follow the path. - # If they can't move and they are already close to the player, - # They hit. + # Monsters move according to a Dijkstra algorithm + # that targets the player. + # If they can not move and are already close to the player, + # they hit. if target and (self.y, self.x) in target.paths: - # Move to target player by choosing the best avaliable path + # Moves to target player by choosing the best available path for next_y, next_x in target.paths[(self.y, self.x)]: moved = self.check_move(next_y, next_x, True) if moved: @@ -52,8 +52,8 @@ class Monster(FightingEntity): self.map.logs.add_message(self.hit(target)) break else: - # Move in a random direction - # If the direction is not available, try another one + # Moves in a random direction + # If the direction is not available, tries another one moves = [self.move_up, self.move_down, self.move_left, self.move_right] shuffle(moves) @@ -64,7 +64,7 @@ class Monster(FightingEntity): class Tiger(Monster): """ - A tiger monster + A tiger monster. """ def __init__(self, name: str = "tiger", strength: int = 2, maxhealth: int = 20, *args, **kwargs) -> None: @@ -74,7 +74,7 @@ class Tiger(Monster): class Hedgehog(Monster): """ - A really mean hedgehog monster + A really mean hedgehog monster. """ def __init__(self, name: str = "hedgehog", strength: int = 3, maxhealth: int = 10, *args, **kwargs) -> None: @@ -84,7 +84,7 @@ class Hedgehog(Monster): class Rabbit(Monster): """ - A rabbit monster + A rabbit monster. """ def __init__(self, name: str = "rabbit", strength: int = 1, maxhealth: int = 15, *args, **kwargs) -> None: @@ -94,7 +94,7 @@ class Rabbit(Monster): class TeddyBear(Monster): """ - A cute teddybear monster + A cute teddybear monster. """ def __init__(self, name: str = "teddy_bear", strength: int = 0, maxhealth: int = 50, *args, **kwargs) -> None: diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 19c8348..36b497f 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -11,7 +11,7 @@ from ..interfaces import FightingEntity, InventoryHolder class Player(InventoryHolder, FightingEntity): """ - The class of the player + The class of the player. """ current_xp: int = 0 max_xp: int = 10 @@ -45,7 +45,7 @@ class Player(InventoryHolder, FightingEntity): def level_up(self) -> None: """ - Add levels to the player as much as it is possible. + Add as many levels as possible to the player. """ while self.current_xp > self.max_xp: self.level += 1 @@ -59,8 +59,8 @@ class Player(InventoryHolder, FightingEntity): def add_xp(self, xp: int) -> None: """ - Add some experience to the player. - If the required amount is reached, level up. + Adds some experience to the player. + If the required amount is reached, the player levels up. """ self.current_xp += xp self.level_up() @@ -89,9 +89,8 @@ class Player(InventoryHolder, FightingEntity): def recalculate_paths(self, max_distance: int = 8) -> None: """ - Use Dijkstra algorithm to calculate best paths for monsters to go to - the player. Actually, the paths are computed for each tile adjacent to - the player then for each step the monsters use the best path avaliable. + Uses Dijkstra algorithm to calculate best paths for monsters to go to + the player. """ distances = [] predecessors = [] diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index 7e4efa4..d248d29 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -16,12 +16,11 @@ class DisplayActions(Enum): """ REFRESH = auto() UPDATE = auto() - MOUSE = auto() class GameMode(Enum): """ - Game mode options + Game mode options. """ MAINMENU = auto() PLAY = auto() @@ -32,9 +31,8 @@ class GameMode(Enum): class KeyValues(Enum): """ - Key values options used in the game + Key values options used in the game. """ - MOUSE = auto() UP = auto() DOWN = auto() LEFT = auto() @@ -46,12 +44,11 @@ class KeyValues(Enum): DROP = auto() SPACE = auto() CHAT = auto() - WAIT = auto() @staticmethod def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]: """ - Translate the raw string key into an enum value that we can use. + Translates the raw string key into an enum value that we can use. """ if key in (settings.KEY_DOWN_SECONDARY, settings.KEY_DOWN_PRIMARY): @@ -79,6 +76,4 @@ class KeyValues(Enum): return KeyValues.SPACE elif key == settings.KEY_CHAT: return KeyValues.CHAT - elif key == settings.KEY_WAIT: - return KeyValues.WAIT return None diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index ed3b60f..35c0b0f 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -30,7 +30,7 @@ class Game: def __init__(self) -> None: """ - Init the game. + Initiates the game. """ self.state = GameMode.MAINMENU self.waiting_for_friendly_key = False @@ -48,7 +48,7 @@ class Game: def new_game(self) -> None: """ - Create a new game on the screen. + Creates a new game on the screen. """ # TODO generate a new map procedurally self.map = Map.load(ResourceManager.get_asset_path("example_map.txt")) @@ -63,8 +63,8 @@ class Game: def run(self, screen: Any) -> None: """ Main infinite loop. - We wait for the player's action, then we do what that should be done - when the given key gets pressed. + We wait for the player's action, then we do what should be done + when a key gets pressed. """ while True: # pragma no cover screen.erase() @@ -81,7 +81,7 @@ class Game: def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str = '')\ -> None: """ - Indicates what should be done when the given key is pressed, + Indicates what should be done when a given key is pressed, according to the current game state. """ if self.message: @@ -133,8 +133,9 @@ class Game: 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 the player tries to talk to a friendly entity, the game waits for + a directional key to be pressed, verifies there is a friendly entity + in that direction and then lets the player interact with it. """ if not self.waiting_for_friendly_key: return @@ -210,7 +211,7 @@ class Game: def handle_key_pressed_main_menu(self, key: KeyValues) -> None: """ - In the main menu, we can navigate through options. + In the main menu, we can navigate through different options. """ if key == KeyValues.DOWN: self.main_menu.go_down() @@ -235,13 +236,13 @@ class Game: def save_state(self) -> dict: """ - Saves the game to a dictionary + Saves the game to a dictionary. """ return self.map.save_state() def load_state(self, d: dict) -> None: """ - Loads the game from a dictionary + Loads the game from a dictionary. """ try: self.map.load_state(d) @@ -265,7 +266,7 @@ class Game: def load_game(self) -> None: """ - Loads the game from a file + Loads the game from a file. """ file_path = ResourceManager.get_config_path("save.json") if os.path.isfile(file_path): @@ -282,7 +283,7 @@ class Game: def save_game(self) -> None: """ - Saves the game to a file + Saves the game to a file. """ with open(ResourceManager.get_config_path("save.json"), "w") as f: f.write(json.dumps(self.save_state())) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 94025bd..71f70ad 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -12,7 +12,7 @@ from .translations import gettext as _ class Logs: """ - The logs object stores the messages to display. It is encapsulating a list + The logs object stores the messages to display. It encapsulates a list of such messages, to allow multiple pointers to keep track of it even if the list was to be reassigned. """ @@ -32,7 +32,7 @@ class Logs: class Map: """ - Object that represents a Map with its width, height + The Map object represents a with its width, height and tiles, that have their custom properties. """ width: int @@ -59,14 +59,14 @@ class Map: def add_entity(self, entity: "Entity") -> None: """ - Register a new entity in the map. + Registers a new entity in the map. """ self.entities.append(entity) entity.map = self def remove_entity(self, entity: "Entity") -> None: """ - Unregister an entity from the map. + Unregisters an entity from the map. """ if entity in self.entities: self.entities.remove(entity) @@ -86,7 +86,7 @@ class Map: def entity_is_present(self, y: int, x: int) -> bool: """ Indicates that the tile at the coordinates (y, x) contains a killable - entity + entity. """ return 0 <= y < self.height and 0 <= x < self.width and \ any(entity.x == x and entity.y == y and entity.is_friendly() @@ -95,7 +95,7 @@ class Map: @staticmethod def load(filename: str) -> "Map": """ - Read a file that contains the content of a map, and build a Map object. + Reads a file that contains the content of a map, and builds a Map object. """ with open(filename, "r") as f: file = f.read() @@ -104,7 +104,7 @@ class Map: @staticmethod def load_from_string(content: str) -> "Map": """ - Load a map represented by its characters and build a Map object. + Loads a map represented by its characters and builds a Map object. """ lines = content.split("\n") first_line = lines[0] @@ -120,7 +120,7 @@ class Map: @staticmethod def load_dungeon_from_string(content: str) -> List[List["Tile"]]: """ - Transforms a string into the list of corresponding tiles + Transforms a string into the list of corresponding tiles. """ lines = content.split("\n") tiles = [[Tile.from_ascii_char(c) @@ -129,7 +129,7 @@ class Map: def draw_string(self, pack: TexturePack) -> str: """ - Draw the current map as a string object that can be rendered + Draws the current map as a string object that can be rendered in the window. """ return "\n".join("".join(tile.char(pack) for tile in line) @@ -137,7 +137,7 @@ class Map: def spawn_random_entities(self, count: int) -> None: """ - Put randomly {count} entities on the map, where it is available. + Puts randomly {count} entities on the map, only on empty ground tiles. """ for ignored in range(count): y, x = 0, 0 @@ -152,14 +152,14 @@ class Map: def tick(self) -> None: """ - Trigger all entity events. + Triggers all entity events. """ for entity in self.entities: entity.act(self) def save_state(self) -> dict: """ - Saves the map's attributes to a dictionary + Saves the map's attributes to a dictionary. """ d = dict() d["width"] = self.width @@ -176,7 +176,7 @@ class Map: def load_state(self, d: dict) -> None: """ - Loads the map's attributes from a dictionary + Loads the map's attributes from a dictionary. """ self.width = d["width"] self.height = d["height"] @@ -193,7 +193,7 @@ class Map: class Tile(Enum): """ - The internal representation of the tiles of the map + The internal representation of the tiles of the map. """ EMPTY = auto() WALL = auto() @@ -202,7 +202,7 @@ class Tile(Enum): @staticmethod def from_ascii_char(ch: str) -> "Tile": """ - Maps an ascii character to its equivalent in the texture pack + Maps an ascii character to its equivalent in the texture pack. """ for tile in Tile: if tile.char(TexturePack.ASCII_PACK) == ch: @@ -212,7 +212,7 @@ class Tile(Enum): def char(self, pack: TexturePack) -> str: """ Translates a Tile to the corresponding character according - to the texture pack + to the texture pack. """ return getattr(pack, self.name) @@ -224,14 +224,14 @@ class Tile(Enum): def can_walk(self) -> bool: """ - Check if an entity (player or not) can move in this tile. + Checks if an entity (player or not) can move in this tile. """ return not self.is_wall() and self != Tile.EMPTY class Entity: """ - An Entity object represents any entity present on the map + An Entity object represents any entity present on the map. """ y: int x: int @@ -249,7 +249,7 @@ class Entity: def check_move(self, y: int, x: int, move_if_possible: bool = False)\ -> bool: """ - Checks if moving to (y,x) is authorized + Checks if moving to (y,x) is authorized. """ free = self.map.is_free(y, x) if free and move_if_possible: @@ -258,7 +258,7 @@ class Entity: def move(self, y: int, x: int) -> bool: """ - Moves an entity to (y,x) coordinates + Moves an entity to (y,x) coordinates. """ self.y = y self.x = x @@ -266,49 +266,49 @@ class Entity: def move_up(self, force: bool = False) -> bool: """ - Moves the entity up one tile, if possible + Moves the entity up one tile, if possible. """ return self.move(self.y - 1, self.x) if force else \ self.check_move(self.y - 1, self.x, True) def move_down(self, force: bool = False) -> bool: """ - Moves the entity down one tile, if possible + Moves the entity down one tile, if possible. """ return self.move(self.y + 1, self.x) if force else \ self.check_move(self.y + 1, self.x, True) def move_left(self, force: bool = False) -> bool: """ - Moves the entity left one tile, if possible + Moves the entity left one tile, if possible. """ return self.move(self.y, self.x - 1) if force else \ self.check_move(self.y, self.x - 1, True) def move_right(self, force: bool = False) -> bool: """ - Moves the entity right one tile, if possible + Moves the entity right one tile, if possible. """ return self.move(self.y, self.x + 1) if force else \ self.check_move(self.y, self.x + 1, True) def act(self, m: Map) -> None: """ - Define the action of the entity that is ran each tick. + Defines the action the entity will do at each tick. By default, does nothing. """ pass def distance_squared(self, other: "Entity") -> int: """ - Get the square of the distance to another entity. - Useful to check distances since square root takes time. + Gives the square of the distance to another entity. + Useful to check distances since taking the square root takes time. """ return (self.y - other.y) ** 2 + (self.x - other.x) ** 2 def distance(self, other: "Entity") -> float: """ - Get the cartesian distance to another entity. + Gives the cartesian distance to another entity. """ return sqrt(self.distance_squared(other)) @@ -340,12 +340,15 @@ class Entity: @property def translated_name(self) -> str: + """ + Translates the name of entities. + """ return _(self.name.replace("_", " ")) @staticmethod def get_all_entity_classes() -> list: """ - Returns all entities subclasses + Returns all entities subclasses. """ from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart from squirrelbattle.entities.monsters import Tiger, Hedgehog, \ @@ -357,7 +360,7 @@ class Entity: @staticmethod def get_all_entity_classes_in_a_dict() -> dict: """ - Returns all entities subclasses in a dictionary + Returns all entities subclasses in a dictionary. """ from squirrelbattle.entities.player import Player from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \ @@ -381,7 +384,7 @@ class Entity: def save_state(self) -> dict: """ - Saves the coordinates of the entity + Saves the coordinates of the entity. """ d = dict() d["x"] = self.x @@ -393,7 +396,7 @@ class Entity: class FightingEntity(Entity): """ A FightingEntity is an entity that can fight, and thus has a health, - level and stats + level and stats. """ maxhealth: int health: int @@ -420,11 +423,15 @@ class FightingEntity(Entity): @property def dead(self) -> bool: + """ + Is this entity dead ? + """ return self.health <= 0 def hit(self, opponent: "FightingEntity") -> str: """ - Deals damage to the opponent, based on the stats + The entity deals damage to the opponent + based on their respective stats. """ return _("{name} hits {opponent}.")\ .format(name=_(self.translated_name.capitalize()), @@ -433,7 +440,8 @@ class FightingEntity(Entity): def take_damage(self, attacker: "Entity", amount: int) -> str: """ - Take damage from the attacker, based on the stats + The entity takes damage from the attacker + based on their respective stats. """ self.health -= amount if self.health <= 0: @@ -446,20 +454,20 @@ class FightingEntity(Entity): def die(self) -> None: """ - If a fighting entity has no more health, it dies and is removed + If a fighting entity has no more health, it dies and is removed. """ self.map.remove_entity(self) def keys(self) -> list: """ - Returns a fighting entity's specific attributes + Returns a fighting entity's specific attributes. """ return ["name", "maxhealth", "health", "level", "strength", "intelligence", "charisma", "dexterity", "constitution"] def save_state(self) -> dict: """ - Saves the state of the entity into a dictionary + Saves the state of the entity into a dictionary. """ d = super().save_state() for name in self.keys(): @@ -469,7 +477,7 @@ class FightingEntity(Entity): class FriendlyEntity(FightingEntity): """ - Friendly entities are living entities which do not attack the player + Friendly entities are living entities which do not attack the player. """ dialogue_option: list @@ -480,7 +488,7 @@ class FriendlyEntity(FightingEntity): def keys(self) -> list: """ - Returns a friendly entity's specific attributes + Returns a friendly entity's specific attributes. """ return ["maxhealth", "health"] @@ -491,7 +499,7 @@ class InventoryHolder(Entity): def translate_inventory(self, inventory: list) -> list: """ - Translate the JSON-state of the inventory into a list of the items in + Translates the JSON save of the inventory into a list of the items in the inventory. """ for i in range(len(inventory)): @@ -501,7 +509,7 @@ class InventoryHolder(Entity): def dict_to_inventory(self, item_dict: dict) -> Entity: """ - Translate a dict object that contains the state of an item + Translates a dictionnary that contains the state of an item into an item object. """ entity_classes = self.get_all_entity_classes_in_a_dict() @@ -511,7 +519,7 @@ class InventoryHolder(Entity): def save_state(self) -> dict: """ - We save the inventory of the merchant formatted as JSON + The inventory of the merchant is saved in a JSON format. """ d = super().save_state() d["hazel"] = self.hazel @@ -520,19 +528,19 @@ class InventoryHolder(Entity): def add_to_inventory(self, obj: Any) -> None: """ - Adds an object to inventory + Adds an object to the inventory. """ self.inventory.append(obj) def remove_from_inventory(self, obj: Any) -> None: """ - Removes an object from the inventory + 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 + Changes the number of hazel the entity has by hz. hz is negative + when the entity loses money and positive when it gains money. """ self.hazel += hz diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index e0087a3..e6e8cef 100644 --- a/squirrelbattle/menus.py +++ b/squirrelbattle/menus.py @@ -14,7 +14,7 @@ from .translations import gettext as _, Translator class Menu: """ - A Menu object is the logical representation of a menu in the game + A Menu object is the logical representation of a menu in the game. """ values: list @@ -23,26 +23,26 @@ class Menu: def go_up(self) -> None: """ - Moves the pointer of the menu on the previous value + Moves the pointer of the menu on the previous value. """ self.position = max(0, self.position - 1) def go_down(self) -> None: """ - Moves the pointer of the menu on the next value + Moves the pointer of the menu on the next value. """ self.position = min(len(self.values) - 1, self.position + 1) def validate(self) -> Any: """ - Selects the value that is pointed by the menu pointer + Selects the value that is pointed by the menu pointer. """ return self.values[self.position] class MainMenuValues(Enum): """ - Values of the main menu + Values of the main menu. """ START = "New game" RESUME = "Resume" @@ -57,14 +57,14 @@ class MainMenuValues(Enum): class MainMenu(Menu): """ - A special instance of a menu : the main menu + A special instance of a menu : the main menu. """ values = [e for e in MainMenuValues] class SettingsMenu(Menu): """ - A special instance of a menu : the settings menu + A special instance of a menu : the settings menu. """ waiting_for_key: bool = False @@ -75,7 +75,7 @@ class SettingsMenu(Menu): def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str, game: Any) -> None: """ - In the setting menu, we van select a setting and change it + In the setting menu, we can select a setting and change it. """ if not self.waiting_for_key: # Navigate normally through the menu. @@ -121,22 +121,40 @@ class SettingsMenu(Menu): class InventoryMenu(Menu): + """ + A special instance of a menu : the menu for the inventory of the player. + """ player: Player def update_player(self, player: Player) -> None: + """ + Updates the player. + """ self.player = player @property def values(self) -> list: + """ + Returns the values of the menu. + """ return self.player.inventory class StoreMenu(Menu): + """ + A special instance of a menu : the menu for the inventory of a merchant. + """ merchant: Merchant def update_merchant(self, merchant: Merchant) -> None: + """ + Updates the merchant. + """ self.merchant = merchant @property def values(self) -> list: + """ + Returns the values of the menu. + """ return self.merchant.inventory diff --git a/squirrelbattle/settings.py b/squirrelbattle/settings.py index 91edfa4..3ff1be7 100644 --- a/squirrelbattle/settings.py +++ b/squirrelbattle/settings.py @@ -13,9 +13,9 @@ from .translations import gettext as _ class Settings: """ This class stores the settings of the game. - Settings can be get by using for example settings.TEXTURE_PACK directly. - The comment can be get by using settings.get_comment('TEXTURE_PACK'). - We can define the setting by simply use settings.TEXTURE_PACK = 'new_key' + Settings can be obtained by using for example settings.TEXTURE_PACK directly. + The comment can be obtained by using settings.get_comment('TEXTURE_PACK'). + We can set the setting by simply using settings.TEXTURE_PACK = 'new_key' """ def __init__(self): self.KEY_UP_PRIMARY = ['z', 'Main key to move up'] @@ -50,7 +50,7 @@ class Settings: def get_comment(self, item: str) -> str: """ - Retrieve the comment of a setting. + Retrieves the comment relative to a setting. """ if item in self.settings_keys: return _(object.__getattribute__(self, item)[1]) @@ -61,13 +61,13 @@ class Settings: @property def settings_keys(self) -> Generator[str, Any, None]: """ - Get the list of all parameters. + Gets the list of all parameters. """ return (key for key in self.__dict__) def loads_from_string(self, json_str: str) -> None: """ - Dump settings + Loads settings. """ d = json.loads(json_str) for key in d: @@ -75,7 +75,7 @@ class Settings: def dumps_to_string(self) -> str: """ - Dump settings + Dumps settings. """ d = dict() for key in self.settings_keys: diff --git a/squirrelbattle/term_manager.py b/squirrelbattle/term_manager.py index 5a98a4a..6484289 100644 --- a/squirrelbattle/term_manager.py +++ b/squirrelbattle/term_manager.py @@ -8,7 +8,7 @@ from types import TracebackType class TermManager: # pragma: no cover """ The TermManager object initializes the terminal, returns a screen object and - de-initializes the terminal after use + de-initializes the terminal after use. """ def __init__(self): self.screen = curses.initscr() diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 70e3748..f1ffb16 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -14,7 +14,7 @@ from squirrelbattle.resources import ResourceManager class TestEntities(unittest.TestCase): def setUp(self) -> None: """ - Load example map that can be used in tests. + Loads example map that can be used in tests. """ self.map = Map.load(ResourceManager.get_asset_path("example_map.txt")) self.player = Player() @@ -23,7 +23,7 @@ class TestEntities(unittest.TestCase): def test_basic_entities(self) -> None: """ - Test some random stuff with basic entities. + Tests some random stuff with basic entities. """ entity = Entity() entity.move(42, 64) @@ -38,7 +38,7 @@ class TestEntities(unittest.TestCase): def test_fighting_entities(self) -> None: """ - Test some random stuff with fighting entities. + Tests some random stuff with fighting entities. """ entity = Tiger() self.map.add_entity(entity) @@ -91,7 +91,7 @@ class TestEntities(unittest.TestCase): def test_items(self) -> None: """ - Test some random stuff with items. + Tests some random stuff with items. """ item = Item() self.map.add_entity(item) @@ -112,7 +112,7 @@ class TestEntities(unittest.TestCase): def test_bombs(self) -> None: """ - Test some random stuff with bombs. + Tests some random stuff with bombs. """ item = Bomb() hedgehog = Hedgehog() @@ -156,7 +156,7 @@ class TestEntities(unittest.TestCase): def test_hearts(self) -> None: """ - Test some random stuff with hearts. + Tests some random stuff with hearts. """ item = Heart() self.map.add_entity(item) @@ -171,7 +171,7 @@ class TestEntities(unittest.TestCase): def test_body_snatch_potion(self) -> None: """ - Test some random stuff with body snatch potions. + Tests some random stuff with body snatch potions. """ item = BodySnatchPotion() self.map.add_entity(item) @@ -189,7 +189,7 @@ class TestEntities(unittest.TestCase): def test_players(self) -> None: """ - Test some random stuff with players. + Tests some random stuff with players. """ player = Player() self.map.add_entity(player) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 52aeeaf..750335f 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -21,7 +21,7 @@ from ..translations import gettext as _, Translator class TestGame(unittest.TestCase): def setUp(self) -> None: """ - Setup game. + Sets the game up. """ self.game = Game() self.game.new_game() @@ -31,7 +31,7 @@ class TestGame(unittest.TestCase): def test_load_game(self) -> None: """ - Save a game and reload it. + Saves a game and reloads it. """ bomb = Bomb() self.game.map.add_entity(bomb) @@ -85,7 +85,7 @@ class TestGame(unittest.TestCase): def test_bootstrap_fail(self) -> None: """ - Ensure that the test can't play the game, + Ensures that the test can't play the game, because there is no associated shell. Yeah, that's only for coverage. """ @@ -94,7 +94,7 @@ class TestGame(unittest.TestCase): def test_key_translation(self) -> None: """ - Test key bindings. + Tests key bindings. """ self.game.settings = Settings() @@ -150,7 +150,7 @@ class TestGame(unittest.TestCase): def test_key_press(self) -> None: """ - Press a key and see what is done. + Presses a key and asserts what is done is correct. """ self.assertEqual(self.game.state, GameMode.MAINMENU) self.assertEqual(self.game.main_menu.validate(), @@ -241,7 +241,7 @@ class TestGame(unittest.TestCase): def test_mouse_click(self) -> None: """ - Simulate mouse clicks. + Simulates mouse clicks. """ self.game.state = GameMode.MAINMENU @@ -271,7 +271,7 @@ class TestGame(unittest.TestCase): def test_new_game(self) -> None: """ - Ensure that the start button starts a new game. + Ensures that the start button starts a new game. """ old_map = self.game.map old_player = self.game.player @@ -294,7 +294,7 @@ class TestGame(unittest.TestCase): def test_settings_menu(self) -> None: """ - Ensure that the settings menu is working properly. + Ensures that the settings menu is working properly. """ self.game.settings = Settings() @@ -380,7 +380,7 @@ class TestGame(unittest.TestCase): def test_dead_screen(self) -> None: """ - Kill player and render dead screen. + Kills the player and renders the dead message on the fake screen. """ self.game.state = GameMode.PLAY # Kill player @@ -396,13 +396,13 @@ class TestGame(unittest.TestCase): def test_not_implemented(self) -> None: """ - Check that some functions are not implemented, only for coverage. + Checks that some functions are not implemented, only for coverage. """ self.assertRaises(NotImplementedError, Display.display, None) def test_messages(self) -> None: """ - Display error messages. + Displays error messages. """ self.game.message = "I am an error" self.game.display_actions(DisplayActions.UPDATE) @@ -412,7 +412,7 @@ class TestGame(unittest.TestCase): def test_inventory_menu(self) -> None: """ - Open the inventory menu and interact with items. + Opens the inventory menu and interacts with items. """ self.game.state = GameMode.PLAY # Open and close the inventory @@ -473,7 +473,7 @@ class TestGame(unittest.TestCase): def test_talk_to_sunflowers(self) -> None: """ - Interact with sunflowers + Interacts with sunflowers. """ self.game.state = GameMode.PLAY @@ -524,7 +524,7 @@ class TestGame(unittest.TestCase): def test_talk_to_merchant(self) -> None: """ - Interact with merchants + Interacts with merchants. """ self.game.state = GameMode.PLAY diff --git a/squirrelbattle/tests/interfaces_test.py b/squirrelbattle/tests/interfaces_test.py index c9f7253..6f2a4bb 100644 --- a/squirrelbattle/tests/interfaces_test.py +++ b/squirrelbattle/tests/interfaces_test.py @@ -11,7 +11,7 @@ from squirrelbattle.resources import ResourceManager class TestInterfaces(unittest.TestCase): def test_map(self) -> None: """ - Create a map and check that it is well parsed. + Creates a map and checks that it is well parsed. """ m = Map.load_from_string("0 0\n.#\n#.\n") self.assertEqual(m.width, 2) @@ -20,7 +20,7 @@ class TestInterfaces(unittest.TestCase): def test_load_map(self) -> None: """ - Try to load a map from a file. + Tries to load a map from a file. """ m = Map.load(ResourceManager.get_asset_path("example_map.txt")) self.assertEqual(m.width, 52) @@ -28,7 +28,7 @@ class TestInterfaces(unittest.TestCase): def test_tiles(self) -> None: """ - Test some things about tiles. + Tests some things about tiles. """ self.assertFalse(Tile.FLOOR.is_wall()) self.assertTrue(Tile.WALL.is_wall()) diff --git a/squirrelbattle/tests/settings_test.py b/squirrelbattle/tests/settings_test.py index 06225b2..65cb25a 100644 --- a/squirrelbattle/tests/settings_test.py +++ b/squirrelbattle/tests/settings_test.py @@ -13,7 +13,7 @@ class TestSettings(unittest.TestCase): def test_settings(self) -> None: """ - Ensure that settings are well loaded. + Ensures that settings are well loaded. """ settings = Settings() self.assertEqual(settings.KEY_UP_PRIMARY, 'z') diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index 0bd8873..72b8562 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -11,7 +11,7 @@ class TestTranslations(unittest.TestCase): def test_main_menu_translation(self) -> None: """ - Ensure that the main menu is translated. + Ensures that the main menu is translated. """ self.assertEqual(_("New game"), "Nouvelle partie") self.assertEqual(_("Resume"), "Continuer") @@ -22,7 +22,7 @@ class TestTranslations(unittest.TestCase): def test_settings_menu_translation(self) -> None: """ - Ensure that the settings menu is translated. + Ensures that the settings menu is translated. """ self.assertEqual(_("Main key to move up"), "Touche principale pour aller vers le haut") diff --git a/squirrelbattle/translations.py b/squirrelbattle/translations.py index 08d40d1..df140a2 100644 --- a/squirrelbattle/translations.py +++ b/squirrelbattle/translations.py @@ -13,7 +13,7 @@ class Translator: """ This module uses gettext to translate strings. Translator.setlocale defines the language of the strings, - then gettext() translates the message. + then gettext() translates the messages. """ SUPPORTED_LOCALES: List[str] = ["de", "en", "es", "fr"] locale: str = "en" @@ -22,7 +22,7 @@ class Translator: @classmethod def refresh_translations(cls) -> None: """ - Load compiled translations. + Loads compiled translations. """ for language in cls.SUPPORTED_LOCALES: rep = Path(__file__).parent / "locale" / language / "LC_MESSAGES" @@ -37,7 +37,7 @@ class Translator: @classmethod def setlocale(cls, lang: str) -> None: """ - Define the language used to translate the game. + Defines the language used to translate the game. The language must be supported, otherwise nothing is done. """ lang = lang[:2] @@ -51,7 +51,7 @@ class Translator: @classmethod def makemessages(cls) -> None: # pragma: no cover """ - Analyse all strings in the project and extract them. + Analyses all strings in the project and extracts them. """ for language in cls.SUPPORTED_LOCALES: if language == "en": @@ -83,7 +83,7 @@ class Translator: @classmethod def compilemessages(cls) -> None: """ - Compile translation messages from source files. + Compiles translation messages from source files. """ for language in cls.SUPPORTED_LOCALES: if language == "en": @@ -99,7 +99,7 @@ class Translator: def gettext(message: str) -> str: """ - Translate a message. + Translates a message. """ return Translator.get_translator().gettext(message) From a3e059a97bc743315064508538f19cf7463cad97 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Tue, 15 Dec 2020 17:37:23 +0100 Subject: [PATCH 03/40] Some required code mysteriously disappeared --- squirrelbattle/enums.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index d248d29..d9b0735 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): @@ -44,6 +45,7 @@ class KeyValues(Enum): DROP = auto() SPACE = auto() CHAT = auto() + WAIT = auto() @staticmethod def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]: @@ -76,4 +78,6 @@ class KeyValues(Enum): return KeyValues.SPACE elif key == settings.KEY_CHAT: return KeyValues.CHAT + elif key == settings.KEY_WAIT: + return KeyValues.WAIT return None From 8ecbf13eae822864d5fdb24e62d86e724ff07374 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 18 Dec 2020 15:31:23 +0100 Subject: [PATCH 04/40] Being able to calculate paths is now a property of fighting entities. --- squirrelbattle/entities/player.py | 52 ---------------------------- squirrelbattle/interfaces.py | 57 ++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 53 deletions(-) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 36b497f..5f389cf 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -1,8 +1,6 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later -from functools import reduce -from queue import PriorityQueue from random import randint from typing import Dict, Tuple @@ -15,7 +13,6 @@ class Player(InventoryHolder, FightingEntity): """ current_xp: int = 0 max_xp: int = 10 - paths: Dict[Tuple[int, int], Tuple[int, int]] def __init__(self, name: str = "player", maxhealth: int = 20, strength: int = 5, intelligence: int = 1, charisma: int = 1, @@ -87,55 +84,6 @@ class Player(InventoryHolder, FightingEntity): entity.hold(self) return super().check_move(y, x, move_if_possible) - def recalculate_paths(self, max_distance: int = 8) -> None: - """ - Uses Dijkstra algorithm to calculate best paths for monsters to go to - the player. - """ - distances = [] - predecessors = [] - # four Dijkstras, one for each adjacent tile - for dir_y, dir_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]: - queue = PriorityQueue() - new_y, new_x = self.y + dir_y, self.x + dir_x - if not 0 <= new_y < self.map.height or \ - not 0 <= new_x < self.map.width or \ - not self.map.tiles[new_y][new_x].can_walk(): - continue - queue.put(((1, 0), (new_y, new_x))) - visited = [(self.y, self.x)] - distances.append({(self.y, self.x): (0, 0), (new_y, new_x): (1, 0)}) - predecessors.append({(new_y, new_x): (self.y, self.x)}) - while not queue.empty(): - dist, (y, x) = queue.get() - if dist[0] >= max_distance or (y, x) in visited: - continue - visited.append((y, x)) - for diff_y, diff_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]: - new_y, new_x = y + diff_y, x + diff_x - if not 0 <= new_y < self.map.height or \ - not 0 <= new_x < self.map.width or \ - not self.map.tiles[new_y][new_x].can_walk(): - continue - new_distance = (dist[0] + 1, - dist[1] + (not self.map.is_free(y, x))) - if not (new_y, new_x) in distances[-1] or \ - distances[-1][(new_y, new_x)] > new_distance: - predecessors[-1][(new_y, new_x)] = (y, x) - distances[-1][(new_y, new_x)] = new_distance - queue.put((new_distance, (new_y, new_x))) - # For each tile that is reached by at least one Dijkstra, sort the - # different paths by distance to the player. For the technical bits : - # The reduce function is a fold starting on the first element of the - # iterable, and we associate the points to their distance, sort - # along the distance, then only keep the points. - self.paths = {} - for y, x in reduce(set.union, - [set(p.keys()) for p in predecessors], set()): - self.paths[(y, x)] = [p for d, p in sorted( - [(distances[i][(y, x)], predecessors[i][(y, x)]) - for i in range(len(distances)) if (y, x) in predecessors[i]])] - def save_state(self) -> dict: """ Saves the state of the entity into a dictionary diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 71f70ad..afe4c44 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -4,7 +4,9 @@ from enum import Enum, auto from math import sqrt from random import choice, randint -from typing import List, Optional, Any +from typing import List, Optional, Any, Dict, Tuple +from queue import PriorityQueue +from functools import reduce from .display.texturepack import TexturePack from .translations import gettext as _ @@ -237,6 +239,7 @@ class Entity: x: int name: str map: Map + paths: Dict[Tuple[int, int], Tuple[int, int]] # noinspection PyShadowingBuiltins def __init__(self, y: int = 0, x: int = 0, name: Optional[str] = None, @@ -245,6 +248,7 @@ class Entity: self.x = x self.name = name self.map = map + self.paths = None def check_move(self, y: int, x: int, move_if_possible: bool = False)\ -> bool: @@ -292,6 +296,57 @@ class Entity: return self.move(self.y, self.x + 1) if force else \ self.check_move(self.y, self.x + 1, True) + def recalculate_paths(self, max_distance: int = 8) -> None: + """ + Uses Dijkstra algorithm to calculate best paths for other entities to + go to this entity. If self.paths is None, does nothing. + """ + if self.paths == None : + return + distances = [] + predecessors = [] + # four Dijkstras, one for each adjacent tile + for dir_y, dir_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]: + queue = PriorityQueue() + new_y, new_x = self.y + dir_y, self.x + dir_x + if not 0 <= new_y < self.map.height or \ + not 0 <= new_x < self.map.width or \ + not self.map.tiles[new_y][new_x].can_walk(): + continue + queue.put(((1, 0), (new_y, new_x))) + visited = [(self.y, self.x)] + distances.append({(self.y, self.x): (0, 0), (new_y, new_x): (1, 0)}) + predecessors.append({(new_y, new_x): (self.y, self.x)}) + while not queue.empty(): + dist, (y, x) = queue.get() + if dist[0] >= max_distance or (y, x) in visited: + continue + visited.append((y, x)) + for diff_y, diff_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]: + new_y, new_x = y + diff_y, x + diff_x + if not 0 <= new_y < self.map.height or \ + not 0 <= new_x < self.map.width or \ + not self.map.tiles[new_y][new_x].can_walk(): + continue + new_distance = (dist[0] + 1, + dist[1] + (not self.map.is_free(y, x))) + if not (new_y, new_x) in distances[-1] or \ + distances[-1][(new_y, new_x)] > new_distance: + predecessors[-1][(new_y, new_x)] = (y, x) + distances[-1][(new_y, new_x)] = new_distance + queue.put((new_distance, (new_y, new_x))) + # For each tile that is reached by at least one Dijkstra, sort the + # different paths by distance to the player. For the technical bits : + # The reduce function is a fold starting on the first element of the + # iterable, and we associate the points to their distance, sort + # along the distance, then only keep the points. + self.paths = {} + for y, x in reduce(set.union, + [set(p.keys()) for p in predecessors], set()): + self.paths[(y, x)] = [p for d, p in sorted( + [(distances[i][(y, x)], predecessors[i][(y, x)]) + for i in range(len(distances)) if (y, x) in predecessors[i]])] + def act(self, m: Map) -> None: """ Defines the action the entity will do at each tick. From 86628fdea6b6f9b279ae65c9562e3a2086b93f1f Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 18 Dec 2020 17:04:45 +0100 Subject: [PATCH 05/40] Working visibility and displaying it, still need to hide things that aren't visible --- squirrelbattle/display/mapdisplay.py | 11 ++++- squirrelbattle/display/texturepack.py | 19 ++++--- squirrelbattle/entities/player.py | 4 +- squirrelbattle/interfaces.py | 71 ++++++++++++++++----------- 4 files changed, 64 insertions(+), 41 deletions(-) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index 54d9432..c4f29e3 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -15,8 +15,15 @@ class MapDisplay(Display): self.pad = self.newpad(m.height, self.pack.tile_width * m.width + 1) def update_pad(self) -> None: - self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), - self.pack.tile_fg_color, self.pack.tile_bg_color) + for j in range(len(self.map.tiles)): + for i in range(len(self.map.tiles[j])): + color = self.pack.tile_fg_visible_color if \ + self.map.visibility[j][i] else self.pack.tile_fg_color + self.addstr(self.pad, j, self.pack.tile_width * i, + self.map.tiles[j][i].char(self.pack), + color, self.pack.tile_bg_color) + # self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), + # self.pack.tile_fg_color, self.pack.tile_bg_color) for e in self.map.entities: self.addstr(self.pad, e.y, self.pack.tile_width * e.x, self.pack[e.name.upper()], diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index f72cd97..fb56dd5 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import curses -from typing import Any +from typing import Any, Union, Tuple class TexturePack: @@ -10,10 +10,11 @@ class TexturePack: name: str tile_width: int - tile_fg_color: int - tile_bg_color: int - entity_fg_color: int - entity_bg_color: int + tile_fg_color: Union[int, Tuple[int, int, int]] + tile_fg_visible_color: Union[int, Tuple[int, int, int]] + tile_bg_color: Union[int, Tuple[int, int, int]] + entity_fg_color: Union[int, Tuple[int, int, int]] + entity_bg_color: Union[int, Tuple[int, int, int]] BODY_SNATCH_POTION: str BOMB: str @@ -54,9 +55,10 @@ class TexturePack: TexturePack.ASCII_PACK = TexturePack( name="ascii", tile_width=1, + tile_fg_visible_color=(1000, 1000, 1000), tile_fg_color=curses.COLOR_WHITE, tile_bg_color=curses.COLOR_BLACK, - entity_fg_color=curses.COLOR_WHITE, + entity_fg_color=(1000, 1000, 1000), entity_bg_color=curses.COLOR_BLACK, BODY_SNATCH_POTION='S', @@ -80,10 +82,11 @@ TexturePack.ASCII_PACK = TexturePack( TexturePack.SQUIRREL_PACK = TexturePack( name="squirrel", tile_width=2, + tile_fg_visible_color=(1000, 1000, 1000), tile_fg_color=curses.COLOR_WHITE, tile_bg_color=curses.COLOR_BLACK, - entity_fg_color=curses.COLOR_WHITE, - entity_bg_color=curses.COLOR_WHITE, + entity_fg_color=(1000, 1000, 1000), + entity_bg_color=(1000, 1000, 1000), BODY_SNATCH_POTION='🔀', BOMB='💣', diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 19c8348..6d18884 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -21,7 +21,7 @@ class Player(InventoryHolder, FightingEntity): 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) \ + hazel: int = 42, vision: int = 5, *args, **kwargs) \ -> None: super().__init__(name=name, maxhealth=maxhealth, strength=strength, intelligence=intelligence, charisma=charisma, @@ -32,6 +32,7 @@ class Player(InventoryHolder, FightingEntity): self.inventory = self.translate_inventory(inventory or []) self.paths = dict() self.hazel = hazel + self.vision = vision def move(self, y: int, x: int) -> None: """ @@ -42,6 +43,7 @@ class Player(InventoryHolder, FightingEntity): self.map.currenty = y self.map.currentx = x self.recalculate_paths() + self.map.compute_visibility(self.y, self.x, self.vision) def level_up(self) -> None: """ diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 6458df7..89399f5 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -4,7 +4,8 @@ from enum import Enum, auto from math import sqrt from random import choice, randint -from typing import List, Optional, Union, Tuple, Any +from typing import List, Optional, Tuple, Any +from math import ceil from .display.texturepack import TexturePack from .translations import gettext as _ @@ -38,19 +39,25 @@ class Slope(): self.Y = y self.X = x - def compare(self, other: Union[Tuple[int, int], "Slope"]) -> int: - if isinstance(other, Slope): - y, x = other.Y, other.X - else: - y, x = other + def compare(self, other: "Slope") -> int: + y, x = other.Y, other.X return self.Y * x - self.X * y - def __lt__(self, other: Union[Tuple[int, int], "Slope"]) -> bool: + def __lt__(self, other: "Slope") -> bool: return self.compare(other) < 0 - def __eq__(self, other: Union[Tuple[int, int], "Slope"]) -> bool: + def __eq__(self, other: "Slope") -> bool: return self.compare(other) == 0 + def __gt__(self, other: "Slope") -> bool: + return self.compare(other) > 0 + + def __le__(self, other: "Slope") -> bool: + return self.compare(other) <= 0 + + def __ge__(self, other: "Slope") -> bool: + return self.compare(other) >= 0 + class Map: """ @@ -194,16 +201,16 @@ class Map: if top.X == 1: top_y = x else: - top_y = ((x * 2 - 1) * top.Y + top.X) / (top.X * 2) + top_y = ceil(((x * 2 - 1) * top.Y + top.X) / (top.X * 2)) if self.is_wall(top_y, x, octant, origin): - if top >= (top_y * 2 + 1, x * 2) and not\ + if top >= Slope(top_y * 2 + 1, x * 2) and not\ self.is_wall(top_y + 1, x, octant, origin): top_y += 1 else: ax = x * 2 if self.is_wall(top_y + 1, x + 1, octant, origin): ax += 1 - if top > (top_y * 2 + 1, ax): + if top > Slope(top_y * 2 + 1, ax): top_y += 1 return top_y @@ -212,9 +219,9 @@ class Map: if bottom.Y == 0: bottom_y = 0 else: - bottom_y = ((x * 2 + 1) * bottom.Y + bottom.X) /\ - (bottom.X * 2) - if bottom >= (bottom_y * 2 + 1, x * 2) and\ + bottom_y = ceil(((x * 2 - 1) * bottom.Y + bottom.X) + / (bottom.X * 2)) + if bottom >= Slope(bottom_y * 2 + 1, x * 2) and\ self.is_wall(bottom_y, x, octant, origin) and\ not self.is_wall(bottom_y + 1, x, octant, origin): bottom_y += 1 @@ -223,17 +230,18 @@ class Map: def compute_visibility_octant(self, octant: int, origin: Tuple[int, int], max_range: int, distance: int, top: Slope, bottom: Slope) -> None: - for x in range(distance, max_range): + for x in range(distance, max_range + 1): top_y = self.crop_top_visibility(octant, origin, x, top) bottom_y = self.crop_bottom_visibility(octant, origin, x, bottom) was_opaque = -1 for y in range(top_y, bottom_y - 1, -1): - if sqrt(x**2 + y**2) > max_range: + if x + y > max_range: continue is_opaque = self.is_wall(y, x, octant, origin) is_visible = is_opaque\ - or ((y != top_y or top > (y * 4 - 1, x * 4 - 1)) - and (y != bottom_y or bottom < (y * 4 + 1, x * 4 + 1))) + or ((y != top_y or top > Slope(y * 4 - 1, x * 4 + 1)) + and (y != bottom_y + or bottom < Slope(y * 4 + 1, x * 4 - 1))) if is_visible: self.set_visible(y, x, octant, origin) if x == max_range: @@ -242,7 +250,7 @@ class Map: nx, ny = x * 2, y * 2 + 1 if self.is_wall(y + 1, x, octant, origin): nx -= 1 - if top > (ny, nx): + if top > Slope(ny, nx): if y == bottom_y: bottom = Slope(ny, nx) break @@ -257,8 +265,9 @@ class Map: nx, ny = x * 2, y * 2 + 1 if self.is_wall(y + 1, x + 1, octant, origin): nx += 1 - if bottom >= (ny, nx): + if bottom >= Slope(ny, nx): return + top = Slope(ny, nx) was_opaque = is_opaque if was_opaque != 0: break @@ -268,31 +277,33 @@ class Map: origin: Tuple[int, int]) -> Tuple[int, int]: ny, nx = origin if octant == 0: - return nx + x, ny - y + return ny - y, nx + x elif octant == 1: - return nx + y, ny - x + return ny - x, nx + y elif octant == 2: - return nx - y, ny - x + return ny - x, nx - y elif octant == 3: - return nx - x, ny - y + return ny - y, nx - x elif octant == 4: - return nx - x, ny + y + return ny + y, nx - x elif octant == 5: - return nx - y, ny + x + return ny + x, nx - y elif octant == 6: - return nx + y, ny + x + return ny + x, nx + y elif octant == 7: - return nx + x, ny + y + return ny + y, nx + x def is_wall(self, y: int, x: int, octant: int, origin: Tuple[int, int]) -> bool: y, x = self.translate_coord(y, x, octant, origin) - return self.tiles[y][x].is_wall() + return 0 <= y < len(self.tiles) and 0 <= x < len(self.tiles[0]) and \ + self.tiles[y][x].is_wall() def set_visible(self, y: int, x: int, octant: int, origin: Tuple[int, int]) -> None: y, x = self.translate_coord(y, x, octant, origin) - self.visibility[y][x] = True + if 0 <= y < len(self.tiles) and 0 <= x < len(self.tiles[0]): + self.visibility[y][x] = True def tick(self) -> None: """ From dadafc84eb57670df6298ed32e1248aa9f202f68 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 18 Dec 2020 17:29:59 +0100 Subject: [PATCH 06/40] Added a familiar class that follows the player around and hits monsters when it sees one. Added a trumpet, an instance of familiar. Closes #46. --- squirrelbattle/display/texturepack.py | 3 ++ squirrelbattle/entities/friendly.py | 71 +++++++++++++++++++++++++-- squirrelbattle/entities/monsters.py | 7 ++- squirrelbattle/game.py | 10 ++-- squirrelbattle/interfaces.py | 26 +++++++--- 5 files changed, 100 insertions(+), 17 deletions(-) diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index e4c181e..449a2b7 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -32,6 +32,7 @@ class TexturePack: SWORD: str TEDDY_BEAR: str TIGER: str + TRUMPET: str WALL: str ASCII_PACK: "TexturePack" @@ -77,6 +78,7 @@ TexturePack.ASCII_PACK = TexturePack( SWORD='\u2020', TEDDY_BEAR='8', TIGER='n', + TRUMPET='/', WALL='#', ) @@ -103,5 +105,6 @@ TexturePack.SQUIRREL_PACK = TexturePack( SWORD='🗡️', TEDDY_BEAR='🧸', TIGER='🐅', + TRUMPET='🎺', WALL='🧱', ) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index d06f35b..1c94ed8 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -1,8 +1,9 @@ -from ..interfaces import FriendlyEntity, InventoryHolder +from ..interfaces import FriendlyEntity, InventoryHolder, FightingEntity, Map from ..translations import gettext as _ from .player import Player +from .monsters import Monster from .items import Item -from random import choice +from random import choice, shuffle class Merchant(InventoryHolder, FriendlyEntity): @@ -11,7 +12,7 @@ class Merchant(InventoryHolder, FriendlyEntity): """ def keys(self) -> list: """ - Returns a friendly entitie's specific attributes + Returns a friendly entitie's specific attributes. """ return super().keys() + ["inventory", "hazel"] @@ -20,7 +21,6 @@ class Merchant(InventoryHolder, FriendlyEntity): super().__init__(name=name, *args, **kwargs) self.inventory = self.translate_inventory(inventory or []) self.hazel = hazel - if not self.inventory: for i in range(5): self.inventory.append(choice(Item.get_all_items())()) @@ -41,7 +41,7 @@ class Merchant(InventoryHolder, FriendlyEntity): class Sunflower(FriendlyEntity): """ - A friendly sunflower + A friendly sunflower. """ def __init__(self, maxhealth: int = 15, *args, **kwargs) -> None: @@ -53,3 +53,64 @@ class Sunflower(FriendlyEntity): Lists all that a sunflower can say to the player. """ return [_("Flower power!!"), _("The sun is warm today")] + +class Familiar(FightingEntity): + """ + A friendly familiar that helps the player defeat monsters. + """ + def __init__(self, maxhealth: int = 25, + *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.target = None + def act(self, p: Player, m : Map) : + """ + By default, the familiar tries to stay at distance at most 2 of the + player and if a monster comes in range 3, it focuses on the monster + and attacks it. + """ + if self.target == None: + self.target = p + if self.target == p: + for entity in m.entities: + if (self.y - entity.y) ** 2 + (self.x - entity.x) ** 2 <= 9 and \ + isinstance(entity, Monster): + self.target = entity + entity.paths = dict() #Allows the paths to be calculated. + break + + + # Familiars move according to a Dijkstra algorithm + # that targets their target. + # If they can not move and are already close to their target, + # they hit, except if their target is the player. + if self.target and (self.y, self.x) in self.target.paths: + # Moves to target player by choosing the best available path + for next_y, next_x in self.target.paths[(self.y, self.x)]: + moved = self.check_move(next_y, next_x, True) + if moved: + break + if self.distance_squared(self.target) <= 1 and \ + not isinstance(self.target, Player): + self.map.logs.add_message(self.hit(self.target)) + if self.target.dead : + self.target.paths = None + self.target = None + break + else: + # Moves in a random direction + # If the direction is not available, tries another one + moves = [self.move_up, self.move_down, + self.move_left, self.move_right] + shuffle(moves) + for move in moves: + if move(): + break + +class Trumpet(Familiar) : + """ + A class of familiars. + """ + def __init__(self, name: str = "trumpet", strength: int = 3, + maxhealth: int = 20, *args, **kwargs) -> None: + super().__init__(name=name, strength=strength, + maxhealth=maxhealth, *args, **kwargs) diff --git a/squirrelbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py index 5453235..87f643e 100644 --- a/squirrelbattle/entities/monsters.py +++ b/squirrelbattle/entities/monsters.py @@ -60,7 +60,12 @@ class Monster(FightingEntity): for move in moves: if move(): break - + def move(self, y: int, x:int) -> None: + """ + Overwrites the move function to recalculate paths. + """ + super().move(y, x) + self.recalculate_paths() class Tiger(Monster): """ diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 35c0b0f..72bbe2f 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -111,16 +111,16 @@ class Game: """ if key == KeyValues.UP: if self.player.move_up(): - self.map.tick() + self.map.tick(self.player) elif key == KeyValues.DOWN: if self.player.move_down(): - self.map.tick() + self.map.tick(self.player) elif key == KeyValues.LEFT: if self.player.move_left(): - self.map.tick() + self.map.tick(self.player) elif key == KeyValues.RIGHT: if self.player.move_right(): - self.map.tick() + self.map.tick(self.player) elif key == KeyValues.INVENTORY: self.state = GameMode.INVENTORY elif key == KeyValues.SPACE: @@ -129,7 +129,7 @@ class Game: # Wait for the direction of the friendly entity self.waiting_for_friendly_key = True elif key == KeyValues.WAIT: - self.map.tick() + self.map.tick(self.player) def handle_friendly_entity_chat(self, key: KeyValues) -> None: """ diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index afe4c44..0fec4d0 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -63,7 +63,10 @@ class Map: """ Registers a new entity in the map. """ - self.entities.append(entity) + if entity.is_familiar() : + self.entities.insert(1,entity) + else : + self.entities.append(entity) entity.map = self def remove_entity(self, entity: "Entity") -> None: @@ -152,12 +155,15 @@ class Map: entity.move(y, x) self.add_entity(entity) - def tick(self) -> None: + def tick(self, p: Any) -> None: """ Triggers all entity events. """ for entity in self.entities: - entity.act(self) + if entity.is_familiar(): + entity.act(p, self) + else : + entity.act(self) def save_state(self) -> dict: """ @@ -296,7 +302,7 @@ class Entity: return self.move(self.y, self.x + 1) if force else \ self.check_move(self.y, self.x + 1, True) - def recalculate_paths(self, max_distance: int = 8) -> None: + def recalculate_paths(self, max_distance: int = 12) -> None: """ Uses Dijkstra algorithm to calculate best paths for other entities to go to this entity. If self.paths is None, does nothing. @@ -386,6 +392,13 @@ class Entity: """ return isinstance(self, FriendlyEntity) + def is_familiar(self) -> bool: + """ + Is this entity a familiar? + """ + from squirrelbattle.entities.friendly import Familiar + return isinstance(self, Familiar) + def is_merchant(self) -> bool: """ Is this entity a merchant? @@ -408,9 +421,10 @@ 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 + from squirrelbattle.entities.friendly import Merchant, Sunflower, \ + Trumpet return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear, - Sunflower, Tiger, Merchant] + Sunflower, Tiger, Merchant, Trumpet] @staticmethod def get_all_entity_classes_in_a_dict() -> dict: From e1918ab06696418d6d29bef357b4bd4129a9761e Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 17:40:52 +0100 Subject: [PATCH 07/40] tick function takes the player as argument --- squirrelbattle/tests/entities_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index f1ffb16..221fa5f 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -57,17 +57,17 @@ class TestEntities(unittest.TestCase): self.map.add_entity(entity) entity.move(15, 44) # Move randomly - self.map.tick() + self.map.tick(self.player) self.assertFalse(entity.y == 15 and entity.x == 44) # Move to the player entity.move(3, 6) - self.map.tick() + self.map.tick(self.player) self.assertTrue(entity.y == 2 and entity.x == 6) # Rabbit should fight old_health = self.player.health - self.map.tick() + self.map.tick(self.player) self.assertTrue(entity.y == 2 and entity.x == 6) self.assertEqual(old_health - entity.strength, self.player.health) self.assertEqual(self.map.logs.messages[-1], From 0394c5d15dde312d32769503322aad029a94cfea Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 17:46:38 +0100 Subject: [PATCH 08/40] Linting --- squirrelbattle/entities/friendly.py | 24 +++++++++++++----------- squirrelbattle/entities/monsters.py | 4 +++- squirrelbattle/entities/player.py | 1 - squirrelbattle/interfaces.py | 17 +++++++++-------- squirrelbattle/settings.py | 3 ++- 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 1c94ed8..94b1b8a 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -54,31 +54,32 @@ class Sunflower(FriendlyEntity): """ return [_("Flower power!!"), _("The sun is warm today")] + class Familiar(FightingEntity): """ A friendly familiar that helps the player defeat monsters. """ - def __init__(self, maxhealth: int = 25, + def __init__(self, maxhealth: int = 25, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) + super().__init__(maxhealth=maxhealth, *args, **kwargs) self.target = None - def act(self, p: Player, m : Map) : + + def act(self, p: Player, m: Map) -> None: """ By default, the familiar tries to stay at distance at most 2 of the player and if a monster comes in range 3, it focuses on the monster and attacks it. """ - if self.target == None: + if self.target is None: self.target = p if self.target == p: for entity in m.entities: - if (self.y - entity.y) ** 2 + (self.x - entity.x) ** 2 <= 9 and \ + if (self.y - entity.y) ** 2 + (self.x - entity.x) ** 2 <= 9 and\ isinstance(entity, Monster): self.target = entity - entity.paths = dict() #Allows the paths to be calculated. + entity.paths = dict() # Allows the paths to be calculated. break - - + # Familiars move according to a Dijkstra algorithm # that targets their target. # If they can not move and are already close to their target, @@ -90,9 +91,9 @@ class Familiar(FightingEntity): if moved: break if self.distance_squared(self.target) <= 1 and \ - not isinstance(self.target, Player): + not isinstance(self.target, Player): self.map.logs.add_message(self.hit(self.target)) - if self.target.dead : + if self.target.dead: self.target.paths = None self.target = None break @@ -106,7 +107,8 @@ class Familiar(FightingEntity): if move(): break -class Trumpet(Familiar) : + +class Trumpet(Familiar): """ A class of familiars. """ diff --git a/squirrelbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py index 87f643e..27c96a6 100644 --- a/squirrelbattle/entities/monsters.py +++ b/squirrelbattle/entities/monsters.py @@ -60,13 +60,15 @@ class Monster(FightingEntity): for move in moves: if move(): break - def move(self, y: int, x:int) -> None: + + def move(self, y: int, x: int) -> None: """ Overwrites the move function to recalculate paths. """ super().move(y, x) self.recalculate_paths() + class Tiger(Monster): """ A tiger monster. diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 5f389cf..b5e146b 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: GPL-3.0-or-later from random import randint -from typing import Dict, Tuple from ..interfaces import FightingEntity, InventoryHolder diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 0fec4d0..75b49ca 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -63,9 +63,9 @@ class Map: """ Registers a new entity in the map. """ - if entity.is_familiar() : - self.entities.insert(1,entity) - else : + if entity.is_familiar(): + self.entities.insert(1, entity) + else: self.entities.append(entity) entity.map = self @@ -100,7 +100,8 @@ class Map: @staticmethod def load(filename: str) -> "Map": """ - Reads a file that contains the content of a map, and builds a Map object. + Reads a file that contains the content of a map, + and builds a Map object. """ with open(filename, "r") as f: file = f.read() @@ -162,7 +163,7 @@ class Map: for entity in self.entities: if entity.is_familiar(): entity.act(p, self) - else : + else: entity.act(self) def save_state(self) -> dict: @@ -307,7 +308,7 @@ class Entity: Uses Dijkstra algorithm to calculate best paths for other entities to go to this entity. If self.paths is None, does nothing. """ - if self.paths == None : + if self.paths is None: return distances = [] predecessors = [] @@ -352,7 +353,7 @@ class Entity: self.paths[(y, x)] = [p for d, p in sorted( [(distances[i][(y, x)], predecessors[i][(y, x)]) for i in range(len(distances)) if (y, x) in predecessors[i]])] - + def act(self, m: Map) -> None: """ Defines the action the entity will do at each tick. @@ -422,7 +423,7 @@ class Entity: from squirrelbattle.entities.monsters import Tiger, Hedgehog, \ Rabbit, TeddyBear from squirrelbattle.entities.friendly import Merchant, Sunflower, \ - Trumpet + Trumpet return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear, Sunflower, Tiger, Merchant, Trumpet] diff --git a/squirrelbattle/settings.py b/squirrelbattle/settings.py index 3ff1be7..58e8cc1 100644 --- a/squirrelbattle/settings.py +++ b/squirrelbattle/settings.py @@ -13,7 +13,8 @@ from .translations import gettext as _ class Settings: """ This class stores the settings of the game. - Settings can be obtained by using for example settings.TEXTURE_PACK directly. + Settings can be obtained by using for example settings.TEXTURE_PACK + directly. The comment can be obtained by using settings.get_comment('TEXTURE_PACK'). We can set the setting by simply using settings.TEXTURE_PACK = 'new_key' """ From b876dab156f1e626ba791026ba5fc3b08dcabae1 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 18:13:39 +0100 Subject: [PATCH 09/40] Register Trumpet as savable entity --- squirrelbattle/interfaces.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 75b49ca..33b1466 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -435,7 +435,8 @@ 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, \ + Trumpet from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \ Heart, Sword return { @@ -450,6 +451,7 @@ class Entity: "Merchant": Merchant, "Sunflower": Sunflower, "Sword": Sword, + "Trumpet": Trumpet, } def save_state(self) -> dict: From 762bed888af208d9df1ba2eeb5846187811f8bc9 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 18 Dec 2020 21:21:00 +0100 Subject: [PATCH 10/40] Working visibility (at least relatively good), but a few lines untested --- squirrelbattle/assets/example_map_3.txt | 41 +++++++++++++++++++++++++ squirrelbattle/display/mapdisplay.py | 10 ++++-- squirrelbattle/entities/player.py | 2 +- squirrelbattle/interfaces.py | 9 +++++- squirrelbattle/tests/entities_test.py | 2 +- squirrelbattle/tests/interfaces_test.py | 20 +++++++++++- 6 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 squirrelbattle/assets/example_map_3.txt diff --git a/squirrelbattle/assets/example_map_3.txt b/squirrelbattle/assets/example_map_3.txt new file mode 100644 index 0000000..c5dd8e3 --- /dev/null +++ b/squirrelbattle/assets/example_map_3.txt @@ -0,0 +1,41 @@ +1 6 +################################################################################ +#..............................................................................# +#..#...........................................................................# +#...........#..................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +################################################################################ \ No newline at end of file diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index c4f29e3..17d7d6d 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -17,6 +17,8 @@ class MapDisplay(Display): def update_pad(self) -> None: for j in range(len(self.map.tiles)): for i in range(len(self.map.tiles[j])): + if not self.map.seen_tiles[j][i]: + continue color = self.pack.tile_fg_visible_color if \ self.map.visibility[j][i] else self.pack.tile_fg_color self.addstr(self.pad, j, self.pack.tile_width * i, @@ -25,9 +27,11 @@ class MapDisplay(Display): # self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), # self.pack.tile_fg_color, self.pack.tile_bg_color) for e in self.map.entities: - self.addstr(self.pad, e.y, self.pack.tile_width * e.x, - self.pack[e.name.upper()], - self.pack.entity_fg_color, self.pack.entity_bg_color) + if self.map.visibility[e.y][e.x]: + self.addstr(self.pad, e.y, self.pack.tile_width * e.x, + self.pack[e.name.upper()], + self.pack.entity_fg_color, + self.pack.entity_bg_color) # Display Path map for debug purposes # from squirrelbattle.entities.player import Player diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 6d18884..aad35dd 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -21,7 +21,7 @@ class Player(InventoryHolder, FightingEntity): 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, vision: int = 5, *args, **kwargs) \ + hazel: int = 42, vision: int = 50, *args, **kwargs) \ -> None: super().__init__(name=name, maxhealth=maxhealth, strength=strength, intelligence=intelligence, charisma=charisma, diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 89399f5..30e59f4 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -70,6 +70,7 @@ class Map: start_x: int tiles: List[List["Tile"]] visibility: List[List[bool]] + seen_tiles: List[List[bool]] entities: List["Entity"] logs: Logs # coordinates of the point that should be @@ -86,6 +87,8 @@ class Map: self.tiles = tiles self.visibility = [[False for _ in range(len(tiles[0]))] for _ in range(len(tiles))] + self.seen_tiles = [[False for _ in range(len(tiles[0]))] + for _ in range(len(tiles))] self.entities = [] self.logs = Logs() @@ -191,7 +194,7 @@ class Map: for line in self.visibility: for i in range(len(line)): line[i] = False - self.visibility[y][x] = True + self.set_visible(0, 0, 0, (y, x)) for octant in range(8): self.compute_visibility_octant(octant, (y, x), max_range, 1, Slope(1, 1), Slope(0, 1)) @@ -242,6 +245,9 @@ class Map: or ((y != top_y or top > Slope(y * 4 - 1, x * 4 + 1)) and (y != bottom_y or bottom < Slope(y * 4 + 1, x * 4 - 1))) + # is_visible = is_opaque\ + # or ((y != top_y or top >= Slope(y, x)) + # and (y != bottom_y or bottom <= Slope(y, x))) if is_visible: self.set_visible(y, x, octant, origin) if x == max_range: @@ -304,6 +310,7 @@ class Map: y, x = self.translate_coord(y, x, octant, origin) if 0 <= y < len(self.tiles) and 0 <= x < len(self.tiles[0]): self.visibility[y][x] = True + self.seen_tiles[y][x] = True def tick(self) -> None: """ diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 70e3748..c51fd56 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -133,7 +133,7 @@ class TestEntities(unittest.TestCase): self.assertEqual(item.y, 42) self.assertEqual(item.x, 42) # Wait for the explosion - for ignored in range(5): + for _ignored in range(5): item.act(self.map) self.assertTrue(hedgehog.dead) self.assertTrue(teddy_bear.dead) diff --git a/squirrelbattle/tests/interfaces_test.py b/squirrelbattle/tests/interfaces_test.py index c9f7253..1713f77 100644 --- a/squirrelbattle/tests/interfaces_test.py +++ b/squirrelbattle/tests/interfaces_test.py @@ -4,7 +4,7 @@ import unittest from squirrelbattle.display.texturepack import TexturePack -from squirrelbattle.interfaces import Map, Tile +from squirrelbattle.interfaces import Map, Tile, Slope from squirrelbattle.resources import ResourceManager @@ -37,3 +37,21 @@ class TestInterfaces(unittest.TestCase): self.assertFalse(Tile.WALL.can_walk()) self.assertFalse(Tile.EMPTY.can_walk()) self.assertRaises(ValueError, Tile.from_ascii_char, 'unknown') + + def test_slope(self) -> None: + """ + Test good behaviour of slopes (basically vectors, compared according to + the determinant) + """ + a = Slope(1, 1) + b = Slope(0, 1) + self.assertTrue(b < a) + self.assertTrue(b <= a) + self.assertTrue(a <= a) + self.assertTrue(a == a) + self.assertTrue(a > b) + self.assertTrue(a >= b) + + # def test_visibility(self) -> None: + # m = Map.load(ResourceManager.get_asset_path("example_map_3.txt")) + # m.compute_visibility(1, 1, 50) From ea5f5c142878cb91daba7418b5fe0c7c99e55dac Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 18 Dec 2020 21:30:16 +0100 Subject: [PATCH 11/40] Added an original text art to serve as the project's logo. --- squirrelbattle/assets/ascii-art-ecureuil.txt | 44 ++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 squirrelbattle/assets/ascii-art-ecureuil.txt diff --git a/squirrelbattle/assets/ascii-art-ecureuil.txt b/squirrelbattle/assets/ascii-art-ecureuil.txt new file mode 100644 index 0000000..4266b58 --- /dev/null +++ b/squirrelbattle/assets/ascii-art-ecureuil.txt @@ -0,0 +1,44 @@ + + ━ + ┃ ┃ + ┃ ┃ ▓▓▒ ▓▓ + ┃ ┃ ▓▓ ▓▓▒ + ┃ ┃ ▓▓▓ ▓▓ ▓▓▓ ▒▒▒▒▒▒▒▒▒ + ┃ ┃ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒ + ┃ ┃ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ┃ ┃ ▓▓▓░█▓▓▓▓▓▓░█▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ┃ ┃ ▓▓▓▓░██░░▓▓░░██░▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ━━▓▓▓▓━━ ▓▓░░░░░░░░██░░░░░░░░▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ▓▓▓▓▓▓ ▓░░░░░░░░░░░░░░░░░░░░▓▓▒▒▒▒▒▒▒▒▒▒▒▒ + █ ▓▓▓▓▓ ▓░░░░░░░░▄▄▄▄░░░░░░░▓▒▒▒▒▒▒▒▒▒▒▒▒▒ + █ ▓▓▓▓▓ ▓▓░░░░░░░░░░░░░░░▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ▓▓▓▓ ▓▓▓▓░░░░░░░▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ▓▓▓▓▓▓▒▒░░░░░░░░░▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ▓▓▓▓▒░░░░░░░░░░░░▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒ + ▓▓▒░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒ + ▓▒▒░░░░░░░░░░░░▓▓▓▓▒▒▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒ + ▓▓▒░░░░░░░░░░░░░░░▓▒▒▒▒▒▒▓▓▓▓▓▒▒▒▒▒▒▒▒▒ + ▓▓▒▒░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▓▓▒▒▒▒▒▒▒▒▒ + ▓▓▓▒░░░░░░░░░░░░░▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ▓▓▒▓▒▒░░░░░░░░░░░░░░▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒ + ▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒ + ▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▒▒▒▒▒ + ▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▒▒▒ + ▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▒ + ▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▒░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓ ░ + ▓▓▓▓▓▓▓▓▓▒░░░░░░░░░░░░▓▓▓▓▓▓▓▓▓ ░░ + ▓▓▓▓▓▓▓▓▒▒░░░▒▒▒▒░░░░░░▓▓░▒▒▒▓▓▓▓▓▓▓▓▓▓░░░ ░ + ▓▓▓▓▓▓▓▒░░░░░░░░░▒░░░░░░░░░░░░▒▒▒▓▓▓▓▓▓▓▓░░ ░░▒ + ░ ░░░░░▒░░░░░░▒░░░▒░░░░░░░░░░░░░░░░░▒▒▒▒▒▒░░░░░░░▒ + ▒▒░░▓▓░░▒░░░░░░░░▒░░░░░░▒░░░░░░░░▒░░░░░░░░░░▒░░░░░▒ ░░ + ▒▒▒▒▒▓▒▒▓░░░░░░░░░▒░░░░░░░░▒░░░░░░░░▒░░░░░░░░▒░░░░░░░░░░░░ + ▒▒█▒█▒▒▒▓░░▒░░░░░░░░░░░░░░░▒░░░░░░░░▒░░░░░░░░░░░░░░░░░░░░░ + ▒▒▒▒█▒▒▒▒░░░░▒░░░▒░░░░░░░░░░░░░░░░░░░░░░▒░░░░░░░░░░░░▒░░░ + ▓█▒▒▒▒█▒█▒▒▒▒░░▒░░░░░▒░░░░▒░░░░░░░░░░░░░░░░░▒░░░░▒░░░░░░░▒░░░░░▒▒ + ██▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░▒░░░░░░▒░░░░░░░░▒░░░░░░▒░░░░░░▒░░░░░▒░░░░░ + ▒▒▒▒█▒▒▒▒▒▒▒░░░░░░░░░░▒░░░░░░░░░░▒░░░░░░░░░░░▒░░░░░░░░░░░░░░░ + ▒▒█▒▒▒▒▒░▒░▒░░░░▓▓▓░░░░░░░▒░░░░▒░░░▒░░░░░░░▓▓░░░░░░░░░░░░ ░ + ▒▒▒▒▒▒▒░▒░░░▓▓▓▓▓▓░░░░░░░▒░░░░░░░░▒░░░░▓▓▓▓▓▓░░░░░░░░ ░ + ░▓▓▓▓▓▓░░░░░░▒░░░░░░░░▒░░░░░░▓▓▓▓▓░░░ ░ ░░ From 411744bf1052b1dd23b3fdf1789bda6ead56f78f Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 22:24:41 +0100 Subject: [PATCH 12/40] Add credits menu, see #42 --- squirrelbattle/display/creditsdisplay.py | 76 +++++++++++++++++++++++ squirrelbattle/display/display_manager.py | 10 ++- squirrelbattle/display/menudisplay.py | 7 ++- squirrelbattle/enums.py | 1 + squirrelbattle/game.py | 2 + 5 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 squirrelbattle/display/creditsdisplay.py diff --git a/squirrelbattle/display/creditsdisplay.py b/squirrelbattle/display/creditsdisplay.py new file mode 100644 index 0000000..453fe8d --- /dev/null +++ b/squirrelbattle/display/creditsdisplay.py @@ -0,0 +1,76 @@ +# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse +# SPDX-License-Identifier: GPL-3.0-or-later + +import curses + +from ..display.display import Box, Display +from ..game import Game +from ..resources import ResourceManager +from ..translations import gettext as _ + + +class CreditsDisplay(Display): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.box = Box(*args, **kwargs) + self.pad = self.newpad(1, 1) + self.ascii_art_displayed = False + + def display(self) -> None: + self.box.refresh(self.y, self.x, self.height, self.width) + self.box.display() + self.pad.erase() + + messages = [ + _("Credits"), + "", + "Squirrel Battle", + "", + _("Developers:"), + "Yohann \"ÿnérant\" D'ANELLO", + "Mathilde \"eichhornchen\" DÉPRÉS", + "Nicolas \"nicomarg\" MARGULIES", + "Charles \"charsle\" PEYRAT", + "", + _("Translators:"), + "Hugo \"ifugao\" JACOB (español)", + ] + + for i, msg in enumerate(messages): + self.addstr(self.pad, i + (self.height - len(messages)) // 2, + (self.width - len(msg)) // 2, msg, + bold=(i == 0), italic=(":" in msg)) + + if self.ascii_art_displayed: + self.display_ascii_art() + + self.refresh_pad(self.pad, 0, 0, self.y + 1, self.x + 1, + self.height + self.y - 2, + self.width + self.x - 2) + + def display_ascii_art(self): + with open(ResourceManager.get_asset_path("ascii-art-ecureuil.txt"))\ + as f: + ascii_art = f.read().split("\n") + + height, width = len(ascii_art), len(ascii_art[0]) + y_offset, x_offset = (self.height - height) // 2,\ + (self.width - width) // 2 + + for i, line in enumerate(ascii_art): + for j, c in enumerate(line): + bg_color = curses.COLOR_WHITE + fg_color = curses.COLOR_BLACK + if c == '▓': + fg_color = (700, 300, 0) + elif c == '▒': + fg_color = (700, 300, 0) + bg_color = curses.COLOR_BLACK + elif c == '░': + fg_color = (350, 150, 0) + self.addstr(self.pad, y_offset + i, x_offset + j, c, + fg_color, bg_color) + + def handle_click(self, y: int, x: int, game: Game) -> None: + if self.pad.inch(y, x) != ord(" "): + self.ascii_art_displayed = True diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index b9d819c..d87ed9d 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -2,6 +2,8 @@ # SPDX-License-Identifier: GPL-3.0-or-later import curses + +from squirrelbattle.display.creditsdisplay import CreditsDisplay from squirrelbattle.display.display import VerticalSplit, HorizontalSplit, \ Display from squirrelbattle.display.mapdisplay import MapDisplay @@ -30,14 +32,15 @@ class DisplayManager: self.mainmenudisplay = MainMenuDisplay(self.game.main_menu, screen, pack) self.settingsmenudisplay = SettingsMenuDisplay(screen, pack) - self.messagedisplay = MessageDisplay(screen=screen, pack=None) + self.messagedisplay = MessageDisplay(screen, pack) self.hbar = HorizontalSplit(screen, pack) self.vbar = VerticalSplit(screen, pack) + self.creditsdisplay = CreditsDisplay(screen, pack) self.displays = [self.statsdisplay, self.mapdisplay, self.mainmenudisplay, self.settingsmenudisplay, self.logsdisplay, self.messagedisplay, self.playerinventorydisplay, - self.storeinventorydisplay] + self.storeinventorydisplay, self.creditsdisplay] self.update_game_components() def handle_display_action(self, action: DisplayActions, *params) -> None: @@ -123,6 +126,9 @@ class DisplayManager: elif self.game.state == GameMode.SETTINGS: self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols) displays.append(self.settingsmenudisplay) + elif self.game.state == GameMode.CREDITS: + self.creditsdisplay.refresh(0, 0, self.rows, self.cols) + displays.append(self.creditsdisplay) if self.game.message: height, width = 0, 0 diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 06bae1d..84a20ff 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -7,7 +7,7 @@ from typing import List from squirrelbattle.menus import Menu, MainMenu from .display import Box, Display -from ..enums import KeyValues +from ..enums import KeyValues, GameMode from ..game import Game from ..resources import ResourceManager from ..translations import gettext as _ @@ -113,6 +113,8 @@ class MainMenuDisplay(Display): self.addstr(self.pad, 4 + i, max(self.width // 2 - len(self.title[0]) // 2 - 1, 0), self.title[i], self.fg_color) + msg = _("Credits") + self.addstr(self.pad, self.height - 1, self.width - 1 - len(msg), msg) self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.height + self.y - 1, self.width + self.x - 1) @@ -133,6 +135,9 @@ class MainMenuDisplay(Display): if y <= len(self.title): self.fg_color = randint(0, 1000), randint(0, 1000), randint(0, 1000) + if y == self.height - 1 and x >= self.width - 1 - len(_("Credits")): + game.state = GameMode.CREDITS + class PlayerInventoryDisplay(MenuDisplay): """ diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index d9b0735..2e1dd64 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -28,6 +28,7 @@ class GameMode(Enum): SETTINGS = auto() INVENTORY = auto() STORE = auto() + CREDITS = auto() class KeyValues(Enum): diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 72bbe2f..03dbfe7 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -103,6 +103,8 @@ class Game: self.settings_menu.handle_key_pressed(key, raw_key, self) elif self.state == GameMode.STORE: self.handle_key_pressed_store(key) + elif self.state == GameMode.CREDITS: + self.state = GameMode.MAINMENU self.display_actions(DisplayActions.REFRESH) def handle_key_pressed_play(self, key: KeyValues) -> None: From 4b174f26e4d961e6ff3d72ea3472b4859efa6637 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 23:36:08 +0100 Subject: [PATCH 13/40] Better colors --- squirrelbattle/assets/ascii-art-ecureuil.txt | 34 ++++++++++---------- squirrelbattle/display/creditsdisplay.py | 24 ++++++++++++-- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/squirrelbattle/assets/ascii-art-ecureuil.txt b/squirrelbattle/assets/ascii-art-ecureuil.txt index 4266b58..bd56744 100644 --- a/squirrelbattle/assets/ascii-art-ecureuil.txt +++ b/squirrelbattle/assets/ascii-art-ecureuil.txt @@ -1,17 +1,17 @@ - ━ - ┃ ┃ - ┃ ┃ ▓▓▒ ▓▓ - ┃ ┃ ▓▓ ▓▓▒ - ┃ ┃ ▓▓▓ ▓▓ ▓▓▓ ▒▒▒▒▒▒▒▒▒ - ┃ ┃ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒ - ┃ ┃ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - ┃ ┃ ▓▓▓░█▓▓▓▓▓▓░█▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - ┃ ┃ ▓▓▓▓░██░░▓▓░░██░▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - ━━▓▓▓▓━━ ▓▓░░░░░░░░██░░░░░░░░▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ⋀ + ┃|┃ + ┃|┃ ▓▓▒ ▓▓ + ┃|┃ ▓▓ ▓▓▒ + ┃|┃ ▓▓▓ ▓▓ ▓▓▓ ▒▒▒▒▒▒▒▒▒ + ┃|┃ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒ + ┃|┃ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ┃|┃ ▓▓▓▬█▓▓▓▓▓▓▬█▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ┃|┃ ▓▓▓▓░██░░▓▓░░██░▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ━━▓▓▓▓━━ ▓▓░░░░░░░░ ░░░░░░░░▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓ ▓░░░░░░░░░░░░░░░░░░░░▓▓▒▒▒▒▒▒▒▒▒▒▒▒ - █ ▓▓▓▓▓ ▓░░░░░░░░▄▄▄▄░░░░░░░▓▒▒▒▒▒▒▒▒▒▒▒▒▒ - █ ▓▓▓▓▓ ▓▓░░░░░░░░░░░░░░░▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ + ┃ ▓▓▓▓▓ ▓░░░░░░░░▄▄▄▄░░░░░░░▓▒▒▒▒▒▒▒▒▒▒▒▒▒ + ┃ ▓▓▓▓▓ ▓▓░░░░░░░░░░░░░░░▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓ ▓▓▓▓░░░░░░░▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓▒▒░░░░░░░░░▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▓▓▓▒░░░░░░░░░░░░▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒ @@ -34,11 +34,11 @@ ░ ░░░░░▒░░░░░░▒░░░▒░░░░░░░░░░░░░░░░░▒▒▒▒▒▒░░░░░░░▒ ▒▒░░▓▓░░▒░░░░░░░░▒░░░░░░▒░░░░░░░░▒░░░░░░░░░░▒░░░░░▒ ░░ ▒▒▒▒▒▓▒▒▓░░░░░░░░░▒░░░░░░░░▒░░░░░░░░▒░░░░░░░░▒░░░░░░░░░░░░ - ▒▒█▒█▒▒▒▓░░▒░░░░░░░░░░░░░░░▒░░░░░░░░▒░░░░░░░░░░░░░░░░░░░░░ - ▒▒▒▒█▒▒▒▒░░░░▒░░░▒░░░░░░░░░░░░░░░░░░░░░░▒░░░░░░░░░░░░▒░░░ - ▓█▒▒▒▒█▒█▒▒▒▒░░▒░░░░░▒░░░░▒░░░░░░░░░░░░░░░░░▒░░░░▒░░░░░░░▒░░░░░▒▒ + ▒▒█▒█▒▒▒▓░░▒░░░░░░░░░░░░░░░▒░░░░░░░░▒░░░░░░░░░░░░░░░░░░░░░ + ▒▒▒▒█▒▒▒▒░░░░▒░░░▒░░░░░░░░░░░░░░░░░░░░░░▒░░░░░░░░░░░░▒░░░ + ▓█▒▒▒▒█▒█▒▒▒▒░░▒░░░░░▒░░░░▒░░░░░░░░░░░░░░░░░▒░░░░▒░░░░░░░▒░░░░░▒▒ ██▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░▒░░░░░░▒░░░░░░░░▒░░░░░░▒░░░░░░▒░░░░░▒░░░░░ - ▒▒▒▒█▒▒▒▒▒▒▒░░░░░░░░░░▒░░░░░░░░░░▒░░░░░░░░░░░▒░░░░░░░░░░░░░░░ - ▒▒█▒▒▒▒▒░▒░▒░░░░▓▓▓░░░░░░░▒░░░░▒░░░▒░░░░░░░▓▓░░░░░░░░░░░░ ░ + ▒▒▒▒█▒▒▒▒▒▒▒░░░░░░░░░░▒░░░░░░░░░░▒░░░░░░░░░░░▒░░░░░░░░░░░░░░░ + ▒▒█▒▒▒▒▒░▒░▒░░░░▓▓▓░░░░░░░▒░░░░▒░░░▒░░░░░░░▓▓░░░░░░░░░░░░ ░ ▒▒▒▒▒▒▒░▒░░░▓▓▓▓▓▓░░░░░░░▒░░░░░░░░▒░░░░▓▓▓▓▓▓░░░░░░░░ ░ ░▓▓▓▓▓▓░░░░░░▒░░░░░░░░▒░░░░░░▓▓▓▓▓░░░ ░ ░░ diff --git a/squirrelbattle/display/creditsdisplay.py b/squirrelbattle/display/creditsdisplay.py index 453fe8d..c3bbe10 100644 --- a/squirrelbattle/display/creditsdisplay.py +++ b/squirrelbattle/display/creditsdisplay.py @@ -61,16 +61,34 @@ class CreditsDisplay(Display): for j, c in enumerate(line): bg_color = curses.COLOR_WHITE fg_color = curses.COLOR_BLACK - if c == '▓': + bold = False + if c == ' ': + bg_color = curses.COLOR_BLACK + elif c == '━' or c == '┃' or c == '⋀': + bold = True + fg_color = curses.COLOR_WHITE + bg_color = curses.COLOR_BLACK + elif c == '|': + bold = True # c = '┃' + fg_color = (100, 700, 1000) + bg_color = curses.COLOR_BLACK + elif c == '▓': fg_color = (700, 300, 0) elif c == '▒': fg_color = (700, 300, 0) bg_color = curses.COLOR_BLACK elif c == '░': fg_color = (350, 150, 0) + elif c == '█': + fg_color = (0, 0, 0) + bg_color = curses.COLOR_BLACK + elif c == '▬': + c = '█' + fg_color = (1000, 1000, 1000) + bg_color = curses.COLOR_BLACK self.addstr(self.pad, y_offset + i, x_offset + j, c, - fg_color, bg_color) + fg_color, bg_color, bold=bold) def handle_click(self, y: int, x: int, game: Game) -> None: - if self.pad.inch(y, x) != ord(" "): + if self.pad.inch(y - 1, x - 1) != ord(" "): self.ascii_art_displayed = True From ed6457e94d0f6b9c32e8bd6e10d5c9e7fb7ee5cc Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 23:39:56 +0100 Subject: [PATCH 14/40] Test credits menu --- squirrelbattle/tests/game_test.py | 11 +++++++++++ squirrelbattle/tests/screen.py | 3 +++ 2 files changed, 14 insertions(+) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 750335f..40a2331 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -585,3 +585,14 @@ class TestGame(unittest.TestCase): # Exit the menu self.game.handle_key_pressed(KeyValues.SPACE) self.assertEqual(self.game.state, GameMode.PLAY) + + def test_credits(self) -> None: + """ + Load credits menu. + """ + self.game.state = GameMode.MAINMENU + + self.game.display_actions(DisplayActions.MOUSE, 41, 41) + self.assertEqual(self.game.state, GameMode.CREDITS) + self.game.display_actions(DisplayActions.MOUSE, 21, 21) + self.game.display_actions(DisplayActions.REFRESH) diff --git a/squirrelbattle/tests/screen.py b/squirrelbattle/tests/screen.py index 9a8afe6..549fdc1 100644 --- a/squirrelbattle/tests/screen.py +++ b/squirrelbattle/tests/screen.py @@ -24,3 +24,6 @@ class FakePad: def getmaxyx(self) -> Tuple[int, int]: return 42, 42 + + def inch(self, y, x) -> str: + return "i" From 505e0a4efbf38dc27e3dd4c3f104c491bdd1c63e Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Mon, 21 Dec 2020 14:23:58 +0100 Subject: [PATCH 15/40] Fixes issue #54, repaired the attribution of the familiars' target --- squirrelbattle/entities/friendly.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 94b1b8a..ff40d8b 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -55,7 +55,7 @@ class Sunflower(FriendlyEntity): return [_("Flower power!!"), _("The sun is warm today")] -class Familiar(FightingEntity): +class Familiar(FriendlyEntity): #FightingEntity """ A friendly familiar that helps the player defeat monsters. """ @@ -64,6 +64,13 @@ class Familiar(FightingEntity): super().__init__(maxhealth=maxhealth, *args, **kwargs) self.target = None +## @property +## def dialogue_option(self) -> list: +## """ +## Debug function (to see if used in the real game) +## """ +## return [_("My target is"+str(self.target))] + def act(self, p: Player, m: Map) -> None: """ By default, the familiar tries to stay at distance at most 2 of the @@ -71,10 +78,16 @@ class Familiar(FightingEntity): and attacks it. """ if self.target is None: + #If the previous target is dead(or if there was no previous target) + #the familiar tries to get closer to the player. + self.target = p + elif self.target.dead: self.target = p if self.target == p: + #Look for monsters around the player to kill TOFIX : if monster is out + #of range, continue targetting player. for entity in m.entities: - if (self.y - entity.y) ** 2 + (self.x - entity.x) ** 2 <= 9 and\ + if (p.y - entity.y) ** 2 + (p.x - entity.x) ** 2 <= 9 and\ isinstance(entity, Monster): self.target = entity entity.paths = dict() # Allows the paths to be calculated. @@ -93,9 +106,6 @@ class Familiar(FightingEntity): if self.distance_squared(self.target) <= 1 and \ not isinstance(self.target, Player): self.map.logs.add_message(self.hit(self.target)) - if self.target.dead: - self.target.paths = None - self.target = None break else: # Moves in a random direction From ad5ae22e5f869523bbc3ddb19c83f2ac3366f145 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 26 Dec 2020 00:45:17 +0100 Subject: [PATCH 16/40] Manage multiple maps in one game --- squirrelbattle/game.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 0553d2e..7ef8fc9 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -3,7 +3,7 @@ from json import JSONDecodeError from random import randint -from typing import Any, Optional +from typing import Any, Optional, List import curses import json import os @@ -22,7 +22,8 @@ class Game: """ The game object controls all actions in the game. """ - map: Map + maps: List[Map] + map_index: int player: Player screen: Any # display_actions is a display interface set by the bootstrapper @@ -52,6 +53,8 @@ class Game: Create a new game on the screen. """ # TODO generate a new map procedurally + self.maps = [] + self.map_index = 0 self.map = Map.load(ResourceManager.get_asset_path("example_map_2.txt")) self.map.logs = self.logs self.logs.clear() @@ -61,6 +64,24 @@ class Game: self.map.spawn_random_entities(randint(3, 10)) self.inventory_menu.update_player(self.player) + @property + def map(self) -> Map: + """ + Return the current map where the user is. + """ + return self.maps[self.map_index] + + @map.setter + def map(self, m: Map) -> None: + """ + Redefine the current map. + """ + if len(self.maps) == self.map_index: + # Insert new map + self.maps.append(m) + # Redefine the current map + self.maps[self.map_index] = m + def run(self, screen: Any) -> None: # pragma no cover """ Main infinite loop. From 8636d571b597095f176fe634d7e1537f74356556 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 26 Dec 2020 00:52:47 +0100 Subject: [PATCH 17/40] Add ladders in the map --- squirrelbattle/assets/example_map.txt | 4 ++-- squirrelbattle/assets/example_map_2.txt | 4 ++-- squirrelbattle/display/texturepack.py | 2 ++ squirrelbattle/interfaces.py | 7 +++++++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/squirrelbattle/assets/example_map.txt b/squirrelbattle/assets/example_map.txt index 5aaade9..e52172c 100644 --- a/squirrelbattle/assets/example_map.txt +++ b/squirrelbattle/assets/example_map.txt @@ -1,8 +1,8 @@ 1 6 ####### ############# - #.....# #...........# + #.H...# #...........# #.....# #####...........# - #.....# #...............# + #.....# #............H..# #.##### #.###...........# #.# #.# #...........# #.# #.# ############# diff --git a/squirrelbattle/assets/example_map_2.txt b/squirrelbattle/assets/example_map_2.txt index 8864f04..c959659 100644 --- a/squirrelbattle/assets/example_map_2.txt +++ b/squirrelbattle/assets/example_map_2.txt @@ -1,6 +1,6 @@ 1 17 ########### ######### - #.........# #.......# + #....H....# #.......# #.........# ############.......# #.........###############..........#.......############## #.........#........................#....................# @@ -13,7 +13,7 @@ ########.##########......# #.........# #.........# #...........##......# #.........# #.........# #...........##......# #.........# #.........# - #...........##......# #.........# ################.###### + #...........##..H...# #.........# ################.###### #...........##......# #.........# #.................############ #...........##......# ########.########.......#.........#..........# #...........##......# #...............#.......#.........#..........# diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 1f6dc76..77bbfc7 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -64,6 +64,7 @@ TexturePack.ASCII_PACK = TexturePack( EMPTY=' ', EXPLOSION='%', FLOOR='.', + LADDER='H', HAZELNUT='¤', HEART='❤', HEDGEHOG='*', @@ -90,6 +91,7 @@ TexturePack.SQUIRREL_PACK = TexturePack( EMPTY=' ', EXPLOSION='💥', FLOOR='██', + LADDER='🪜', HAZELNUT='🌰', HEART='💜', HEDGEHOG='🦔', diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 94025bd..3394af8 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -198,6 +198,7 @@ class Tile(Enum): EMPTY = auto() WALL = auto() FLOOR = auto() + LADDER = auto() @staticmethod def from_ascii_char(ch: str) -> "Tile": @@ -222,6 +223,12 @@ class Tile(Enum): """ return self == Tile.WALL + def is_ladder(self) -> bool: + """ + Is this Tile a ladder? + """ + return self == Tile.LADDER + def can_walk(self) -> bool: """ Check if an entity (player or not) can move in this tile. From 9a56b4d7e99cf43404e30973b2784f5a969d0503 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 26 Dec 2020 01:08:43 +0100 Subject: [PATCH 18/40] Navigate through different maps while climbing ladders --- squirrelbattle/assets/example_map.txt | 4 ++-- squirrelbattle/assets/example_map_2.txt | 4 ++-- squirrelbattle/game.py | 20 +++++++++++++++++++- squirrelbattle/interfaces.py | 11 +++++++---- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/squirrelbattle/assets/example_map.txt b/squirrelbattle/assets/example_map.txt index e52172c..be2e798 100644 --- a/squirrelbattle/assets/example_map.txt +++ b/squirrelbattle/assets/example_map.txt @@ -1,8 +1,8 @@ 1 6 ####### ############# - #.H...# #...........# + #.H...# #...........# #.....# #####...........# - #.....# #............H..# + #.....# #............H..# #.##### #.###...........# #.# #.# #...........# #.# #.# ############# diff --git a/squirrelbattle/assets/example_map_2.txt b/squirrelbattle/assets/example_map_2.txt index c959659..b9c751f 100644 --- a/squirrelbattle/assets/example_map_2.txt +++ b/squirrelbattle/assets/example_map_2.txt @@ -1,6 +1,6 @@ 1 17 ########### ######### - #....H....# #.......# + #....H....# #.......# #.........# ############.......# #.........###############..........#.......############## #.........#........................#....................# @@ -13,7 +13,7 @@ ########.##########......# #.........# #.........# #...........##......# #.........# #.........# #...........##......# #.........# #.........# - #...........##..H...# #.........# ################.###### + #...........##..H...# #.........# ################.###### #...........##......# #.........# #.................############ #...........##......# ########.########.......#.........#..........# #...........##......# #...............#.......#.........#..........# diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 7ef8fc9..7807882 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -55,7 +55,7 @@ class Game: # TODO generate a new map procedurally self.maps = [] self.map_index = 0 - 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() @@ -102,6 +102,24 @@ class Game: self.handle_key_pressed( KeyValues.translate_key(key, self.settings), key) + # FIXME This code should not be there, but rather in Map.tick + y, x = self.player.y, self.player.x + if self.map.tiles[y][x].is_ladder(): + # We move up on the ladder of the beginning, + # down at the end of the stage + move_down = y != self.map.start_y and x != self.map.start_x + old_map = self.map + self.map_index += 1 if move_down else -1 + self.map_index = max(0, self.map_index) + while self.map_index >= len(self.maps): + self.maps.append(Map.load(ResourceManager.get_asset_path( + "example_map_2.txt"))) + new_map = self.map + old_map.remove_entity(self.player) + new_map.add_entity(self.player) + self.player.move(self.map.start_y, self.map.start_x) + self.display_actions(DisplayActions.UPDATE) + def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str = '')\ -> None: """ diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 3394af8..788edaa 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -140,12 +140,15 @@ class Map: Put randomly {count} entities on the map, where it is available. """ for ignored in range(count): - y, x = 0, 0 while True: y, x = randint(0, self.height - 1), randint(0, self.width - 1) - tile = self.tiles[y][x] - if tile.can_walk(): - break + try: + tile = self.tiles[y][x] + except Exception as e: + raise Exception(y, x, len(self.tiles)) + else: + if tile.can_walk(): + break entity = choice(Entity.get_all_entity_classes())() entity.move(y, x) self.add_entity(entity) From 6b7f8867facfecb817cc8da9a4c74bc961efc447 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 26 Dec 2020 14:02:35 +0100 Subject: [PATCH 19/40] Tile colors can be overwritten --- squirrelbattle/display/mapdisplay.py | 7 +++++-- squirrelbattle/display/texturepack.py | 2 +- squirrelbattle/interfaces.py | 16 ++++++++++++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index 7e21adb..3a21470 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -19,8 +19,11 @@ class MapDisplay(Display): def update_pad(self) -> None: self.pad.resize(500, 500) - self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), - self.pack.tile_fg_color, self.pack.tile_bg_color) + for i in range(self.map.height): + for j in range(self.map.width): + self.addstr(self.pad, i, j * self.pack.tile_width, + self.map.tiles[i][j].char(self.pack), + *self.map.tiles[i][j].color(self.pack)) for e in self.map.entities: self.addstr(self.pad, e.y, self.pack.tile_width * e.x, self.pack[e.name.upper()], diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 77bbfc7..44a8265 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -91,7 +91,7 @@ TexturePack.SQUIRREL_PACK = TexturePack( EMPTY=' ', EXPLOSION='💥', FLOOR='██', - LADDER='🪜', + LADDER=('🪜', curses.COLOR_WHITE, curses.COLOR_WHITE), HAZELNUT='🌰', HEART='💜', HEDGEHOG='🦔', diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 788edaa..ff94cc6 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -4,7 +4,7 @@ from enum import Enum, auto from math import sqrt from random import choice, randint -from typing import List, Optional, Any +from typing import List, Optional, Any, Tuple from .display.texturepack import TexturePack from .translations import gettext as _ @@ -218,7 +218,19 @@ class Tile(Enum): Translates a Tile to the corresponding character according to the texture pack """ - return getattr(pack, self.name) + val = getattr(pack, self.name) + if isinstance(val, tuple): + return val[0] + return val + + def color(self, pack: TexturePack) -> Tuple[int, int]: + """ + Retrieve the tuple (fg_color, bg_color) of the current Tile. + """ + val = getattr(pack, self.name) + if isinstance(val, tuple): + return val[1], val[2] + return pack.tile_fg_color, pack.tile_bg_color def is_wall(self) -> bool: """ From 663fc0eecd451ff48c43cd045e645be9aa06b77d Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 26 Dec 2020 21:13:17 +0100 Subject: [PATCH 20/40] Better teleport --- squirrelbattle/game.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 7807882..1b856e7 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -110,14 +110,28 @@ class Game: move_down = y != self.map.start_y and x != self.map.start_x old_map = self.map self.map_index += 1 if move_down else -1 - self.map_index = max(0, self.map_index) + if self.map_index == -1: + self.map_index = 0 + return while self.map_index >= len(self.maps): self.maps.append(Map.load(ResourceManager.get_asset_path( "example_map_2.txt"))) new_map = self.map old_map.remove_entity(self.player) new_map.add_entity(self.player) - self.player.move(self.map.start_y, self.map.start_x) + if move_down: + self.player.move(self.map.start_y, self.map.start_x) + elif KeyValues.translate_key(key, self.settings) \ + in [KeyValues.UP, KeyValues.DOWN, + KeyValues.LEFT, KeyValues.RIGHT]: + ladder_y, ladder_x = -1, -1 + for y in range(self.map.height): + for x in range(self.map.width): + if (y, x) != (self.map.start_y, self.map.start_x) \ + and self.map.tiles[y][x].is_ladder(): + ladder_y, ladder_x = y, x + break + self.player.move(ladder_y, ladder_x) self.display_actions(DisplayActions.UPDATE) def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str = '')\ From de3aba396de797edbee9fbd997fbcd3004aad241 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Thu, 31 Dec 2020 14:51:17 +0100 Subject: [PATCH 21/40] Re-changed familiar's inheritance from friendlyEntity to FightingEntity (just a leftover from debug) --- squirrelbattle/entities/friendly.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index ff40d8b..fb33a3f 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -1,4 +1,4 @@ -from ..interfaces import FriendlyEntity, InventoryHolder, FightingEntity, Map +from ..interfaces import FriendlyEntity, InventoryHolder, Map, FightingEntity from ..translations import gettext as _ from .player import Player from .monsters import Monster @@ -55,7 +55,7 @@ class Sunflower(FriendlyEntity): return [_("Flower power!!"), _("The sun is warm today")] -class Familiar(FriendlyEntity): #FightingEntity +class Familiar(FightingEntity): """ A friendly familiar that helps the player defeat monsters. """ From 7cd4721daa14a2a66c4bac02d8c6ee99c539c2d6 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Thu, 31 Dec 2020 15:00:20 +0100 Subject: [PATCH 22/40] linting --- squirrelbattle/display/creditsdisplay.py | 2 +- squirrelbattle/entities/friendly.py | 20 ++++++++++---------- squirrelbattle/tests/screen.py | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/squirrelbattle/display/creditsdisplay.py b/squirrelbattle/display/creditsdisplay.py index c3bbe10..af8d879 100644 --- a/squirrelbattle/display/creditsdisplay.py +++ b/squirrelbattle/display/creditsdisplay.py @@ -48,7 +48,7 @@ class CreditsDisplay(Display): self.height + self.y - 2, self.width + self.x - 2) - def display_ascii_art(self): + def display_ascii_art(self) -> None: with open(ResourceManager.get_asset_path("ascii-art-ecureuil.txt"))\ as f: ascii_art = f.read().split("\n") diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index fb33a3f..974fe1f 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -64,12 +64,12 @@ class Familiar(FightingEntity): super().__init__(maxhealth=maxhealth, *args, **kwargs) self.target = None -## @property -## def dialogue_option(self) -> list: -## """ -## Debug function (to see if used in the real game) -## """ -## return [_("My target is"+str(self.target))] +# @property +# def dialogue_option(self) -> list: +# """ +# Debug function (to see if used in the real game) +# """ +# return [_("My target is"+str(self.target))] def act(self, p: Player, m: Map) -> None: """ @@ -78,14 +78,14 @@ class Familiar(FightingEntity): and attacks it. """ if self.target is None: - #If the previous target is dead(or if there was no previous target) - #the familiar tries to get closer to the player. + # If the previous target is dead(or if there was no previous target) + # the familiar tries to get closer to the player. self.target = p elif self.target.dead: self.target = p if self.target == p: - #Look for monsters around the player to kill TOFIX : if monster is out - #of range, continue targetting player. + # Look for monsters around the player to kill TOFIX : if monster is + # out of range, continue targetting player. for entity in m.entities: if (p.y - entity.y) ** 2 + (p.x - entity.x) ** 2 <= 9 and\ isinstance(entity, Monster): diff --git a/squirrelbattle/tests/screen.py b/squirrelbattle/tests/screen.py index 549fdc1..386b3d3 100644 --- a/squirrelbattle/tests/screen.py +++ b/squirrelbattle/tests/screen.py @@ -25,5 +25,5 @@ class FakePad: def getmaxyx(self) -> Tuple[int, int]: return 42, 42 - def inch(self, y, x) -> str: + def inch(self, y: int, x: int) -> str: return "i" From 8e0b8d4fee2a2ce4989635a4006d854de59d5371 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Thu, 31 Dec 2020 15:16:40 +0100 Subject: [PATCH 23/40] Added a test for lines 106-107 of game.py --- squirrelbattle/tests/game_test.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 40a2331..f8a6e93 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -596,3 +596,8 @@ class TestGame(unittest.TestCase): self.assertEqual(self.game.state, GameMode.CREDITS) self.game.display_actions(DisplayActions.MOUSE, 21, 21) self.game.display_actions(DisplayActions.REFRESH) + + self.game.state = GameMode.CREDITS + self.game.handle_key_pressed(KeyValues.ENTER) + + self.assertEqual(self.game.state, GameMode.MAINMENU) From 73952a42f32893b1a27494d3c28d58ccac6ef3be Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Thu, 31 Dec 2020 15:43:38 +0100 Subject: [PATCH 24/40] Added tests for familiars --- squirrelbattle/tests/entities_test.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 221fa5f..5f9d298 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -6,6 +6,7 @@ import unittest from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart, Item, \ Explosion from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, TeddyBear +from squirrelbattle.entities.friendly import Trumpet from squirrelbattle.entities.player import Player from squirrelbattle.interfaces import Entity, Map from squirrelbattle.resources import ResourceManager @@ -89,6 +90,31 @@ class TestEntities(unittest.TestCase): self.assertTrue(entity.dead) self.assertGreaterEqual(self.player.current_xp, 3) + # Test the familiars + fam = Trumpet() + entity = Rabbit() + self.map.add_entity(entity) + self.map.add_entity(fam) + + self.player.move(1,6) + entity.move(2,6) + fam.move(2,7) + + entity.health = 2 + entity.paths = [] + entity.recalculate_paths() + fam.target = entity + self.map.tick(self.player) + self.assertTrue(entity.dead) + + self.player.move(5,5) + fam.move(4,5) + fam.target = self.player + self.player.move_down() + self.map.tick(self.player) + self.assertEqual(fam.y, 5) + self.assertEqual(fam.x, 5) + def test_items(self) -> None: """ Tests some random stuff with items. @@ -219,3 +245,4 @@ class TestEntities(unittest.TestCase): player_state = player.save_state() self.assertEqual(player_state["current_xp"], 10) + From c329150aac067c8f26089a50658abd44133431fe Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Thu, 31 Dec 2020 17:15:24 +0100 Subject: [PATCH 25/40] Linting again --- squirrelbattle/tests/entities_test.py | 13 ++++++------- squirrelbattle/tests/game_test.py | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 5f9d298..341461f 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -96,10 +96,10 @@ class TestEntities(unittest.TestCase): self.map.add_entity(entity) self.map.add_entity(fam) - self.player.move(1,6) - entity.move(2,6) - fam.move(2,7) - + self.player.move(1, 6) + entity.move(2, 6) + fam.move(2, 7) + entity.health = 2 entity.paths = [] entity.recalculate_paths() @@ -107,8 +107,8 @@ class TestEntities(unittest.TestCase): self.map.tick(self.player) self.assertTrue(entity.dead) - self.player.move(5,5) - fam.move(4,5) + self.player.move(5, 5) + fam.move(4, 5) fam.target = self.player self.player.move_down() self.map.tick(self.player) @@ -245,4 +245,3 @@ class TestEntities(unittest.TestCase): player_state = player.save_state() self.assertEqual(player_state["current_xp"], 10) - diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index f8a6e93..8cd816b 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -599,5 +599,5 @@ class TestGame(unittest.TestCase): self.game.state = GameMode.CREDITS self.game.handle_key_pressed(KeyValues.ENTER) - + self.assertEqual(self.game.state, GameMode.MAINMENU) From f821fef43017346a159208208ccb9acfa9ca4f6c Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Tue, 5 Jan 2021 09:38:49 +0100 Subject: [PATCH 26/40] added tests --- squirrelbattle/tests/entities_test.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 341461f..7d4981f 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -100,6 +100,7 @@ class TestEntities(unittest.TestCase): entity.move(2, 6) fam.move(2, 7) + #test fighting entity.health = 2 entity.paths = [] entity.recalculate_paths() @@ -107,14 +108,30 @@ class TestEntities(unittest.TestCase): self.map.tick(self.player) self.assertTrue(entity.dead) + #test finding a new target + entity2 = Rabbit() + self.map.add_entity(entity2) + entity2.move(2, 6) + self.map.tick(self.player) + self.assertTrue(fam.target==entity2) + self.map.remove_entity(entity2) + + #test following the player and finding the player as target self.player.move(5, 5) fam.move(4, 5) - fam.target = self.player + fam.target = None self.player.move_down() self.map.tick(self.player) + self.assertTrue(fam.target==self.player) self.assertEqual(fam.y, 5) self.assertEqual(fam.x, 5) + #test random move + fam.move(13, 20) + fam.target=self.player + self.map.tick(self.player) + self.assertTrue(fam.x!=20 or fam.y!=13) + def test_items(self) -> None: """ Tests some random stuff with items. From c378eead74e89ca610c07145b8f1d3229adee299 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Tue, 5 Jan 2021 10:11:55 +0100 Subject: [PATCH 27/40] Fixed the game loading tests : removed trumpets from the test. --- squirrelbattle/tests/game_test.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 8cd816b..3ee00b7 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -41,6 +41,11 @@ class TestGame(unittest.TestCase): bomb.hold(self.game.player) sword.hold(self.game.player) + for entity in self.game.map.entities : + #trumpets change order when they are loaded, so it is unsuitable for simple testing + if entity.name == 'trumpet' : + self.game.map.remove_entity(entity) + # Ensure that merchants can be saved merchant = Merchant() merchant.move(3, 6) From 6341f39fb000675190f8697197a791eed6755668 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Tue, 5 Jan 2021 10:20:55 +0100 Subject: [PATCH 28/40] Linting --- squirrelbattle/tests/entities_test.py | 17 ++++++++--------- squirrelbattle/tests/game_test.py | 6 +++--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 7d4981f..b2272f2 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -95,12 +95,11 @@ class TestEntities(unittest.TestCase): entity = Rabbit() self.map.add_entity(entity) self.map.add_entity(fam) - self.player.move(1, 6) entity.move(2, 6) fam.move(2, 7) - #test fighting + # Test fighting entity.health = 2 entity.paths = [] entity.recalculate_paths() @@ -108,29 +107,29 @@ class TestEntities(unittest.TestCase): self.map.tick(self.player) self.assertTrue(entity.dead) - #test finding a new target + # Test finding a new target entity2 = Rabbit() self.map.add_entity(entity2) entity2.move(2, 6) self.map.tick(self.player) - self.assertTrue(fam.target==entity2) + self.assertTrue(fam.target == entity2) self.map.remove_entity(entity2) - #test following the player and finding the player as target + # Test following the player and finding the player as target self.player.move(5, 5) fam.move(4, 5) fam.target = None self.player.move_down() self.map.tick(self.player) - self.assertTrue(fam.target==self.player) + self.assertTrue(fam.target == self.player) self.assertEqual(fam.y, 5) self.assertEqual(fam.x, 5) - #test random move + # Test random move fam.move(13, 20) - fam.target=self.player + fam.target = self.player self.map.tick(self.player) - self.assertTrue(fam.x!=20 or fam.y!=13) + self.assertTrue(fam.x != 20 or fam.y != 13) def test_items(self) -> None: """ diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 3ee00b7..54e18d6 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -41,9 +41,9 @@ class TestGame(unittest.TestCase): bomb.hold(self.game.player) sword.hold(self.game.player) - for entity in self.game.map.entities : - #trumpets change order when they are loaded, so it is unsuitable for simple testing - if entity.name == 'trumpet' : + for entity in self.game.map.entities: + # trumpets change order when they are loaded, this breaks the test. + if entity.name == 'trumpet': self.game.map.remove_entity(entity) # Ensure that merchants can be saved From bb77dab628dbc6399bce4a39223f4ae5917cff3a Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Tue, 5 Jan 2021 10:49:04 +0100 Subject: [PATCH 29/40] Fixed a error induced by the merge: creditsdisplay did not have an update function --- squirrelbattle/display/creditsdisplay.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/squirrelbattle/display/creditsdisplay.py b/squirrelbattle/display/creditsdisplay.py index af8d879..1f8ea32 100644 --- a/squirrelbattle/display/creditsdisplay.py +++ b/squirrelbattle/display/creditsdisplay.py @@ -16,6 +16,9 @@ class CreditsDisplay(Display): self.pad = self.newpad(1, 1) self.ascii_art_displayed = False + def update(self, game: Game) -> None: + return + def display(self) -> None: self.box.refresh(self.y, self.x, self.height, self.width) self.box.display() From 9cb5c9157f1f2b2d246cce77b31f394f17db5e8c Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Tue, 5 Jan 2021 10:59:17 +0100 Subject: [PATCH 30/40] Linting --- squirrelbattle/display/creditsdisplay.py | 2 +- squirrelbattle/display/logsdisplay.py | 1 + squirrelbattle/display/mapdisplay.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/display/creditsdisplay.py b/squirrelbattle/display/creditsdisplay.py index 1f8ea32..a97c10e 100644 --- a/squirrelbattle/display/creditsdisplay.py +++ b/squirrelbattle/display/creditsdisplay.py @@ -17,7 +17,7 @@ class CreditsDisplay(Display): self.ascii_art_displayed = False def update(self, game: Game) -> None: - return + return def display(self) -> None: self.box.refresh(self.y, self.x, self.height, self.width) diff --git a/squirrelbattle/display/logsdisplay.py b/squirrelbattle/display/logsdisplay.py index 434e60b..5c30b41 100644 --- a/squirrelbattle/display/logsdisplay.py +++ b/squirrelbattle/display/logsdisplay.py @@ -12,6 +12,7 @@ class LogsDisplay(Display): """ logs: Logs + def __init__(self, *args) -> None: super().__init__(*args) self.pad = self.newpad(self.rows, self.cols) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index 46b85e9..37081cb 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -12,6 +12,7 @@ class MapDisplay(Display): """ map: Map + def __init__(self, *args): super().__init__(*args) From d06a405120acb02ae80e9ee70bde8b6d72d9b45c Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 6 Jan 2021 14:55:16 +0100 Subject: [PATCH 31/40] Use a key to use ladders --- squirrelbattle/enums.py | 3 + squirrelbattle/game.py | 66 ++++++++++--------- .../locale/de/LC_MESSAGES/squirrelbattle.po | 60 +++++++++-------- .../locale/es/LC_MESSAGES/squirrelbattle.po | 60 +++++++++-------- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 60 +++++++++-------- squirrelbattle/settings.py | 1 + squirrelbattle/tests/translations_test.py | 2 + 7 files changed, 136 insertions(+), 116 deletions(-) diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index 7e4efa4..5495bd3 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -47,6 +47,7 @@ class KeyValues(Enum): SPACE = auto() CHAT = auto() WAIT = auto() + LADDER = auto() @staticmethod def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]: @@ -81,4 +82,6 @@ class KeyValues(Enum): return KeyValues.CHAT elif key == settings.KEY_WAIT: return KeyValues.WAIT + elif key == settings.KEY_LADDER: + return KeyValues.LADDER return None diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 1b856e7..dff09e0 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -102,38 +102,6 @@ class Game: self.handle_key_pressed( KeyValues.translate_key(key, self.settings), key) - # FIXME This code should not be there, but rather in Map.tick - y, x = self.player.y, self.player.x - if self.map.tiles[y][x].is_ladder(): - # We move up on the ladder of the beginning, - # down at the end of the stage - move_down = y != self.map.start_y and x != self.map.start_x - old_map = self.map - self.map_index += 1 if move_down else -1 - if self.map_index == -1: - self.map_index = 0 - return - while self.map_index >= len(self.maps): - self.maps.append(Map.load(ResourceManager.get_asset_path( - "example_map_2.txt"))) - new_map = self.map - old_map.remove_entity(self.player) - new_map.add_entity(self.player) - if move_down: - self.player.move(self.map.start_y, self.map.start_x) - elif KeyValues.translate_key(key, self.settings) \ - in [KeyValues.UP, KeyValues.DOWN, - KeyValues.LEFT, KeyValues.RIGHT]: - ladder_y, ladder_x = -1, -1 - for y in range(self.map.height): - for x in range(self.map.width): - if (y, x) != (self.map.start_y, self.map.start_x) \ - and self.map.tiles[y][x].is_ladder(): - ladder_y, ladder_x = y, x - break - self.player.move(ladder_y, ladder_x) - self.display_actions(DisplayActions.UPDATE) - def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str = '')\ -> None: """ @@ -186,6 +154,40 @@ class Game: self.waiting_for_friendly_key = True elif key == KeyValues.WAIT: self.map.tick() + elif key == KeyValues.LADDER: + # On a ladder, we switch level + y, x = self.player.y, self.player.x + if not self.map.tiles[y][x].is_ladder(): + return + + # We move up on the ladder of the beginning, + # down at the end of the stage + move_down = y != self.map.start_y and x != self.map.start_x + old_map = self.map + self.map_index += 1 if move_down else -1 + if self.map_index == -1: + self.map_index = 0 + return + while self.map_index >= len(self.maps): + # TODO: generate a new map + self.maps.append(Map.load(ResourceManager.get_asset_path( + "example_map_2.txt"))) + new_map = self.map + old_map.remove_entity(self.player) + new_map.add_entity(self.player) + if move_down: + self.player.move(self.map.start_y, self.map.start_x) + else: + # Find the ladder of the end of the game + ladder_y, ladder_x = -1, -1 + for y in range(self.map.height): + for x in range(self.map.width): + if (y, x) != (self.map.start_y, self.map.start_x) \ + and self.map.tiles[y][x].is_ladder(): + ladder_y, ladder_x = y, x + break + self.player.move(ladder_y, ladder_x) + self.display_actions(DisplayActions.UPDATE) def handle_friendly_entity_chat(self, key: KeyValues) -> None: """ diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 29bf10e..591b5ae 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-12 18:02+0100\n" +"POT-Creation-Date: 2021-01-06 14:53+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,19 +17,19 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/display/menudisplay.py:139 +#: squirrelbattle/display/menudisplay.py:160 msgid "INVENTORY" msgstr "BESTAND" -#: squirrelbattle/display/menudisplay.py:164 +#: squirrelbattle/display/menudisplay.py:202 msgid "STALL" msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:33 +#: squirrelbattle/display/statsdisplay.py:34 msgid "Inventory:" msgstr "Bestand:" -#: squirrelbattle/display/statsdisplay.py:52 +#: squirrelbattle/display/statsdisplay.py:53 msgid "YOU ARE DEAD" msgstr "SIE WURDEN GESTORBEN" @@ -58,11 +58,11 @@ msgstr "Die Bombe explodiert." msgid "{player} exchanged its body with {entity}." msgstr "{player} täuscht seinem Körper mit {entity} aus." -#: squirrelbattle/game.py:205 squirrelbattle/tests/game_test.py:573 +#: squirrelbattle/game.py:277 squirrelbattle/tests/game_test.py:592 msgid "The buyer does not have enough money" msgstr "Der Kaufer hat nicht genug Geld" -#: squirrelbattle/game.py:249 +#: squirrelbattle/game.py:320 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -70,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:257 +#: squirrelbattle/game.py:328 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -78,7 +78,7 @@ msgstr "" "Auf dieser Karte wurde kein Spieler gefunden!\n" "Vielleicht sind Sie gestorben?" -#: squirrelbattle/game.py:277 +#: squirrelbattle/game.py:348 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -86,22 +86,22 @@ msgstr "" "Die JSON-Datei ist nicht korrekt.\n" "Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." -#: squirrelbattle/interfaces.py:429 +#: squirrelbattle/interfaces.py:451 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} schlägt {opponent}." -#: squirrelbattle/interfaces.py:441 +#: squirrelbattle/interfaces.py:463 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} nimmt {amount} Schadenspunkte." -#: squirrelbattle/interfaces.py:443 +#: squirrelbattle/interfaces.py:465 #, python-brace-format msgid "{name} dies." msgstr "{name} stirbt." -#: squirrelbattle/interfaces.py:477 +#: squirrelbattle/interfaces.py:499 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} hat gesagt: {message}" @@ -110,8 +110,8 @@ msgstr "{entity} hat gesagt: {message}" msgid "Back" msgstr "Zurück" -#: squirrelbattle/tests/game_test.py:344 squirrelbattle/tests/game_test.py:347 -#: squirrelbattle/tests/game_test.py:350 squirrelbattle/tests/game_test.py:353 +#: squirrelbattle/tests/game_test.py:358 squirrelbattle/tests/game_test.py:361 +#: squirrelbattle/tests/game_test.py:364 squirrelbattle/tests/game_test.py:367 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Neu Spiel" @@ -197,57 +197,61 @@ msgid "Key used to wait" msgstr "Wartentaste" #: squirrelbattle/tests/translations_test.py:56 +msgid "Key used to use ladders" +msgstr "Leitertaste" + +#: squirrelbattle/tests/translations_test.py:58 msgid "Texture pack" msgstr "Textur-Packung" -#: squirrelbattle/tests/translations_test.py:57 +#: squirrelbattle/tests/translations_test.py:59 msgid "Language" msgstr "Sprache" -#: squirrelbattle/tests/translations_test.py:60 +#: squirrelbattle/tests/translations_test.py:62 msgid "player" msgstr "Spieler" -#: squirrelbattle/tests/translations_test.py:62 +#: squirrelbattle/tests/translations_test.py:64 msgid "hedgehog" msgstr "Igel" -#: squirrelbattle/tests/translations_test.py:63 +#: squirrelbattle/tests/translations_test.py:65 msgid "merchant" msgstr "Kaufmann" -#: squirrelbattle/tests/translations_test.py:64 +#: squirrelbattle/tests/translations_test.py:66 msgid "rabbit" msgstr "Kanninchen" -#: squirrelbattle/tests/translations_test.py:65 +#: squirrelbattle/tests/translations_test.py:67 msgid "sunflower" msgstr "Sonnenblume" -#: squirrelbattle/tests/translations_test.py:66 +#: squirrelbattle/tests/translations_test.py:68 msgid "teddy bear" msgstr "Teddybär" -#: squirrelbattle/tests/translations_test.py:67 +#: squirrelbattle/tests/translations_test.py:69 msgid "tiger" msgstr "Tiger" -#: squirrelbattle/tests/translations_test.py:69 +#: squirrelbattle/tests/translations_test.py:71 msgid "body snatch potion" msgstr "Leichenfleddererzaubertrank" -#: squirrelbattle/tests/translations_test.py:70 +#: squirrelbattle/tests/translations_test.py:72 msgid "bomb" msgstr "Bombe" -#: squirrelbattle/tests/translations_test.py:71 +#: squirrelbattle/tests/translations_test.py:73 msgid "explosion" msgstr "Explosion" -#: squirrelbattle/tests/translations_test.py:72 +#: squirrelbattle/tests/translations_test.py:74 msgid "heart" msgstr "Herz" -#: squirrelbattle/tests/translations_test.py:73 +#: squirrelbattle/tests/translations_test.py:75 msgid "sword" msgstr "schwert" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index 8a7bc3e..035cad2 100644 --- a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/es/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-12 18:02+0100\n" +"POT-Creation-Date: 2021-01-06 14:53+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,19 +17,19 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/display/menudisplay.py:139 +#: squirrelbattle/display/menudisplay.py:160 msgid "INVENTORY" msgstr "INVENTORIO" -#: squirrelbattle/display/menudisplay.py:164 +#: squirrelbattle/display/menudisplay.py:202 msgid "STALL" msgstr "PUESTO" -#: squirrelbattle/display/statsdisplay.py:33 +#: squirrelbattle/display/statsdisplay.py:34 msgid "Inventory:" msgstr "Inventorio :" -#: squirrelbattle/display/statsdisplay.py:52 +#: squirrelbattle/display/statsdisplay.py:53 msgid "YOU ARE DEAD" msgstr "ERES MUERTO" @@ -57,11 +57,11 @@ msgstr "La bomba está explotando." msgid "{player} exchanged its body with {entity}." msgstr "{player} intercambió su cuerpo con {entity}." -#: squirrelbattle/game.py:205 squirrelbattle/tests/game_test.py:573 +#: squirrelbattle/game.py:277 squirrelbattle/tests/game_test.py:592 msgid "The buyer does not have enough money" msgstr "El comprador no tiene suficiente dinero" -#: squirrelbattle/game.py:249 +#: squirrelbattle/game.py:320 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -69,7 +69,7 @@ msgstr "" "Algunas claves faltan en su archivo de guarda.\n" "Su guarda parece a ser corruptido. Fue eliminado." -#: squirrelbattle/game.py:257 +#: squirrelbattle/game.py:328 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -77,7 +77,7 @@ msgstr "" "No jugador encontrado sobre la carta !\n" "¿ Quizas murió ?" -#: squirrelbattle/game.py:277 +#: squirrelbattle/game.py:348 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -85,22 +85,22 @@ msgstr "" "El JSON archivo no es correcto.\n" "Su guarda parece corrupta. Fue eliminada." -#: squirrelbattle/interfaces.py:429 +#: squirrelbattle/interfaces.py:451 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} golpea a {opponent}." -#: squirrelbattle/interfaces.py:441 +#: squirrelbattle/interfaces.py:463 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} recibe {amount} daño." -#: squirrelbattle/interfaces.py:443 +#: squirrelbattle/interfaces.py:465 #, python-brace-format msgid "{name} dies." msgstr "{name} se muere." -#: squirrelbattle/interfaces.py:477 +#: squirrelbattle/interfaces.py:499 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} dijo : {message}" @@ -109,8 +109,8 @@ msgstr "{entity} dijo : {message}" msgid "Back" msgstr "Volver" -#: squirrelbattle/tests/game_test.py:344 squirrelbattle/tests/game_test.py:347 -#: squirrelbattle/tests/game_test.py:350 squirrelbattle/tests/game_test.py:353 +#: squirrelbattle/tests/game_test.py:358 squirrelbattle/tests/game_test.py:361 +#: squirrelbattle/tests/game_test.py:364 squirrelbattle/tests/game_test.py:367 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Nuevo partido" @@ -196,57 +196,61 @@ msgid "Key used to wait" msgstr "Tecla para espera" #: squirrelbattle/tests/translations_test.py:56 +msgid "Key used to use ladders" +msgstr "Tecla para el uso de las escaleras" + +#: squirrelbattle/tests/translations_test.py:58 msgid "Texture pack" msgstr "Paquete de texturas" -#: squirrelbattle/tests/translations_test.py:57 +#: squirrelbattle/tests/translations_test.py:59 msgid "Language" msgstr "Languaje" -#: squirrelbattle/tests/translations_test.py:60 +#: squirrelbattle/tests/translations_test.py:62 msgid "player" msgstr "jugador" -#: squirrelbattle/tests/translations_test.py:62 +#: squirrelbattle/tests/translations_test.py:64 msgid "hedgehog" msgstr "erizo" -#: squirrelbattle/tests/translations_test.py:63 +#: squirrelbattle/tests/translations_test.py:65 msgid "merchant" msgstr "comerciante" -#: squirrelbattle/tests/translations_test.py:64 +#: squirrelbattle/tests/translations_test.py:66 msgid "rabbit" msgstr "conejo" -#: squirrelbattle/tests/translations_test.py:65 +#: squirrelbattle/tests/translations_test.py:67 msgid "sunflower" msgstr "girasol" -#: squirrelbattle/tests/translations_test.py:66 +#: squirrelbattle/tests/translations_test.py:68 msgid "teddy bear" msgstr "osito de peluche" -#: squirrelbattle/tests/translations_test.py:67 +#: squirrelbattle/tests/translations_test.py:69 msgid "tiger" msgstr "tigre" -#: squirrelbattle/tests/translations_test.py:69 +#: squirrelbattle/tests/translations_test.py:71 msgid "body snatch potion" msgstr "poción de intercambio" -#: squirrelbattle/tests/translations_test.py:70 +#: squirrelbattle/tests/translations_test.py:72 msgid "bomb" msgstr "bomba" -#: squirrelbattle/tests/translations_test.py:71 +#: squirrelbattle/tests/translations_test.py:73 msgid "explosion" msgstr "explosión" -#: squirrelbattle/tests/translations_test.py:72 +#: squirrelbattle/tests/translations_test.py:74 msgid "heart" msgstr "corazón" -#: squirrelbattle/tests/translations_test.py:73 +#: squirrelbattle/tests/translations_test.py:75 msgid "sword" msgstr "espada" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index ffbfdce..a64d96c 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-12 18:02+0100\n" +"POT-Creation-Date: 2021-01-06 14:53+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,19 +17,19 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/display/menudisplay.py:139 +#: squirrelbattle/display/menudisplay.py:160 msgid "INVENTORY" msgstr "INVENTAIRE" -#: squirrelbattle/display/menudisplay.py:164 +#: squirrelbattle/display/menudisplay.py:202 msgid "STALL" msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:33 +#: squirrelbattle/display/statsdisplay.py:34 msgid "Inventory:" msgstr "Inventaire :" -#: squirrelbattle/display/statsdisplay.py:52 +#: squirrelbattle/display/statsdisplay.py:53 msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" @@ -58,11 +58,11 @@ msgstr "La bombe explose." msgid "{player} exchanged its body with {entity}." msgstr "{player} a échangé son corps avec {entity}." -#: squirrelbattle/game.py:205 squirrelbattle/tests/game_test.py:573 +#: squirrelbattle/game.py:277 squirrelbattle/tests/game_test.py:592 msgid "The buyer does not have enough money" msgstr "L'acheteur n'a pas assez d'argent" -#: squirrelbattle/game.py:249 +#: squirrelbattle/game.py:320 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -70,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:257 +#: squirrelbattle/game.py:328 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -78,7 +78,7 @@ msgstr "" "Aucun joueur n'a été trouvé sur la carte !\n" "Peut-être êtes-vous mort ?" -#: squirrelbattle/game.py:277 +#: squirrelbattle/game.py:348 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -86,22 +86,22 @@ msgstr "" "Le fichier JSON de sauvegarde est incorrect.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/interfaces.py:429 +#: squirrelbattle/interfaces.py:451 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} frappe {opponent}." -#: squirrelbattle/interfaces.py:441 +#: squirrelbattle/interfaces.py:463 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} prend {amount} points de dégât." -#: squirrelbattle/interfaces.py:443 +#: squirrelbattle/interfaces.py:465 #, python-brace-format msgid "{name} dies." msgstr "{name} meurt." -#: squirrelbattle/interfaces.py:477 +#: squirrelbattle/interfaces.py:499 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} a dit : {message}" @@ -110,8 +110,8 @@ msgstr "{entity} a dit : {message}" msgid "Back" msgstr "Retour" -#: squirrelbattle/tests/game_test.py:344 squirrelbattle/tests/game_test.py:347 -#: squirrelbattle/tests/game_test.py:350 squirrelbattle/tests/game_test.py:353 +#: squirrelbattle/tests/game_test.py:358 squirrelbattle/tests/game_test.py:361 +#: squirrelbattle/tests/game_test.py:364 squirrelbattle/tests/game_test.py:367 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Nouvelle partie" @@ -197,57 +197,61 @@ msgid "Key used to wait" msgstr "Touche pour attendre" #: squirrelbattle/tests/translations_test.py:56 +msgid "Key used to use ladders" +msgstr "Touche pour utiliser les échelles" + +#: squirrelbattle/tests/translations_test.py:58 msgid "Texture pack" msgstr "Pack de textures" -#: squirrelbattle/tests/translations_test.py:57 +#: squirrelbattle/tests/translations_test.py:59 msgid "Language" msgstr "Langue" -#: squirrelbattle/tests/translations_test.py:60 +#: squirrelbattle/tests/translations_test.py:62 msgid "player" msgstr "joueur" -#: squirrelbattle/tests/translations_test.py:62 +#: squirrelbattle/tests/translations_test.py:64 msgid "hedgehog" msgstr "hérisson" -#: squirrelbattle/tests/translations_test.py:63 +#: squirrelbattle/tests/translations_test.py:65 msgid "merchant" msgstr "marchand" -#: squirrelbattle/tests/translations_test.py:64 +#: squirrelbattle/tests/translations_test.py:66 msgid "rabbit" msgstr "lapin" -#: squirrelbattle/tests/translations_test.py:65 +#: squirrelbattle/tests/translations_test.py:67 msgid "sunflower" msgstr "tournesol" -#: squirrelbattle/tests/translations_test.py:66 +#: squirrelbattle/tests/translations_test.py:68 msgid "teddy bear" msgstr "nounours" -#: squirrelbattle/tests/translations_test.py:67 +#: squirrelbattle/tests/translations_test.py:69 msgid "tiger" msgstr "tigre" -#: squirrelbattle/tests/translations_test.py:69 +#: squirrelbattle/tests/translations_test.py:71 msgid "body snatch potion" msgstr "potion d'arrachage de corps" -#: squirrelbattle/tests/translations_test.py:70 +#: squirrelbattle/tests/translations_test.py:72 msgid "bomb" msgstr "bombe" -#: squirrelbattle/tests/translations_test.py:71 +#: squirrelbattle/tests/translations_test.py:73 msgid "explosion" msgstr "" -#: squirrelbattle/tests/translations_test.py:72 +#: squirrelbattle/tests/translations_test.py:74 msgid "heart" msgstr "cœur" -#: squirrelbattle/tests/translations_test.py:73 +#: squirrelbattle/tests/translations_test.py:75 msgid "sword" msgstr "épée" diff --git a/squirrelbattle/settings.py b/squirrelbattle/settings.py index 91edfa4..ad986eb 100644 --- a/squirrelbattle/settings.py +++ b/squirrelbattle/settings.py @@ -33,6 +33,7 @@ class Settings: 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.KEY_WAIT = ['w', 'Key used to wait'] + self.KEY_LADDER = ['<', 'Key used to use ladders'] self.TEXTURE_PACK = ['ascii', 'Texture pack'] self.LOCALE = [locale.getlocale()[0][:2], 'Language'] diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index 0bd8873..9ec39c7 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -53,6 +53,8 @@ class TestTranslations(unittest.TestCase): self.assertEqual(_("Key used to talk to a friendly entity"), "Touche pour parler à une entité pacifique") self.assertEqual(_("Key used to wait"), "Touche pour attendre") + self.assertEqual(_("Key used to use ladders"), + "Touche pour utiliser les échelles") self.assertEqual(_("Texture pack"), "Pack de textures") self.assertEqual(_("Language"), "Langue") From 4cd4bc90052a1bd5ccd932a551115ddb7de1fb3e Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 6 Jan 2021 15:17:02 +0100 Subject: [PATCH 32/40] Display the current floor in the StatsDisplay --- squirrelbattle/display/statsdisplay.py | 18 ++++++++++-------- squirrelbattle/game.py | 1 + squirrelbattle/interfaces.py | 2 ++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index ef358bb..fb5160f 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -20,15 +20,17 @@ class StatsDisplay(Display): self.player = game.player def update_pad(self) -> None: - string2 = "Player -- LVL {}\nEXP {}/{}\nHP {}/{}"\ - .format(self.player.level, self.player.current_xp, - self.player.max_xp, self.player.health, - self.player.maxhealth) + string2 = f"{_(self.player.name).capitalize()} " \ + f"-- LVL {self.player.level} -- " \ + f"FLOOR {-self.player.map.floor}\n" \ + f"EXP {self.player.current_xp}/{self.player.max_xp}\n" \ + f"HP {self.player.health}/{self.player.maxhealth}" self.addstr(self.pad, 0, 0, string2) - string3 = "STR {}\nINT {}\nCHR {}\nDEX {}\nCON {}"\ - .format(self.player.strength, - self.player.intelligence, self.player.charisma, - self.player.dexterity, self.player.constitution) + string3 = f"STR {self.player.strength}\n" \ + f"INT {self.player.intelligence}\n" \ + f"CHR {self.player.charisma}\n" \ + f"DEX {self.player.dexterity}\n" \ + f"CON {self.player.constitution}" self.addstr(self.pad, 3, 0, string3) inventory_str = _("Inventory:") + " " diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index dff09e0..05b4003 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -173,6 +173,7 @@ class Game: self.maps.append(Map.load(ResourceManager.get_asset_path( "example_map_2.txt"))) new_map = self.map + new_map.floor = self.map_index old_map.remove_entity(self.player) new_map.add_entity(self.player) if move_down: diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index ff94cc6..556f55c 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -35,6 +35,7 @@ class Map: Object that represents a Map with its width, height and tiles, that have their custom properties. """ + floor: int width: int height: int start_y: int @@ -49,6 +50,7 @@ class Map: def __init__(self, width: int, height: int, tiles: list, start_y: int, start_x: int): + self.floor = 0 self.width = width self.height = height self.start_y = start_y From a48e6325fe2dd48640d8f45a2c3ef2114b5a510e Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 6 Jan 2021 15:55:44 +0100 Subject: [PATCH 33/40] Add log message when the player switches floor --- squirrelbattle/game.py | 7 ++++ .../locale/de/LC_MESSAGES/squirrelbattle.po | 32 ++++++++++++------- .../locale/es/LC_MESSAGES/squirrelbattle.po | 32 ++++++++++++------- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 32 ++++++++++++------- 4 files changed, 70 insertions(+), 33 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 05b4003..f522196 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -178,6 +178,9 @@ class Game: new_map.add_entity(self.player) if move_down: self.player.move(self.map.start_y, self.map.start_x) + self.logs.add_message( + _("The player climbs down to the floor {floor}.") + .format(floor=-self.map_index)) else: # Find the ladder of the end of the game ladder_y, ladder_x = -1, -1 @@ -188,6 +191,10 @@ class Game: ladder_y, ladder_x = y, x break self.player.move(ladder_y, ladder_x) + self.logs.add_message( + _("The player climbs up the floor {floor}.") + .format(floor=-self.map_index)) + self.display_actions(DisplayActions.UPDATE) def handle_friendly_entity_chat(self, key: KeyValues) -> None: diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 591b5ae..56d4540 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: 2021-01-06 14:53+0100\n" +"POT-Creation-Date: 2021-01-06 15:19+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -25,11 +25,11 @@ msgstr "BESTAND" msgid "STALL" msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:34 +#: squirrelbattle/display/statsdisplay.py:36 msgid "Inventory:" msgstr "Bestand:" -#: squirrelbattle/display/statsdisplay.py:53 +#: squirrelbattle/display/statsdisplay.py:55 msgid "YOU ARE DEAD" msgstr "SIE WURDEN GESTORBEN" @@ -58,11 +58,21 @@ msgstr "Die Bombe explodiert." msgid "{player} exchanged its body with {entity}." msgstr "{player} täuscht seinem Körper mit {entity} aus." -#: squirrelbattle/game.py:277 squirrelbattle/tests/game_test.py:592 +#: squirrelbattle/game.py:182 +#, python-brace-format +msgid "The player climbs down to the floor {floor}." +msgstr "Der Spieler klettert auf dem Stock {floor} hinunter." + +#: squirrelbattle/game.py:195 +#, python-brace-format +msgid "The player climbs up the floor {floor}." +msgstr "Der Spieler klettert auf dem Stock {floor} hinoben." + +#: squirrelbattle/game.py:285 squirrelbattle/tests/game_test.py:592 msgid "The buyer does not have enough money" msgstr "Der Kaufer hat nicht genug Geld" -#: squirrelbattle/game.py:320 +#: squirrelbattle/game.py:328 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -70,7 +80,7 @@ msgstr "" "In Ihrer Speicherdatei fehlen einige Schlüssel.\n" "Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht." -#: squirrelbattle/game.py:328 +#: squirrelbattle/game.py:336 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -78,7 +88,7 @@ msgstr "" "Auf dieser Karte wurde kein Spieler gefunden!\n" "Vielleicht sind Sie gestorben?" -#: squirrelbattle/game.py:348 +#: squirrelbattle/game.py:356 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -86,22 +96,22 @@ msgstr "" "Die JSON-Datei ist nicht korrekt.\n" "Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." -#: squirrelbattle/interfaces.py:451 +#: squirrelbattle/interfaces.py:453 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} schlägt {opponent}." -#: squirrelbattle/interfaces.py:463 +#: squirrelbattle/interfaces.py:465 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} nimmt {amount} Schadenspunkte." -#: squirrelbattle/interfaces.py:465 +#: squirrelbattle/interfaces.py:467 #, python-brace-format msgid "{name} dies." msgstr "{name} stirbt." -#: squirrelbattle/interfaces.py:499 +#: squirrelbattle/interfaces.py:501 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} hat gesagt: {message}" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index 035cad2..f85e681 100644 --- a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/es/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: 2021-01-06 14:53+0100\n" +"POT-Creation-Date: 2021-01-06 15:19+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -25,11 +25,11 @@ msgstr "INVENTORIO" msgid "STALL" msgstr "PUESTO" -#: squirrelbattle/display/statsdisplay.py:34 +#: squirrelbattle/display/statsdisplay.py:36 msgid "Inventory:" msgstr "Inventorio :" -#: squirrelbattle/display/statsdisplay.py:53 +#: squirrelbattle/display/statsdisplay.py:55 msgid "YOU ARE DEAD" msgstr "ERES MUERTO" @@ -57,11 +57,21 @@ msgstr "La bomba está explotando." msgid "{player} exchanged its body with {entity}." msgstr "{player} intercambió su cuerpo con {entity}." -#: squirrelbattle/game.py:277 squirrelbattle/tests/game_test.py:592 +#: squirrelbattle/game.py:182 +#, python-brace-format +msgid "The player climbs down to the floor {floor}." +msgstr "" + +#: squirrelbattle/game.py:195 +#, python-brace-format +msgid "The player climbs up the floor {floor}." +msgstr "" + +#: squirrelbattle/game.py:285 squirrelbattle/tests/game_test.py:592 msgid "The buyer does not have enough money" msgstr "El comprador no tiene suficiente dinero" -#: squirrelbattle/game.py:320 +#: squirrelbattle/game.py:328 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -69,7 +79,7 @@ msgstr "" "Algunas claves faltan en su archivo de guarda.\n" "Su guarda parece a ser corruptido. Fue eliminado." -#: squirrelbattle/game.py:328 +#: squirrelbattle/game.py:336 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -77,7 +87,7 @@ msgstr "" "No jugador encontrado sobre la carta !\n" "¿ Quizas murió ?" -#: squirrelbattle/game.py:348 +#: squirrelbattle/game.py:356 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -85,22 +95,22 @@ msgstr "" "El JSON archivo no es correcto.\n" "Su guarda parece corrupta. Fue eliminada." -#: squirrelbattle/interfaces.py:451 +#: squirrelbattle/interfaces.py:453 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} golpea a {opponent}." -#: squirrelbattle/interfaces.py:463 +#: squirrelbattle/interfaces.py:465 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} recibe {amount} daño." -#: squirrelbattle/interfaces.py:465 +#: squirrelbattle/interfaces.py:467 #, python-brace-format msgid "{name} dies." msgstr "{name} se muere." -#: squirrelbattle/interfaces.py:499 +#: squirrelbattle/interfaces.py:501 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} dijo : {message}" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index a64d96c..81a4ab3 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: 2021-01-06 14:53+0100\n" +"POT-Creation-Date: 2021-01-06 15:19+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -25,11 +25,11 @@ msgstr "INVENTAIRE" msgid "STALL" msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:34 +#: squirrelbattle/display/statsdisplay.py:36 msgid "Inventory:" msgstr "Inventaire :" -#: squirrelbattle/display/statsdisplay.py:53 +#: squirrelbattle/display/statsdisplay.py:55 msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" @@ -58,11 +58,21 @@ msgstr "La bombe explose." msgid "{player} exchanged its body with {entity}." msgstr "{player} a échangé son corps avec {entity}." -#: squirrelbattle/game.py:277 squirrelbattle/tests/game_test.py:592 +#: squirrelbattle/game.py:182 +#, python-brace-format +msgid "The player climbs down to the floor {floor}." +msgstr "Le joueur descend à l'étage {floor}." + +#: squirrelbattle/game.py:195 +#, python-brace-format +msgid "The player climbs up the floor {floor}." +msgstr "Le joueur monte à l'étage {floor}." + +#: squirrelbattle/game.py:285 squirrelbattle/tests/game_test.py:592 msgid "The buyer does not have enough money" msgstr "L'acheteur n'a pas assez d'argent" -#: squirrelbattle/game.py:320 +#: squirrelbattle/game.py:328 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -70,7 +80,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:328 +#: squirrelbattle/game.py:336 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -78,7 +88,7 @@ msgstr "" "Aucun joueur n'a été trouvé sur la carte !\n" "Peut-être êtes-vous mort ?" -#: squirrelbattle/game.py:348 +#: squirrelbattle/game.py:356 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -86,22 +96,22 @@ msgstr "" "Le fichier JSON de sauvegarde est incorrect.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/interfaces.py:451 +#: squirrelbattle/interfaces.py:453 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} frappe {opponent}." -#: squirrelbattle/interfaces.py:463 +#: squirrelbattle/interfaces.py:465 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} prend {amount} points de dégât." -#: squirrelbattle/interfaces.py:465 +#: squirrelbattle/interfaces.py:467 #, python-brace-format msgid "{name} dies." msgstr "{name} meurt." -#: squirrelbattle/interfaces.py:499 +#: squirrelbattle/interfaces.py:501 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} a dit : {message}" From 41548504de19231b5b15b7f9c8685e16fd4bf64f Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 6 Jan 2021 16:09:53 +0100 Subject: [PATCH 34/40] Test ladders --- squirrelbattle/interfaces.py | 10 +++------ squirrelbattle/tests/game_test.py | 36 ++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 556f55c..472ee95 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -144,13 +144,9 @@ class Map: for ignored in range(count): while True: y, x = randint(0, self.height - 1), randint(0, self.width - 1) - try: - tile = self.tiles[y][x] - except Exception as e: - raise Exception(y, x, len(self.tiles)) - else: - if tile.can_walk(): - break + tile = self.tiles[y][x] + if tile.can_walk(): + break entity = choice(Entity.get_all_entity_classes())() entity.move(y, x) self.add_entity(entity) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 6751016..ab66cb3 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -148,6 +148,9 @@ class TestGame(unittest.TestCase): self.assertEqual(KeyValues.translate_key( self.game.settings.KEY_WAIT, self.game.settings), KeyValues.WAIT) + self.assertEqual(KeyValues.translate_key( + self.game.settings.KEY_LADDER, self.game.settings), + KeyValues.LADDER) self.assertEqual(KeyValues.translate_key(' ', self.game.settings), KeyValues.SPACE) self.assertEqual(KeyValues.translate_key('plop', self.game.settings), @@ -338,7 +341,7 @@ class TestGame(unittest.TestCase): self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a') # Navigate to "texture pack" - for ignored in range(11): + for ignored in range(12): self.game.handle_key_pressed(KeyValues.DOWN) # Change texture pack @@ -609,3 +612,34 @@ class TestGame(unittest.TestCase): # Exit the menu self.game.handle_key_pressed(KeyValues.SPACE) self.assertEqual(self.game.state, GameMode.PLAY) + + def test_ladders(self) -> None: + """ + Ensure that the player can climb on ladders. + """ + self.game.state = GameMode.PLAY + + self.assertEqual(self.game.player.map.floor, 0) + self.game.handle_key_pressed(KeyValues.LADDER) + self.assertEqual(self.game.player.map.floor, 0) + + # Move nowhere + self.game.player.move(10, 10) + self.game.handle_key_pressed(KeyValues.LADDER) + self.assertEqual(self.game.player.map.floor, 0) + + # Move down + self.game.player.move(3, 40) # Move on a ladder + self.game.handle_key_pressed(KeyValues.LADDER) + self.assertEqual(self.game.map_index, 1) + self.assertEqual(self.game.player.map.floor, 1) + self.assertEqual(self.game.player.y, 1) + self.assertEqual(self.game.player.x, 17) + self.game.display_actions(DisplayActions.UPDATE) + + # Move up + self.game.handle_key_pressed(KeyValues.LADDER) + self.assertEqual(self.game.player.map.floor, 0) + self.assertEqual(self.game.player.y, 3) + self.assertEqual(self.game.player.x, 40) + self.game.display_actions(DisplayActions.UPDATE) From 887a190f113c143dc9bcc813d9380c4ab45c49d8 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 6 Jan 2021 17:00:43 +0100 Subject: [PATCH 35/40] Less complexity on the handle key function --- squirrelbattle/game.py | 84 ++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index f522196..bb9ee86 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -155,47 +155,53 @@ class Game: elif key == KeyValues.WAIT: self.map.tick() elif key == KeyValues.LADDER: - # On a ladder, we switch level - y, x = self.player.y, self.player.x - if not self.map.tiles[y][x].is_ladder(): - return + self.handle_ladder() - # We move up on the ladder of the beginning, - # down at the end of the stage - move_down = y != self.map.start_y and x != self.map.start_x - old_map = self.map - self.map_index += 1 if move_down else -1 - if self.map_index == -1: - self.map_index = 0 - return - while self.map_index >= len(self.maps): - # TODO: generate a new map - self.maps.append(Map.load(ResourceManager.get_asset_path( - "example_map_2.txt"))) - new_map = self.map - new_map.floor = self.map_index - old_map.remove_entity(self.player) - new_map.add_entity(self.player) - if move_down: - self.player.move(self.map.start_y, self.map.start_x) - self.logs.add_message( - _("The player climbs down to the floor {floor}.") - .format(floor=-self.map_index)) - else: - # Find the ladder of the end of the game - ladder_y, ladder_x = -1, -1 - for y in range(self.map.height): - for x in range(self.map.width): - if (y, x) != (self.map.start_y, self.map.start_x) \ - and self.map.tiles[y][x].is_ladder(): - ladder_y, ladder_x = y, x - break - self.player.move(ladder_y, ladder_x) - self.logs.add_message( - _("The player climbs up the floor {floor}.") - .format(floor=-self.map_index)) + def handle_ladder(self) -> None: + """ + The player pressed the ladder key to switch map + """ + # On a ladder, we switch level + y, x = self.player.y, self.player.x + if not self.map.tiles[y][x].is_ladder(): + return - self.display_actions(DisplayActions.UPDATE) + # We move up on the ladder of the beginning, + # down at the end of the stage + move_down = y != self.map.start_y and x != self.map.start_x + old_map = self.map + self.map_index += 1 if move_down else -1 + if self.map_index == -1: + self.map_index = 0 + return + while self.map_index >= len(self.maps): + # TODO: generate a new map + self.maps.append(Map.load(ResourceManager.get_asset_path( + "example_map_2.txt"))) + new_map = self.map + new_map.floor = self.map_index + old_map.remove_entity(self.player) + new_map.add_entity(self.player) + if move_down: + self.player.move(self.map.start_y, self.map.start_x) + self.logs.add_message( + _("The player climbs down to the floor {floor}.") + .format(floor=-self.map_index)) + else: + # Find the ladder of the end of the game + ladder_y, ladder_x = -1, -1 + for y in range(self.map.height): + for x in range(self.map.width): + if (y, x) != (self.map.start_y, self.map.start_x) \ + and self.map.tiles[y][x].is_ladder(): + ladder_y, ladder_x = y, x + break + self.player.move(ladder_y, ladder_x) + self.logs.add_message( + _("The player climbs up the floor {floor}.") + .format(floor=-self.map_index)) + + self.display_actions(DisplayActions.UPDATE) def handle_friendly_entity_chat(self, key: KeyValues) -> None: """ From 0c2b10b0310834fe1ff92b776669d2a291468845 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 6 Jan 2021 17:21:17 +0100 Subject: [PATCH 36/40] Use ternary conditions to gain coverage --- squirrelbattle/interfaces.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 472ee95..8fde7a9 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -217,18 +217,15 @@ class Tile(Enum): to the texture pack """ val = getattr(pack, self.name) - if isinstance(val, tuple): - return val[0] - return val + return val[0] if isinstance(val, tuple) else val def color(self, pack: TexturePack) -> Tuple[int, int]: """ Retrieve the tuple (fg_color, bg_color) of the current Tile. """ val = getattr(pack, self.name) - if isinstance(val, tuple): - return val[1], val[2] - return pack.tile_fg_color, pack.tile_bg_color + return (val[1], val[2]) if isinstance(val, tuple) else \ + (pack.tile_fg_color, pack.tile_bg_color) def is_wall(self) -> bool: """ From 093c105120290c038b85c44a79155679f64e9751 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 6 Jan 2021 17:54:43 +0100 Subject: [PATCH 37/40] The broken test is mysteriously working now --- squirrelbattle/game.py | 2 +- squirrelbattle/tests/game_test.py | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 8da50a0..779e98f 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -155,7 +155,7 @@ class Game: # Wait for the direction of the friendly entity self.waiting_for_friendly_key = True elif key == KeyValues.WAIT: - self.map.tick() + self.map.tick(self.player) elif key == KeyValues.LADDER: self.handle_ladder() diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 0e8bf9e..efc20da 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -46,11 +46,6 @@ class TestGame(unittest.TestCase): bomb.hold(self.game.player) sword.hold(self.game.player) - for entity in self.game.map.entities: - # trumpets change order when they are loaded, this breaks the test. - if entity.name == 'trumpet': - self.game.map.remove_entity(entity) - # Ensure that merchants can be saved merchant = Merchant() merchant.move(3, 6) From e9c8f43e7eb92d32090f6f80d61ae071fdbf643b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 7 Jan 2021 16:31:39 +0100 Subject: [PATCH 38/40] Use ternary conditions to add coverage --- squirrelbattle/interfaces.py | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 30e59f4..c1c224f 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -206,15 +206,12 @@ class Map: else: top_y = ceil(((x * 2 - 1) * top.Y + top.X) / (top.X * 2)) if self.is_wall(top_y, x, octant, origin): - if top >= Slope(top_y * 2 + 1, x * 2) and not\ - self.is_wall(top_y + 1, x, octant, origin): - top_y += 1 + top_y += top >= Slope(top_y * 2 + 1, x * 2) and not \ + self.is_wall(top_y + 1, x, octant, origin) else: ax = x * 2 - if self.is_wall(top_y + 1, x + 1, octant, origin): - ax += 1 - if top > Slope(top_y * 2 + 1, ax): - top_y += 1 + ax += self.is_wall(top_y + 1, x + 1, octant, origin) + top_y += top > Slope(top_y * 2 + 1, ax) return top_y def crop_bottom_visibility(self, octant: int, origin: Tuple[int, int], @@ -224,10 +221,9 @@ class Map: else: bottom_y = ceil(((x * 2 - 1) * bottom.Y + bottom.X) / (bottom.X * 2)) - if bottom >= Slope(bottom_y * 2 + 1, x * 2) and\ - self.is_wall(bottom_y, x, octant, origin) and\ - not self.is_wall(bottom_y + 1, x, octant, origin): - bottom_y += 1 + bottom_y += bottom >= Slope(bottom_y * 2 + 1, x * 2) and \ + self.is_wall(bottom_y, x, octant, origin) and \ + not self.is_wall(bottom_y + 1, x, octant, origin) return bottom_y def compute_visibility_octant(self, octant: int, origin: Tuple[int, int], @@ -254,8 +250,7 @@ class Map: continue if is_opaque and was_opaque == 0: nx, ny = x * 2, y * 2 + 1 - if self.is_wall(y + 1, x, octant, origin): - nx -= 1 + nx -= self.is_wall(y + 1, x, octant, origin) if top > Slope(ny, nx): if y == bottom_y: bottom = Slope(ny, nx) @@ -264,14 +259,12 @@ class Map: self.compute_visibility_octant( octant, origin, max_range, x + 1, top, Slope(ny, nx)) - else: - if y == bottom_y: - return + elif y == bottom_y: # pragma: no cover + return elif not is_opaque and was_opaque == 1: nx, ny = x * 2, y * 2 + 1 - if self.is_wall(y + 1, x + 1, octant, origin): - nx += 1 - if bottom >= Slope(ny, nx): + nx += self.is_wall(y + 1, x + 1, octant, origin) + if bottom >= Slope(ny, nx): # pragma: no cover return top = Slope(ny, nx) was_opaque = is_opaque From c36e68d6e43579d0d3a604be637b0a47060ac332 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 7 Jan 2021 16:34:12 +0100 Subject: [PATCH 39/40] Reduce player vision --- squirrelbattle/entities/player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index aad35dd..6d18884 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -21,7 +21,7 @@ class Player(InventoryHolder, FightingEntity): 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, vision: int = 50, *args, **kwargs) \ + hazel: int = 42, vision: int = 5, *args, **kwargs) \ -> None: super().__init__(name=name, maxhealth=maxhealth, strength=strength, intelligence=intelligence, charisma=charisma, From 478a655751a53221a5cc7c5dda875acca3c777bf Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 7 Jan 2021 16:49:40 +0100 Subject: [PATCH 40/40] Fix fg/bg custom colors --- squirrelbattle/display/mapdisplay.py | 10 ++++------ squirrelbattle/display/texturepack.py | 3 ++- squirrelbattle/interfaces.py | 13 +++++++++++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index 6cbf9ae..701f33e 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -26,13 +26,11 @@ class MapDisplay(Display): for i in range(len(self.map.tiles[j])): if not self.map.seen_tiles[j][i]: continue - color = self.pack.tile_fg_visible_color if \ - self.map.visibility[j][i] else self.pack.tile_fg_color + fg, bg = self.map.tiles[j][i].visible_color(self.pack) if \ + self.map.visibility[j][i] else \ + self.map.tiles[j][i].hidden_color(self.pack) self.addstr(self.pad, j, self.pack.tile_width * i, - self.map.tiles[j][i].char(self.pack), - color, self.pack.tile_bg_color) - # self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), - # self.pack.tile_fg_color, self.pack.tile_bg_color) + self.map.tiles[j][i].char(self.pack), fg, bg) for e in self.map.entities: if self.map.visibility[e.y][e.x]: self.addstr(self.pad, e.y, self.pack.tile_width * e.x, diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 796b32a..dae0763 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -99,7 +99,8 @@ TexturePack.SQUIRREL_PACK = TexturePack( EMPTY=' ', EXPLOSION='💥', FLOOR='██', - LADDER=('🪜', curses.COLOR_WHITE, curses.COLOR_WHITE), + LADDER=('🪜', curses.COLOR_WHITE, (1000, 1000, 1000), + curses.COLOR_WHITE, (1000, 1000, 1000)), HAZELNUT='🌰', HEART='💜', HEDGEHOG='🦔', diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index c1e509b..601229b 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -383,12 +383,21 @@ class Tile(Enum): val = getattr(pack, self.name) return val[0] if isinstance(val, tuple) else val - def color(self, pack: TexturePack) -> Tuple[int, int]: + def visible_color(self, pack: TexturePack) -> Tuple[int, int]: + """ + Retrieve the tuple (fg_color, bg_color) of the current Tile + if it is visible. + """ + val = getattr(pack, self.name) + return (val[2], val[4]) if isinstance(val, tuple) else \ + (pack.tile_fg_visible_color, pack.tile_bg_color) + + def hidden_color(self, pack: TexturePack) -> Tuple[int, int]: """ Retrieve the tuple (fg_color, bg_color) of the current Tile. """ val = getattr(pack, self.name) - return (val[1], val[2]) if isinstance(val, tuple) else \ + return (val[1], val[3]) if isinstance(val, tuple) else \ (pack.tile_fg_color, pack.tile_bg_color) def is_wall(self) -> bool: