From 1cf5e7bd8b7329b6d3254a83685a3a3b11de0860 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 11 Dec 2020 19:23:21 +0100 Subject: [PATCH 01/95] 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 777668848e76ecd85657ae045682cc1f6eb19108 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 21:17:21 +0100 Subject: [PATCH 02/95] Clicking on the main image changes its color --- squirrelbattle/display/display.py | 4 ++++ squirrelbattle/display/menudisplay.py | 11 ++++++++++- squirrelbattle/tests/game_test.py | 3 +++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index c5b1aa3..0ca5673 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -48,6 +48,10 @@ class Display: def color_pair(self, number: int) -> int: return curses.color_pair(number) if self.screen else 0 + def init_color(self, number: int, red: int, green: int, blue: int) -> None: + return curses.init_color(number, red, green, blue) \ + if self.screen else None + def resize(self, y: int, x: int, height: int, width: int, resize_pad: bool = True) -> None: self.x = x diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 8c2cce9..7b49a64 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -102,13 +102,17 @@ class MainMenuDisplay(Display): self.pad = self.newpad(max(self.rows, len(self.title) + 30), max(len(self.title[0]) + 5, self.cols)) + self.init_color(42, 1000, 1000, 1000) + self.init_pair(42, 42, curses.COLOR_BLACK) + self.menudisplay = MenuDisplay(self.screen, self.pack) self.menudisplay.update_menu(self.menu) def display(self) -> None: for i in range(len(self.title)): self.addstr(self.pad, 4 + i, max(self.width // 2 - - len(self.title[0]) // 2 - 1, 0), self.title[i]) + - len(self.title[0]) // 2 - 1, 0), self.title[i], + self.color_pair(42)) self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.height + self.y - 1, self.width + self.x - 1) @@ -126,6 +130,11 @@ class MainMenuDisplay(Display): if menuy <= y < menuy + menuheight and menux <= x < menux + menuwidth: self.menudisplay.handle_click(y - menuy, x - menux, game) + if y <= len(self.title): + from random import randint + self.init_color(42, randint(0, 1000), randint(0, 1000), + randint(0, 1000)) + class PlayerInventoryDisplay(MenuDisplay): message = _("== INVENTORY ==") diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index bc3ce12..77b63c3 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -236,6 +236,9 @@ class TestGame(unittest.TestCase): """ self.game.state = GameMode.MAINMENU + # Change the color of the artwork + self.game.display_actions(DisplayActions.MOUSE, 0, 10) + # Settings menu self.game.display_actions(DisplayActions.MOUSE, 25, 21) self.assertEqual(self.game.main_menu.position, 4) From 48318a91feffa98f95940d6b0d32fbd02547acf0 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 11 Dec 2020 21:18:50 +0100 Subject: [PATCH 03/95] Give more hazels to the player when testing the interaction with merchants --- squirrelbattle/tests/game_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 77b63c3..27c8ca8 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -540,6 +540,8 @@ class TestGame(unittest.TestCase): self.game.handle_key_pressed(KeyValues.UP) self.assertEqual(self.game.store_menu.position, 1) + self.game.player.hazel = 0x7ffff42ff + # The second item is not a heart merchant.inventory[1] = Sword() # Buy the second item by clicking on it From 04ae56e451e949fc231b09d71db316fe93256162 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 12 Dec 2020 13:46:45 +0100 Subject: [PATCH 04/95] Higher abstraction level on addmsg, fixes #43 --- squirrelbattle/display/display.py | 90 +++++++++++++++++++++--- squirrelbattle/display/mapdisplay.py | 12 ++-- squirrelbattle/display/menudisplay.py | 20 +++--- squirrelbattle/display/messagedisplay.py | 2 +- squirrelbattle/display/statsdisplay.py | 6 +- 5 files changed, 97 insertions(+), 33 deletions(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 0ca5673..f6dd6db 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import curses -from typing import Any, Optional, Union +from typing import Any, Optional, Tuple, Union from squirrelbattle.display.texturepack import TexturePack from squirrelbattle.game import Game @@ -16,6 +16,9 @@ class Display: height: int pad: Any + _color_pairs = {(curses.COLOR_WHITE, curses.COLOR_BLACK): 0} + _colors_rgb = {} + def __init__(self, screen: Any, pack: Optional[TexturePack] = None): self.screen = screen self.pack = pack or TexturePack.get_pack("ascii") @@ -31,15 +34,84 @@ class Display: lines = [line[:width] for line in lines] return "\n".join(lines) - def addstr(self, pad: Any, y: int, x: int, msg: str, *options) -> None: + 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. + 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 + between 0 and 255. + """ + if isinstance(color, tuple): + # The color is a tuple (R, G, B), that is potentially unknown. + # We translate it into a curses color number. + if color not in self._colors_rgb: + # The color does not exist, we create it. + color_nb = len(self._colors_rgb) + 8 + self.init_color(color_nb, color[0], color[1], color[2]) + self._colors_rgb[color] = color_nb + color = self._colors_rgb[color] + return color + + def addstr(self, pad: Any, y: int, x: int, msg: str, + fg_color: Union[int, Tuple[int, int, int]] = curses.COLOR_WHITE, + bg_color: Union[int, Tuple[int, int, int]] = curses.COLOR_BLACK, + *, altcharset: bool = False, blink: bool = False, + bold: bool = False, dim: bool = False, invis: bool = False, + italic: bool = False, normal: bool = False, + protect: bool = False, reverse: bool = False, + standout: bool = False, underline: bool = False, + horizontal: bool = False, left: bool = False, + low: bool = False, right: bool = False, top: bool = False, + vertical: bool = False, chartext: bool = False) -> None: """ Display 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 + 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 + the color. R, G, B must be between 0 and 1000, and not 0 and 255. """ height, width = pad.getmaxyx() + # Truncate message if it is too large msg = self.truncate(msg, height - y, width - x - 1) if msg.replace("\n", "") and x >= 0 and y >= 0: - return pad.addstr(y, x, msg, *options) + fg_color = self.translate_color(fg_color) + bg_color = self.translate_color(bg_color) + + # Get the pair number for the tuple (fg, bg) + # If it does not exist, create it and give a new unique id. + if (fg_color, bg_color) in self._color_pairs: + pair_nb = self._color_pairs[(fg_color, bg_color)] + else: + pair_nb = len(self._color_pairs) + self.init_pair(pair_nb, fg_color, bg_color) + self._color_pairs[(fg_color, bg_color)] = pair_nb + + # Compute curses attributes from the parameters + attr = self.color_pair(pair_nb) + attr |= curses.A_ALTCHARSET if altcharset else 0 + attr |= curses.A_BLINK if blink else 0 + attr |= curses.A_BOLD if bold else 0 + attr |= curses.A_DIM if dim else 0 + attr |= curses.A_INVIS if invis else 0 + attr |= curses.A_ITALIC if italic else 0 + attr |= curses.A_NORMAL if normal else 0 + attr |= curses.A_PROTECT if protect else 0 + attr |= curses.A_REVERSE if reverse else 0 + attr |= curses.A_STANDOUT if standout else 0 + attr |= curses.A_UNDERLINE if underline else 0 + attr |= curses.A_HORIZONTAL if horizontal else 0 + attr |= curses.A_LEFT if left else 0 + attr |= curses.A_LOW if low else 0 + attr |= curses.A_RIGHT if right else 0 + attr |= curses.A_TOP if top else 0 + attr |= curses.A_VERTICAL if vertical else 0 + attr |= curses.A_CHARTEXT if chartext else 0 + + return pad.addstr(y, x, msg, attr) def init_pair(self, number: int, foreground: int, background: int) -> None: return curses.init_pair(number, foreground, background) \ @@ -156,17 +228,13 @@ class Box(Display): self.pad = self.newpad(self.rows, self.cols) self.fg_border_color = fg_border_color or curses.COLOR_WHITE - pair_number = 4 + self.fg_border_color - self.init_pair(pair_number, self.fg_border_color, curses.COLOR_BLACK) - self.pair = self.color_pair(pair_number) - def display(self) -> None: self.addstr(self.pad, 0, 0, "┏" + "━" * (self.width - 2) + "┓", - self.pair) + self.fg_border_color) for i in range(1, self.height - 1): - self.addstr(self.pad, i, 0, "┃", self.pair) - self.addstr(self.pad, i, self.width - 1, "┃", self.pair) + self.addstr(self.pad, i, 0, "┃", self.fg_border_color) + self.addstr(self.pad, i, self.width - 1, "┃", self.fg_border_color) self.addstr(self.pad, self.height - 1, 0, - "┗" + "━" * (self.width - 2) + "┛", self.pair) + "┗" + "━" * (self.width - 2) + "┛", self.fg_border_color) self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.y + self.height - 1, self.x + self.width - 1) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index d403f7f..54d9432 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -15,15 +15,14 @@ class MapDisplay(Display): self.pad = self.newpad(m.height, self.pack.tile_width * m.width + 1) def update_pad(self) -> None: - self.init_pair(1, self.pack.tile_fg_color, self.pack.tile_bg_color) - self.init_pair(2, self.pack.entity_fg_color, self.pack.entity_bg_color) self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), - self.color_pair(1)) + 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.color_pair(2)) + self.pack[e.name.upper()], + self.pack.entity_fg_color, self.pack.entity_bg_color) - # Display Path map for deubg purposes + # Display Path map for debug purposes # from squirrelbattle.entities.player import Player # players = [ p for p in self.map.entities if isinstance(p,Player) ] # player = players[0] if len(players) > 0 else None @@ -42,7 +41,8 @@ class MapDisplay(Display): # else: # character = '←' # self.addstr(self.pad, y, self.pack.tile_width * x, - # character, self.color_pair(1)) + # character, self.pack.tile_fg_color, + # self.pack.tile_bg_color) def display(self) -> None: y, x = self.map.currenty, self.pack.tile_width * self.map.currentx diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 7b49a64..d067547 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import curses +from random import randint from typing import List from squirrelbattle.menus import Menu, MainMenu @@ -30,9 +31,9 @@ class MenuDisplay(Display): def update_pad(self) -> None: for i in range(self.trueheight): - self.addstr(self.pad, i, 0, " " + self.values[i]) + self.addstr(self.pad, i, 0, " " + self.values[i]) # set a marker on the selected line - self.addstr(self.pad, self.menu.position, 0, ">") + self.addstr(self.pad, self.menu.position, 0, " >") def display(self) -> None: cornery = 0 if self.height - 2 >= self.menu.position - 1 \ @@ -43,7 +44,7 @@ class MenuDisplay(Display): self.menubox.refresh(self.y, self.x, self.height, self.width) self.pad.erase() self.update_pad() - self.refresh_pad(self.pad, cornery, 0, self.y + 1, self.x + 2, + self.refresh_pad(self.pad, cornery, 0, self.y + 1, self.x + 1, self.height - 2 + self.y, self.width - 2 + self.x) @@ -102,8 +103,7 @@ class MainMenuDisplay(Display): self.pad = self.newpad(max(self.rows, len(self.title) + 30), max(len(self.title[0]) + 5, self.cols)) - self.init_color(42, 1000, 1000, 1000) - self.init_pair(42, 42, curses.COLOR_BLACK) + self.fg_color = curses.COLOR_WHITE self.menudisplay = MenuDisplay(self.screen, self.pack) self.menudisplay.update_menu(self.menu) @@ -112,7 +112,7 @@ class MainMenuDisplay(Display): for i in range(len(self.title)): self.addstr(self.pad, 4 + i, max(self.width // 2 - len(self.title[0]) // 2 - 1, 0), self.title[i], - self.color_pair(42)) + self.fg_color) self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.height + self.y - 1, self.width + self.x - 1) @@ -131,9 +131,7 @@ class MainMenuDisplay(Display): self.menudisplay.handle_click(y - menuy, x - menux, game) if y <= len(self.title): - from random import randint - self.init_color(42, randint(0, 1000), randint(0, 1000), - randint(0, 1000)) + self.fg_color = randint(0, 1000), randint(0, 1000), randint(0, 1000) class PlayerInventoryDisplay(MenuDisplay): @@ -141,7 +139,7 @@ class PlayerInventoryDisplay(MenuDisplay): def update_pad(self) -> None: self.addstr(self.pad, 0, (self.width - len(self.message)) // 2, - self.message, curses.A_BOLD | curses.A_ITALIC) + self.message, bold=True, italic=True) for i, item in enumerate(self.menu.values): rep = self.pack[item.name.upper()] selection = f"[{rep}]" if i == self.menu.position else f" {rep} " @@ -169,7 +167,7 @@ class StoreInventoryDisplay(MenuDisplay): def update_pad(self) -> None: self.addstr(self.pad, 0, (self.width - len(self.message)) // 2, - self.message, curses.A_BOLD | curses.A_ITALIC) + self.message, bold=True, italic=True) for i, item in enumerate(self.menu.values): rep = self.pack[item.name.upper()] selection = f"[{rep}]" if i == self.menu.position else f" {rep} " diff --git a/squirrelbattle/display/messagedisplay.py b/squirrelbattle/display/messagedisplay.py index bcc2539..32f7139 100644 --- a/squirrelbattle/display/messagedisplay.py +++ b/squirrelbattle/display/messagedisplay.py @@ -25,7 +25,7 @@ class MessageDisplay(Display): self.height + 2, self.width + 4) self.box.display() self.pad.erase() - self.addstr(self.pad, 0, 0, self.message, curses.A_BOLD) + self.addstr(self.pad, 0, 0, self.message, bold=True) self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.height + self.y - 1, self.width + self.x - 1) diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index ec0c90a..9937c3e 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -14,7 +14,6 @@ class StatsDisplay(Display): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.pad = self.newpad(self.rows, self.cols) - self.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK) def update_player(self, p: Player) -> None: self.player = p @@ -50,9 +49,8 @@ class StatsDisplay(Display): f"x{self.player.hazel}") if self.player.dead: - self.addstr(self.pad, 11, 0, _("YOU ARE DEAD"), - curses.A_BOLD | curses.A_BLINK | curses.A_STANDOUT - | self.color_pair(3)) + self.addstr(self.pad, 11, 0, _("YOU ARE DEAD"), curses.COLOR_RED, + bold=True, blink=True, standout=True) def display(self) -> None: self.pad.erase() From 8608ce346fdcac66dda919937ef5b37aba13d458 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 12 Dec 2020 16:50:01 +0100 Subject: [PATCH 05/95] Add a small bomb exploding animation, fixes #31 --- squirrelbattle/display/texturepack.py | 2 ++ squirrelbattle/entities/items.py | 24 ++++++++++++++++++++++++ squirrelbattle/tests/entities_test.py | 17 ++++++++++++++++- 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index a6ec3af..f72cd97 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -62,6 +62,7 @@ TexturePack.ASCII_PACK = TexturePack( BODY_SNATCH_POTION='S', BOMB='o', EMPTY=' ', + EXPLOSION='%', FLOOR='.', HAZELNUT='¤', HEART='❤', @@ -87,6 +88,7 @@ TexturePack.SQUIRREL_PACK = TexturePack( BODY_SNATCH_POTION='🔀', BOMB='💣', EMPTY=' ', + EXPLOSION='💥', FLOOR='██', HAZELNUT='🌰', HEART='💜', diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index b115f8d..865a703 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -158,6 +158,10 @@ class Bomb(Item): m.logs.add_message(log_message) m.entities.remove(self) + # Add sparkles where the bomb exploded. + explosion = Explosion(y=self.y, x=self.x) + self.map.add_entity(explosion) + def save_state(self) -> dict: """ Saves the state of the bomb into a dictionary @@ -168,6 +172,26 @@ class Bomb(Item): return d +class Explosion(Item): + """ + When a bomb explodes, the explosion is displayed. + """ + def __init__(self, *args, **kwargs): + super().__init__(name="explosion", *args, **kwargs) + + def act(self, m: Map) -> None: + """ + The explosion instant dies. + """ + m.remove_entity(self) + + def hold(self, player: InventoryHolder) -> None: + """ + The player can't hold any explosion. + """ + pass + + class Weapon(Item): """ Non-throwable items that improve player damage diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 2c72abd..70e3748 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -3,7 +3,8 @@ import unittest -from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart, Item +from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart, Item, \ + Explosion from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, TeddyBear from squirrelbattle.entities.player import Player from squirrelbattle.interfaces import Entity, Map @@ -138,6 +139,20 @@ class TestEntities(unittest.TestCase): self.assertTrue(teddy_bear.dead) bomb_state = item.save_state() self.assertEqual(bomb_state["damage"], item.damage) + explosions = self.map.find_entities(Explosion) + self.assertTrue(explosions) + explosion = explosions[0] + self.assertEqual(explosion.y, item.y) + self.assertEqual(explosion.x, item.x) + + # The player can't hold the explosion + explosion.hold(self.player) + self.assertNotIn(explosion, self.player.inventory) + self.assertFalse(explosion.held) + + # The explosion disappears after one tick + explosion.act(self.map) + self.assertNotIn(explosion, self.map.entities) def test_hearts(self) -> None: """ From 73e1fac89a5c4c114419d848a7008fcb7db06f90 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 12 Dec 2020 17:15:08 +0100 Subject: [PATCH 06/95] Add title to boxes to have pretty boxes, fixes #28 --- squirrelbattle/display/display.py | 10 ++ squirrelbattle/display/menudisplay.py | 16 +--- .../locale/de/LC_MESSAGES/squirrelbattle.po | 40 ++++---- .../locale/es/LC_MESSAGES/squirrelbattle.po | 93 ++++++++++++------- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 41 ++++---- 5 files changed, 116 insertions(+), 84 deletions(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index f6dd6db..d1bea1b 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -222,6 +222,10 @@ class HorizontalSplit(Display): class Box(Display): + title: str = "" + + def update_title(self, title: str) -> None: + self.title = title def __init__(self, *args, fg_border_color: Optional[int] = None, **kwargs): super().__init__(*args, **kwargs) @@ -236,5 +240,11 @@ class Box(Display): self.addstr(self.pad, i, self.width - 1, "┃", self.fg_border_color) self.addstr(self.pad, self.height - 1, 0, "┗" + "━" * (self.width - 2) + "┛", self.fg_border_color) + + if self.title: + self.addstr(self.pad, 0, (self.width - len(self.title) - 8) // 2, + f" == {self.title} == ", curses.COLOR_GREEN, + italic=True, bold=True) + self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.y + self.height - 1, self.x + self.width - 1) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index d067547..135abda 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -6,7 +6,7 @@ from random import randint from typing import List from squirrelbattle.menus import Menu, MainMenu -from .display import Display, Box +from .display import Box, Display from ..enums import KeyValues from ..game import Game from ..resources import ResourceManager @@ -135,15 +135,12 @@ class MainMenuDisplay(Display): class PlayerInventoryDisplay(MenuDisplay): - message = _("== INVENTORY ==") - def update_pad(self) -> None: - self.addstr(self.pad, 0, (self.width - len(self.message)) // 2, - self.message, bold=True, italic=True) + self.menubox.update_title(_("INVENTORY")) for i, item in enumerate(self.menu.values): rep = self.pack[item.name.upper()] selection = f"[{rep}]" if i == self.menu.position else f" {rep} " - self.addstr(self.pad, 2 + i, 0, selection + self.addstr(self.pad, i + 1, 0, selection + " " + item.translated_name.capitalize()) @property @@ -163,15 +160,12 @@ class PlayerInventoryDisplay(MenuDisplay): class StoreInventoryDisplay(MenuDisplay): - message = _("== STALL ==") - def update_pad(self) -> None: - self.addstr(self.pad, 0, (self.width - len(self.message)) // 2, - self.message, bold=True, italic=True) + self.menubox.update_title(_("STALL")) for i, item in enumerate(self.menu.values): rep = self.pack[item.name.upper()] selection = f"[{rep}]" if i == self.menu.position else f" {rep} " - self.addstr(self.pad, 2 + i, 0, selection + self.addstr(self.pad, i + 1, 0, selection + " " + item.translated_name.capitalize() + ": " + str(item.price) + " Hazels") diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index b5e8225..9405951 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -1,12 +1,14 @@ -# German translation of Squirrel Battle -# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao # This file is distributed under the same license as the squirrelbattle package. +# FIRST AUTHOR , YEAR. # +#, fuzzy msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-12-11 18:06+0100\n" +"POT-Creation-Date: 2020-12-12 17:07+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,19 +17,19 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/display/menudisplay.py:113 -msgid "== INVENTORY ==" -msgstr "== BESTAND ==" +#: squirrelbattle/display/menudisplay.py:139 +msgid "INVENTORY" +msgstr "BESTAND" -#: squirrelbattle/display/menudisplay.py:134 -msgid "== STALL ==" -msgstr "== STAND ==" +#: squirrelbattle/display/menudisplay.py:164 +msgid "STALL" +msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:34 +#: squirrelbattle/display/statsdisplay.py:33 msgid "Inventory:" msgstr "Bestand:" -#: squirrelbattle/display/statsdisplay.py:53 +#: squirrelbattle/display/statsdisplay.py:52 msgid "YOU ARE DEAD" msgstr "SIE WURDEN GESTORBEN" @@ -51,16 +53,16 @@ msgstr "Die Sonne ist warm heute" msgid "Bomb is exploding." msgstr "Die Bombe explodiert." -#: squirrelbattle/entities/items.py:224 +#: squirrelbattle/entities/items.py:248 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} täuscht seinem Körper mit {entity} aus." -#: squirrelbattle/game.py:199 squirrelbattle/tests/game_test.py:537 +#: squirrelbattle/game.py:203 squirrelbattle/tests/game_test.py:573 msgid "You do not have enough money" -msgstr "" +msgstr "Sie haben nicht genug Geld" -#: squirrelbattle/game.py:243 +#: squirrelbattle/game.py:247 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -68,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:251 +#: squirrelbattle/game.py:255 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -76,7 +78,7 @@ msgstr "" "Auf dieser Karte wurde kein Spieler gefunden!\n" "Vielleicht sind Sie gestorben?" -#: squirrelbattle/game.py:271 +#: squirrelbattle/game.py:275 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -103,8 +105,8 @@ msgstr "{name} stirbt." msgid "Back" msgstr "Zurück" -#: squirrelbattle/tests/game_test.py:314 squirrelbattle/tests/game_test.py:317 -#: squirrelbattle/tests/game_test.py:320 +#: 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/translations_test.py:16 msgid "New game" msgstr "Neu Spiel" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index acbfb5a..3f67e3a 100644 --- a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po @@ -1,49 +1,67 @@ -# Spanish translation of Squirrel Battle -# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao # This file is distributed under the same license as the squirrelbattle package. -# Translation by ifugaao +# FIRST AUTHOR , YEAR. # +#, fuzzy msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-12-05 14:46+0100\n" +"POT-Creation-Date: 2020-12-12 17:07+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: ifugao\n" +"Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -# Suggested in Weblate: == INVENTORIO == -#: squirrelbattle/display/menudisplay.py:105 -msgid "== INVENTORY ==" -msgstr "== INVENTORIO ==" +#: squirrelbattle/display/menudisplay.py:139 +msgid "INVENTORY" +msgstr "INVENTORIO" -# Suggested in Weblate: Inventorio : -#: squirrelbattle/display/statsdisplay.py:34 +#: squirrelbattle/display/menudisplay.py:164 +msgid "STALL" +msgstr "PUESTO" + +#: squirrelbattle/display/statsdisplay.py:33 msgid "Inventory:" msgstr "Inventorio :" -# Suggested in Weblate: ERES MUERTO -#: squirrelbattle/display/statsdisplay.py:50 +#: squirrelbattle/display/statsdisplay.py:52 msgid "YOU ARE DEAD" msgstr "ERES MUERTO" +#: squirrelbattle/entities/friendly.py:33 +msgid "I don't sell any squirrel" +msgstr "No vendo ninguna ardilla" + +#: squirrelbattle/entities/friendly.py:46 +msgid "Flower power!!" +msgstr "Poder de las flores!!" + +#: squirrelbattle/entities/friendly.py:46 +msgid "The sun is warm today" +msgstr "El sol está caliente hoy" + #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. -#: squirrelbattle/entities/items.py:128 +#: squirrelbattle/entities/items.py:151 msgid "Bomb is exploding." msgstr "La bomba está explotando." -#: squirrelbattle/entities/items.py:172 +#: squirrelbattle/entities/items.py:248 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} intercambió su cuerpo con {entity}." -#: squirrelbattle/game.py:177 +#: squirrelbattle/game.py:203 squirrelbattle/tests/game_test.py:573 +msgid "You do not have enough money" +msgstr "No tienes suficiente dinero" + +#: squirrelbattle/game.py:247 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -51,7 +69,7 @@ msgstr "" "Algunas claves faltan en su archivo de guarda.\n" "Su guarda parece a ser corruptido. Fue eliminado." -#: squirrelbattle/game.py:185 +#: squirrelbattle/game.py:255 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -59,7 +77,7 @@ msgstr "" "No jugador encontrado sobre la carta !\n" "¿ Quizas murió ?" -#: squirrelbattle/game.py:205 +#: squirrelbattle/game.py:275 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -67,28 +85,27 @@ msgstr "" "El JSON archivo no es correcto.\n" "Su guarda parece corrupta. Fue eliminada." -#: squirrelbattle/interfaces.py:400 +#: squirrelbattle/interfaces.py:429 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} golpea a {opponent}." -#: squirrelbattle/interfaces.py:412 +#: squirrelbattle/interfaces.py:441 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} recibe {amount} daño." -#: squirrelbattle/interfaces.py:414 +#: squirrelbattle/interfaces.py:443 #, python-brace-format msgid "{name} dies." msgstr "{name} se muere." -#: squirrelbattle/menus.py:72 +#: squirrelbattle/menus.py:73 msgid "Back" msgstr "Volver" -#: squirrelbattle/tests/game_test.py:300, -#: squirrelbattle/tests/game_test.py:303, -#: squirrelbattle/tests/game_test.py:306, +#: 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/translations_test.py:16 msgid "New game" msgstr "Nuevo partido" @@ -166,41 +183,49 @@ msgid "Key used to drop an item in the inventory" msgstr "Tecla para dejar un objeto del inventorio" #: squirrelbattle/tests/translations_test.py:53 +msgid "Key used to talk to a friendly entity" +msgstr "Tecla para hablar con una entidad amiga" + +#: squirrelbattle/tests/translations_test.py:55 msgid "Texture pack" msgstr "Paquete de texturas" -#: squirrelbattle/tests/translations_test.py:54 +#: squirrelbattle/tests/translations_test.py:56 msgid "Language" msgstr "Languaje" -#: squirrelbattle/tests/translations_test.py:57 +#: squirrelbattle/tests/translations_test.py:59 msgid "player" msgstr "jugador" -#: squirrelbattle/tests/translations_test.py:59 +#: squirrelbattle/tests/translations_test.py:61 msgid "tiger" msgstr "tigre" -#: squirrelbattle/tests/translations_test.py:60 +#: squirrelbattle/tests/translations_test.py:62 msgid "hedgehog" msgstr "erizo" -#: squirrelbattle/tests/translations_test.py:61 +#: squirrelbattle/tests/translations_test.py:63 msgid "rabbit" msgstr "conejo" -#: squirrelbattle/tests/translations_test.py:62 +#: squirrelbattle/tests/translations_test.py:64 msgid "teddy bear" msgstr "osito de peluche" -#: squirrelbattle/tests/translations_test.py:64 +#: squirrelbattle/tests/translations_test.py:66 msgid "body snatch potion" msgstr "poción de intercambio" -#: squirrelbattle/tests/translations_test.py:65 +#: squirrelbattle/tests/translations_test.py:67 msgid "bomb" msgstr "bomba" -#: squirrelbattle/tests/translations_test.py:66 +#: squirrelbattle/tests/translations_test.py:68 msgid "heart" msgstr "corazón" + +#: squirrelbattle/tests/translations_test.py:69 +msgid "sword" +msgstr "espada" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 9c9f5b0..0669940 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -1,34 +1,35 @@ -# French translation of Squirrel Battle -# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao # This file is distributed under the same license as the squirrelbattle package. +# FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-12-11 18:06+0100\n" +"POT-Creation-Date: 2020-12-12 17:07+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" -"Language: fr\n" +"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/display/menudisplay.py:113 -msgid "== INVENTORY ==" -msgstr "== INVENTAIRE ==" +#: squirrelbattle/display/menudisplay.py:139 +msgid "INVENTORY" +msgstr "INVENTAIRE" -#: squirrelbattle/display/menudisplay.py:134 -msgid "== STALL ==" -msgstr "== STAND ==" +#: squirrelbattle/display/menudisplay.py:164 +msgid "STALL" +msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:34 +#: squirrelbattle/display/statsdisplay.py:33 msgid "Inventory:" msgstr "Inventaire :" -#: squirrelbattle/display/statsdisplay.py:53 +#: squirrelbattle/display/statsdisplay.py:52 msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" @@ -52,16 +53,16 @@ msgstr "Le soleil est chaud aujourd'hui" msgid "Bomb is exploding." msgstr "La bombe explose." -#: squirrelbattle/entities/items.py:224 +#: squirrelbattle/entities/items.py:248 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} a échangé son corps avec {entity}." -#: squirrelbattle/game.py:199 squirrelbattle/tests/game_test.py:537 +#: squirrelbattle/game.py:203 squirrelbattle/tests/game_test.py:573 msgid "You do not have enough money" -msgstr "" +msgstr "Vous n'avez pas assez d'argent" -#: squirrelbattle/game.py:243 +#: squirrelbattle/game.py:247 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -69,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:251 +#: squirrelbattle/game.py:255 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -77,7 +78,7 @@ msgstr "" "Aucun joueur n'a été trouvé sur la carte !\n" "Peut-être êtes-vous mort ?" -#: squirrelbattle/game.py:271 +#: squirrelbattle/game.py:275 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -104,8 +105,8 @@ msgstr "{name} meurt." msgid "Back" msgstr "Retour" -#: squirrelbattle/tests/game_test.py:314 squirrelbattle/tests/game_test.py:317 -#: squirrelbattle/tests/game_test.py:320 +#: 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/translations_test.py:16 msgid "New game" msgstr "Nouvelle partie" From 288a6ac2c9573d33e89270b8ade20bc80eaa2988 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 12 Dec 2020 17:39:12 +0100 Subject: [PATCH 07/95] Some translations were missing concerning sunflowers, fixes #44 --- squirrelbattle/display/menudisplay.py | 4 +- squirrelbattle/entities/friendly.py | 6 ++- squirrelbattle/interfaces.py | 6 +-- .../locale/de/LC_MESSAGES/squirrelbattle.po | 37 +++++++++++++----- .../locale/es/LC_MESSAGES/squirrelbattle.po | 37 +++++++++++++----- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 39 +++++++++++++------ squirrelbattle/tests/game_test.py | 6 +-- squirrelbattle/tests/translations_test.py | 5 ++- 8 files changed, 98 insertions(+), 42 deletions(-) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 135abda..a00d0fe 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -155,7 +155,7 @@ class PlayerInventoryDisplay(MenuDisplay): """ We can select a menu item with the mouse. """ - self.menu.position = max(0, min(len(self.menu.values) - 1, y - 3)) + self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2)) game.handle_key_pressed(KeyValues.ENTER) @@ -181,5 +181,5 @@ class StoreInventoryDisplay(MenuDisplay): """ We can select a menu item with the mouse. """ - self.menu.position = max(0, min(len(self.menu.values) - 1, y - 3)) + self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2)) game.handle_key_pressed(KeyValues.ENTER) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 3e965d5..d5ca628 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -43,8 +43,10 @@ class Sunflower(FriendlyEntity): """ A friendly sunflower """ - dialogue_option = [_("Flower power!!"), _("The sun is warm today")] - def __init__(self, maxhealth: int = 15, *args, **kwargs) -> None: super().__init__(name="sunflower", maxhealth=maxhealth, *args, **kwargs) + + @property + def dialogue_option(self): + return [_("Flower power!!"), _("The sun is warm today")] diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index c035a38..94025bd 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -474,9 +474,9 @@ class FriendlyEntity(FightingEntity): dialogue_option: list def talk_to(self, player: Any) -> str: - a = randint(0, len(self.dialogue_option) - 1) - return "The " + self.translated_name \ - + " said : " + self.dialogue_option[a] + return _("{entity} said: {message}").format( + entity=self.translated_name.capitalize(), + message=choice(self.dialogue_option)) def keys(self) -> list: """ diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 9405951..8bcd104 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 17:07+0100\n" +"POT-Creation-Date: 2020-12-12 17:24+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -38,11 +38,11 @@ msgstr "SIE WURDEN GESTORBEN" msgid "I don't sell any squirrel" msgstr "Ich verkaufe keinen Eichhörnchen." -#: squirrelbattle/entities/friendly.py:46 +#: squirrelbattle/entities/friendly.py:52 msgid "Flower power!!" msgstr "Blumenmacht!!" -#: squirrelbattle/entities/friendly.py:46 +#: squirrelbattle/entities/friendly.py:52 msgid "The sun is warm today" msgstr "Die Sonne ist warm heute" @@ -101,6 +101,11 @@ msgstr "{name} nimmt {amount} Schadenspunkte." msgid "{name} dies." msgstr "{name} stirbt." +#: squirrelbattle/interfaces.py:477 +#, python-brace-format +msgid "{entity} said: {message}" +msgstr "{entity} hat gesagt: {message}" + #: squirrelbattle/menus.py:73 msgid "Back" msgstr "Zurück" @@ -200,33 +205,45 @@ msgid "player" msgstr "Spieler" #: squirrelbattle/tests/translations_test.py:61 -msgid "tiger" -msgstr "Tiger" - -#: squirrelbattle/tests/translations_test.py:62 msgid "hedgehog" msgstr "Igel" +#: squirrelbattle/tests/translations_test.py:62 +msgid "merchant" +msgstr "Kaufmann" + #: squirrelbattle/tests/translations_test.py:63 msgid "rabbit" msgstr "Kanninchen" #: squirrelbattle/tests/translations_test.py:64 +msgid "sunflower" +msgstr "Sonnenblume" + +#: squirrelbattle/tests/translations_test.py:65 msgid "teddy bear" msgstr "Teddybär" #: squirrelbattle/tests/translations_test.py:66 +msgid "tiger" +msgstr "Tiger" + +#: squirrelbattle/tests/translations_test.py:68 msgid "body snatch potion" msgstr "Leichenfleddererzaubertrank" -#: squirrelbattle/tests/translations_test.py:67 +#: squirrelbattle/tests/translations_test.py:69 msgid "bomb" msgstr "Bombe" -#: squirrelbattle/tests/translations_test.py:68 +#: squirrelbattle/tests/translations_test.py:70 +msgid "explosion" +msgstr "Explosion" + +#: squirrelbattle/tests/translations_test.py:71 msgid "heart" msgstr "Herz" -#: squirrelbattle/tests/translations_test.py:69 +#: squirrelbattle/tests/translations_test.py:72 msgid "sword" msgstr "schwert" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index 3f67e3a..fc0239e 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 17:07+0100\n" +"POT-Creation-Date: 2020-12-12 17:24+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -37,11 +37,11 @@ msgstr "ERES MUERTO" msgid "I don't sell any squirrel" msgstr "No vendo ninguna ardilla" -#: squirrelbattle/entities/friendly.py:46 +#: squirrelbattle/entities/friendly.py:52 msgid "Flower power!!" msgstr "Poder de las flores!!" -#: squirrelbattle/entities/friendly.py:46 +#: squirrelbattle/entities/friendly.py:52 msgid "The sun is warm today" msgstr "El sol está caliente hoy" @@ -100,6 +100,11 @@ msgstr "{name} recibe {amount} daño." msgid "{name} dies." msgstr "{name} se muere." +#: squirrelbattle/interfaces.py:477 +#, python-brace-format +msgid "{entity} said: {message}" +msgstr "{entity} dijo : {message}" + #: squirrelbattle/menus.py:73 msgid "Back" msgstr "Volver" @@ -199,33 +204,45 @@ msgid "player" msgstr "jugador" #: squirrelbattle/tests/translations_test.py:61 -msgid "tiger" -msgstr "tigre" - -#: squirrelbattle/tests/translations_test.py:62 msgid "hedgehog" msgstr "erizo" +#: squirrelbattle/tests/translations_test.py:62 +msgid "merchant" +msgstr "comerciante" + #: squirrelbattle/tests/translations_test.py:63 msgid "rabbit" msgstr "conejo" #: squirrelbattle/tests/translations_test.py:64 +msgid "sunflower" +msgstr "girasol" + +#: squirrelbattle/tests/translations_test.py:65 msgid "teddy bear" msgstr "osito de peluche" #: squirrelbattle/tests/translations_test.py:66 +msgid "tiger" +msgstr "tigre" + +#: squirrelbattle/tests/translations_test.py:68 msgid "body snatch potion" msgstr "poción de intercambio" -#: squirrelbattle/tests/translations_test.py:67 +#: squirrelbattle/tests/translations_test.py:69 msgid "bomb" msgstr "bomba" -#: squirrelbattle/tests/translations_test.py:68 +#: squirrelbattle/tests/translations_test.py:70 +msgid "explosion" +msgstr "explosión" + +#: squirrelbattle/tests/translations_test.py:71 msgid "heart" msgstr "corazón" -#: squirrelbattle/tests/translations_test.py:69 +#: squirrelbattle/tests/translations_test.py:72 msgid "sword" msgstr "espada" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 0669940..e0282e2 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 17:07+0100\n" +"POT-Creation-Date: 2020-12-12 17:24+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -38,11 +38,11 @@ msgstr "VOUS ÊTES MORT" msgid "I don't sell any squirrel" msgstr "Je ne vends pas d'écureuil" -#: squirrelbattle/entities/friendly.py:46 +#: squirrelbattle/entities/friendly.py:52 msgid "Flower power!!" -msgstr "Pouvoir des fleurs !" +msgstr "Pouvoir des fleurs !!" -#: squirrelbattle/entities/friendly.py:46 +#: squirrelbattle/entities/friendly.py:52 msgid "The sun is warm today" msgstr "Le soleil est chaud aujourd'hui" @@ -101,6 +101,11 @@ msgstr "{name} prend {amount} points de dégât." msgid "{name} dies." msgstr "{name} meurt." +#: squirrelbattle/interfaces.py:477 +#, python-brace-format +msgid "{entity} said: {message}" +msgstr "{entity} a dit : {message}" + #: squirrelbattle/menus.py:73 msgid "Back" msgstr "Retour" @@ -200,33 +205,45 @@ msgid "player" msgstr "joueur" #: squirrelbattle/tests/translations_test.py:61 -msgid "tiger" -msgstr "tigre" - -#: squirrelbattle/tests/translations_test.py:62 msgid "hedgehog" msgstr "hérisson" +#: squirrelbattle/tests/translations_test.py:62 +msgid "merchant" +msgstr "marchand" + #: squirrelbattle/tests/translations_test.py:63 msgid "rabbit" msgstr "lapin" #: squirrelbattle/tests/translations_test.py:64 +msgid "sunflower" +msgstr "tournesol" + +#: squirrelbattle/tests/translations_test.py:65 msgid "teddy bear" msgstr "nounours" #: squirrelbattle/tests/translations_test.py:66 +msgid "tiger" +msgstr "tigre" + +#: squirrelbattle/tests/translations_test.py:68 msgid "body snatch potion" msgstr "potion d'arrachage de corps" -#: squirrelbattle/tests/translations_test.py:67 +#: squirrelbattle/tests/translations_test.py:69 msgid "bomb" msgstr "bombe" -#: squirrelbattle/tests/translations_test.py:68 +#: squirrelbattle/tests/translations_test.py:70 +msgid "explosion" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:71 msgid "heart" msgstr "cœur" -#: squirrelbattle/tests/translations_test.py:69 +#: squirrelbattle/tests/translations_test.py:72 msgid "sword" msgstr "épée" diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 27c8ca8..e355086 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -496,8 +496,8 @@ class TestGame(unittest.TestCase): self.assertEqual(self.game.state, GameMode.PLAY) self.assertTrue(self.game.logs.messages) # Ensure that the message is a good message - self.assertIn(self.game.logs.messages[1][21:], - Sunflower.dialogue_option) + self.assertIn(self.game.logs.messages[1][16:], + Sunflower().dialogue_option) # Test all directions to detect the friendly entity self.game.player.move(3, 6) @@ -547,7 +547,7 @@ class TestGame(unittest.TestCase): # Buy the second item by clicking on it item = self.game.store_menu.validate() self.assertIn(item, merchant.inventory) - self.game.display_actions(DisplayActions.MOUSE, 8, 25) + self.game.display_actions(DisplayActions.MOUSE, 7, 25) self.game.handle_key_pressed(KeyValues.ENTER) self.assertIn(item, self.game.player.inventory) self.assertNotIn(item, merchant.inventory) diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index 8176fd4..4b8444d 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -58,12 +58,15 @@ class TestTranslations(unittest.TestCase): def test_entities_translation(self) -> None: self.assertEqual(_("player"), "joueur") - self.assertEqual(_("tiger"), "tigre") self.assertEqual(_("hedgehog"), "hérisson") + self.assertEqual(_("merchant"), "marchand") self.assertEqual(_("rabbit"), "lapin") + self.assertEqual(_("sunflower"), "tournesol") self.assertEqual(_("teddy bear"), "nounours") + self.assertEqual(_("tiger"), "tigre") self.assertEqual(_("body snatch potion"), "potion d'arrachage de corps") self.assertEqual(_("bomb"), "bombe") + self.assertEqual(_("explosion"), "explosion") self.assertEqual(_("heart"), "cœur") self.assertEqual(_("sword"), "épée") From 7971a1f70edb6dcd41fc8eda619ab786b68dc5c5 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 12 Dec 2020 18:12:37 +0100 Subject: [PATCH 08/95] Add waiting key, fixes #16 --- squirrelbattle/entities/friendly.py | 2 +- squirrelbattle/enums.py | 3 ++ squirrelbattle/game.py | 2 + .../locale/de/LC_MESSAGES/squirrelbattle.po | 40 ++++++++++--------- .../locale/es/LC_MESSAGES/squirrelbattle.po | 40 ++++++++++--------- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 40 ++++++++++--------- squirrelbattle/settings.py | 1 + squirrelbattle/tests/game_test.py | 23 +++++++---- squirrelbattle/tests/translations_test.py | 1 + 9 files changed, 90 insertions(+), 62 deletions(-) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index d5ca628..6c99090 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -48,5 +48,5 @@ class Sunflower(FriendlyEntity): super().__init__(name="sunflower", maxhealth=maxhealth, *args, **kwargs) @property - def dialogue_option(self): + def dialogue_option(self) -> list: return [_("Flower power!!"), _("The sun is warm today")] diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index c04011b..7e4efa4 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -46,6 +46,7 @@ class KeyValues(Enum): DROP = auto() SPACE = auto() CHAT = auto() + WAIT = auto() @staticmethod def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]: @@ -78,4 +79,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 diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 558f25a..ed3b60f 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -128,6 +128,8 @@ class Game: elif key == KeyValues.CHAT: # Wait for the direction of the friendly entity self.waiting_for_friendly_key = True + elif key == KeyValues.WAIT: + self.map.tick() 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 8bcd104..39cfeec 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 17:24+0100\n" +"POT-Creation-Date: 2020-12-12 18:02+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -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:203 squirrelbattle/tests/game_test.py:573 +#: squirrelbattle/game.py:205 squirrelbattle/tests/game_test.py:573 msgid "You do not have enough money" msgstr "Sie haben nicht genug Geld" -#: squirrelbattle/game.py:247 +#: squirrelbattle/game.py:249 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:255 +#: squirrelbattle/game.py:257 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:275 +#: squirrelbattle/game.py:277 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -193,57 +193,61 @@ msgid "Key used to talk to a friendly entity" msgstr "Taste um mit einer friedlicher Entität zu sprechen" #: squirrelbattle/tests/translations_test.py:55 +msgid "Key used to wait" +msgstr "Wartentaste" + +#: squirrelbattle/tests/translations_test.py:56 msgid "Texture pack" msgstr "Textur-Packung" -#: squirrelbattle/tests/translations_test.py:56 +#: squirrelbattle/tests/translations_test.py:57 msgid "Language" msgstr "Sprache" -#: squirrelbattle/tests/translations_test.py:59 +#: squirrelbattle/tests/translations_test.py:60 msgid "player" msgstr "Spieler" -#: squirrelbattle/tests/translations_test.py:61 +#: squirrelbattle/tests/translations_test.py:62 msgid "hedgehog" msgstr "Igel" -#: squirrelbattle/tests/translations_test.py:62 +#: squirrelbattle/tests/translations_test.py:63 msgid "merchant" msgstr "Kaufmann" -#: squirrelbattle/tests/translations_test.py:63 +#: squirrelbattle/tests/translations_test.py:64 msgid "rabbit" msgstr "Kanninchen" -#: squirrelbattle/tests/translations_test.py:64 +#: squirrelbattle/tests/translations_test.py:65 msgid "sunflower" msgstr "Sonnenblume" -#: squirrelbattle/tests/translations_test.py:65 +#: squirrelbattle/tests/translations_test.py:66 msgid "teddy bear" msgstr "Teddybär" -#: squirrelbattle/tests/translations_test.py:66 +#: squirrelbattle/tests/translations_test.py:67 msgid "tiger" msgstr "Tiger" -#: squirrelbattle/tests/translations_test.py:68 +#: squirrelbattle/tests/translations_test.py:69 msgid "body snatch potion" msgstr "Leichenfleddererzaubertrank" -#: squirrelbattle/tests/translations_test.py:69 +#: squirrelbattle/tests/translations_test.py:70 msgid "bomb" msgstr "Bombe" -#: squirrelbattle/tests/translations_test.py:70 +#: squirrelbattle/tests/translations_test.py:71 msgid "explosion" msgstr "Explosion" -#: squirrelbattle/tests/translations_test.py:71 +#: squirrelbattle/tests/translations_test.py:72 msgid "heart" msgstr "Herz" -#: squirrelbattle/tests/translations_test.py:72 +#: squirrelbattle/tests/translations_test.py:73 msgid "sword" msgstr "schwert" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index fc0239e..f4d1c3c 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 17:24+0100\n" +"POT-Creation-Date: 2020-12-12 18:02+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -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:203 squirrelbattle/tests/game_test.py:573 +#: squirrelbattle/game.py:205 squirrelbattle/tests/game_test.py:573 msgid "You do not have enough money" msgstr "No tienes suficiente dinero" -#: squirrelbattle/game.py:247 +#: squirrelbattle/game.py:249 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:255 +#: squirrelbattle/game.py:257 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:275 +#: squirrelbattle/game.py:277 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -192,57 +192,61 @@ msgid "Key used to talk to a friendly entity" msgstr "Tecla para hablar con una entidad amiga" #: squirrelbattle/tests/translations_test.py:55 +msgid "Key used to wait" +msgstr "Tecla para espera" + +#: squirrelbattle/tests/translations_test.py:56 msgid "Texture pack" msgstr "Paquete de texturas" -#: squirrelbattle/tests/translations_test.py:56 +#: squirrelbattle/tests/translations_test.py:57 msgid "Language" msgstr "Languaje" -#: squirrelbattle/tests/translations_test.py:59 +#: squirrelbattle/tests/translations_test.py:60 msgid "player" msgstr "jugador" -#: squirrelbattle/tests/translations_test.py:61 +#: squirrelbattle/tests/translations_test.py:62 msgid "hedgehog" msgstr "erizo" -#: squirrelbattle/tests/translations_test.py:62 +#: squirrelbattle/tests/translations_test.py:63 msgid "merchant" msgstr "comerciante" -#: squirrelbattle/tests/translations_test.py:63 +#: squirrelbattle/tests/translations_test.py:64 msgid "rabbit" msgstr "conejo" -#: squirrelbattle/tests/translations_test.py:64 +#: squirrelbattle/tests/translations_test.py:65 msgid "sunflower" msgstr "girasol" -#: squirrelbattle/tests/translations_test.py:65 +#: squirrelbattle/tests/translations_test.py:66 msgid "teddy bear" msgstr "osito de peluche" -#: squirrelbattle/tests/translations_test.py:66 +#: squirrelbattle/tests/translations_test.py:67 msgid "tiger" msgstr "tigre" -#: squirrelbattle/tests/translations_test.py:68 +#: squirrelbattle/tests/translations_test.py:69 msgid "body snatch potion" msgstr "poción de intercambio" -#: squirrelbattle/tests/translations_test.py:69 +#: squirrelbattle/tests/translations_test.py:70 msgid "bomb" msgstr "bomba" -#: squirrelbattle/tests/translations_test.py:70 +#: squirrelbattle/tests/translations_test.py:71 msgid "explosion" msgstr "explosión" -#: squirrelbattle/tests/translations_test.py:71 +#: squirrelbattle/tests/translations_test.py:72 msgid "heart" msgstr "corazón" -#: squirrelbattle/tests/translations_test.py:72 +#: squirrelbattle/tests/translations_test.py:73 msgid "sword" msgstr "espada" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index e0282e2..8b1c4db 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 17:24+0100\n" +"POT-Creation-Date: 2020-12-12 18:02+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -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:203 squirrelbattle/tests/game_test.py:573 +#: squirrelbattle/game.py:205 squirrelbattle/tests/game_test.py:573 msgid "You do not have enough money" msgstr "Vous n'avez pas assez d'argent" -#: squirrelbattle/game.py:247 +#: squirrelbattle/game.py:249 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:255 +#: squirrelbattle/game.py:257 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:275 +#: squirrelbattle/game.py:277 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -193,57 +193,61 @@ msgid "Key used to talk to a friendly entity" msgstr "Touche pour parler à une entité pacifique" #: squirrelbattle/tests/translations_test.py:55 +msgid "Key used to wait" +msgstr "Touche pour attendre" + +#: squirrelbattle/tests/translations_test.py:56 msgid "Texture pack" msgstr "Pack de textures" -#: squirrelbattle/tests/translations_test.py:56 +#: squirrelbattle/tests/translations_test.py:57 msgid "Language" msgstr "Langue" -#: squirrelbattle/tests/translations_test.py:59 +#: squirrelbattle/tests/translations_test.py:60 msgid "player" msgstr "joueur" -#: squirrelbattle/tests/translations_test.py:61 +#: squirrelbattle/tests/translations_test.py:62 msgid "hedgehog" msgstr "hérisson" -#: squirrelbattle/tests/translations_test.py:62 +#: squirrelbattle/tests/translations_test.py:63 msgid "merchant" msgstr "marchand" -#: squirrelbattle/tests/translations_test.py:63 +#: squirrelbattle/tests/translations_test.py:64 msgid "rabbit" msgstr "lapin" -#: squirrelbattle/tests/translations_test.py:64 +#: squirrelbattle/tests/translations_test.py:65 msgid "sunflower" msgstr "tournesol" -#: squirrelbattle/tests/translations_test.py:65 +#: squirrelbattle/tests/translations_test.py:66 msgid "teddy bear" msgstr "nounours" -#: squirrelbattle/tests/translations_test.py:66 +#: squirrelbattle/tests/translations_test.py:67 msgid "tiger" msgstr "tigre" -#: squirrelbattle/tests/translations_test.py:68 +#: squirrelbattle/tests/translations_test.py:69 msgid "body snatch potion" msgstr "potion d'arrachage de corps" -#: squirrelbattle/tests/translations_test.py:69 +#: squirrelbattle/tests/translations_test.py:70 msgid "bomb" msgstr "bombe" -#: squirrelbattle/tests/translations_test.py:70 +#: squirrelbattle/tests/translations_test.py:71 msgid "explosion" msgstr "" -#: squirrelbattle/tests/translations_test.py:71 +#: squirrelbattle/tests/translations_test.py:72 msgid "heart" msgstr "cœur" -#: squirrelbattle/tests/translations_test.py:72 +#: squirrelbattle/tests/translations_test.py:73 msgid "sword" msgstr "épée" diff --git a/squirrelbattle/settings.py b/squirrelbattle/settings.py index 4e6040c..91edfa4 100644 --- a/squirrelbattle/settings.py +++ b/squirrelbattle/settings.py @@ -32,6 +32,7 @@ class Settings: self.KEY_EQUIP = ['e', 'Key used to equip an item in the inventory'] self.KEY_DROP = ['r', 'Key used to drop an item in the inventory'] self.KEY_CHAT = ['t', 'Key used to talk to a friendly entity'] + self.KEY_WAIT = ['w', 'Key used to wait'] self.TEXTURE_PACK = ['ascii', 'Texture pack'] self.LOCALE = [locale.getlocale()[0][:2], 'Language'] diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index e355086..52aeeaf 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -8,7 +8,7 @@ from ..bootstrap import Bootstrap from ..display.display import Display from ..display.display_manager import DisplayManager from ..entities.friendly import Merchant, Sunflower -from ..entities.items import Bomb, Heart, Sword +from ..entities.items import Bomb, Heart, Sword, Explosion from ..entities.player import Player from ..enums import DisplayActions from ..game import Game, KeyValues, GameMode @@ -140,6 +140,9 @@ class TestGame(unittest.TestCase): self.assertEqual(KeyValues.translate_key( self.game.settings.KEY_DROP, self.game.settings), KeyValues.DROP) + self.assertEqual(KeyValues.translate_key( + self.game.settings.KEY_WAIT, self.game.settings), + KeyValues.WAIT) self.assertEqual(KeyValues.translate_key(' ', self.game.settings), KeyValues.SPACE) self.assertEqual(KeyValues.translate_key('plop', self.game.settings), @@ -227,6 +230,12 @@ class TestGame(unittest.TestCase): self.assertEqual(new_y, y) self.assertEqual(new_x, x - 1) + explosion = Explosion() + self.game.map.add_entity(explosion) + self.assertIn(explosion, self.game.map.entities) + self.game.handle_key_pressed(KeyValues.WAIT) + self.assertNotIn(explosion, self.game.map.entities) + self.game.handle_key_pressed(KeyValues.SPACE) self.assertEqual(self.game.state, GameMode.MAINMENU) @@ -297,13 +306,13 @@ class TestGame(unittest.TestCase): self.game.handle_key_pressed(KeyValues.ENTER) self.assertEqual(self.game.state, GameMode.SETTINGS) - # Define the "move up" key to 'w' + # Define the "move up" key to 'h' self.assertFalse(self.game.settings_menu.waiting_for_key) self.game.handle_key_pressed(KeyValues.ENTER) self.assertTrue(self.game.settings_menu.waiting_for_key) - self.game.handle_key_pressed(None, 'w') + self.game.handle_key_pressed(None, 'h') self.assertFalse(self.game.settings_menu.waiting_for_key) - self.assertEqual(self.game.settings.KEY_UP_PRIMARY, 'w') + self.assertEqual(self.game.settings.KEY_UP_PRIMARY, 'h') # Navigate to "move left" self.game.handle_key_pressed(KeyValues.DOWN) @@ -324,7 +333,7 @@ class TestGame(unittest.TestCase): self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a') # Navigate to "texture pack" - for ignored in range(10): + for ignored in range(11): self.game.handle_key_pressed(KeyValues.DOWN) # Change texture pack @@ -496,8 +505,8 @@ class TestGame(unittest.TestCase): self.assertEqual(self.game.state, GameMode.PLAY) self.assertTrue(self.game.logs.messages) # Ensure that the message is a good message - self.assertIn(self.game.logs.messages[1][16:], - Sunflower().dialogue_option) + self.assertTrue(any(self.game.logs.messages[1].endswith(msg) + for msg in Sunflower().dialogue_option)) # Test all directions to detect the friendly entity self.game.player.move(3, 6) diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index 4b8444d..0bd8873 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -52,6 +52,7 @@ class TestTranslations(unittest.TestCase): "Touche pour jeter un objet de l'inventaire") self.assertEqual(_("Key used to talk to a friendly entity"), "Touche pour parler à une entité pacifique") + self.assertEqual(_("Key used to wait"), "Touche pour attendre") self.assertEqual(_("Texture pack"), "Pack de textures") self.assertEqual(_("Language"), "Langue") From 599f75b676894a1f98a9b1556d4600d721d225bf Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 12 Dec 2020 21:19:55 +0100 Subject: [PATCH 09/95] Erase pad before resizing, fixes #45 --- squirrelbattle/display/display.py | 1 + 1 file changed, 1 insertion(+) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index d1bea1b..29295de 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -132,6 +132,7 @@ class Display: self.height = height if hasattr(self, "pad") and resize_pad and \ self.height >= 0 and self.width >= 0: + self.pad.erase() self.pad.resize(self.height + 1, self.width + 1) def refresh(self, *args, resize_pad: bool = True) -> None: From 646e0063bea284109aeff6703e071118fa486eba Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 13 Dec 2020 21:29:25 +0100 Subject: [PATCH 10/95] 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 11/95] 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 a5890a341d87734f50c7542a598b6aef2e3dc2df Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 17 Dec 2020 23:46:20 +0100 Subject: [PATCH 12/95] Display inventory menu next to the merchant menu --- squirrelbattle/display/display_manager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index f9b3f01..4e2381a 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -104,7 +104,11 @@ class DisplayManager: self.storeinventorydisplay.refresh( self.rows // 10, self.cols // 2, 8 * self.rows // 10, 2 * self.cols // 5) + self.playerinventorydisplay.refresh( + self.rows // 10, self.cols // 10, + 8 * self.rows // 10, 2 * self.cols // 5) displays.append(self.storeinventorydisplay) + displays.append(self.playerinventorydisplay) elif self.game.state == GameMode.MAINMENU: self.mainmenudisplay.refresh(0, 0, self.rows, self.cols) displays.append(self.mainmenudisplay) From 9a556ba669d24027c2ebd381efbbbfd3e3370388 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 01:05:50 +0100 Subject: [PATCH 13/95] We can now buy items to merchants, closes #47 --- squirrelbattle/game.py | 28 +++++++++++++------ .../locale/de/LC_MESSAGES/squirrelbattle.po | 4 +-- .../locale/es/LC_MESSAGES/squirrelbattle.po | 4 +-- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 4 +-- squirrelbattle/tests/game_test.py | 3 +- 5 files changed, 27 insertions(+), 16 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index ed3b60f..5fc130b 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -34,6 +34,7 @@ class Game: """ self.state = GameMode.MAINMENU self.waiting_for_friendly_key = False + self.is_in_store_menu = True self.settings = Settings() self.settings.load_settings() self.settings.write_settings() @@ -191,22 +192,31 @@ class Game: """ In a store menu, we can buy items or close the menu. """ - if key == KeyValues.SPACE: + menu = self.store_menu if self.is_in_store_menu else self.inventory_menu + + if key == KeyValues.SPACE or key == KeyValues.INVENTORY: self.state = GameMode.PLAY elif key == KeyValues.UP: - self.store_menu.go_up() + menu.go_up() elif key == KeyValues.DOWN: - self.store_menu.go_down() - if self.store_menu.values and not self.player.dead: + menu.go_down() + elif key == KeyValues.LEFT: + self.is_in_store_menu = False + elif key == KeyValues.RIGHT: + self.is_in_store_menu = True + if menu.values and not self.player.dead: if key == KeyValues.ENTER: - item = self.store_menu.validate() - flag = item.be_sold(self.player, self.store_menu.merchant) + item = menu.validate() + owner = self.store_menu.merchant if self.is_in_store_menu \ + else self.player + buyer = self.player if self.is_in_store_menu \ + else self.store_menu.merchant + flag = item.be_sold(buyer, owner) if not flag: - self.message = _("You do not have enough money") + self.message = _("The buyer does not have enough money") self.display_actions(DisplayActions.UPDATE) # Ensure that the cursor has a good position - self.store_menu.position = min(self.store_menu.position, - len(self.store_menu.values) - 1) + menu.position = min(menu.position, len(menu.values) - 1) def handle_key_pressed_main_menu(self, key: KeyValues) -> None: """ diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 39cfeec..29bf10e 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -59,8 +59,8 @@ 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 -msgid "You do not have enough money" -msgstr "Sie haben nicht genug Geld" +msgid "The buyer does not have enough money" +msgstr "Der Kaufer hat nicht genug Geld" #: squirrelbattle/game.py:249 msgid "" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index f4d1c3c..8a7bc3e 100644 --- a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po @@ -58,8 +58,8 @@ msgid "{player} exchanged its body with {entity}." msgstr "{player} intercambió su cuerpo con {entity}." #: squirrelbattle/game.py:205 squirrelbattle/tests/game_test.py:573 -msgid "You do not have enough money" -msgstr "No tienes suficiente dinero" +msgid "The buyer does not have enough money" +msgstr "El comprador no tiene suficiente dinero" #: squirrelbattle/game.py:249 msgid "" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 8b1c4db..ffbfdce 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -59,8 +59,8 @@ 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 -msgid "You do not have enough money" -msgstr "Vous n'avez pas assez d'argent" +msgid "The buyer does not have enough money" +msgstr "L'acheteur n'a pas assez d'argent" #: squirrelbattle/game.py:249 msgid "" diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 52aeeaf..c70ca8f 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -579,7 +579,8 @@ class TestGame(unittest.TestCase): self.game.handle_key_pressed(KeyValues.ENTER) self.assertNotIn(item, self.game.player.inventory) self.assertIn(item, merchant.inventory) - self.assertEqual(self.game.message, _("You do not have enough money")) + self.assertEqual(self.game.message, + _("The buyer does not have enough money")) self.game.handle_key_pressed(KeyValues.ENTER) # Exit the menu From c55a7451e78dca7b2e705500179e01d5d9abbc02 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 01:50:11 +0100 Subject: [PATCH 14/95] Display more precisely where we are in the store menu --- squirrelbattle/display/menudisplay.py | 16 ++++++++++++---- squirrelbattle/display/texturepack.py | 2 +- squirrelbattle/game.py | 8 +++++++- squirrelbattle/tests/game_test.py | 4 ++++ 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index a00d0fe..f9a5e09 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 _ @@ -139,9 +139,14 @@ class PlayerInventoryDisplay(MenuDisplay): self.menubox.update_title(_("INVENTORY")) for i, item in enumerate(self.menu.values): rep = self.pack[item.name.upper()] - selection = f"[{rep}]" if i == self.menu.position else f" {rep} " + selection = f"[{rep}]" if i == self.menu.position \ + and (Game.INSTANCE.state == GameMode.INVENTORY + or Game.INSTANCE.state == GameMode.STORE + and not Game.INSTANCE.is_in_store_menu) else f" {rep} " self.addstr(self.pad, i + 1, 0, selection - + " " + item.translated_name.capitalize()) + + " " + item.translated_name.capitalize() + + (": " + str(item.price) + " Hazels" + if Game.INSTANCE.state == GameMode.STORE else "")) @property def truewidth(self) -> int: @@ -156,6 +161,7 @@ class PlayerInventoryDisplay(MenuDisplay): We can select a menu item with the mouse. """ self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2)) + game.is_in_store_menu = False game.handle_key_pressed(KeyValues.ENTER) @@ -164,7 +170,8 @@ class StoreInventoryDisplay(MenuDisplay): self.menubox.update_title(_("STALL")) for i, item in enumerate(self.menu.values): rep = self.pack[item.name.upper()] - selection = f"[{rep}]" if i == self.menu.position else f" {rep} " + selection = f"[{rep}]" if i == self.menu.position \ + and Game.INSTANCE.is_in_store_menu else f" {rep} " self.addstr(self.pad, i + 1, 0, selection + " " + item.translated_name.capitalize() + ": " + str(item.price) + " Hazels") @@ -182,4 +189,5 @@ class StoreInventoryDisplay(MenuDisplay): We can select a menu item with the mouse. """ self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2)) + game.is_in_store_menu = True game.handle_key_pressed(KeyValues.ENTER) diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index f72cd97..1f6dc76 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -97,7 +97,7 @@ TexturePack.SQUIRREL_PACK = TexturePack( MERCHANT='🦜', RABBIT='🐇', SUNFLOWER='🌻', - SWORD='🗡️', + SWORD='🗡️ ', TEDDY_BEAR='🧸', TIGER='🐅', WALL='🧱', diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 5fc130b..2204508 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -22,6 +22,9 @@ class Game: """ The game object controls all actions in the game. """ + # Global instance of the game + INSTANCE: "Game" + map: Map player: Player screen: Any @@ -32,6 +35,8 @@ class Game: """ Init the game. """ + Game.INSTANCE = self + self.state = GameMode.MAINMENU self.waiting_for_friendly_key = False self.is_in_store_menu = True @@ -52,7 +57,7 @@ class Game: Create a new game on the screen. """ # TODO generate a new map procedurally - self.map = Map.load(ResourceManager.get_asset_path("example_map.txt")) + self.map = Map.load(ResourceManager.get_asset_path("example_map_2.txt")) self.map.logs = self.logs self.logs.clear() self.player = Player() @@ -163,6 +168,7 @@ class Game: self.logs.add_message(msg) if entity.is_merchant(): self.state = GameMode.STORE + self.is_in_store_menu = True self.store_menu.update_merchant(entity) def handle_key_pressed_inventory(self, key: KeyValues) -> None: diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index c70ca8f..2b604be 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -546,6 +546,10 @@ class TestGame(unittest.TestCase): # Navigate in the menu self.game.handle_key_pressed(KeyValues.DOWN) self.game.handle_key_pressed(KeyValues.DOWN) + self.game.handle_key_pressed(KeyValues.LEFT) + self.assertFalse(self.game.is_in_store_menu) + self.game.handle_key_pressed(KeyValues.RIGHT) + self.assertTrue(self.game.is_in_store_menu) self.game.handle_key_pressed(KeyValues.UP) self.assertEqual(self.game.store_menu.position, 1) From b8d32b29c8b3d500b696a2d77f123756e7a1d9e8 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 02:17:06 +0100 Subject: [PATCH 15/95] Test selling items --- squirrelbattle/display/display_manager.py | 1 - squirrelbattle/menus.py | 4 ++-- squirrelbattle/tests/game_test.py | 22 ++++++++++++++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 4e2381a..83df543 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -54,7 +54,6 @@ class DisplayManager: self.mapdisplay.update_map(self.game.map) self.statsdisplay.update_player(self.game.player) self.game.inventory_menu.update_player(self.game.player) - self.game.store_menu.update_merchant(self.game.player) self.playerinventorydisplay.update_menu(self.game.inventory_menu) self.storeinventorydisplay.update_menu(self.game.store_menu) self.settingsmenudisplay.update_menu(self.game.settings_menu) diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index e0087a3..458c7b4 100644 --- a/squirrelbattle/menus.py +++ b/squirrelbattle/menus.py @@ -132,11 +132,11 @@ class InventoryMenu(Menu): class StoreMenu(Menu): - merchant: Merchant + merchant: Merchant = None def update_merchant(self, merchant: Merchant) -> None: self.merchant = merchant @property def values(self) -> list: - return self.merchant.inventory + return self.merchant.inventory if self.merchant else [] diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 2b604be..89d6ed6 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -12,6 +12,7 @@ from ..entities.items import Bomb, Heart, Sword, Explosion from ..entities.player import Player from ..enums import DisplayActions from ..game import Game, KeyValues, GameMode +from ..interfaces import Map from ..menus import MainMenuValues from ..resources import ResourceManager from ..settings import Settings @@ -25,6 +26,10 @@ class TestGame(unittest.TestCase): """ self.game = Game() self.game.new_game() + self.game.map = Map.load( + ResourceManager.get_asset_path("example_map.txt")) + self.game.map.add_entity(self.game.player) + self.game.player.move(self.game.map.start_y, self.game.map.start_x) self.game.logs.add_message("Hello World !") display = DisplayManager(None, self.game) self.game.display_actions = display.handle_display_action @@ -556,12 +561,11 @@ class TestGame(unittest.TestCase): self.game.player.hazel = 0x7ffff42ff # The second item is not a heart - merchant.inventory[1] = Sword() + merchant.inventory[1] = sword = Sword() # Buy the second item by clicking on it item = self.game.store_menu.validate() self.assertIn(item, merchant.inventory) self.game.display_actions(DisplayActions.MOUSE, 7, 25) - self.game.handle_key_pressed(KeyValues.ENTER) self.assertIn(item, self.game.player.inventory) self.assertNotIn(item, merchant.inventory) @@ -587,6 +591,20 @@ class TestGame(unittest.TestCase): _("The buyer does not have enough money")) self.game.handle_key_pressed(KeyValues.ENTER) + # Sell an item + self.game.inventory_menu.position = len(self.game.player.inventory) - 1 + self.game.handle_key_pressed(KeyValues.LEFT) + self.assertFalse(self.game.is_in_store_menu) + self.assertIn(sword, self.game.player.inventory) + self.assertEqual(self.game.inventory_menu.validate(), sword) + old_player_money, old_merchant_money = self.game.player.hazel,\ + merchant.hazel + self.game.handle_key_pressed(KeyValues.ENTER) + self.assertNotIn(sword, self.game.player.inventory) + self.assertIn(sword, merchant.inventory) + self.assertEqual(self.game.player.hazel, old_player_money + sword.price) + self.assertEqual(merchant.hazel, old_merchant_money - sword.price) + # Exit the menu self.game.handle_key_pressed(KeyValues.SPACE) self.assertEqual(self.game.state, GameMode.PLAY) From 85870494a077190d49529d061f55b3aae5f3378b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 15:07:09 +0100 Subject: [PATCH 16/95] More generic display update --- squirrelbattle/display/display.py | 10 ++++++++++ squirrelbattle/display/display_manager.py | 13 +++++-------- squirrelbattle/display/logsdisplay.py | 5 +++-- squirrelbattle/display/mapdisplay.py | 8 +++++--- squirrelbattle/display/menudisplay.py | 17 ++++++++++++++++- squirrelbattle/display/messagedisplay.py | 5 +++-- squirrelbattle/display/statsdisplay.py | 5 +++-- squirrelbattle/tests/game_test.py | 1 + 8 files changed, 46 insertions(+), 18 deletions(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 29295de..023ad6e 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -162,6 +162,16 @@ class Display: pad.refresh(top_y, top_x, window_y, window_x, last_y, last_x) def display(self) -> None: + """ + Draw the content of the display and refresh pads. + """ + raise NotImplementedError + + def update(self, game: Game) -> None: + """ + The game state was updated. + Indicate what to do with the new state. + """ raise NotImplementedError def handle_click(self, y: int, x: int, game: Game) -> None: diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 83df543..3481348 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -49,16 +49,13 @@ class DisplayManager: self.handle_mouse_click(*params) def update_game_components(self) -> None: + """ + The game state was updated. + Trigger all displays of these modifications. + """ for d in self.displays: d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK) - self.mapdisplay.update_map(self.game.map) - self.statsdisplay.update_player(self.game.player) - self.game.inventory_menu.update_player(self.game.player) - self.playerinventorydisplay.update_menu(self.game.inventory_menu) - self.storeinventorydisplay.update_menu(self.game.store_menu) - self.settingsmenudisplay.update_menu(self.game.settings_menu) - self.logsdisplay.update_logs(self.game.logs) - self.messagedisplay.update_message(self.game.message) + d.update(self.game) def handle_mouse_click(self, y: int, x: int) -> None: displays = self.refresh() diff --git a/squirrelbattle/display/logsdisplay.py b/squirrelbattle/display/logsdisplay.py index b768a0e..b21273d 100644 --- a/squirrelbattle/display/logsdisplay.py +++ b/squirrelbattle/display/logsdisplay.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from squirrelbattle.display.display import Display +from squirrelbattle.game import Game from squirrelbattle.interfaces import Logs @@ -11,8 +12,8 @@ class LogsDisplay(Display): super().__init__(*args) self.pad = self.newpad(self.rows, self.cols) - def update_logs(self, logs: Logs) -> None: - self.logs = logs + def update(self, game: Game) -> None: + self.logs = game.logs def display(self) -> None: messages = self.logs.messages[-self.height:] diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index 54d9432..72e339c 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -3,6 +3,7 @@ from squirrelbattle.interfaces import Map from .display import Display +from ..game import Game class MapDisplay(Display): @@ -10,9 +11,10 @@ class MapDisplay(Display): def __init__(self, *args): super().__init__(*args) - def update_map(self, m: Map) -> None: - self.map = m - self.pad = self.newpad(m.height, self.pack.tile_width * m.width + 1) + def update(self, game: Game) -> None: + self.map = game.map + self.pad = self.newpad(self.map.height, + self.pack.tile_width * self.map.width + 1) def update_pad(self) -> None: self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index f9a5e09..ed10fec 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -5,7 +5,7 @@ import curses from random import randint from typing import List -from squirrelbattle.menus import Menu, MainMenu +from squirrelbattle.menus import Menu, MainMenu, SettingsMenu from .display import Box, Display from ..enums import KeyValues, GameMode from ..game import Game @@ -17,6 +17,7 @@ class MenuDisplay(Display): """ A class to display the menu objects """ + menu: Menu position: int def __init__(self, *args, **kwargs): @@ -80,6 +81,11 @@ class SettingsMenuDisplay(MenuDisplay): """ A class to display specifically a settingsmenu object """ + menu: SettingsMenu + + def update(self, game: Game) -> None: + self.update_menu(game.settings_menu) + @property def values(self) -> List[str]: return [_(a[1][1]) + (" : " @@ -122,6 +128,9 @@ class MainMenuDisplay(Display): menuy, menux, min(self.menudisplay.preferred_height, self.height - menuy), menuwidth) + def update(self, game: Game) -> None: + self.menudisplay.update_menu(game.main_menu) + def handle_click(self, y: int, x: int, game: Game) -> None: menuwidth = min(self.menudisplay.preferred_width, self.width) menuy, menux = len(self.title) + 8, self.width // 2 - menuwidth // 2 - 1 @@ -135,6 +144,9 @@ class MainMenuDisplay(Display): class PlayerInventoryDisplay(MenuDisplay): + def update(self, game: Game) -> None: + self.update_menu(game.inventory_menu) + def update_pad(self) -> None: self.menubox.update_title(_("INVENTORY")) for i, item in enumerate(self.menu.values): @@ -166,6 +178,9 @@ class PlayerInventoryDisplay(MenuDisplay): class StoreInventoryDisplay(MenuDisplay): + def update(self, game: Game) -> None: + self.update_menu(game.store_menu) + 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..271055a 100644 --- a/squirrelbattle/display/messagedisplay.py +++ b/squirrelbattle/display/messagedisplay.py @@ -3,6 +3,7 @@ import curses from squirrelbattle.display.display import Box, Display +from squirrelbattle.game import Game class MessageDisplay(Display): @@ -17,8 +18,8 @@ class MessageDisplay(Display): self.message = "" self.pad = self.newpad(1, 1) - def update_message(self, msg: str) -> None: - self.message = msg + def update(self, game: Game) -> None: + self.message = game.message def display(self) -> None: self.box.refresh(self.y - 1, self.x - 2, diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index 9937c3e..ef358bb 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -4,6 +4,7 @@ import curses from ..entities.player import Player +from ..game import Game from ..translations import gettext as _ from .display import Display @@ -15,8 +16,8 @@ class StatsDisplay(Display): super().__init__(*args, **kwargs) self.pad = self.newpad(self.rows, self.cols) - def update_player(self, p: Player) -> None: - self.player = p + def update(self, game: Game) -> None: + self.player = game.player def update_pad(self) -> None: string2 = "Player -- LVL {}\nEXP {}/{}\nHP {}/{}"\ diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 89d6ed6..6751016 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -404,6 +404,7 @@ class TestGame(unittest.TestCase): Check that some functions are not implemented, only for coverage. """ self.assertRaises(NotImplementedError, Display.display, None) + self.assertRaises(NotImplementedError, Display.update, None, self.game) def test_messages(self) -> None: """ From 46ce7c33bf3dcabc5465c89c89490ed69bbc0056 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 15:15:47 +0100 Subject: [PATCH 17/95] Merchant menu is updated through its update function, and does not access globally to the Game instance --- squirrelbattle/display/logsdisplay.py | 1 + squirrelbattle/display/mapdisplay.py | 1 + squirrelbattle/display/menudisplay.py | 17 ++++++++++++----- squirrelbattle/game.py | 10 ++++------ 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/squirrelbattle/display/logsdisplay.py b/squirrelbattle/display/logsdisplay.py index b21273d..bb53e48 100644 --- a/squirrelbattle/display/logsdisplay.py +++ b/squirrelbattle/display/logsdisplay.py @@ -7,6 +7,7 @@ from squirrelbattle.interfaces import Logs class LogsDisplay(Display): + logs: Logs def __init__(self, *args) -> None: super().__init__(*args) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index 72e339c..b10619e 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -7,6 +7,7 @@ from ..game import Game class MapDisplay(Display): + map: Map def __init__(self, *args): super().__init__(*args) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index ed10fec..c5dfda5 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -144,21 +144,25 @@ class MainMenuDisplay(Display): class PlayerInventoryDisplay(MenuDisplay): + selected: bool = True + store_mode: bool = False + def update(self, game: Game) -> None: self.update_menu(game.inventory_menu) + self.store_mode = game.state == GameMode.STORE + self.selected = game.state == GameMode.INVENTORY \ + or self.store_mode and not game.is_in_store_menu def update_pad(self) -> None: self.menubox.update_title(_("INVENTORY")) for i, item in enumerate(self.menu.values): rep = self.pack[item.name.upper()] selection = f"[{rep}]" if i == self.menu.position \ - and (Game.INSTANCE.state == GameMode.INVENTORY - or Game.INSTANCE.state == GameMode.STORE - and not Game.INSTANCE.is_in_store_menu) else f" {rep} " + and self.selected else f" {rep} " self.addstr(self.pad, i + 1, 0, selection + " " + item.translated_name.capitalize() + (": " + str(item.price) + " Hazels" - if Game.INSTANCE.state == GameMode.STORE else "")) + if self.store_mode else "")) @property def truewidth(self) -> int: @@ -178,15 +182,18 @@ class PlayerInventoryDisplay(MenuDisplay): class StoreInventoryDisplay(MenuDisplay): + selected: bool = False + def update(self, game: Game) -> None: self.update_menu(game.store_menu) + self.selected = game.is_in_store_menu def update_pad(self) -> None: self.menubox.update_title(_("STALL")) for i, item in enumerate(self.menu.values): rep = self.pack[item.name.upper()] selection = f"[{rep}]" if i == self.menu.position \ - and Game.INSTANCE.is_in_store_menu else f" {rep} " + and self.selected else f" {rep} " self.addstr(self.pad, i + 1, 0, selection + " " + item.translated_name.capitalize() + ": " + str(item.price) + " Hazels") diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 2204508..b0a65f4 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -22,9 +22,6 @@ class Game: """ The game object controls all actions in the game. """ - # Global instance of the game - INSTANCE: "Game" - map: Map player: Player screen: Any @@ -35,8 +32,6 @@ class Game: """ Init the game. """ - Game.INSTANCE = self - self.state = GameMode.MAINMENU self.waiting_for_friendly_key = False self.is_in_store_menu = True @@ -170,6 +165,7 @@ class Game: self.state = GameMode.STORE self.is_in_store_menu = True self.store_menu.update_merchant(entity) + self.display_actions(DisplayActions.UPDATE) def handle_key_pressed_inventory(self, key: KeyValues) -> None: """ @@ -208,8 +204,10 @@ class Game: menu.go_down() elif key == KeyValues.LEFT: self.is_in_store_menu = False + self.display_actions(DisplayActions.UPDATE) elif key == KeyValues.RIGHT: self.is_in_store_menu = True + self.display_actions(DisplayActions.UPDATE) if menu.values and not self.player.dead: if key == KeyValues.ENTER: item = menu.validate() @@ -220,7 +218,7 @@ class Game: flag = item.be_sold(buyer, owner) if not flag: self.message = _("The buyer does not have enough money") - self.display_actions(DisplayActions.UPDATE) + self.display_actions(DisplayActions.UPDATE) # Ensure that the cursor has a good position menu.position = min(menu.position, len(menu.values) - 1) From 8ecbf13eae822864d5fdb24e62d86e724ff07374 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 18 Dec 2020 15:31:23 +0100 Subject: [PATCH 18/95] 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 5ae49e71ff0d14e9890802e1cc2762dc465e5b9b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 15:36:25 +0100 Subject: [PATCH 19/95] Display the amount of hazels in the store menu, closes #49 --- squirrelbattle/display/menudisplay.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index c5dfda5..9140ca7 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -5,8 +5,9 @@ import curses from random import randint from typing import List -from squirrelbattle.menus import Menu, MainMenu, SettingsMenu +from squirrelbattle.menus import Menu, MainMenu, SettingsMenu, StoreMenu from .display import Box, Display +from ..entities.player import Player from ..enums import KeyValues, GameMode from ..game import Game from ..resources import ResourceManager @@ -144,10 +145,12 @@ class MainMenuDisplay(Display): class PlayerInventoryDisplay(MenuDisplay): + player: Player = None selected: bool = True store_mode: bool = False def update(self, game: Game) -> None: + self.player = game.player self.update_menu(game.inventory_menu) self.store_mode = game.state == GameMode.STORE self.selected = game.state == GameMode.INVENTORY \ @@ -164,6 +167,12 @@ class PlayerInventoryDisplay(MenuDisplay): + (": " + str(item.price) + " Hazels" if self.store_mode else "")) + if self.store_mode: + price = f"{self.pack.HAZELNUT} {self.player.hazel} Hazels" + width = len(price) + (self.pack.tile_width - 1) + self.addstr(self.pad, self.height - 3, self.width - width - 2, + price, italic=True) + @property def truewidth(self) -> int: return max(1, self.height if hasattr(self, "height") else 10) @@ -182,6 +191,7 @@ class PlayerInventoryDisplay(MenuDisplay): class StoreInventoryDisplay(MenuDisplay): + menu: StoreMenu selected: bool = False def update(self, game: Game) -> None: @@ -198,6 +208,11 @@ class StoreInventoryDisplay(MenuDisplay): + " " + item.translated_name.capitalize() + ": " + str(item.price) + " Hazels") + price = f"{self.pack.HAZELNUT} {self.menu.merchant.hazel} Hazels" + width = len(price) + (self.pack.tile_width - 1) + self.addstr(self.pad, self.height - 3, self.width - width - 2, price, + italic=True) + @property def truewidth(self) -> int: return max(1, self.height if hasattr(self, "height") else 10) From 77f52b627614577f4c581d291e29398f541c601a Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 16:40:52 +0100 Subject: [PATCH 20/95] Screen is refreshed only when pads are all refreshed, fixes #50 --- squirrelbattle/display/display.py | 2 +- squirrelbattle/display/display_manager.py | 22 +++++++++++++++------- squirrelbattle/display/mapdisplay.py | 1 + squirrelbattle/display/menudisplay.py | 2 +- squirrelbattle/game.py | 8 +++++--- squirrelbattle/tests/screen.py | 4 ++-- 6 files changed, 25 insertions(+), 14 deletions(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 023ad6e..6257427 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -159,7 +159,7 @@ class Display: if last_y >= window_y and last_x >= window_x: # Refresh the pad only if coordinates are valid - pad.refresh(top_y, top_x, window_y, window_x, last_y, last_x) + pad.noutrefresh(top_y, top_x, window_y, window_x, last_y, last_x) def display(self) -> None: """ diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 3481348..70879b1 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -71,6 +71,7 @@ class DisplayManager: def refresh(self) -> List[Display]: displays = [] + pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK) if self.game.state == GameMode.PLAY \ or self.game.state == GameMode.INVENTORY \ @@ -93,16 +94,22 @@ class DisplayManager: if self.game.state == GameMode.INVENTORY: self.playerinventorydisplay.refresh( - self.rows // 10, self.cols // 2, - 8 * self.rows // 10, 2 * self.cols // 5) + self.rows // 10, + pack.tile_width * (self.cols // (2 * pack.tile_width)), + 8 * self.rows // 10, + pack.tile_width * (2 * self.cols // (5 * pack.tile_width))) displays.append(self.playerinventorydisplay) elif self.game.state == GameMode.STORE: self.storeinventorydisplay.refresh( - self.rows // 10, self.cols // 2, - 8 * self.rows // 10, 2 * self.cols // 5) + self.rows // 10, + pack.tile_width * (self.cols // (2 * pack.tile_width)), + 8 * self.rows // 10, + pack.tile_width * (2 * self.cols // (5 * pack.tile_width))) self.playerinventorydisplay.refresh( - self.rows // 10, self.cols // 10, - 8 * self.rows // 10, 2 * self.cols // 5) + self.rows // 10, + pack.tile_width * (self.cols // (10 * pack.tile_width)), + 8 * self.rows // 10, + pack.tile_width * (2 * self.cols // (5 * pack.tile_width))) displays.append(self.storeinventorydisplay) displays.append(self.playerinventorydisplay) elif self.game.state == GameMode.MAINMENU: @@ -117,7 +124,8 @@ class DisplayManager: for line in self.game.message.split("\n"): height += 1 width = max(width, len(line)) - y, x = (self.rows - height) // 2, (self.cols - width) // 2 + y = pack.tile_width * (self.rows - height) // (2 * pack.tile_width) + x = pack.tile_width * ((self.cols - width) // (2 * pack.tile_width)) self.messagedisplay.refresh(y, x, height, width) displays.append(self.messagedisplay) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index b10619e..7e21adb 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -18,6 +18,7 @@ class MapDisplay(Display): self.pack.tile_width * self.map.width + 1) 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 e in self.map.entities: diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 9140ca7..c71142e 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -154,7 +154,7 @@ class PlayerInventoryDisplay(MenuDisplay): self.update_menu(game.inventory_menu) self.store_mode = game.state == GameMode.STORE self.selected = game.state == GameMode.INVENTORY \ - or self.store_mode and not game.is_in_store_menu + or (self.store_mode and not game.is_in_store_menu) def update_pad(self) -> None: self.menubox.update_title(_("INVENTORY")) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index b0a65f4..0553d2e 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -61,16 +61,18 @@ class Game: self.map.spawn_random_entities(randint(3, 10)) self.inventory_menu.update_player(self.player) - def run(self, screen: Any) -> None: + def run(self, screen: Any) -> None: # pragma no cover """ Main infinite loop. We wait for the player's action, then we do what that should be done when the given key gets pressed. """ - while True: # pragma no cover + screen.refresh() + while True: screen.erase() - screen.refresh() + screen.noutrefresh() self.display_actions(DisplayActions.REFRESH) + curses.doupdate() key = screen.getkey() if key == "KEY_MOUSE": _ignored1, x, y, _ignored2, _ignored3 = curses.getmouse() diff --git a/squirrelbattle/tests/screen.py b/squirrelbattle/tests/screen.py index 9a8afe6..470aeef 100644 --- a/squirrelbattle/tests/screen.py +++ b/squirrelbattle/tests/screen.py @@ -12,8 +12,8 @@ class FakePad: def addstr(self, y: int, x: int, message: str, color: int = 0) -> None: pass - def refresh(self, pminrow: int, pmincol: int, sminrow: int, - smincol: int, smaxrow: int, smaxcol: int) -> None: + def noutrefresh(self, pminrow: int, pmincol: int, sminrow: int, + smincol: int, smaxrow: int, smaxcol: int) -> None: pass def erase(self) -> None: From 86628fdea6b6f9b279ae65c9562e3a2086b93f1f Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 18 Dec 2020 17:04:45 +0100 Subject: [PATCH 21/95] 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 22/95] 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 fe769c4488a360d6562ec3bcdf9399fc99aac723 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 17:30:03 +0100 Subject: [PATCH 23/95] We can equip items --- squirrelbattle/display/statsdisplay.py | 17 +++++++++++++---- squirrelbattle/entities/items.py | 20 +++++++++++++++----- squirrelbattle/entities/player.py | 14 ++++++++++++-- squirrelbattle/game.py | 1 + squirrelbattle/interfaces.py | 10 ++++++---- 5 files changed, 47 insertions(+), 15 deletions(-) diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index ef358bb..00f6675 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -20,7 +20,7 @@ class StatsDisplay(Display): self.player = game.player def update_pad(self) -> None: - string2 = "Player -- LVL {}\nEXP {}/{}\nHP {}/{}"\ + string2 = _("player").capitalize() + " -- LVL {}\nEXP {}/{}\nHP {}/{}"\ .format(self.player.level, self.player.current_xp, self.player.max_xp, self.player.health, self.player.maxhealth) @@ -46,11 +46,20 @@ class StatsDisplay(Display): printed_items.append(item) self.addstr(self.pad, 8, 0, inventory_str) - self.addstr(self.pad, 9, 0, f"{self.pack.HAZELNUT} " - f"x{self.player.hazel}") + if self.player.equipped_item: + self.addstr(self.pad, 9, 0, + _("Equipped item:") + " " + f"{self.pack[self.player.equipped_item.name.upper()]}") + if self.player.equipped_armor: + self.addstr(self.pad, 10, 0, + _("Equipped armor:") + " " + f"{self.pack[self.player.equipped_armor.name.upper()]}") + + self.addstr(self.pad, 11, 0, f"{self.pack.HAZELNUT} " + f"x{self.player.hazel}") if self.player.dead: - self.addstr(self.pad, 11, 0, _("YOU ARE DEAD"), curses.COLOR_RED, + self.addstr(self.pad, 13, 0, _("YOU ARE DEAD"), curses.COLOR_RED, bold=True, blink=True, standout=True) def display(self) -> None: diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 865a703..9719e3a 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -4,7 +4,6 @@ from random import choice, randint from typing import Optional -from .player import Player from ..interfaces import Entity, FightingEntity, Map, InventoryHolder from ..translations import gettext as _ @@ -45,15 +44,26 @@ class Item(Entity): """ Indicates what should be done when the item is equipped. """ + if self.held_by.equipped_item: + self.held_by.equipped_item.unequip() + self.held_by.equipped_item = self + self.held_by.remove_from_inventory(self) - def hold(self, player: InventoryHolder) -> None: + def unequip(self) -> None: + """ + Indicates what should be done when the item is unequipped. + """ + self.held_by.add_to_inventory(self) + self.held_by.equipped_item = None + + def hold(self, holder: InventoryHolder) -> None: """ The item is taken from the floor and put into the inventory """ self.held = True - self.held_by = player + self.held_by = holder self.held_by.map.remove_entity(self) - player.add_to_inventory(self) + holder.add_to_inventory(self) def save_state(self) -> dict: """ @@ -116,7 +126,7 @@ class Bomb(Item): """ damage: int = 5 exploding: bool - owner: Optional["Player"] + owner: Optional["InventoryHolder"] tick: int def __init__(self, name: str = "bomb", damage: int = 5, diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 19c8348..d7c9e28 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -4,8 +4,9 @@ from functools import reduce from queue import PriorityQueue from random import randint -from typing import Dict, Tuple +from typing import Dict, Optional, Tuple +from .items import Item from ..interfaces import FightingEntity, InventoryHolder @@ -16,12 +17,15 @@ class Player(InventoryHolder, FightingEntity): current_xp: int = 0 max_xp: int = 10 paths: Dict[Tuple[int, int], Tuple[int, int]] + equipped_item: Optional[Item] + equipped_armor: Optional[Item] def __init__(self, name: str = "player", maxhealth: int = 20, strength: int = 5, intelligence: int = 1, charisma: int = 1, dexterity: int = 1, constitution: int = 1, level: int = 1, current_xp: int = 0, max_xp: int = 10, inventory: list = None, - hazel: int = 42, *args, **kwargs) \ + hazel: int = 42, equipped_item: Optional[Item] = None, + equipped_armor: Optional[Item] = None, *args, **kwargs) \ -> None: super().__init__(name=name, maxhealth=maxhealth, strength=strength, intelligence=intelligence, charisma=charisma, @@ -32,6 +36,12 @@ class Player(InventoryHolder, FightingEntity): self.inventory = self.translate_inventory(inventory or []) self.paths = dict() self.hazel = hazel + if isinstance(equipped_item, dict): + equipped_item = self.dict_to_item(equipped_item) + if isinstance(equipped_armor, dict): + equipped_armor = self.dict_to_item(equipped_armor) + self.equipped_item = equipped_item + self.equipped_armor = equipped_armor def move(self, y: int, x: int) -> None: """ diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 0553d2e..e0f3df9 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -126,6 +126,7 @@ class Game: self.map.tick() elif key == KeyValues.INVENTORY: self.state = GameMode.INVENTORY + self.display_actions(DisplayActions.UPDATE) elif key == KeyValues.SPACE: self.state = GameMode.MAINMENU elif key == KeyValues.CHAT: diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 94025bd..c5d9e0e 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -496,10 +496,10 @@ class InventoryHolder(Entity): """ for i in range(len(inventory)): if isinstance(inventory[i], dict): - inventory[i] = self.dict_to_inventory(inventory[i]) + inventory[i] = self.dict_to_item(inventory[i]) return inventory - def dict_to_inventory(self, item_dict: dict) -> Entity: + def dict_to_item(self, item_dict: dict) -> Entity: """ Translate a dict object that contains the state of an item into an item object. @@ -522,13 +522,15 @@ class InventoryHolder(Entity): """ Adds an object to inventory """ - self.inventory.append(obj) + if obj not in self.inventory: + self.inventory.append(obj) def remove_from_inventory(self, obj: Any) -> None: """ Removes an object from the inventory """ - self.inventory.remove(obj) + if obj in self.inventory: + self.inventory.remove(obj) def change_hazel_balance(self, hz: int) -> None: """ From 1b4612afd05991a62f0ea75308e986f500ae996e Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 17:39:11 +0100 Subject: [PATCH 24/95] Swords add strength --- squirrelbattle/entities/items.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 9719e3a..5564cb0 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -225,9 +225,28 @@ class Sword(Weapon): """ A basic weapon """ - def __init__(self, name: str = "sword", price: int = 20, *args, **kwargs): + strength: int + + def __init__(self, name: str = "sword", price: int = 20, strength: int = 3, + *args, **kwargs): super().__init__(name=name, price=price, *args, **kwargs) self.name = name + self.strength = strength + + def equip(self) -> None: + """ + When a sword is equipped, the player gains strength. + """ + super().equip() + self.held_by.strength += self.strength + + def unequip(self) -> None: + """ + Remove the strength earned by the sword. + :return: + """ + super().unequip() + self.held_by.strength -= self.strength class BodySnatchPotion(Item): From e1918ab06696418d6d29bef357b4bd4129a9761e Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 17:40:52 +0100 Subject: [PATCH 25/95] 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 26/95] 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 947572522842624d9ad3882d5c8d115a1037f7a3 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 17:50:26 +0100 Subject: [PATCH 27/95] Save equipped item and armor --- squirrelbattle/entities/player.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index d7c9e28..a370264 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -154,4 +154,8 @@ class Player(InventoryHolder, FightingEntity): d = super().save_state() d["current_xp"] = self.current_xp d["max_xp"] = self.max_xp + d["equipped_item"] = self.equipped_item.save_state()\ + if self.equipped_item else None + d["equipped_armor"] = self.equipped_armor.save_state()\ + if self.equipped_armor else None return d From 9aa684fb7741e3fc3c188d9ff42bb4db853e07c8 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 17:57:42 +0100 Subject: [PATCH 28/95] Use directly equipped items outside the inventory --- squirrelbattle/entities/items.py | 4 ++-- squirrelbattle/entities/player.py | 11 +++++++++++ squirrelbattle/game.py | 2 ++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 5564cb0..4da4a8c 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -29,7 +29,7 @@ class Item(Entity): The item is dropped from the inventory onto the floor """ if self.held: - self.held_by.inventory.remove(self) + self.held_by.remove_from_inventory(self) self.held_by.map.add_entity(self) self.move(self.held_by.y, self.held_by.x) self.held = False @@ -46,8 +46,8 @@ class Item(Entity): """ if self.held_by.equipped_item: self.held_by.equipped_item.unequip() - self.held_by.equipped_item = self self.held_by.remove_from_inventory(self) + self.held_by.equipped_item = self def unequip(self) -> None: """ diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index a370264..89f16c7 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -75,6 +75,17 @@ class Player(InventoryHolder, FightingEntity): self.current_xp += xp self.level_up() + def remove_from_inventory(self, obj: Item) -> None: + """ + Remove the given item from the inventory, even if the item is equipped. + """ + if obj == self.equipped_item: + self.equipped_item = None + elif obj == self.equipped_armor: + self.equipped_armor = None + else: + return super().remove_from_inventory(obj) + # noinspection PyTypeChecker,PyUnresolvedReferences def check_move(self, y: int, x: int, move_if_possible: bool = False) \ -> bool: diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index e0f3df9..d0aaf84 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -127,6 +127,8 @@ class Game: elif key == KeyValues.INVENTORY: self.state = GameMode.INVENTORY self.display_actions(DisplayActions.UPDATE) + elif key == KeyValues.USE and self.player.equipped_item: + self.player.equipped_item.use() elif key == KeyValues.SPACE: self.state = GameMode.MAINMENU elif key == KeyValues.CHAT: From b876dab156f1e626ba791026ba5fc3b08dcabae1 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 18:13:39 +0100 Subject: [PATCH 29/95] 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 c01307202a8125a222b2600b4cbcaa746591164d Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 18 Dec 2020 20:01:52 +0100 Subject: [PATCH 30/95] Add shields to be more protected, see #48 --- squirrelbattle/display/texturepack.py | 2 ++ squirrelbattle/entities/items.py | 50 ++++++++++++++++++++------- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 1f6dc76..2295667 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -70,6 +70,7 @@ TexturePack.ASCII_PACK = TexturePack( MERCHANT='M', PLAYER='@', RABBIT='Y', + SHIELD='D', SUNFLOWER='I', SWORD='\u2020', TEDDY_BEAR='8', @@ -96,6 +97,7 @@ TexturePack.SQUIRREL_PACK = TexturePack( PLAYER='🐿️ ️', MERCHANT='🦜', RABBIT='🐇', + SHIELD='🛡️ ', SUNFLOWER='🌻', SWORD='🗡️ ', TEDDY_BEAR='🧸', diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 4da4a8c..b3f8a84 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -40,14 +40,20 @@ class Item(Entity): Indicates what should be done when the item is used. """ - def equip(self) -> None: + def equip(self, armor: bool = False) -> None: """ Indicates what should be done when the item is equipped. """ - if self.held_by.equipped_item: - self.held_by.equipped_item.unequip() - self.held_by.remove_from_inventory(self) - self.held_by.equipped_item = self + if armor: + if self.held_by.equipped_armor: + self.held_by.equipped_armor.unequip() + self.held_by.remove_from_inventory(self) + self.held_by.equipped_armor = self + else: + if self.held_by.equipped_item: + self.held_by.equipped_item.unequip() + self.held_by.remove_from_inventory(self) + self.held_by.equipped_item = self def unequip(self) -> None: """ @@ -75,7 +81,7 @@ class Item(Entity): @staticmethod def get_all_items() -> list: - return [BodySnatchPotion, Bomb, Heart, Sword] + return [BodySnatchPotion, Bomb, Heart, Shield, Sword] def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder) -> bool: """ @@ -225,20 +231,17 @@ class Sword(Weapon): """ A basic weapon """ - strength: int - - def __init__(self, name: str = "sword", price: int = 20, strength: int = 3, + def __init__(self, name: str = "sword", price: int = 20, *args, **kwargs): super().__init__(name=name, price=price, *args, **kwargs) self.name = name - self.strength = strength - def equip(self) -> None: + def equip(self, armor: bool = False) -> None: """ When a sword is equipped, the player gains strength. """ super().equip() - self.held_by.strength += self.strength + self.held_by.strength += self.damage def unequip(self) -> None: """ @@ -246,7 +249,28 @@ class Sword(Weapon): :return: """ super().unequip() - self.held_by.strength -= self.strength + self.held_by.strength -= self.damage + + +class Shield(Item): + constitution: int + + def __init__(self, constitution: int = 2, *args, **kwargs): + super().__init__(name="shield", *args, **kwargs) + self.constitution = constitution + + def equip(self, armor: bool = True) -> None: + super().equip(armor) + self.held_by.constitution += self.constitution + + def unequip(self) -> None: + super().unequip() + self.held_by.constitution -= self.constitution + + def save_state(self) -> dict: + d = super().save_state() + d["constitution"] = self.constitution + return d class BodySnatchPotion(Item): From 762bed888af208d9df1ba2eeb5846187811f8bc9 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 18 Dec 2020 21:21:00 +0100 Subject: [PATCH 31/95] 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 32/95] 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 33/95] 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 34/95] 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 35/95] 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 32072ade4207702d09b1dca535e965430eae3443 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 19 Dec 2020 13:37:28 +0100 Subject: [PATCH 36/95] Fix some documentation issues --- README.md | 1 + docs/deployment.rst | 6 +++--- docs/index.rst | 4 ++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6ad3063..d6898db 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ [![pipeline status](https://gitlab.crans.org/ynerant/squirrel-battle/badges/master/pipeline.svg)](https://gitlab.crans.org/ynerant/squirrel-battle/-/commits/master) [![coverage report](https://gitlab.crans.org/ynerant/squirrel-battle/badges/master/coverage.svg)](https://gitlab.crans.org/ynerant/squirrel-battle/-/commits/master) [![Documentation Status](https://readthedocs.org/projects/squirrel-battle/badge/?version=latest)](https://squirrel-battle.readthedocs.io/fr/latest/?badge=latest) +[![Supported Python versions](https://img.shields.io/pypi/pyversions/squirrel-battle)](https://pypi.org/project/squirrel-battle/) [![PyPI](https://img.shields.io/pypi/v/squirrel-battle)](https://pypi.org/project/squirrel-battle/) [![PYPI downloads](https://img.shields.io/pypi/dm/squirrel-battle)](https://pypi.org/project/squirrel-battle/) [![AUR version](https://img.shields.io/aur/version/python-squirrel-battle)](https://aur.archlinux.org/packages/python-squirrel-battle/) diff --git a/docs/deployment.rst b/docs/deployment.rst index 6bde6f0..b96246b 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -275,7 +275,7 @@ Construction du paquet Debian ----------------------------- Structure du paquet -------------------- +~~~~~~~~~~~~~~~~~~~ L'ensemble des instructions pour construire le paquet Debian est situé dans le dossier ``debian/``. @@ -292,14 +292,14 @@ Le paquet ``fonts-noto-color-emoji`` est en dépendance pour le bon affichage des émojis. Mettre à jour le paquet ------------------------ +~~~~~~~~~~~~~~~~~~~~~~~ Pour changer la version du paquet, il faut ajouter des lignes dans le fichier ``changelog``. Construire le paquet --------------------- +~~~~~~~~~~~~~~~~~~~~ Il faut partir d'une installation de Debian. diff --git a/docs/index.rst b/docs/index.rst index 1cb7d83..71d7343 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,6 +17,10 @@ Bienvenue dans la documentation de Squirrel Battle ! :target: https://pypi.org/project/squirrel-battle/ :alt: PyPI +.. image:: https://img.shields.io/pypi/pyversions/squirrel-battle + :target: https://pypi.org/project/squirrel-battle/ + :alt: Supported Python versions + .. image:: https://img.shields.io/pypi/dm/squirrel-battle :target: https://pypi.org/project/squirrel-battle/ :alt: PyPI downloads From 0b9606cbfe9b4527e96447e52310ca2f4703f88f Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 20 Dec 2020 17:52:44 +0100 Subject: [PATCH 37/95] Update entities documentation --- docs/entities/friendly.rst | 50 ++++++++++++++++++++++++++++++++++ docs/entities/index.rst | 15 ++++++++++- docs/entities/items.rst | 55 +++++++++++++++++++++++++++++++------- docs/texture-pack.rst | 35 +++++++++++++++++++----- 4 files changed, 137 insertions(+), 18 deletions(-) create mode 100644 docs/entities/friendly.rst diff --git a/docs/entities/friendly.rst b/docs/entities/friendly.rst new file mode 100644 index 0000000..11eea62 --- /dev/null +++ b/docs/entities/friendly.rst @@ -0,0 +1,50 @@ +Entités pacifiques +================== + +.. _`entité attaquante`: index.html#entites-attaquantes +.. _`pack de textures`: ../texture-pack.html + +Chaque entité pacifique est en particulier une `entité attaquante`_, +et hérite donc de ses attributs, et peut alors être attaquée. +Ils sont cependant non-hostiles. + +Il est possible d'interagir avec ces entités. En s'approchant d'elles, en +appuyant sur la touche ``T`` suivie de la direction où regarder, un échange +débute. + +On dénombre actuellement 2 types d'entités pacifiques : + +Tournesol +--------- + +Son nom est fixé à `sunflower`. Il a par défaut une **15** points de vie. + +Interagir avec un tournesol n'a pas de réel intérêt, si ce n'est déclencher +le « pouvoir des fleurs !! » ou bien savoir que « le soleil est chaud +aujourd'hui ». + +Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``I``. + +Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🌻``. + + +Marchand +-------- + +Son nom est fixé à `merchant`. Il a par défaut **5** points de vie. + +En interagissant avec un marchand, il est possible de lui acheter et de lui +vendre différents objets contre des Hazels, la monnaie du jeu. +Les prix sont fixés : + +* Bombe : 4 Hazels +* Coeur : 3 Hazels +* Potion d'arrachage de corps : 14 Hazels +* Épée : 20 Hazels +* Bouclier : 18 Hazels + +Le marchand commence avec 75 Hazels en sa possession, contre 42 pour le joueur. + +Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``M``. + +Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🦜``. diff --git a/docs/entities/index.rst b/docs/entities/index.rst index 1d63bbf..d23f79c 100644 --- a/docs/entities/index.rst +++ b/docs/entities/index.rst @@ -7,6 +7,7 @@ Entités player monsters + friendly items Entité @@ -38,11 +39,12 @@ Entité attaquante ----------------- .. _monstre: monsters.html +.. _entité pacifique: friendly.html .. _joueur: player.html Une entité attaquante (``FightingEntity``) est un type d'entités représentant les personnages présents sur la carte, pouvant alors se battre. Ce peut être -un monstre_ ou bien le joueur_. +un monstre_, une `entité pacifique`_ ou bien le joueur_. Elles disposent toutes, en plus des paramètres d'entité, des attributs suivants : @@ -77,3 +79,14 @@ en-dessous de 0 point de vie. À ce moment-là, l'entité est retirée de la car Lorsqu'une entité en frappe une autre, celle-ci inflige autant de dégâts qu'elle n'a de force, et autant de points de vie sont perdus. + + +Entité pacifique +---------------- + +Une entité pacifique (``FriendlyEntity``) est un cas particulier d'entité +attaquante. Contrairement aux montres, elles ne peuvent pas attaquer le joueur. + +On peut parler à une entité pacifique en appuyant sur la touche ``T`` puis en +appuyant sur la direction dans laquelle on veut parler à l'entité. + diff --git a/docs/entities/items.rst b/docs/entities/items.rst index 521ca91..918586c 100644 --- a/docs/entities/items.rst +++ b/docs/entities/items.rst @@ -28,23 +28,58 @@ Bombe .. _entités attaquantes: index.html#entite-attaquante Une bombe est un objet que l'on peut ramasser. Une fois ramassée, elle est placée -dans l'inventaire. Le joueur peut ensuite lâcher la bombe, qui fera alors -3 dégâts à toutes les `entités attaquantes`_ situées à moins de une case. +dans l'inventaire. Le joueur peut ensuite utiliser la bombe, via l'inventaire +ou après l'avoir équipée, qui fera alors 3 dégâts à toutes les +`entités attaquantes`_ situées à moins de trois cases au bout de 4 ticks de jeu. Elle est représentée dans le `pack de textures`_ ASCII par le caractère ``o`` -et dans le `pack de textures`_ écureuil par l'émoji ``💣``. +et dans le `pack de textures`_ écureuil par l'émoji ``💣``. Lors de l'explosion, +la bombe est remplacée par un symbole ``%`` ou l'émoji ``💥`` selon le pack de +textures utilisé. -.. note:: - - La gestion de l'inventaire n'ayant pas encore été implémentée, il n'est à - l'heure actuelle pas possible de lancer une bombe. +La bombe coûte 4 Hazels auprès des marchands. Cœur ---- -Une cœur est un objet que l'on ne peut pas ramasser. Dès que le joueur s'en -approche, il est régénéré automatiquement de 3 points de vie, et le cœur disparaît. +Un cœur est un objet que l'on ne peut pas ramasser. Dès que le joueur s'en +approche ou qu'il l'achète auprès d'un marchand, il est régénéré automatiquement +de 3 points de vie, et le cœur disparaît. -Elle est représentée dans le `pack de textures`_ ASCII par le caractère ``❤`` +Il est représenté dans le `pack de textures`_ ASCII par le caractère ``❤`` et dans le `pack de textures`_ écureuil par l'émoji ``💜``. + +Le cœur coûte 3 Hazels auprès des marchands. + + +Potion d'arrachage de corps +--------------------------- + +Cette potion permet, une fois utilisée, d'échanger toutes ses caractéristiques +avec une autre entité aléatoire sur la carte. Cela inclut la force, la position, +l'icône, ... + +Elle est représentée par les caractères ``I`` et ``🔀`` + +Cette potion coûte 14 Hazels auprès des marchands. + + +Épée +---- + +L'épée est un objet que l'on peut trouver uniquement par achat auprès d'un +marchand pour le coût de 20 Hazels. Une fois équipée, l'épée ajoute 3 de force +à son porteur. + +Elle est représentée par les caractères ``†`` et ``🗡️``. + + +Bouclier +-------- + +Le bouclier est un objet que l'on peut trouver uniquement par achat auprès d'un +marchand pour le coût de 18 Hazels. Une fois équipé, le bouclier ajoute 2 de +constitution à son porteur, le permettant de parer plus de coups. + +Il est représenté par les caractères ``D`` et ``🛡️``. diff --git a/docs/texture-pack.rst b/docs/texture-pack.rst index 377a3cf..c81e62a 100644 --- a/docs/texture-pack.rst +++ b/docs/texture-pack.rst @@ -9,18 +9,25 @@ Pack de textures .. _Joueur: entities/player.html .. _Hérisson: entities/monsters.html#herisson -.. _Cœur: entities/items.html#coeur -.. _Bombe: entities/items.html#bombe .. _Lapin: entities/monsters.html#lapin .. _Tigre: entities/monsters.html#tigre .. _Nounours: entities/monsters.html#nounours +.. _Tournesol: entities/friendly.html#tournesol +.. _Marchand: entities/friendly.html#marchand +.. _Cœur: entities/items.html#coeur +.. _Bombe: entities/items.html#bombe +.. _Explosion: entities/items.html#bombe +.. _Potion d'arrachage de corps: entities/items.html +.. _Épée: entities/items.html#epee +.. _Bouclier: entities/items.html#bouclier +.. _Hazel: ../index.html Chaque entité_ et chaque tuile_ de la carte_ est associé à un caractère pour être affiché dans le terminal. Cependant, afin de pouvoir proposer plusieurs expériences graphiques (notamment en fonction du support des émojis), différents packs de textures sont proposés. -Il est possible de changer de pack dans les paramètres. +Il est possible de changer de pack dans les paramètres_. Les packs de textures peuvent influencer la taille que prennent les tuiles_, en raison du fait que les émojis ne sont pas monospace. @@ -39,11 +46,18 @@ Chaque tuile fait un caractère de large. * Entités * Joueur_ : ``@`` * Hérisson_ : ``*`` - * Cœur_ : ``❤`` - * Bombe_ : ``o`` * Lapin_ : ``Y`` * Tigre_ : ``n`` * Nounours_ : ``8`` + * Tournesol_ : ``I`` + * Marchand_ : ``M`` + * Cœur_ : ``❤`` + * Bombe_ : ``o`` + * Explosion_ : ``%`` + * `Potion d'arrachage de corps`_ : ``S`` + * Épée_ : ``†`` + * Bouclier_ : ``D`` + * Hazel_ : ``¤`` Pack Écureuil @@ -58,8 +72,15 @@ Chaque tuile fait 2 caractères de large pour afficher les émojis proprement. * Entités * Joueur_ : ``🐿`` * Hérisson_ : ``🦔`` - * Cœur_ : ``💜`` - * Bombe_ : ``💣`` * Lapin_ : ``🐇`` * Tigre_ : ``🐅`` * Nounours_ : ``🧸`` + * Tournesol_ : ``🌻`` + * Marchand_ : ``🦜`` + * Cœur_ : ``💜`` + * Bombe_ : ``💣`` + * Explosion_ : ``💥`` + * `Potion d'arrachage de corps`_ : ``🔀`` + * Épée_ : ``🗡️`` + * Bouclier_ : ``🛡️`` + * Hazel : ``🌰`` From 8afa0827085ec6e347ba7c82635badb1a67ae97d Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 20 Dec 2020 17:54:33 +0100 Subject: [PATCH 38/95] Add Python 3.6 and 3.10 support, fixes #53 --- .gitlab-ci.yml | 51 +++++++++++++++++++++++++------ squirrelbattle/display/display.py | 3 ++ 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ff5c142..602bc97 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,21 +3,16 @@ stages: - quality-assurance - build -py37: - stage: test - image: python:3.7-alpine - before_script: - - apk add --no-cache gettext - - pip install tox - script: tox -e py3 -py38: +py310: stage: test - image: python:3.8-alpine + image: python:rc-alpine before_script: - apk add --no-cache gettext - pip install tox script: tox -e py3 + only: + - master py39: @@ -28,6 +23,38 @@ py39: - pip install tox script: tox -e py3 + +py38: + stage: test + image: python:3.8-alpine + before_script: + - apk add --no-cache gettext + - pip install tox + script: tox -e py3 + + +py37: + stage: test + image: python:3.7-alpine + before_script: + - apk add --no-cache gettext + - pip install tox + script: tox -e py3 + only: + - master + + +py36: + stage: test + image: python:3.6-alpine + before_script: + - apk add --no-cache gettext + - pip install tox + script: tox -e py3 + only: + - master + + linters: stage: quality-assurance image: python:3-alpine @@ -36,11 +63,15 @@ linters: script: tox -e linters allow_failure: true + build-deb: image: debian:buster-slim stage: build before_script: - - apt-get update && apt-get -y --no-install-recommends install build-essential debmake dh-python debhelper gettext python3-all python3-setuptools + - > + apt-get update && apt-get -y --no-install-recommends install + build-essential debmake dh-python debhelper gettext python3-all + python3-setuptools script: - dpkg-buildpackage - mkdir build && cp ../*.deb build/ diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 6257427..a7a900d 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import curses +import sys from typing import Any, Optional, Tuple, Union from squirrelbattle.display.texturepack import TexturePack @@ -97,6 +98,8 @@ class Display: attr |= curses.A_BOLD if bold else 0 attr |= curses.A_DIM if dim else 0 attr |= curses.A_INVIS if invis else 0 + # Italic is supported since Python 3.7 + italic &= sys.version_info >= (3, 7,) attr |= curses.A_ITALIC if italic else 0 attr |= curses.A_NORMAL if normal else 0 attr |= curses.A_PROTECT if protect else 0 From 505e0a4efbf38dc27e3dd4c3f104c491bdd1c63e Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Mon, 21 Dec 2020 14:23:58 +0100 Subject: [PATCH 39/95] 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 40/95] 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 41/95] 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 42/95] 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 43/95] 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 44/95] 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 45/95] 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 46/95] 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 47/95] 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 48/95] 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 49/95] 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 50/95] 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 51/95] 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 52/95] 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 53/95] 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 54/95] 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 9b8dfb00da8cfa38f9ad5abca3a95defa4ebc7e2 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Tue, 5 Jan 2021 19:07:15 +0100 Subject: [PATCH 55/95] Added critical hit system: the player and rabbit entities have a chance of making x4 damage! Closes #52 --- squirrelbattle/entities/monsters.py | 4 ++-- squirrelbattle/entities/player.py | 6 +++--- squirrelbattle/interfaces.py | 14 +++++++++++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/squirrelbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py index 34cd4bf..e50fad0 100644 --- a/squirrelbattle/entities/monsters.py +++ b/squirrelbattle/entities/monsters.py @@ -87,9 +87,9 @@ class Rabbit(Monster): A rabbit monster """ def __init__(self, name: str = "rabbit", strength: int = 1, - maxhealth: int = 15, *args, **kwargs) -> None: + maxhealth: int = 15, critical: int = 30, *args, **kwargs) -> None: super().__init__(name=name, strength=strength, - maxhealth=maxhealth, *args, **kwargs) + maxhealth=maxhealth, critical=critical, *args, **kwargs) class TeddyBear(Monster): diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 89f16c7..2a023d8 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -25,12 +25,12 @@ class Player(InventoryHolder, FightingEntity): dexterity: int = 1, constitution: int = 1, level: int = 1, current_xp: int = 0, max_xp: int = 10, inventory: list = None, hazel: int = 42, equipped_item: Optional[Item] = None, - equipped_armor: Optional[Item] = None, *args, **kwargs) \ - -> None: + equipped_armor: Optional[Item] = None, critical: int = 5,\ + *args, **kwargs) -> None: super().__init__(name=name, maxhealth=maxhealth, strength=strength, intelligence=intelligence, charisma=charisma, dexterity=dexterity, constitution=constitution, - level=level, *args, **kwargs) + level=level, critical=critical, *args, **kwargs) self.current_xp = current_xp self.max_xp = max_xp self.inventory = self.translate_inventory(inventory or []) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index c5d9e0e..599f546 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -403,11 +403,12 @@ class FightingEntity(Entity): dexterity: int constitution: int level: int + critical: int def __init__(self, maxhealth: int = 0, health: Optional[int] = None, strength: int = 0, intelligence: int = 0, charisma: int = 0, dexterity: int = 0, constitution: int = 0, level: int = 0, - *args, **kwargs) -> None: + critical: int = 0, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.maxhealth = maxhealth self.health = maxhealth if health is None else health @@ -417,6 +418,7 @@ class FightingEntity(Entity): self.dexterity = dexterity self.constitution = constitution self.level = level + self.critical = critical @property def dead(self) -> bool: @@ -426,10 +428,16 @@ class FightingEntity(Entity): """ Deals damage to the opponent, based on the stats """ + diceroll = randint(0, 100) + damage = self.strength + string = " " + if diceroll <= self.critical: # It is a critical hit + damage *= 4 + string = _(" It's a critical hit! ") return _("{name} hits {opponent}.")\ .format(name=_(self.translated_name.capitalize()), - opponent=_(opponent.translated_name)) + " " + \ - opponent.take_damage(self, self.strength) + opponent=_(opponent.translated_name)) + string + \ + opponent.take_damage(self, damage) def take_damage(self, attacker: "Entity", amount: int) -> str: """ From 4bddf076ea5de4efdd80cf26184cd444059a1be0 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Tue, 5 Jan 2021 19:18:25 +0100 Subject: [PATCH 56/95] Fighting now takes into account the constitution. Closes #51 --- squirrelbattle/entities/items.py | 30 +++++++++++++++--------------- squirrelbattle/interfaces.py | 7 ++++--- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index b3f8a84..6c54fef 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -226,6 +226,21 @@ class Weapon(Item): d["damage"] = self.damage return d + def equip(self, armor: bool = False) -> None: + """ + When a weapon is equipped, the player gains strength. + """ + super().equip() + self.held_by.strength += self.damage + + def unequip(self) -> None: + """ + Remove the strength earned by the weapon. + :return: + """ + super().unequip() + self.held_by.strength -= self.damage + class Sword(Weapon): """ @@ -236,21 +251,6 @@ class Sword(Weapon): super().__init__(name=name, price=price, *args, **kwargs) self.name = name - def equip(self, armor: bool = False) -> None: - """ - When a sword is equipped, the player gains strength. - """ - super().equip() - self.held_by.strength += self.damage - - def unequip(self) -> None: - """ - Remove the strength earned by the sword. - :return: - """ - super().unequip() - self.held_by.strength -= self.damage - class Shield(Item): constitution: int diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 599f546..dceea6a 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -443,11 +443,12 @@ class FightingEntity(Entity): """ Take damage from the attacker, based on the stats """ - self.health -= amount + damage = max(0, amount - self.constitution) + self.health -= damage if self.health <= 0: self.die() - return _("{name} takes {amount} damage.")\ - .format(name=self.translated_name.capitalize(), amount=str(amount))\ + return _("{name} takes {damage} damage.")\ + .format(name=self.translated_name.capitalize(), damage=str(damage))\ + (" " + _("{name} dies.") .format(name=self.translated_name.capitalize()) if self.health <= 0 else "") From 3ace133037f62e511d7cd021e530fc5259aaf751 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Tue, 5 Jan 2021 19:23:25 +0100 Subject: [PATCH 57/95] Critical hit chance is now displayed along with the other stats. --- squirrelbattle/display/statsdisplay.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index 00f6675..b3ae831 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -25,10 +25,11 @@ class StatsDisplay(Display): self.player.max_xp, self.player.health, self.player.maxhealth) self.addstr(self.pad, 0, 0, string2) - string3 = "STR {}\nINT {}\nCHR {}\nDEX {}\nCON {}"\ + string3 = "STR {}\nINT {}\nCHR {}\nDEX {}\nCON {}\nCRI {}%"\ .format(self.player.strength, self.player.intelligence, self.player.charisma, - self.player.dexterity, self.player.constitution) + self.player.dexterity, self.player.constitution,\ + self.player.critical) self.addstr(self.pad, 3, 0, string3) inventory_str = _("Inventory:") + " " @@ -44,22 +45,22 @@ class StatsDisplay(Display): if count > 1: inventory_str += f"x{count} " printed_items.append(item) - self.addstr(self.pad, 8, 0, inventory_str) + self.addstr(self.pad, 9, 0, inventory_str) if self.player.equipped_item: - self.addstr(self.pad, 9, 0, + self.addstr(self.pad, 10, 0, _("Equipped item:") + " " f"{self.pack[self.player.equipped_item.name.upper()]}") if self.player.equipped_armor: - self.addstr(self.pad, 10, 0, + self.addstr(self.pad, 11, 0, _("Equipped armor:") + " " f"{self.pack[self.player.equipped_armor.name.upper()]}") - self.addstr(self.pad, 11, 0, f"{self.pack.HAZELNUT} " + self.addstr(self.pad, 12, 0, f"{self.pack.HAZELNUT} " f"x{self.player.hazel}") if self.player.dead: - self.addstr(self.pad, 13, 0, _("YOU ARE DEAD"), curses.COLOR_RED, + self.addstr(self.pad, 14, 0, _("YOU ARE DEAD"), curses.COLOR_RED, bold=True, blink=True, standout=True) def display(self) -> None: From 424044a5e4f1998d2c2949f895bf631d8a370d67 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Tue, 5 Jan 2021 19:40:11 +0100 Subject: [PATCH 58/95] Added an overpowered eagle boss. To avoid seing it too often, entities now have a certain chance of being spawned. Closes #19. --- squirrelbattle/display/texturepack.py | 3 +++ squirrelbattle/entities/monsters.py | 9 +++++++++ squirrelbattle/interfaces.py | 21 ++++++++++++++++----- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 2295667..ed89be6 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -30,6 +30,7 @@ class TexturePack: TEDDY_BEAR: str TIGER: str WALL: str + EAGLE: str ASCII_PACK: "TexturePack" SQUIRREL_PACK: "TexturePack" @@ -76,6 +77,7 @@ TexturePack.ASCII_PACK = TexturePack( TEDDY_BEAR='8', TIGER='n', WALL='#', + EAGLE='µ', ) TexturePack.SQUIRREL_PACK = TexturePack( @@ -103,4 +105,5 @@ TexturePack.SQUIRREL_PACK = TexturePack( TEDDY_BEAR='🧸', TIGER='🐅', WALL='🧱', + EAGLE='🦅', ) diff --git a/squirrelbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py index e50fad0..bcdff11 100644 --- a/squirrelbattle/entities/monsters.py +++ b/squirrelbattle/entities/monsters.py @@ -100,3 +100,12 @@ class TeddyBear(Monster): maxhealth: int = 50, *args, **kwargs) -> None: super().__init__(name=name, strength=strength, maxhealth=maxhealth, *args, **kwargs) + +class GiantSeaEagle(Monster): + """ + An eagle boss + """ + def __init__(self, name: str = "eagle", strength: int = 1000, + maxhealth: int = 5000, *args, **kwargs) -> None: + super().__init__(name=name, strength=strength, + maxhealth=maxhealth, *args, **kwargs) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index dceea6a..25a8baa 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -3,7 +3,7 @@ from enum import Enum, auto from math import sqrt -from random import choice, randint +from random import choice, randint, choices from typing import List, Optional, Any from .display.texturepack import TexturePack @@ -146,7 +146,8 @@ class Map: tile = self.tiles[y][x] if tile.can_walk(): break - entity = choice(Entity.get_all_entity_classes())() + entity = choices(Entity.get_all_entity_classes(),\ + weights = Entity.get_weights(), k=1)[0]() entity.move(y, x) self.add_entity(entity) @@ -349,10 +350,19 @@ class Entity: """ from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart from squirrelbattle.entities.monsters import Tiger, Hedgehog, \ - Rabbit, TeddyBear + Rabbit, TeddyBear, GiantSeaEagle from squirrelbattle.entities.friendly import Merchant, Sunflower return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear, - Sunflower, Tiger, Merchant] + Sunflower, Tiger, Merchant, GiantSeaEagle] + + @staticmethod + def get_weights() -> list: + """ + Returns a weigth list associated to the above function, to + be used to spawn random entities with a certain probability. + """ + return [3, 5, 6, 5, 5, 5, + 5, 4, 4, 1] @staticmethod def get_all_entity_classes_in_a_dict() -> dict: @@ -361,7 +371,7 @@ class Entity: """ from squirrelbattle.entities.player import Player from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \ - TeddyBear + TeddyBear, GiantSeaEagle from squirrelbattle.entities.friendly import Merchant, Sunflower from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \ Heart, Sword @@ -377,6 +387,7 @@ class Entity: "Merchant": Merchant, "Sunflower": Sunflower, "Sword": Sword, + "Eagle": GiantSeaEagle, } def save_state(self) -> dict: From 601062237db8ffa1de7bb026d874eba68a2834be Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Tue, 5 Jan 2021 19:50:25 +0100 Subject: [PATCH 59/95] Repairing part of the tests. --- squirrelbattle/tests/entities_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 70e3748..1d7a7a9 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -18,6 +18,7 @@ class TestEntities(unittest.TestCase): """ self.map = Map.load(ResourceManager.get_asset_path("example_map.txt")) self.player = Player() + self.player.constitution = 0 self.map.add_entity(self.player) self.player.move(self.map.start_y, self.map.start_x) @@ -54,6 +55,7 @@ class TestEntities(unittest.TestCase): self.assertTrue(entity.dead) entity = Rabbit() + entity.critical = 0 self.map.add_entity(entity) entity.move(15, 44) # Move randomly From f3fe04e13a217c1251310438f726e7bb06aff158 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Wed, 6 Jan 2021 10:46:36 +0100 Subject: [PATCH 60/95] The player now has two hands and a slot for a helmet and a chestplate. Accordingly, new classes of items have been added. --- squirrelbattle/display/statsdisplay.py | 22 ++++--- squirrelbattle/display/texturepack.py | 7 +++ squirrelbattle/entities/items.py | 84 +++++++++++++++++++++----- squirrelbattle/entities/player.py | 36 ++++++++--- 4 files changed, 118 insertions(+), 31 deletions(-) diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index b3ae831..e24c969 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -47,20 +47,28 @@ class StatsDisplay(Display): printed_items.append(item) self.addstr(self.pad, 9, 0, inventory_str) - if self.player.equipped_item: + if self.player.equipped_main: self.addstr(self.pad, 10, 0, - _("Equipped item:") + " " - f"{self.pack[self.player.equipped_item.name.upper()]}") - if self.player.equipped_armor: + _("Equipped main:") + " " + f"{self.pack[self.player.equipped_main.name.upper()]}") + if self.player.equipped_secondary: self.addstr(self.pad, 11, 0, - _("Equipped armor:") + " " + _("Equipped secondary:") + " " + f"{self.pack[self.player.equipped_secondary.name.upper()]}") + if self.player.equipped_armor: + self.addstr(self.pad, 12, 0, + _("Equipped chestplate:") + " " f"{self.pack[self.player.equipped_armor.name.upper()]}") + if self.player.equipped_helmet: + self.addstr(self.pad, 13, 0, + _("Equipped helmet:") + " " + f"{self.pack[self.player.equipped_helmet.name.upper()]}") - self.addstr(self.pad, 12, 0, f"{self.pack.HAZELNUT} " + self.addstr(self.pad, 14, 0, f"{self.pack.HAZELNUT} " f"x{self.player.hazel}") if self.player.dead: - self.addstr(self.pad, 14, 0, _("YOU ARE DEAD"), curses.COLOR_RED, + self.addstr(self.pad, 15, 0, _("YOU ARE DEAD"), curses.COLOR_RED, bold=True, blink=True, standout=True) def display(self) -> None: diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index ed89be6..8d4e785 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -31,6 +31,9 @@ class TexturePack: TIGER: str WALL: str EAGLE: str + SHIELD: str + CHESTPLATE: str + HELMET: str ASCII_PACK: "TexturePack" SQUIRREL_PACK: "TexturePack" @@ -78,6 +81,8 @@ TexturePack.ASCII_PACK = TexturePack( TIGER='n', WALL='#', EAGLE='µ', + CHESTPLATE='(', + HELMET='0', ) TexturePack.SQUIRREL_PACK = TexturePack( @@ -106,4 +111,6 @@ TexturePack.SQUIRREL_PACK = TexturePack( TIGER='🐅', WALL='🧱', EAGLE='🦅', + CHESTPLATE='🦺', + HELMET='⛑️', ) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 6c54fef..8b99a8d 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -40,27 +40,53 @@ class Item(Entity): Indicates what should be done when the item is used. """ - def equip(self, armor: bool = False) -> None: + def equip(self) -> None: """ Indicates what should be done when the item is equipped. """ - if armor: + if isinstance(self, Chestplate): if self.held_by.equipped_armor: self.held_by.equipped_armor.unequip() self.held_by.remove_from_inventory(self) self.held_by.equipped_armor = self - else: - if self.held_by.equipped_item: - self.held_by.equipped_item.unequip() + elif isinstance(self, Helmet): + if self.held_by.equipped_helmet: + self.held_by.equipped_helmet.unequip() self.held_by.remove_from_inventory(self) - self.held_by.equipped_item = self + self.held_by.equipped_helmet = self + elif isinstance(self, Weapon): + if self.held_by.equipped_main: + if self.held_by.equipped_secondary: + self.held_by.equipped_secondary.unequip() + self.held_by.remove_from_inventory(self) + self.held_by.equipped_secondary = self + # For weapons, they are equipped as main only if main is empty. + else: + self.held_by.remove_from_inventory(self) + self.held_by.equipped_main = self + else: + # Other objects are only equipped as secondary. + if self.held_by.equipped_secondary: + self.held_by.equipped_secondary.unequip() + self.held_by.remove_from_inventory(self) + self.held_by.equipped_secondary = self def unequip(self) -> None: """ Indicates what should be done when the item is unequipped. """ + if isinstance(self, Chestplate): + self.held_by.equipped_armor = None + elif isinstance(self, Helmet): + self.held_by.equipped_helmet = None + elif isinstance(self, Weapon): + if self.held_by.equipped_main == self: + self.held_by.equipped_main = None + else: + self.held_by.equipped_secondary = None + else: + self.held_by.equipped_secondary = None self.held_by.add_to_inventory(self) - self.held_by.equipped_item = None def hold(self, holder: InventoryHolder) -> None: """ @@ -81,7 +107,8 @@ class Item(Entity): @staticmethod def get_all_items() -> list: - return [BodySnatchPotion, Bomb, Heart, Shield, Sword] + return [BodySnatchPotion, Bomb, Heart, Shield, Sword,\ + Chestplate, Helmet] def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder) -> bool: """ @@ -226,7 +253,7 @@ class Weapon(Item): d["damage"] = self.damage return d - def equip(self, armor: bool = False) -> None: + def equip(self) -> None: """ When a weapon is equipped, the player gains strength. """ @@ -252,15 +279,18 @@ class Sword(Weapon): self.name = name -class Shield(Item): +class Armor(Item): + """ + Class of items that increase the player's constitution. + """ constitution: int - - def __init__(self, constitution: int = 2, *args, **kwargs): - super().__init__(name="shield", *args, **kwargs) + + def __init__(self, constitution: int, *args, **kwargs): + super().__init__(*args, **kwargs) self.constitution = constitution - def equip(self, armor: bool = True) -> None: - super().equip(armor) + def equip(self) -> None: + super().equip() self.held_by.constitution += self.constitution def unequip(self) -> None: @@ -272,6 +302,30 @@ class Shield(Item): d["constitution"] = self.constitution return d +class Shield(Armor): + """ + Class of shield items, they can be equipped in the other hand. + """ + + def __init__(self, constitution: int = 2, *args, **kwargs): + super().__init__(name="shield", constitution=constitution, *args, **kwargs) + +class Helmet(Armor): + """ + Class of helmet items, they can be equipped on the head. + """ + + def __init__(self, constitution: int = 2, *args, **kwargs): + super().__init__(name="helmet", constitution=constitution, *args, **kwargs) + +class Chestplate(Armor): + """ + Class of chestplate items, they can be equipped on the body. + """ + + def __init__(self, constitution: int = 4, *args, **kwargs): + super().__init__(name="chestplate", constitution=constitution, *args, **kwargs) + class BodySnatchPotion(Item): """ diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 2a023d8..a0ddd6c 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -17,15 +17,19 @@ class Player(InventoryHolder, FightingEntity): current_xp: int = 0 max_xp: int = 10 paths: Dict[Tuple[int, int], Tuple[int, int]] - equipped_item: Optional[Item] + equipped_main: Optional[Item] + equipped_secondary: Optional[Item] + equipped_helmet: Optional[Item] equipped_armor: Optional[Item] def __init__(self, name: str = "player", maxhealth: int = 20, strength: int = 5, intelligence: int = 1, charisma: int = 1, dexterity: int = 1, constitution: int = 1, level: int = 1, current_xp: int = 0, max_xp: int = 10, inventory: list = None, - hazel: int = 42, equipped_item: Optional[Item] = None, + hazel: int = 42, equipped_main: Optional[Item] = None, equipped_armor: Optional[Item] = None, critical: int = 5,\ + equipped_secondary: Optional[Item] = None, \ + equipped_helmet: Optional[Item] = None, \ *args, **kwargs) -> None: super().__init__(name=name, maxhealth=maxhealth, strength=strength, intelligence=intelligence, charisma=charisma, @@ -36,12 +40,18 @@ class Player(InventoryHolder, FightingEntity): self.inventory = self.translate_inventory(inventory or []) self.paths = dict() self.hazel = hazel - if isinstance(equipped_item, dict): - equipped_item = self.dict_to_item(equipped_item) + if isinstance(equipped_main, dict): + equipped_main = self.dict_to_item(equipped_main) if isinstance(equipped_armor, dict): equipped_armor = self.dict_to_item(equipped_armor) - self.equipped_item = equipped_item + if isinstance(equipped_secondary, dict): + equipped_secondary = self.dict_to_item(equipped_secondary) + if isinstance(equipped_helmet, dict): + equipped_helmet = self.dict_to_item(equipped_helmet) + self.equipped_main = equipped_main self.equipped_armor = equipped_armor + self.equipped_secondary = equipped_secondary + self.equipped_helmet = equipped_helmet def move(self, y: int, x: int) -> None: """ @@ -79,10 +89,14 @@ class Player(InventoryHolder, FightingEntity): """ Remove the given item from the inventory, even if the item is equipped. """ - if obj == self.equipped_item: - self.equipped_item = None + if obj == self.equipped_main: + self.equipped_main = None elif obj == self.equipped_armor: self.equipped_armor = None + elif obj == self.equipped_secondary: + self.equipped_secondary = None + elif obj == self.equipped_helmet: + self.equipped_helmet = None else: return super().remove_from_inventory(obj) @@ -165,8 +179,12 @@ class Player(InventoryHolder, FightingEntity): d = super().save_state() d["current_xp"] = self.current_xp d["max_xp"] = self.max_xp - d["equipped_item"] = self.equipped_item.save_state()\ - if self.equipped_item else None + d["equipped_main"] = self.equipped_main.save_state()\ + if self.equipped_main else None d["equipped_armor"] = self.equipped_armor.save_state()\ if self.equipped_armor else None + d["equipped_secondary"] = self.equipped_secondary.save_state()\ + if self.equipped_secondary else None + d["equipped_helmet"] = self.equipped_helmet.save_state()\ + if self.equipped_helmet else None return d From a9aeb9ca3a9748234166a45771bb67c16a1da131 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Wed, 6 Jan 2021 11:13:17 +0100 Subject: [PATCH 61/95] Repaired the use functionnality of the main item, as well as the load system for Armor items. --- squirrelbattle/entities/items.py | 16 +++++++++------- squirrelbattle/game.py | 4 ++-- squirrelbattle/interfaces.py | 5 ++++- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 8b99a8d..05654e9 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -276,7 +276,6 @@ class Sword(Weapon): def __init__(self, name: str = "sword", price: int = 20, *args, **kwargs): super().__init__(name=name, price=price, *args, **kwargs) - self.name = name class Armor(Item): @@ -307,24 +306,27 @@ class Shield(Armor): Class of shield items, they can be equipped in the other hand. """ - def __init__(self, constitution: int = 2, *args, **kwargs): - super().__init__(name="shield", constitution=constitution, *args, **kwargs) + def __init__(self, name: str = "shield", constitution: int = 2,\ + price: int = 6, *args, **kwargs): + super().__init__(name=name, constitution=constitution, *args, **kwargs) class Helmet(Armor): """ Class of helmet items, they can be equipped on the head. """ - def __init__(self, constitution: int = 2, *args, **kwargs): - super().__init__(name="helmet", constitution=constitution, *args, **kwargs) + def __init__(self, name: str = "helmet", constitution: int = 2, \ + price: int = 8, *args, **kwargs): + super().__init__(name=name, constitution=constitution, *args, **kwargs) class Chestplate(Armor): """ Class of chestplate items, they can be equipped on the body. """ - def __init__(self, constitution: int = 4, *args, **kwargs): - super().__init__(name="chestplate", constitution=constitution, *args, **kwargs) + def __init__(self, name: str = "chestplate", constitution: int = 4,\ + price: int = 15, *args, **kwargs): + super().__init__(name=name, constitution=constitution, *args, **kwargs) class BodySnatchPotion(Item): diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index d0aaf84..939325c 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -127,8 +127,8 @@ class Game: elif key == KeyValues.INVENTORY: self.state = GameMode.INVENTORY self.display_actions(DisplayActions.UPDATE) - elif key == KeyValues.USE and self.player.equipped_item: - self.player.equipped_item.use() + elif key == KeyValues.USE and self.player.equipped_main: + self.player.equipped_main.use() elif key == KeyValues.SPACE: self.state = GameMode.MAINMENU elif key == KeyValues.CHAT: diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 25a8baa..cc5b36e 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -374,7 +374,7 @@ class Entity: TeddyBear, GiantSeaEagle from squirrelbattle.entities.friendly import Merchant, Sunflower from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \ - Heart, Sword + Heart, Sword, Shield, Chestplate, Helmet return { "Tiger": Tiger, "Bomb": Bomb, @@ -388,6 +388,9 @@ class Entity: "Sunflower": Sunflower, "Sword": Sword, "Eagle": GiantSeaEagle, + "Shield": Shield, + "Chestplate": Chestplate, + "Helmet": Helmet, } def save_state(self) -> dict: From 4ad7d6c37cca74080536c9a1f684d6138ce664b7 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Wed, 6 Jan 2021 11:44:52 +0100 Subject: [PATCH 62/95] Added rings that can augment the player's statistics. Also added a new statistic : xp_buff, which helps the player level up sooner. --- squirrelbattle/display/texturepack.py | 8 +++- squirrelbattle/entities/items.py | 68 ++++++++++++++++++++++++++- squirrelbattle/entities/player.py | 6 ++- squirrelbattle/interfaces.py | 4 +- 4 files changed, 81 insertions(+), 5 deletions(-) diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 8d4e785..bd4b563 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -34,6 +34,8 @@ class TexturePack: SHIELD: str CHESTPLATE: str HELMET: str + RING_OF_MORE_EXPERIENCE: str + RING_OF_CRITICAL_DAMAGE: str ASCII_PACK: "TexturePack" SQUIRREL_PACK: "TexturePack" @@ -64,7 +66,7 @@ TexturePack.ASCII_PACK = TexturePack( entity_bg_color=curses.COLOR_BLACK, BODY_SNATCH_POTION='S', - BOMB='o', + BOMB='ç', EMPTY=' ', EXPLOSION='%', FLOOR='.', @@ -83,6 +85,8 @@ TexturePack.ASCII_PACK = TexturePack( EAGLE='µ', CHESTPLATE='(', HELMET='0', + RING_OF_MORE_EXPERIENCE='o', + RING_OF_CRITICAL_DAMAGE='o', ) TexturePack.SQUIRREL_PACK = TexturePack( @@ -113,4 +117,6 @@ TexturePack.SQUIRREL_PACK = TexturePack( EAGLE='🦅', CHESTPLATE='🦺', HELMET='⛑️', + RING_OF_MORE_EXPERIENCE='💍', + RING_OF_CRITICAL_DAMAGE='💍', ) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 05654e9..336d3e5 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -108,7 +108,7 @@ class Item(Entity): @staticmethod def get_all_items() -> list: return [BodySnatchPotion, Bomb, Heart, Shield, Sword,\ - Chestplate, Helmet] + Chestplate, Helmet, RingCritical, RingXP] def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder) -> bool: """ @@ -361,3 +361,69 @@ class BodySnatchPotion(Item): self.held_by.recalculate_paths() self.held_by.inventory.remove(self) + +class Ring(Item): + """ + A class of rings that boost the player's statistics. + """ + maxhealth: int + strength: int + intelligence: int + charisma: int + dexterity: int + constitution: int + critical: int + experience: float + + def __init__(self, maxhealth: int = 0, strength: int = 0,\ + intelligence: int = 0, charisma: int = 0,\ + dexterity: int = 0, constitution: int = 0,\ + critical: int = 0, experience: float = 0, *args, **kwargs): + super().__init__(*args, **kwargs) + self.maxhealth = maxhealth + self.strength = strength + self.intelligence = intelligence + self.charisma = charisma + self.dexterity = dexterity + self.constitution = constitution + self.critical = critical + self.experience = experience + + def equip(self) -> None: + super().equip() + self.held_by.maxhealth += self.maxhealth + self.held_by.strength += self.strength + self.held_by.intelligence += self.intelligence + self.held_by.charisma += self.charisma + self.held_by.dexterity += self.dexterity + self.held_by.constitution += self.constitution + self.held_by.critical += self.critical + self.held_by.xp_buff += self.experience + + def unequip(self) -> None: + super().unequip() + self.held_by.maxhealth -= self.maxhealth + self.held_by.strength -= self.strength + self.held_by.intelligence -= self.intelligence + self.held_by.charisma -= self.charisma + self.held_by.dexterity -= self.dexterity + self.held_by.constitution -= self.constitution + self.held_by.critical -= self.critical + self.held_by.xp_buff -= self.experience + + def save_state(self) -> dict: + d = super().save_state() + d["constitution"] = self.constitution + return d + +class RingCritical(Ring): + def __init__(self, name: str = "ring_of_critical_damage", price: int = 15, + critical: int = 20, *args, **kwargs): + super().__init__(name=name, price=price, critical=critical, \ + *args, **kwargs) + +class RingXP(Ring): + def __init__(self, name: str = "ring_of_more_experience", price: int = 25, + experience: float = 2, *args, **kwargs): + super().__init__(name=name, price=price, experience=experience, \ + *args, **kwargs) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index a0ddd6c..854bd93 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -16,6 +16,7 @@ class Player(InventoryHolder, FightingEntity): """ current_xp: int = 0 max_xp: int = 10 + xp_buff: float = 1 paths: Dict[Tuple[int, int], Tuple[int, int]] equipped_main: Optional[Item] equipped_secondary: Optional[Item] @@ -29,7 +30,7 @@ class Player(InventoryHolder, FightingEntity): hazel: int = 42, equipped_main: Optional[Item] = None, equipped_armor: Optional[Item] = None, critical: int = 5,\ equipped_secondary: Optional[Item] = None, \ - equipped_helmet: Optional[Item] = None, \ + equipped_helmet: Optional[Item] = None, xp_buff: float = 1,\ *args, **kwargs) -> None: super().__init__(name=name, maxhealth=maxhealth, strength=strength, intelligence=intelligence, charisma=charisma, @@ -37,6 +38,7 @@ class Player(InventoryHolder, FightingEntity): level=level, critical=critical, *args, **kwargs) self.current_xp = current_xp self.max_xp = max_xp + self.xp_buff = xp_buff self.inventory = self.translate_inventory(inventory or []) self.paths = dict() self.hazel = hazel @@ -82,7 +84,7 @@ class Player(InventoryHolder, FightingEntity): Add some experience to the player. If the required amount is reached, level up. """ - self.current_xp += xp + self.current_xp += int(xp*self.xp_buff) self.level_up() def remove_from_inventory(self, obj: Item) -> None: diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index cc5b36e..b6a3a51 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -374,7 +374,7 @@ class Entity: TeddyBear, GiantSeaEagle from squirrelbattle.entities.friendly import Merchant, Sunflower from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \ - Heart, Sword, Shield, Chestplate, Helmet + Heart, Sword, Shield, Chestplate, Helmet, RingCritical, RingXP return { "Tiger": Tiger, "Bomb": Bomb, @@ -391,6 +391,8 @@ class Entity: "Shield": Shield, "Chestplate": Chestplate, "Helmet": Helmet, + "RingCritical": RingCritical, + "RingXP": RingXP, } def save_state(self) -> dict: From 10788a24ecaadb91d9e8aacb2f71177947d36dbb Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 6 Jan 2021 13:53:42 +0100 Subject: [PATCH 63/95] Pin Sphinx version --- docs/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index cbf1e36..540deda 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx -sphinx-rtd-theme +sphinx>=3.3 +sphinx-rtd-theme>=0.5 From d06a405120acb02ae80e9ee70bde8b6d72d9b45c Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 6 Jan 2021 14:55:16 +0100 Subject: [PATCH 64/95] 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 65/95] 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 15ef6af998ebc6afdbe3947288af5c606d908682 Mon Sep 17 00:00:00 2001 From: Eichhornchen Date: Wed, 6 Jan 2021 15:49:54 +0100 Subject: [PATCH 66/95] Put the texturepack in alphabetical order again. --- squirrelbattle/display/texturepack.py | 36 +++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index fbd5273..10b9532 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -20,26 +20,26 @@ class TexturePack: BODY_SNATCH_POTION: str BOMB: str - HEART: str - HEDGEHOG: str + CHESTPLATE: str + EAGLE: str EMPTY: str FLOOR: str HAZELNUT: str + HEART: str + HEDGEHOG: str + HELMET: str MERCHANT: str PLAYER: str RABBIT: str + RING_OF_CRITICAL_DAMAGE: str + RING_OF_MORE_EXPERIENCE: str + SHIELD: str SUNFLOWER: str SWORD: str TEDDY_BEAR: str TIGER: str TRUMPET: str WALL: str - EAGLE: str - SHIELD: str - CHESTPLATE: str - HELMET: str - RING_OF_MORE_EXPERIENCE: str - RING_OF_CRITICAL_DAMAGE: str ASCII_PACK: "TexturePack" SQUIRREL_PACK: "TexturePack" @@ -71,15 +71,20 @@ TexturePack.ASCII_PACK = TexturePack( BODY_SNATCH_POTION='S', BOMB='ç', + CHESTPLATE='(', + EAGLE='µ', EMPTY=' ', EXPLOSION='%', FLOOR='.', HAZELNUT='¤', HEART='❤', HEDGEHOG='*', + HELMET='0', MERCHANT='M', PLAYER='@', RABBIT='Y', + RING_OF_CRITICAL_DAMAGE='o', + RING_OF_MORE_EXPERIENCE='o', SHIELD='D', SUNFLOWER='I', SWORD='\u2020', @@ -87,11 +92,6 @@ TexturePack.ASCII_PACK = TexturePack( TIGER='n', TRUMPET='/', WALL='#', - EAGLE='µ', - CHESTPLATE='(', - HELMET='0', - RING_OF_MORE_EXPERIENCE='o', - RING_OF_CRITICAL_DAMAGE='o', ) TexturePack.SQUIRREL_PACK = TexturePack( @@ -104,15 +104,20 @@ TexturePack.SQUIRREL_PACK = TexturePack( BODY_SNATCH_POTION='🔀', BOMB='💣', + CHESTPLATE='🦺', + EAGLE='🦅', EMPTY=' ', EXPLOSION='💥', FLOOR='██', HAZELNUT='🌰', HEART='💜', HEDGEHOG='🦔', + HELMET='⛑️', PLAYER='🐿️ ️', MERCHANT='🦜', RABBIT='🐇', + RING_OF_CRITICAL_DAMAGE='💍', + RING_OF_MORE_EXPERIENCE='💍', SHIELD='🛡️ ', SUNFLOWER='🌻', SWORD='🗡️ ', @@ -120,9 +125,4 @@ TexturePack.SQUIRREL_PACK = TexturePack( TIGER='🐅', TRUMPET='🎺', WALL='🧱', - EAGLE='🦅', - CHESTPLATE='🦺', - HELMET='⛑️', - RING_OF_MORE_EXPERIENCE='💍', - RING_OF_CRITICAL_DAMAGE='💍', ) From a48e6325fe2dd48640d8f45a2c3ef2114b5a510e Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 6 Jan 2021 15:55:44 +0100 Subject: [PATCH 67/95] 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 68/95] 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 69/95] 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 70/95] 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 ae505166b7815ef4acc4fb5d552f7367643492ef Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 6 Jan 2021 17:39:13 +0100 Subject: [PATCH 71/95] Disable critical hits during tests --- squirrelbattle/interfaces.py | 4 ++-- squirrelbattle/tests/entities_test.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index b6a3a51..e4557d1 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -444,10 +444,10 @@ class FightingEntity(Entity): """ Deals damage to the opponent, based on the stats """ - diceroll = randint(0, 100) + diceroll = randint(1, 100) damage = self.strength string = " " - if diceroll <= self.critical: # It is a critical hit + if diceroll <= self.critical: # It is a critical hit damage *= 4 string = _(" It's a critical hit! ") return _("{name} hits {opponent}.")\ diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 1d7a7a9..97f0ef8 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -77,6 +77,7 @@ class TestEntities(unittest.TestCase): {self.player.name.capitalize()} takes {entity.strength} damage.") # Fight the rabbit + self.player.critical = 0 old_health = entity.health self.player.move_down() self.assertEqual(entity.health, old_health - self.player.strength) From b403bc47842f891af2ce7e6694eef6cd4b9fc56f Mon Sep 17 00:00:00 2001 From: Eichhornchen Date: Wed, 6 Jan 2021 17:48:03 +0100 Subject: [PATCH 72/95] documentation --- docs/display/logs.rst | 2 +- docs/display/map.rst | 4 +++- docs/display/menu.rst | 12 ++++++++++- docs/display/stats.rst | 24 +++++++++++++++++++++- docs/entities/friendly.rst | 24 ++++++++++++++++++++-- docs/entities/items.rst | 42 +++++++++++++++++++++++++++++++++++--- docs/entities/monsters.rst | 15 +++++++++++++- docs/settings.rst | 36 +++++++++++++++++++++++++++++++- docs/texture-pack.rst | 17 ++++++++++++++- 9 files changed, 164 insertions(+), 12 deletions(-) diff --git a/docs/display/logs.rst b/docs/display/logs.rst index 3ad130d..9e925a3 100644 --- a/docs/display/logs.rst +++ b/docs/display/logs.rst @@ -1,4 +1,4 @@ Affichage de l'historique ========================= -Pas encore documenté. +L'historique des actions est affiché en bas de l'écran. À chaque action d'une entité, comme frapper quelqu'un, ou lorsque le joueur parle à une entité, cela s'affiche dans l'historique. diff --git a/docs/display/map.rst b/docs/display/map.rst index 1daa85a..749b32c 100644 --- a/docs/display/map.rst +++ b/docs/display/map.rst @@ -1,4 +1,6 @@ Affichage de la carte ===================== -Pas encore documenté. +La carte s'affiche dans la partie en haut à gauche de l'écran, sur la plus grande partie de l'écran. On affiche les tuiles une par une, selon le texture pack sélectionné. La map est actualisée à chaque action d'une entité. + +L'afffichage de la carte suit les déplacements du joueur. diff --git a/docs/display/menu.rst b/docs/display/menu.rst index 84be36c..30c4e98 100644 --- a/docs/display/menu.rst +++ b/docs/display/menu.rst @@ -1,4 +1,14 @@ Affichage des menus =================== -Pas encore documenté. +Les menus sont affichés dans une boîte. On peut naviguer dedans avec les flèches haut et bas, +et valider avec la touche entrée. + +Il y a plusieurs menus dans le jeu : + +* Le main menu, qui s'affiche au lancement du jeu. +* Le menu des paramètres : si on sélectionne un choix de touche et qu'on appuie sur entrée, on peut ensuite appuyer sur une touche pour remplacer la touche utilisée. +* Le menu des crédits : ce menu fonctionne avec la souris. En cliquant on affiche une image. +* Le menu d'inventaire : dans l'inventaire, on peut utiliser les touches pour utiliser un item ou l'équiper... +* Le menu de vente : on peut utiliser les touches gauche et droite pour switcher entre l'inventaire du joueur et celui du marchand. +* Menu des warnings : Pas vraiment un menu, mais affiche juste un message dans une petite boite pour prévenir le joueur que quelquechose ne va pas. diff --git a/docs/display/stats.rst b/docs/display/stats.rst index 1b5f697..e993c7f 100644 --- a/docs/display/stats.rst +++ b/docs/display/stats.rst @@ -1,4 +1,26 @@ Affichage des statistiques ========================== -Pas encore documenté. +.. _Hazel: ../index.html + +Les statistiques du joueur sont affichées en haut à droite de l'écran +et séparées du reste de l'affichage par une barre verticale. + +Les informations affichées sont : + +* **LVL** - le niveau du joueur +* **EXP** - la quantité d'expérience que le joueur a gagné et combien il lui en faut avant le prochain niveau. +* **HP** - la quantité de vie que le joueur a actuellement et combien il peut en avoir au maximum. +* **STR** - la force du joueur. +* **INT** - l'intelligence du joueur. +* **CHR** - le charisme du joueur. +* **DEX** - la dextérité du joueur. +* **CON** - la constitution du joueur. +* **CRI** - le pourcentage de chance de coup critique. +* **Inventory** - le contenu de l'inventaire du joueur. +* **Equipped main** - l'objet équipé dans la main principale. +* **Equipped secondary** - l'objet équipé dans la main secondaire. +* **Equipped armor** - le plastron porté par le joueur. +* **Equipped helmet** - le casque porté par le joueur. +* **Hazel** - le nombre d'Hazel_ que le joueur possède. +* **Vous êtes mort** - Éventuellement, si le joueur est mort. \ No newline at end of file diff --git a/docs/entities/friendly.rst b/docs/entities/friendly.rst index 11eea62..9fca40f 100644 --- a/docs/entities/friendly.rst +++ b/docs/entities/friendly.rst @@ -12,7 +12,12 @@ Il est possible d'interagir avec ces entités. En s'approchant d'elles, en appuyant sur la touche ``T`` suivie de la direction où regarder, un échange débute. -On dénombre actuellement 2 types d'entités pacifiques : +Si l'on s'adresse à un marchand, on devrait voir à l'écran l'inventaire du joueur +et l'inventaire du marchand. Les flèches haut et bas permettent de sélectionner +un objet, les touches droite et gauche de passer d'un inventaire à l'autre, et la +touche entrée valide l'action. + +On dénombre actuellement 3 types d'entités pacifiques : Tournesol --------- @@ -41,10 +46,25 @@ Les prix sont fixés : * Coeur : 3 Hazels * Potion d'arrachage de corps : 14 Hazels * Épée : 20 Hazels -* Bouclier : 18 Hazels +* Bouclier : 16 Hazels +* Casque : 18 Hazels +* Plastron : 30 Hazels Le marchand commence avec 75 Hazels en sa possession, contre 42 pour le joueur. Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``M``. Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🦜``. + +Trompette +--------- + +Son nom est fixé à 'trumpet'. Une trompette est un familier, c'est à dire que +c'est une entité attaquante qui suit globalement le joueurs et attaque les monstres +qui se rapprochent trop du joueur. + +Elle a 20 point de vie et une attaque de 3. + +Dans le `pack de textures`_ ASCII, elle est représentée par le caractère ``/``. + +Dans le `pack de textures`_ écureuil, elle est représentée par l'émoji ``🎺``. \ No newline at end of file diff --git a/docs/entities/items.rst b/docs/entities/items.rst index 918586c..e4f1c0a 100644 --- a/docs/entities/items.rst +++ b/docs/entities/items.rst @@ -19,7 +19,7 @@ Un objet dispose de deux paramètres : Si l'objet est dans l'inventaire, renvoie son propriétaire. -Deux types d'objets sont pour l'instant présents : +Il y a plusieurs types d'objets : Bombe @@ -79,7 +79,43 @@ Bouclier -------- Le bouclier est un objet que l'on peut trouver uniquement par achat auprès d'un -marchand pour le coût de 18 Hazels. Une fois équipé, le bouclier ajoute 2 de -constitution à son porteur, le permettant de parer plus de coups. +marchand pour le coût de 16 Hazels. Il s'équippe dans la main secondaire. +Une fois équipé, le bouclier ajoute 1 de +constitution à son porteur, lui permettant de parer mieux les coups. Il est représenté par les caractères ``D`` et ``🛡️``. + +Casque +------ + +Le casque est un objet que l'on peut trouver uniquement par achat auprès d'un +marchand pour le coût de 18 Hazels. Il s'équippe sur la tête. +Une fois équipé, le casque ajoute 2 de +constitution à son porteur, lui permettant de prendre moins de dêgats. + +Il est représenté par les caractères ``0`` et ``⛑️``. + +Plastron +-------- + +Le plastron est un objet que l'on peut trouver uniquement par achat auprès d'un +marchand pour le coût de 30 Hazels. Il s'équippe sur le corps. +Une fois équipé, le casque ajoute 4 de +constitution à son porteur, lui permettant de prendre moins de dêgats. + +Il est représenté par les caractères ``(`` et ``🦺``. + +Anneau +------ + +L'anneau est un objet que l'on peut trouver uniquement par achat auprès d'un +marchand. Il s'équippe sur la main secondaire. +Une fois équipé, l'anneau ajoute un bonus à une ou plusieurs statistiques du +joueur, améliorant sa capacité à se débarasser des monstres. + +Il y a plusieurs types d'anneaux : + +* **Anneau de coup critique**, qui augmente la chance de coup critique de 20%. Il coute 15 Hazels. +* **Anneau de gain d'expérience amélioré**, qui multiplie le gain d'expérience du joueur par 2. Il coûte 25 Hazels. + +Un anneau est représenté par les caractères ``o`` et ``💍``. \ No newline at end of file diff --git a/docs/entities/monsters.rst b/docs/entities/monsters.rst index b6f287c..31735bd 100644 --- a/docs/entities/monsters.rst +++ b/docs/entities/monsters.rst @@ -13,7 +13,7 @@ au plus vite sur le joueur pour le frapper selon l'algorithme de Dijkstra, et s'il est suffisamment proche frappe le joueur et lui fait autant de dégâts qu'il n'a de force. -On dénombre actuellement 4 types de monstres : +On dénombre actuellement 5 types de monstres : Hérisson -------- @@ -40,6 +40,8 @@ Lapin Son nom est fixé à `rabbit`. Il a par défaut une force à **1** et **15** points de vie. +Il a une chance de coup critique de 30%. + Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``Y``. Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🐇``. @@ -53,3 +55,14 @@ Son nom est fixé à `teddy_bear`. Il n'a pas de force et **50** points de vie. Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``8``. Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🧸``. + + +Pyguargue +--------- +Son nom est fixé à `eagle`. Il a par défaut une force à **1000** et **5000** points de vie. + +Il s'agit d'un boss difficilement tuable, qui apparait plus rarement que les autres monstres. + +Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``µ``. + +Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🦅``. diff --git a/docs/settings.rst b/docs/settings.rst index a8644d4..60fa5c1 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -1,4 +1,38 @@ Paramètres ========== -Pas encore documenté. +.. _pack de textures: texture-pack.html + +Il est possible de changer les touches utilisées dans le jeu dans le menu des paramètres. + +On peut aussi changer le `pack de textures`_ utilisé. + +Touches +------- + +Les touches utilisées de base sont : + +* **Aller vers le haut** : z +* **Aller vers le haut (secondaire)** : ↑ +* **Aller vers le bas** : s +* **Aller vers le bas (secondaire)** : ↓ +* **Aller à droite** : d +* **Aller à droite (secondaire)** : → +* **Aller à gauche** : q +* **Aller à gauche (secondaire)** : ← +* **Valider le choix** : Entrée +* **Inventaire** : i +* **Utiliser un objet** : u +* **Équiper un objet** : e +* **Lacher un objet** : r +* **Parler** : t +* **Attendre** : w + +Autres +------ + +.. _ascii: texture-pack.html#Pack ASCII +.. _squirrel: texture-pack.html#Pack Écureuil + +* **Texture pack utilisé** : parmi ascii_ et squirrel_ +* **Langue utilisée** : parmi anglais, français, espagnol, allemand \ No newline at end of file diff --git a/docs/texture-pack.rst b/docs/texture-pack.rst index c81e62a..072b6ff 100644 --- a/docs/texture-pack.rst +++ b/docs/texture-pack.rst @@ -21,6 +21,11 @@ Pack de textures .. _Épée: entities/items.html#epee .. _Bouclier: entities/items.html#bouclier .. _Hazel: ../index.html +.. _Plastron: ../entities/items.html#plastron +.. _Pyguargue: ../entities/monsters.html#Pyguargue +.. _Casque: ../entities/items.html#Casque +.. _Anneau: ../entities/items.html#Anneau +.. _Trompette: ../entities/items.html#Trompette Chaque entité_ et chaque tuile_ de la carte_ est associé à un caractère pour être affiché dans le terminal. Cependant, afin de pouvoir proposer plusieurs @@ -58,6 +63,11 @@ Chaque tuile fait un caractère de large. * Épée_ : ``†`` * Bouclier_ : ``D`` * Hazel_ : ``¤`` + * Plastron_ : ``(`` + * Pyguargue_ : ``µ`` + * Casque_ : ``0`` + * Anneau_ : ``o`` + * Trompette_ : ``/`` Pack Écureuil @@ -83,4 +93,9 @@ Chaque tuile fait 2 caractères de large pour afficher les émojis proprement. * `Potion d'arrachage de corps`_ : ``🔀`` * Épée_ : ``🗡️`` * Bouclier_ : ``🛡️`` - * Hazel : ``🌰`` + * Hazel_ : ``🌰`` + * Plastron_ : ``🦺`` + * Pyguargue_ : ``🦅`` + * Casque_ : ``⛑️`` + * Anneau_ : ``💍`` + * Trompette_ : ``🎺`` From 093c105120290c038b85c44a79155679f64e9751 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 6 Jan 2021 17:54:43 +0100 Subject: [PATCH 73/95] 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 5ef12bef3d111ca546e6f5bd8fc952544a44106a Mon Sep 17 00:00:00 2001 From: Eichhornchen Date: Wed, 6 Jan 2021 17:57:23 +0100 Subject: [PATCH 74/95] Changed the prices of armor --- squirrelbattle/entities/items.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index a9e0f13..1954204 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -311,7 +311,7 @@ class Shield(Armor): """ def __init__(self, name: str = "shield", constitution: int = 2,\ - price: int = 6, *args, **kwargs): + price: int = 16, *args, **kwargs): super().__init__(name=name, constitution=constitution, *args, **kwargs) class Helmet(Armor): @@ -320,7 +320,7 @@ class Helmet(Armor): """ def __init__(self, name: str = "helmet", constitution: int = 2, \ - price: int = 8, *args, **kwargs): + price: int = 18, *args, **kwargs): super().__init__(name=name, constitution=constitution, *args, **kwargs) class Chestplate(Armor): @@ -329,7 +329,7 @@ class Chestplate(Armor): """ def __init__(self, name: str = "chestplate", constitution: int = 4,\ - price: int = 15, *args, **kwargs): + price: int = 30, *args, **kwargs): super().__init__(name=name, constitution=constitution, *args, **kwargs) From a8c0c197ed86b2b9947ac0e906f5b153d6126a13 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 6 Jan 2021 18:02:58 +0100 Subject: [PATCH 75/95] Linting --- squirrelbattle/display/statsdisplay.py | 9 +++++---- squirrelbattle/entities/items.py | 26 ++++++++++++++++---------- squirrelbattle/entities/monsters.py | 7 +++++-- squirrelbattle/entities/player.py | 8 ++++---- squirrelbattle/interfaces.py | 4 ++-- 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index e24c969..c74b63f 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -28,7 +28,7 @@ class StatsDisplay(Display): string3 = "STR {}\nINT {}\nCHR {}\nDEX {}\nCON {}\nCRI {}%"\ .format(self.player.strength, self.player.intelligence, self.player.charisma, - self.player.dexterity, self.player.constitution,\ + self.player.dexterity, self.player.constitution, self.player.critical) self.addstr(self.pad, 3, 0, string3) @@ -54,15 +54,16 @@ class StatsDisplay(Display): if self.player.equipped_secondary: self.addstr(self.pad, 11, 0, _("Equipped secondary:") + " " - f"{self.pack[self.player.equipped_secondary.name.upper()]}") + + self.pack[self.player.equipped_secondary + .name.upper()]) if self.player.equipped_armor: self.addstr(self.pad, 12, 0, _("Equipped chestplate:") + " " - f"{self.pack[self.player.equipped_armor.name.upper()]}") + + self.pack[self.player.equipped_armor.name.upper()]) if self.player.equipped_helmet: self.addstr(self.pad, 13, 0, _("Equipped helmet:") + " " - f"{self.pack[self.player.equipped_helmet.name.upper()]}") + + self.pack[self.player.equipped_helmet.name.upper()]) self.addstr(self.pad, 14, 0, f"{self.pack.HAZELNUT} " f"x{self.player.hazel}") diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 336d3e5..3eb3530 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -107,7 +107,7 @@ class Item(Entity): @staticmethod def get_all_items() -> list: - return [BodySnatchPotion, Bomb, Heart, Shield, Sword,\ + return [BodySnatchPotion, Bomb, Heart, Shield, Sword, Chestplate, Helmet, RingCritical, RingXP] def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder) -> bool: @@ -283,7 +283,7 @@ class Armor(Item): Class of items that increase the player's constitution. """ constitution: int - + def __init__(self, constitution: int, *args, **kwargs): super().__init__(*args, **kwargs) self.constitution = constitution @@ -301,30 +301,33 @@ class Armor(Item): d["constitution"] = self.constitution return d + class Shield(Armor): """ Class of shield items, they can be equipped in the other hand. """ - def __init__(self, name: str = "shield", constitution: int = 2,\ + def __init__(self, name: str = "shield", constitution: int = 2, price: int = 6, *args, **kwargs): super().__init__(name=name, constitution=constitution, *args, **kwargs) + class Helmet(Armor): """ Class of helmet items, they can be equipped on the head. """ - def __init__(self, name: str = "helmet", constitution: int = 2, \ + def __init__(self, name: str = "helmet", constitution: int = 2, price: int = 8, *args, **kwargs): super().__init__(name=name, constitution=constitution, *args, **kwargs) + class Chestplate(Armor): """ Class of chestplate items, they can be equipped on the body. """ - def __init__(self, name: str = "chestplate", constitution: int = 4,\ + def __init__(self, name: str = "chestplate", constitution: int = 4, price: int = 15, *args, **kwargs): super().__init__(name=name, constitution=constitution, *args, **kwargs) @@ -362,6 +365,7 @@ class BodySnatchPotion(Item): self.held_by.inventory.remove(self) + class Ring(Item): """ A class of rings that boost the player's statistics. @@ -375,9 +379,9 @@ class Ring(Item): critical: int experience: float - def __init__(self, maxhealth: int = 0, strength: int = 0,\ - intelligence: int = 0, charisma: int = 0,\ - dexterity: int = 0, constitution: int = 0,\ + def __init__(self, maxhealth: int = 0, strength: int = 0, + intelligence: int = 0, charisma: int = 0, + dexterity: int = 0, constitution: int = 0, critical: int = 0, experience: float = 0, *args, **kwargs): super().__init__(*args, **kwargs) self.maxhealth = maxhealth @@ -416,14 +420,16 @@ class Ring(Item): d["constitution"] = self.constitution return d + class RingCritical(Ring): def __init__(self, name: str = "ring_of_critical_damage", price: int = 15, critical: int = 20, *args, **kwargs): - super().__init__(name=name, price=price, critical=critical, \ + super().__init__(name=name, price=price, critical=critical, *args, **kwargs) + class RingXP(Ring): def __init__(self, name: str = "ring_of_more_experience", price: int = 25, experience: float = 2, *args, **kwargs): - super().__init__(name=name, price=price, experience=experience, \ + super().__init__(name=name, price=price, experience=experience, *args, **kwargs) diff --git a/squirrelbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py index bcdff11..d93b24b 100644 --- a/squirrelbattle/entities/monsters.py +++ b/squirrelbattle/entities/monsters.py @@ -87,9 +87,11 @@ class Rabbit(Monster): A rabbit monster """ def __init__(self, name: str = "rabbit", strength: int = 1, - maxhealth: int = 15, critical: int = 30, *args, **kwargs) -> None: + maxhealth: int = 15, critical: int = 30, + *args, **kwargs) -> None: super().__init__(name=name, strength=strength, - maxhealth=maxhealth, critical=critical, *args, **kwargs) + maxhealth=maxhealth, critical=critical, + *args, **kwargs) class TeddyBear(Monster): @@ -101,6 +103,7 @@ class TeddyBear(Monster): super().__init__(name=name, strength=strength, maxhealth=maxhealth, *args, **kwargs) + class GiantSeaEagle(Monster): """ An eagle boss diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 854bd93..63d6536 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -28,9 +28,9 @@ class Player(InventoryHolder, FightingEntity): dexterity: int = 1, constitution: int = 1, level: int = 1, current_xp: int = 0, max_xp: int = 10, inventory: list = None, hazel: int = 42, equipped_main: Optional[Item] = None, - equipped_armor: Optional[Item] = None, critical: int = 5,\ - equipped_secondary: Optional[Item] = None, \ - equipped_helmet: Optional[Item] = None, xp_buff: float = 1,\ + equipped_armor: Optional[Item] = None, critical: int = 5, + equipped_secondary: Optional[Item] = None, + equipped_helmet: Optional[Item] = None, xp_buff: float = 1, *args, **kwargs) -> None: super().__init__(name=name, maxhealth=maxhealth, strength=strength, intelligence=intelligence, charisma=charisma, @@ -84,7 +84,7 @@ class Player(InventoryHolder, FightingEntity): Add some experience to the player. If the required amount is reached, level up. """ - self.current_xp += int(xp*self.xp_buff) + self.current_xp += int(xp * self.xp_buff) self.level_up() def remove_from_inventory(self, obj: Item) -> None: diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index e4557d1..556051c 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -146,8 +146,8 @@ class Map: tile = self.tiles[y][x] if tile.can_walk(): break - entity = choices(Entity.get_all_entity_classes(),\ - weights = Entity.get_weights(), k=1)[0]() + entity = choices(Entity.get_all_entity_classes(), + weights=Entity.get_weights(), k=1)[0]() entity.move(y, x) self.add_entity(entity) From 1a78ad584cd4c973a9de48d0bd87c374cc165b91 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Wed, 6 Jan 2021 18:31:28 +0100 Subject: [PATCH 76/95] Move equip functions for items --- squirrelbattle/entities/items.py | 57 +++++++++++++++----------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 3eb3530..ee5f4a9 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -44,32 +44,11 @@ class Item(Entity): """ Indicates what should be done when the item is equipped. """ - if isinstance(self, Chestplate): - if self.held_by.equipped_armor: - self.held_by.equipped_armor.unequip() - self.held_by.remove_from_inventory(self) - self.held_by.equipped_armor = self - elif isinstance(self, Helmet): - if self.held_by.equipped_helmet: - self.held_by.equipped_helmet.unequip() - self.held_by.remove_from_inventory(self) - self.held_by.equipped_helmet = self - elif isinstance(self, Weapon): - if self.held_by.equipped_main: - if self.held_by.equipped_secondary: - self.held_by.equipped_secondary.unequip() - self.held_by.remove_from_inventory(self) - self.held_by.equipped_secondary = self - # For weapons, they are equipped as main only if main is empty. - else: - self.held_by.remove_from_inventory(self) - self.held_by.equipped_main = self - else: - # Other objects are only equipped as secondary. - if self.held_by.equipped_secondary: - self.held_by.equipped_secondary.unequip() - self.held_by.remove_from_inventory(self) - self.held_by.equipped_secondary = self + # Other objects are only equipped as secondary. + if self.held_by.equipped_secondary: + self.held_by.equipped_secondary.unequip() + self.held_by.remove_from_inventory(self) + self.held_by.equipped_secondary = self def unequip(self) -> None: """ @@ -257,7 +236,8 @@ class Weapon(Item): """ When a weapon is equipped, the player gains strength. """ - super().equip() + self.held_by.remove_from_inventory(self) + self.held_by.equipped_main = self self.held_by.strength += self.damage def unequip(self) -> None: @@ -309,7 +289,8 @@ class Shield(Armor): def __init__(self, name: str = "shield", constitution: int = 2, price: int = 6, *args, **kwargs): - super().__init__(name=name, constitution=constitution, *args, **kwargs) + super().__init__(name=name, constitution=constitution, price=price, + *args, **kwargs) class Helmet(Armor): @@ -319,7 +300,15 @@ class Helmet(Armor): def __init__(self, name: str = "helmet", constitution: int = 2, price: int = 8, *args, **kwargs): - super().__init__(name=name, constitution=constitution, *args, **kwargs) + super().__init__(name=name, constitution=constitution, price=price, + *args, **kwargs) + + def equip(self) -> None: + super().equip() + if self.held_by.equipped_helmet: + self.held_by.equipped_helmet.unequip() + self.held_by.remove_from_inventory(self) + self.held_by.equipped_helmet = self class Chestplate(Armor): @@ -329,7 +318,15 @@ class Chestplate(Armor): def __init__(self, name: str = "chestplate", constitution: int = 4, price: int = 15, *args, **kwargs): - super().__init__(name=name, constitution=constitution, *args, **kwargs) + super().__init__(name=name, constitution=constitution, price=price, + *args, **kwargs) + + def equip(self) -> None: + super().equip() + if self.held_by.equipped_armor: + self.held_by.equipped_armor.unequip() + self.held_by.remove_from_inventory(self) + self.held_by.equipped_armor = self class BodySnatchPotion(Item): From e9c8f43e7eb92d32090f6f80d61ae071fdbf643b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 7 Jan 2021 16:31:39 +0100 Subject: [PATCH 77/95] 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 78/95] 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 79/95] 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: From 6c6a44fb18d4d5bc329fd41018a123599c632fda Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 01:56:54 +0100 Subject: [PATCH 80/95] More tests --- squirrelbattle/entities/items.py | 22 +++---- squirrelbattle/entities/player.py | 20 +++---- squirrelbattle/game.py | 7 ++- squirrelbattle/tests/game_test.py | 99 ++++++++++++++++++++++++++++++- 4 files changed, 119 insertions(+), 29 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index ee5f4a9..7775f74 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -54,17 +54,7 @@ class Item(Entity): """ Indicates what should be done when the item is unequipped. """ - if isinstance(self, Chestplate): - self.held_by.equipped_armor = None - elif isinstance(self, Helmet): - self.held_by.equipped_helmet = None - elif isinstance(self, Weapon): - if self.held_by.equipped_main == self: - self.held_by.equipped_main = None - else: - self.held_by.equipped_secondary = None - else: - self.held_by.equipped_secondary = None + self.held_by.remove_from_inventory(self) self.held_by.add_to_inventory(self) def hold(self, holder: InventoryHolder) -> None: @@ -211,7 +201,6 @@ class Explosion(Item): """ The player can't hold any explosion. """ - pass class Weapon(Item): @@ -304,7 +293,6 @@ class Helmet(Armor): *args, **kwargs) def equip(self) -> None: - super().equip() if self.held_by.equipped_helmet: self.held_by.equipped_helmet.unequip() self.held_by.remove_from_inventory(self) @@ -322,7 +310,6 @@ class Chestplate(Armor): *args, **kwargs) def equip(self) -> None: - super().equip() if self.held_by.equipped_armor: self.held_by.equipped_armor.unequip() self.held_by.remove_from_inventory(self) @@ -414,7 +401,14 @@ class Ring(Item): def save_state(self) -> dict: d = super().save_state() + d["maxhealth"] = self.maxhealth + d["strength"] = self.strength + d["intelligence"] = self.intelligence + d["charisma"] = self.charisma + d["dexterity"] = self.dexterity d["constitution"] = self.constitution + d["critical"] = self.critical + d["experience"] = self.experience return d diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 63d6536..0d939ae 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -42,18 +42,14 @@ class Player(InventoryHolder, FightingEntity): self.inventory = self.translate_inventory(inventory or []) self.paths = dict() self.hazel = hazel - if isinstance(equipped_main, dict): - equipped_main = self.dict_to_item(equipped_main) - if isinstance(equipped_armor, dict): - equipped_armor = self.dict_to_item(equipped_armor) - if isinstance(equipped_secondary, dict): - equipped_secondary = self.dict_to_item(equipped_secondary) - if isinstance(equipped_helmet, dict): - equipped_helmet = self.dict_to_item(equipped_helmet) - self.equipped_main = equipped_main - self.equipped_armor = equipped_armor - self.equipped_secondary = equipped_secondary - self.equipped_helmet = equipped_helmet + self.equipped_main = self.dict_to_item(equipped_main) \ + if isinstance(equipped_main, dict) else equipped_main + self.equipped_armor = self.dict_to_item(equipped_armor) \ + if isinstance(equipped_armor, dict) else equipped_armor + self.equipped_secondary = self.dict_to_item(equipped_secondary) \ + if isinstance(equipped_secondary, dict) else equipped_secondary + self.equipped_helmet = self.dict_to_item(equipped_helmet) \ + if isinstance(equipped_helmet, dict) else equipped_helmet def move(self, y: int, x: int) -> None: """ diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 939325c..17adb7d 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -108,7 +108,7 @@ class Game: self.handle_key_pressed_store(key) self.display_actions(DisplayActions.REFRESH) - def handle_key_pressed_play(self, key: KeyValues) -> None: + def handle_key_pressed_play(self, key: KeyValues) -> None: # noqa: C901 """ In play mode, arrows or zqsd move the main character. """ @@ -128,7 +128,10 @@ class Game: self.state = GameMode.INVENTORY self.display_actions(DisplayActions.UPDATE) elif key == KeyValues.USE and self.player.equipped_main: - self.player.equipped_main.use() + if self.player.equipped_main: + self.player.equipped_main.use() + if self.player.equipped_secondary: + self.player.equipped_secondary.use() elif key == KeyValues.SPACE: self.state = GameMode.MAINMENU elif key == KeyValues.CHAT: diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 6751016..06ddcf6 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -2,13 +2,16 @@ # SPDX-License-Identifier: GPL-3.0-or-later import os +import random import unittest from ..bootstrap import Bootstrap from ..display.display import Display from ..display.display_manager import DisplayManager from ..entities.friendly import Merchant, Sunflower -from ..entities.items import Bomb, Heart, Sword, Explosion +from ..entities.items import Bomb, Heart, Sword, Explosion, Shield, Helmet, \ + Chestplate, RingCritical +from ..entities.monsters import GiantSeaEagle from ..entities.player import Player from ..enums import DisplayActions from ..game import Game, KeyValues, GameMode @@ -609,3 +612,97 @@ class TestGame(unittest.TestCase): # Exit the menu self.game.handle_key_pressed(KeyValues.SPACE) self.assertEqual(self.game.state, GameMode.PLAY) + + def test_equipment(self) -> None: + """ + Ensure that equipment is working. + """ + self.game.state = GameMode.INVENTORY + + # sword goes into the main equipment slot + sword = Sword() + sword.hold(self.game.player) + self.game.handle_key_pressed(KeyValues.EQUIP) + self.assertEqual(self.game.player.equipped_main, sword) + self.assertFalse(self.game.player.inventory) + + # shield goes into the secondary equipment slot + shield = Shield() + shield.hold(self.game.player) + self.game.handle_key_pressed(KeyValues.EQUIP) + self.assertEqual(self.game.player.equipped_secondary, shield) + self.assertFalse(self.game.player.inventory) + + # helmet goes into the helmet slot + helmet = Helmet() + helmet.hold(self.game.player) + self.game.handle_key_pressed(KeyValues.EQUIP) + self.assertEqual(self.game.player.equipped_helmet, helmet) + self.assertFalse(self.game.player.inventory) + + # helmet goes into the armor slot + chestplate = Chestplate() + chestplate.hold(self.game.player) + self.game.handle_key_pressed(KeyValues.EQUIP) + self.assertEqual(self.game.player.equipped_armor, chestplate) + self.assertFalse(self.game.player.inventory) + + # Use bomb + bomb = Bomb() + bomb.hold(self.game.player) + self.game.handle_key_pressed(KeyValues.EQUIP) + self.assertEqual(self.game.player.equipped_secondary, bomb) + self.assertIn(shield, self.game.player.inventory) + self.game.state = GameMode.PLAY + self.game.handle_key_pressed(KeyValues.USE) + self.assertIsNone(self.game.player.equipped_secondary) + self.game.state = GameMode.INVENTORY + self.game.handle_key_pressed(KeyValues.EQUIP) + self.assertEqual(self.game.player.equipped_secondary, shield) + self.assertFalse(self.game.player.inventory) + + # Reequip, which is useless but covers code + sword.equip() + shield.equip() + helmet.equip() + chestplate.equip() + self.game.save_state() + + # Unequip all + sword.unequip() + shield.unequip() + helmet.unequip() + chestplate.unequip() + self.assertIsNone(self.game.player.equipped_main) + self.assertIsNone(self.game.player.equipped_secondary) + self.assertIsNone(self.game.player.equipped_helmet) + self.assertIsNone(self.game.player.equipped_armor) + self.assertIn(sword, self.game.player.inventory) + self.assertIn(shield, self.game.player.inventory) + self.assertIn(helmet, self.game.player.inventory) + self.assertIn(chestplate, self.game.player.inventory) + + # Test rings + self.game.player.inventory.clear() + ring = RingCritical() + ring.hold(self.game.player) + old_critical = self.game.player.critical + self.game.handle_key_pressed(KeyValues.EQUIP) + self.assertEqual(self.game.player.critical, + old_critical + ring.critical) + self.game.save_state() + ring.unequip() + + def test_critical_hit(self) -> None: + """ + Ensure that critical hits are working. + """ + random.seed(2) # Next random.randint(1, 100) will output 8 + self.game.player.critical = 10 + sea_eagle = GiantSeaEagle() + self.game.map.add_entity(sea_eagle) + sea_eagle.move(2, 6) + old_health = sea_eagle.health + self.game.player.hit(sea_eagle) + self.assertEqual(sea_eagle.health, + old_health - self.game.player.strength * 4) From 7aeb659cf5cff18f0b69245886fcb319ecb79c0d Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 02:00:22 +0100 Subject: [PATCH 81/95] Fix french translation --- squirrelbattle/interfaces.py | 2 +- .../locale/de/LC_MESSAGES/squirrelbattle.po | 69 ++++++++++++------ .../locale/es/LC_MESSAGES/squirrelbattle.po | 69 ++++++++++++------ .../locale/fr/LC_MESSAGES/squirrelbattle.po | 71 +++++++++++++------ 4 files changed, 143 insertions(+), 68 deletions(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 556051c..bbac3a1 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -449,7 +449,7 @@ class FightingEntity(Entity): string = " " if diceroll <= self.critical: # It is a critical hit damage *= 4 - string = _(" It's a critical hit! ") + string = " " + _("It's a critical hit!") + " " return _("{name} hits {opponent}.")\ .format(name=_(self.translated_name.capitalize()), opponent=_(opponent.translated_name)) + string + \ diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 29bf10e..2765985 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-08 01:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,19 +17,44 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/display/menudisplay.py:139 +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} nimmt {amount} Schadenspunkte." + +#: 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:23 +#: squirrelbattle/tests/translations_test.py:60 +msgid "player" +msgstr "Spieler" + +#: squirrelbattle/display/statsdisplay.py:35 msgid "Inventory:" msgstr "Bestand:" #: squirrelbattle/display/statsdisplay.py:52 +msgid "Equipped main:" +msgstr "" + +#: squirrelbattle/display/statsdisplay.py:56 +msgid "Equipped secondary:" +msgstr "" + +#: squirrelbattle/display/statsdisplay.py:61 +msgid "Equipped chestplate:" +msgstr "" + +#: squirrelbattle/display/statsdisplay.py:65 +msgid "Equipped helmet:" +msgstr "" + +#: squirrelbattle/display/statsdisplay.py:72 msgid "YOU ARE DEAD" msgstr "SIE WURDEN GESTORBEN" @@ -49,20 +74,20 @@ msgstr "Die Sonne ist warm heute" #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. -#: squirrelbattle/entities/items.py:151 +#: squirrelbattle/entities/items.py:163 msgid "Bomb is exploding." msgstr "Die Bombe explodiert." -#: squirrelbattle/entities/items.py:248 +#: squirrelbattle/entities/items.py:344 #, python-brace-format 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:228 squirrelbattle/tests/game_test.py:595 msgid "The buyer does not have enough money" msgstr "Der Kaufer hat nicht genug Geld" -#: squirrelbattle/game.py:249 +#: squirrelbattle/game.py:271 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -70,7 +95,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:279 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -78,7 +103,7 @@ msgstr "" "Auf dieser Karte wurde kein Spieler gefunden!\n" "Vielleicht sind Sie gestorben?" -#: squirrelbattle/game.py:277 +#: squirrelbattle/game.py:299 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -86,22 +111,26 @@ msgstr "" "Die JSON-Datei ist nicht korrekt.\n" "Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." -#: squirrelbattle/interfaces.py:429 +#: squirrelbattle/interfaces.py:452 +msgid "It's a critical hit!" +msgstr "" + +#: squirrelbattle/interfaces.py:453 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} schlägt {opponent}." -#: squirrelbattle/interfaces.py:441 +#: squirrelbattle/interfaces.py:466 #, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} nimmt {amount} Schadenspunkte." +msgid "{name} takes {damage} damage." +msgstr "" -#: squirrelbattle/interfaces.py:443 +#: squirrelbattle/interfaces.py:468 #, python-brace-format msgid "{name} dies." msgstr "{name} stirbt." -#: squirrelbattle/interfaces.py:477 +#: squirrelbattle/interfaces.py:502 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} hat gesagt: {message}" @@ -110,8 +139,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:361 squirrelbattle/tests/game_test.py:364 +#: squirrelbattle/tests/game_test.py:367 squirrelbattle/tests/game_test.py:370 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Neu Spiel" @@ -204,10 +233,6 @@ msgstr "Textur-Packung" msgid "Language" msgstr "Sprache" -#: squirrelbattle/tests/translations_test.py:60 -msgid "player" -msgstr "Spieler" - #: squirrelbattle/tests/translations_test.py:62 msgid "hedgehog" msgstr "Igel" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index 8a7bc3e..d4d34db 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-08 01:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,19 +17,44 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/display/menudisplay.py:139 +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} recibe {amount} daño." + +#: 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:23 +#: squirrelbattle/tests/translations_test.py:60 +msgid "player" +msgstr "jugador" + +#: squirrelbattle/display/statsdisplay.py:35 msgid "Inventory:" msgstr "Inventorio :" #: squirrelbattle/display/statsdisplay.py:52 +msgid "Equipped main:" +msgstr "" + +#: squirrelbattle/display/statsdisplay.py:56 +msgid "Equipped secondary:" +msgstr "" + +#: squirrelbattle/display/statsdisplay.py:61 +msgid "Equipped chestplate:" +msgstr "" + +#: squirrelbattle/display/statsdisplay.py:65 +msgid "Equipped helmet:" +msgstr "" + +#: squirrelbattle/display/statsdisplay.py:72 msgid "YOU ARE DEAD" msgstr "ERES MUERTO" @@ -48,20 +73,20 @@ msgstr "El sol está caliente hoy" #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. -#: squirrelbattle/entities/items.py:151 +#: squirrelbattle/entities/items.py:163 msgid "Bomb is exploding." msgstr "La bomba está explotando." -#: squirrelbattle/entities/items.py:248 +#: squirrelbattle/entities/items.py:344 #, python-brace-format 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:228 squirrelbattle/tests/game_test.py:595 msgid "The buyer does not have enough money" msgstr "El comprador no tiene suficiente dinero" -#: squirrelbattle/game.py:249 +#: squirrelbattle/game.py:271 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -69,7 +94,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:279 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -77,7 +102,7 @@ msgstr "" "No jugador encontrado sobre la carta !\n" "¿ Quizas murió ?" -#: squirrelbattle/game.py:277 +#: squirrelbattle/game.py:299 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -85,22 +110,26 @@ msgstr "" "El JSON archivo no es correcto.\n" "Su guarda parece corrupta. Fue eliminada." -#: squirrelbattle/interfaces.py:429 +#: squirrelbattle/interfaces.py:452 +msgid "It's a critical hit!" +msgstr "" + +#: squirrelbattle/interfaces.py:453 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} golpea a {opponent}." -#: squirrelbattle/interfaces.py:441 +#: squirrelbattle/interfaces.py:466 #, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} recibe {amount} daño." +msgid "{name} takes {damage} damage." +msgstr "" -#: squirrelbattle/interfaces.py:443 +#: squirrelbattle/interfaces.py:468 #, python-brace-format msgid "{name} dies." msgstr "{name} se muere." -#: squirrelbattle/interfaces.py:477 +#: squirrelbattle/interfaces.py:502 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} dijo : {message}" @@ -109,8 +138,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:361 squirrelbattle/tests/game_test.py:364 +#: squirrelbattle/tests/game_test.py:367 squirrelbattle/tests/game_test.py:370 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Nuevo partido" @@ -203,10 +232,6 @@ msgstr "Paquete de texturas" msgid "Language" msgstr "Languaje" -#: squirrelbattle/tests/translations_test.py:60 -msgid "player" -msgstr "jugador" - #: squirrelbattle/tests/translations_test.py:62 msgid "hedgehog" msgstr "erizo" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index ffbfdce..a9385a6 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-08 01:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,19 +17,44 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/display/menudisplay.py:139 +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} prend {amount} points de dégât." + +#: 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:23 +#: squirrelbattle/tests/translations_test.py:60 +msgid "player" +msgstr "joueur" + +#: squirrelbattle/display/statsdisplay.py:35 msgid "Inventory:" msgstr "Inventaire :" #: squirrelbattle/display/statsdisplay.py:52 +msgid "Equipped main:" +msgstr "Équipement principal :" + +#: squirrelbattle/display/statsdisplay.py:56 +msgid "Equipped secondary:" +msgstr "Équipement secondaire :" + +#: squirrelbattle/display/statsdisplay.py:61 +msgid "Equipped chestplate:" +msgstr "Plastron équipé :" + +#: squirrelbattle/display/statsdisplay.py:65 +msgid "Equipped helmet:" +msgstr "Casque équipé :" + +#: squirrelbattle/display/statsdisplay.py:72 msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" @@ -49,20 +74,20 @@ msgstr "Le soleil est chaud aujourd'hui" #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. -#: squirrelbattle/entities/items.py:151 +#: squirrelbattle/entities/items.py:163 msgid "Bomb is exploding." msgstr "La bombe explose." -#: squirrelbattle/entities/items.py:248 +#: squirrelbattle/entities/items.py:344 #, python-brace-format 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:228 squirrelbattle/tests/game_test.py:595 msgid "The buyer does not have enough money" msgstr "L'acheteur n'a pas assez d'argent" -#: squirrelbattle/game.py:249 +#: squirrelbattle/game.py:271 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -70,7 +95,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:279 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -78,7 +103,7 @@ msgstr "" "Aucun joueur n'a été trouvé sur la carte !\n" "Peut-être êtes-vous mort ?" -#: squirrelbattle/game.py:277 +#: squirrelbattle/game.py:299 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -86,22 +111,26 @@ 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:452 +msgid "It's a critical hit!" +msgstr "C'est un coup critique !" + +#: squirrelbattle/interfaces.py:453 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} frappe {opponent}." -#: squirrelbattle/interfaces.py:441 +#: squirrelbattle/interfaces.py:466 #, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} prend {amount} points de dégât." +msgid "{name} takes {damage} damage." +msgstr "{name} prend {damage} dégâts." -#: squirrelbattle/interfaces.py:443 +#: squirrelbattle/interfaces.py:468 #, python-brace-format msgid "{name} dies." msgstr "{name} meurt." -#: squirrelbattle/interfaces.py:477 +#: squirrelbattle/interfaces.py:502 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} a dit : {message}" @@ -110,8 +139,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:361 squirrelbattle/tests/game_test.py:364 +#: squirrelbattle/tests/game_test.py:367 squirrelbattle/tests/game_test.py:370 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Nouvelle partie" @@ -204,10 +233,6 @@ msgstr "Pack de textures" msgid "Language" msgstr "Langue" -#: squirrelbattle/tests/translations_test.py:60 -msgid "player" -msgstr "joueur" - #: squirrelbattle/tests/translations_test.py:62 msgid "hedgehog" msgstr "hérisson" @@ -242,7 +267,7 @@ msgstr "bombe" #: squirrelbattle/tests/translations_test.py:71 msgid "explosion" -msgstr "" +msgstr "explosion" #: squirrelbattle/tests/translations_test.py:72 msgid "heart" From affc1bae59858a0567e19a1ba0c5e245fef57b01 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 02:15:13 +0100 Subject: [PATCH 82/95] Fix merge --- squirrelbattle/entities/player.py | 3 --- squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po | 4 ---- squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po | 4 ---- squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po | 4 ---- 4 files changed, 15 deletions(-) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 8d7898c..615dfd5 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -2,10 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from random import randint -<<<<<<< squirrelbattle/entities/player.py from typing import Dict, Optional, Tuple -======= ->>>>>>> squirrelbattle/entities/player.py from .items import Item from ..interfaces import FightingEntity, InventoryHolder diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 9c5dbc4..20aa2a7 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -247,10 +247,6 @@ msgstr "Textur-Packung" msgid "Language" msgstr "Sprache" -#: squirrelbattle/tests/translations_test.py:62 -msgid "player" -msgstr "Spieler" - #: squirrelbattle/tests/translations_test.py:64 msgid "hedgehog" msgstr "Igel" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index 74f6a65..0bfdac6 100644 --- a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po @@ -246,10 +246,6 @@ msgstr "Paquete de texturas" msgid "Language" msgstr "Languaje" -#: squirrelbattle/tests/translations_test.py:62 -msgid "player" -msgstr "jugador" - #: squirrelbattle/tests/translations_test.py:64 msgid "hedgehog" msgstr "erizo" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 5028b07..42172c3 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -247,10 +247,6 @@ msgstr "Pack de textures" msgid "Language" msgstr "Langue" -#: squirrelbattle/tests/translations_test.py:62 -msgid "player" -msgstr "joueur" - #: squirrelbattle/tests/translations_test.py:64 msgid "hedgehog" msgstr "hérisson" From 6673b67ffee5b1ca2cc122e9d7454ba90147d1a0 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 8 Jan 2021 10:58:40 +0100 Subject: [PATCH 83/95] Repaired spawn of trumpets. --- squirrelbattle/interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 1b84eb2..92c8aa8 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -437,7 +437,7 @@ class Entity: be used to spawn random entities with a certain probability. """ return [3, 5, 6, 5, 5, 5, - 5, 4, 4, 1] + 5, 4, 4, 1, 2] @staticmethod def get_all_entity_classes_in_a_dict() -> dict: From ac8c7a0a4c040d6ce2e774df49f921fa12a9e000 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 11:07:38 +0100 Subject: [PATCH 84/95] Only read required keys in settings file --- squirrelbattle/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/settings.py b/squirrelbattle/settings.py index b5e2c14..549fc5f 100644 --- a/squirrelbattle/settings.py +++ b/squirrelbattle/settings.py @@ -73,7 +73,8 @@ class Settings: """ d = json.loads(json_str) for key in d: - setattr(self, key, d[key]) + if hasattr(self, key): + setattr(self, key, d[key]) def dumps_to_string(self) -> str: """ From b42f1277b171f31f159d7bda196350c97571c299 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 11:10:10 +0100 Subject: [PATCH 85/95] Exit the game on KeyboardInterrupt (don't log this error) --- squirrelbattle/game.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index ecdfccb..f39cda5 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -94,7 +94,11 @@ class Game: screen.noutrefresh() self.display_actions(DisplayActions.REFRESH) curses.doupdate() - key = screen.getkey() + try: + key = screen.getkey() + except KeyboardInterrupt: + exit(0) + return if key == "KEY_MOUSE": _ignored1, x, y, _ignored2, _ignored3 = curses.getmouse() self.display_actions(DisplayActions.MOUSE, y, x) From 75e93611c3da02002f52af6c7a1aab9fb730dc85 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 11:21:40 +0100 Subject: [PATCH 86/95] Capture all mouse events and take into account mouse attributes, fixes #58 --- squirrelbattle/display/creditsdisplay.py | 2 +- squirrelbattle/display/display.py | 3 +-- squirrelbattle/display/display_manager.py | 4 ++-- squirrelbattle/display/menudisplay.py | 10 +++++----- squirrelbattle/game.py | 4 ++-- squirrelbattle/term_manager.py | 2 +- squirrelbattle/tests/game_test.py | 23 +++++++++++++++-------- 7 files changed, 27 insertions(+), 21 deletions(-) diff --git a/squirrelbattle/display/creditsdisplay.py b/squirrelbattle/display/creditsdisplay.py index a97c10e..93f2f72 100644 --- a/squirrelbattle/display/creditsdisplay.py +++ b/squirrelbattle/display/creditsdisplay.py @@ -92,6 +92,6 @@ class CreditsDisplay(Display): self.addstr(self.pad, y_offset + i, x_offset + j, c, fg_color, bg_color, bold=bold) - def handle_click(self, y: int, x: int, game: Game) -> None: + def handle_click(self, y: int, x: int, attr: int, game: Game) -> None: if self.pad.inch(y - 1, x - 1) != ord(" "): self.ascii_art_displayed = True diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 884c997..94b9ca6 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -187,12 +187,11 @@ class Display: """ raise NotImplementedError - def handle_click(self, y: int, x: int, game: Game) -> None: + def handle_click(self, y: int, x: int, attr: int, game: Game) -> None: """ A mouse click was performed on the coordinates (y, x) of the pad. Maybe it should do something. """ - pass @property def rows(self) -> int: diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 0042615..a1d2ed9 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -63,7 +63,7 @@ class DisplayManager: d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK) d.update(self.game) - def handle_mouse_click(self, y: int, x: int) -> None: + def handle_mouse_click(self, y: int, x: int, attr: int) -> None: """ Handles the mouse clicks. """ @@ -76,7 +76,7 @@ class DisplayManager: # of that display display = d if display: - display.handle_click(y - display.y, x - display.x, self.game) + display.handle_click(y - display.y, x - display.x, attr, self.game) def refresh(self) -> List[Display]: """ diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index cc73010..6c4d991 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -50,7 +50,7 @@ class MenuDisplay(Display): self.height - 2 + self.y, self.width - 2 + self.x) - def handle_click(self, y: int, x: int, game: Game) -> None: + def handle_click(self, y: int, x: int, attr: int, game: Game) -> None: """ We can select a menu item with the mouse. """ @@ -134,13 +134,13 @@ class MainMenuDisplay(Display): def update(self, game: Game) -> None: self.menudisplay.update_menu(game.main_menu) - def handle_click(self, y: int, x: int, game: Game) -> None: + def handle_click(self, y: int, x: int, attr: int, game: Game) -> None: menuwidth = min(self.menudisplay.preferred_width, self.width) menuy, menux = len(self.title) + 8, self.width // 2 - menuwidth // 2 - 1 menuheight = min(self.menudisplay.preferred_height, self.height - menuy) if menuy <= y < menuy + menuheight and menux <= x < menux + menuwidth: - self.menudisplay.handle_click(y - menuy, x - menux, game) + self.menudisplay.handle_click(y - menuy, x - menux, attr, game) if y <= len(self.title): self.fg_color = randint(0, 1000), randint(0, 1000), randint(0, 1000) @@ -189,7 +189,7 @@ class PlayerInventoryDisplay(MenuDisplay): def trueheight(self) -> int: return 2 + super().trueheight - def handle_click(self, y: int, x: int, game: Game) -> None: + def handle_click(self, y: int, x: int, attr: int, game: Game) -> None: """ We can select a menu item with the mouse. """ @@ -232,7 +232,7 @@ class StoreInventoryDisplay(MenuDisplay): def trueheight(self) -> int: return 2 + super().trueheight - def handle_click(self, y: int, x: int, game: Game) -> None: + def handle_click(self, y: int, x: int, attr: int, game: Game) -> None: """ We can select a menu item with the mouse. """ diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index f39cda5..ba8493f 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -100,8 +100,8 @@ class Game: exit(0) return if key == "KEY_MOUSE": - _ignored1, x, y, _ignored2, _ignored3 = curses.getmouse() - self.display_actions(DisplayActions.MOUSE, y, x) + _ignored1, x, y, _ignored2, attr = curses.getmouse() + self.display_actions(DisplayActions.MOUSE, y, x, attr) else: self.handle_key_pressed( KeyValues.translate_key(key, self.settings), key) diff --git a/squirrelbattle/term_manager.py b/squirrelbattle/term_manager.py index 6484289..2e74fff 100644 --- a/squirrelbattle/term_manager.py +++ b/squirrelbattle/term_manager.py @@ -21,7 +21,7 @@ class TermManager: # pragma: no cover # make cursor invisible curses.curs_set(False) # Catch mouse events - curses.mousemask(True) + curses.mousemask(curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION) # Enable colors curses.start_color() diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 0843ae8..dab0e7e 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -1,6 +1,6 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later - +import curses import os import random import unittest @@ -257,10 +257,12 @@ class TestGame(unittest.TestCase): self.game.state = GameMode.MAINMENU # Change the color of the artwork - self.game.display_actions(DisplayActions.MOUSE, 0, 10) + self.game.display_actions(DisplayActions.MOUSE, 0, 10, + curses.BUTTON1_CLICKED) # Settings menu - self.game.display_actions(DisplayActions.MOUSE, 25, 21) + self.game.display_actions(DisplayActions.MOUSE, 25, 21, + curses.BUTTON1_CLICKED) self.assertEqual(self.game.main_menu.position, 4) self.assertEqual(self.game.state, GameMode.SETTINGS) @@ -272,11 +274,13 @@ class TestGame(unittest.TestCase): self.game.state = GameMode.INVENTORY # Click nowhere - self.game.display_actions(DisplayActions.MOUSE, 0, 0) + self.game.display_actions(DisplayActions.MOUSE, 0, 0, + curses.BUTTON1_CLICKED) self.assertEqual(self.game.state, GameMode.INVENTORY) # Click on the second item - self.game.display_actions(DisplayActions.MOUSE, 8, 25) + self.game.display_actions(DisplayActions.MOUSE, 8, 25, + curses.BUTTON1_CLICKED) self.assertEqual(self.game.state, GameMode.INVENTORY) self.assertEqual(self.game.inventory_menu.position, 1) @@ -572,7 +576,8 @@ class TestGame(unittest.TestCase): # Buy the second item by clicking on it item = self.game.store_menu.validate() self.assertIn(item, merchant.inventory) - self.game.display_actions(DisplayActions.MOUSE, 7, 25) + self.game.display_actions(DisplayActions.MOUSE, 7, 25, + curses.BUTTON1_CLICKED) self.assertIn(item, self.game.player.inventory) self.assertNotIn(item, merchant.inventory) @@ -747,9 +752,11 @@ class TestGame(unittest.TestCase): """ self.game.state = GameMode.MAINMENU - self.game.display_actions(DisplayActions.MOUSE, 41, 41) + self.game.display_actions(DisplayActions.MOUSE, 41, 41, + curses.BUTTON1_CLICKED) self.assertEqual(self.game.state, GameMode.CREDITS) - self.game.display_actions(DisplayActions.MOUSE, 21, 21) + self.game.display_actions(DisplayActions.MOUSE, 21, 21, + curses.BUTTON1_CLICKED) self.game.display_actions(DisplayActions.REFRESH) self.game.state = GameMode.CREDITS From e56bdc16c2a50ba2da266ec7bf1605d1e93b744e Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 11:55:25 +0100 Subject: [PATCH 87/95] Add item description, closes #59 --- squirrelbattle/display/menudisplay.py | 2 ++ squirrelbattle/entities/items.py | 28 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 6c4d991..4e08436 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -172,6 +172,7 @@ class PlayerInventoryDisplay(MenuDisplay): and self.selected else f" {rep} " self.addstr(self.pad, i + 1, 0, selection + " " + item.translated_name.capitalize() + + (f" ({item.description})" if item.description else "") + (": " + str(item.price) + " Hazels" if self.store_mode else "")) @@ -217,6 +218,7 @@ class StoreInventoryDisplay(MenuDisplay): and self.selected else f" {rep} " self.addstr(self.pad, i + 1, 0, selection + " " + item.translated_name.capitalize() + + (f" ({item.description})" if item.description else "") + ": " + str(item.price) + " Hazels") price = f"{self.pack.HAZELNUT} {self.menu.merchant.hazel} Hazels" diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 0436e37..9828fb6 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -24,6 +24,13 @@ class Item(Entity): self.held_by = held_by self.price = price + @property + def description(self) -> str: + """ + In the inventory, indicate the usefulness of the item. + """ + return "" + def drop(self) -> None: """ The item is dropped from the inventory onto the floor. @@ -109,6 +116,10 @@ class Heart(Item): super().__init__(name=name, price=price, *args, **kwargs) self.healing = healing + @property + def description(self) -> str: + return "HP+5" + def hold(self, entity: InventoryHolder) -> None: """ When holding a heart, the player is healed and @@ -217,6 +228,10 @@ class Weapon(Item): super().__init__(*args, **kwargs) self.damage = damage + @property + def description(self) -> str: + return f"STR+{self.damage}" if self.damage else super().description + def save_state(self) -> dict: """ Saves the state of the weapon into a dictionary @@ -261,6 +276,11 @@ class Armor(Item): super().__init__(*args, **kwargs) self.constitution = constitution + @property + def description(self) -> str: + return f"CON+{self.constitution}" if self.constitution \ + else super().description + def equip(self) -> None: super().equip() self.held_by.constitution += self.constitution @@ -375,6 +395,14 @@ class Ring(Item): self.critical = critical self.experience = experience + @property + def description(self) -> str: + fields = [("MAX HP", self.maxhealth), ("STR", self.strength), + ("INT", self.intelligence), ("CHR", self.charisma), + ("DEX", self.dexterity), ("CON", self.constitution), + ("CRI", self.critical), ("XP", self.experience)] + return ", ".join(f"{key}+{value}" for key, value in fields if value) + def equip(self) -> None: super().equip() self.held_by.maxhealth += self.maxhealth From 571e9db3e8728695b290e8d1f94e1f142459ef9f Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 12:06:28 +0100 Subject: [PATCH 88/95] Fix french translations --- squirrelbattle/entities/items.py | 7 +- squirrelbattle/interfaces.py | 2 +- .../locale/de/LC_MESSAGES/squirrelbattle.po | 95 +++++++++++++------ .../locale/es/LC_MESSAGES/squirrelbattle.po | 95 +++++++++++++------ .../locale/fr/LC_MESSAGES/squirrelbattle.po | 95 +++++++++++++------ squirrelbattle/tests/translations_test.py | 7 ++ 6 files changed, 205 insertions(+), 96 deletions(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 9828fb6..dab346c 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -86,7 +86,7 @@ class Item(Entity): """ Returns the list of all item classes. """ - return [BodySnatchPotion, Bomb, Heart, Shield, Sword,\ + return [BodySnatchPotion, Bomb, Heart, Shield, Sword, Chestplate, Helmet, RingCritical, RingXP] def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder) -> bool: @@ -118,7 +118,7 @@ class Heart(Item): @property def description(self) -> str: - return "HP+5" + return f"HP+{self.healing}" def hold(self, entity: InventoryHolder) -> None: """ @@ -304,6 +304,7 @@ class Shield(Armor): super().__init__(name=name, constitution=constitution, price=price, *args, **kwargs) + class Helmet(Armor): """ Class of helmet items, they can be equipped on the head. @@ -319,6 +320,7 @@ class Helmet(Armor): self.held_by.remove_from_inventory(self) self.held_by.equipped_helmet = self + class Chestplate(Armor): """ Class of chestplate items, they can be equipped on the body. @@ -334,6 +336,7 @@ class Chestplate(Armor): self.held_by.remove_from_inventory(self) self.held_by.equipped_armor = self + class BodySnatchPotion(Item): """ The body-snatch potion allows to exchange all characteristics with a random diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index f1f740b..fa43c73 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -605,7 +605,7 @@ class Entity: from squirrelbattle.entities.monsters import Tiger, Hedgehog, \ Rabbit, TeddyBear, GiantSeaEagle from squirrelbattle.entities.friendly import Merchant, Sunflower, \ - Trumpet + Trumpet return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear, Sunflower, Tiger, Merchant, GiantSeaEagle, Trumpet] diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 20aa2a7..1d6d8df 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-08 01:57+0100\n" +"POT-Creation-Date: 2021-01-08 12:03+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -21,40 +21,49 @@ msgstr "" msgid "{name} takes {amount} damage." msgstr "{name} nimmt {amount} Schadenspunkte." -#: squirrelbattle/display/menudisplay.py:160 +#: squirrelbattle/display/creditsdisplay.py:28 +#: squirrelbattle/display/menudisplay.py:123 +#: squirrelbattle/display/menudisplay.py:148 +msgid "Credits" +msgstr "" + +#: squirrelbattle/display/creditsdisplay.py:32 +msgid "Developers:" +msgstr "" + +#: squirrelbattle/display/creditsdisplay.py:38 +msgid "Translators:" +msgstr "" + +#: squirrelbattle/display/menudisplay.py:168 msgid "INVENTORY" msgstr "BESTAND" -#: squirrelbattle/display/menudisplay.py:202 +#: squirrelbattle/display/menudisplay.py:214 msgid "STALL" msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:23 -#: squirrelbattle/tests/translations_test.py:60 -msgid "player" -msgstr "Spieler" - -#: squirrelbattle/display/statsdisplay.py:35 +#: squirrelbattle/display/statsdisplay.py:40 msgid "Inventory:" msgstr "Bestand:" -#: squirrelbattle/display/statsdisplay.py:52 +#: squirrelbattle/display/statsdisplay.py:57 msgid "Equipped main:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:56 +#: squirrelbattle/display/statsdisplay.py:61 msgid "Equipped secondary:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:61 +#: squirrelbattle/display/statsdisplay.py:66 msgid "Equipped chestplate:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:65 +#: squirrelbattle/display/statsdisplay.py:70 msgid "Equipped helmet:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:72 +#: squirrelbattle/display/statsdisplay.py:77 msgid "YOU ARE DEAD" msgstr "SIE WURDEN GESTORBEN" @@ -63,41 +72,41 @@ msgstr "SIE WURDEN GESTORBEN" msgid "I don't sell any squirrel" msgstr "Ich verkaufe keinen Eichhörnchen." -#: squirrelbattle/entities/friendly.py:52 +#: squirrelbattle/entities/friendly.py:55 msgid "Flower power!!" msgstr "Blumenmacht!!" -#: squirrelbattle/entities/friendly.py:52 +#: squirrelbattle/entities/friendly.py:55 msgid "The sun is warm today" msgstr "Die Sonne ist warm heute" #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. -#: squirrelbattle/entities/items.py:163 +#: squirrelbattle/entities/items.py:178 msgid "Bomb is exploding." msgstr "Die Bombe explodiert." -#: squirrelbattle/entities/items.py:344 +#: squirrelbattle/entities/items.py:365 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} täuscht seinem Körper mit {entity} aus." -#: squirrelbattle/game.py:182 +#: squirrelbattle/game.py:200 #, 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 +#: squirrelbattle/game.py:213 #, 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 +#: squirrelbattle/game.py:304 squirrelbattle/tests/game_test.py:603 msgid "The buyer does not have enough money" msgstr "Der Kaufer hat nicht genug Geld" -#: squirrelbattle/game.py:328 +#: squirrelbattle/game.py:347 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -105,7 +114,7 @@ msgstr "" "In Ihrer Speicherdatei fehlen einige Schlüssel.\n" "Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht." -#: squirrelbattle/game.py:336 +#: squirrelbattle/game.py:355 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -113,7 +122,7 @@ msgstr "" "Auf dieser Karte wurde kein Spieler gefunden!\n" "Vielleicht sind Sie gestorben?" -#: squirrelbattle/game.py:356 +#: squirrelbattle/game.py:375 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -121,26 +130,26 @@ msgstr "" "Die JSON-Datei ist nicht korrekt.\n" "Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." -#: squirrelbattle/interfaces.py:452 +#: squirrelbattle/interfaces.py:712 msgid "It's a critical hit!" msgstr "" -#: squirrelbattle/interfaces.py:453 +#: squirrelbattle/interfaces.py:713 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} schlägt {opponent}." -#: squirrelbattle/interfaces.py:465 +#: squirrelbattle/interfaces.py:727 #, python-brace-format msgid "{name} takes {damage} damage." msgstr "" -#: squirrelbattle/interfaces.py:467 +#: squirrelbattle/interfaces.py:729 #, python-brace-format msgid "{name} dies." msgstr "{name} stirbt." -#: squirrelbattle/interfaces.py:501 +#: squirrelbattle/interfaces.py:763 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} hat gesagt: {message}" @@ -149,8 +158,8 @@ msgstr "{entity} hat gesagt: {message}" msgid "Back" msgstr "Zurück" -#: 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/game_test.py:368 squirrelbattle/tests/game_test.py:371 +#: squirrelbattle/tests/game_test.py:374 squirrelbattle/tests/game_test.py:377 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Neu Spiel" @@ -247,6 +256,10 @@ msgstr "Textur-Packung" msgid "Language" msgstr "Sprache" +#: squirrelbattle/tests/translations_test.py:62 +msgid "player" +msgstr "Spieler" + #: squirrelbattle/tests/translations_test.py:64 msgid "hedgehog" msgstr "Igel" @@ -290,3 +303,23 @@ msgstr "Herz" #: squirrelbattle/tests/translations_test.py:75 msgid "sword" msgstr "schwert" + +#: squirrelbattle/tests/translations_test.py:76 +msgid "helmet" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:77 +msgid "chestplate" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:78 +msgid "shield" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:79 +msgid "ring_of_critical_damage" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:81 +msgid "ring_of_more_experience" +msgstr "" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index 0bfdac6..9c15730 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 15:19+0100\n" +"POT-Creation-Date: 2021-01-08 12:03+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -21,40 +21,49 @@ msgstr "" msgid "{name} takes {amount} damage." msgstr "{name} recibe {amount} daño." -#: squirrelbattle/display/menudisplay.py:160 +#: squirrelbattle/display/creditsdisplay.py:28 +#: squirrelbattle/display/menudisplay.py:123 +#: squirrelbattle/display/menudisplay.py:148 +msgid "Credits" +msgstr "" + +#: squirrelbattle/display/creditsdisplay.py:32 +msgid "Developers:" +msgstr "" + +#: squirrelbattle/display/creditsdisplay.py:38 +msgid "Translators:" +msgstr "" + +#: squirrelbattle/display/menudisplay.py:168 msgid "INVENTORY" msgstr "INVENTORIO" -#: squirrelbattle/display/menudisplay.py:202 +#: squirrelbattle/display/menudisplay.py:214 msgid "STALL" msgstr "PUESTO" -#: squirrelbattle/display/statsdisplay.py:23 -#: squirrelbattle/tests/translations_test.py:60 -msgid "player" -msgstr "jugador" - -#: squirrelbattle/display/statsdisplay.py:35 +#: squirrelbattle/display/statsdisplay.py:40 msgid "Inventory:" msgstr "Inventorio :" -#: squirrelbattle/display/statsdisplay.py:52 +#: squirrelbattle/display/statsdisplay.py:57 msgid "Equipped main:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:56 +#: squirrelbattle/display/statsdisplay.py:61 msgid "Equipped secondary:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:61 +#: squirrelbattle/display/statsdisplay.py:66 msgid "Equipped chestplate:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:65 +#: squirrelbattle/display/statsdisplay.py:70 msgid "Equipped helmet:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:72 +#: squirrelbattle/display/statsdisplay.py:77 msgid "YOU ARE DEAD" msgstr "ERES MUERTO" @@ -62,41 +71,41 @@ msgstr "ERES MUERTO" msgid "I don't sell any squirrel" msgstr "No vendo ninguna ardilla" -#: squirrelbattle/entities/friendly.py:52 +#: squirrelbattle/entities/friendly.py:55 msgid "Flower power!!" msgstr "Poder de las flores!!" -#: squirrelbattle/entities/friendly.py:52 +#: squirrelbattle/entities/friendly.py:55 msgid "The sun is warm today" msgstr "El sol está caliente hoy" #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. -#: squirrelbattle/entities/items.py:163 +#: squirrelbattle/entities/items.py:178 msgid "Bomb is exploding." msgstr "La bomba está explotando." -#: squirrelbattle/entities/items.py:344 +#: squirrelbattle/entities/items.py:365 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} intercambió su cuerpo con {entity}." -#: squirrelbattle/game.py:182 +#: squirrelbattle/game.py:200 #, python-brace-format msgid "The player climbs down to the floor {floor}." msgstr "" -#: squirrelbattle/game.py:195 +#: squirrelbattle/game.py:213 #, python-brace-format msgid "The player climbs up the floor {floor}." msgstr "" -#: squirrelbattle/game.py:285 squirrelbattle/tests/game_test.py:592 +#: squirrelbattle/game.py:304 squirrelbattle/tests/game_test.py:603 msgid "The buyer does not have enough money" msgstr "El comprador no tiene suficiente dinero" -#: squirrelbattle/game.py:328 +#: squirrelbattle/game.py:347 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -104,7 +113,7 @@ msgstr "" "Algunas claves faltan en su archivo de guarda.\n" "Su guarda parece a ser corruptido. Fue eliminado." -#: squirrelbattle/game.py:336 +#: squirrelbattle/game.py:355 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -112,7 +121,7 @@ msgstr "" "No jugador encontrado sobre la carta !\n" "¿ Quizas murió ?" -#: squirrelbattle/game.py:356 +#: squirrelbattle/game.py:375 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -120,26 +129,26 @@ msgstr "" "El JSON archivo no es correcto.\n" "Su guarda parece corrupta. Fue eliminada." -#: squirrelbattle/interfaces.py:452 +#: squirrelbattle/interfaces.py:712 msgid "It's a critical hit!" msgstr "" -#: squirrelbattle/interfaces.py:453 +#: squirrelbattle/interfaces.py:713 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} golpea a {opponent}." -#: squirrelbattle/interfaces.py:465 +#: squirrelbattle/interfaces.py:727 #, python-brace-format msgid "{name} takes {damage} damage." msgstr "" -#: squirrelbattle/interfaces.py:467 +#: squirrelbattle/interfaces.py:729 #, python-brace-format msgid "{name} dies." msgstr "{name} se muere." -#: squirrelbattle/interfaces.py:501 +#: squirrelbattle/interfaces.py:763 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} dijo : {message}" @@ -148,8 +157,8 @@ msgstr "{entity} dijo : {message}" msgid "Back" msgstr "Volver" -#: 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/game_test.py:368 squirrelbattle/tests/game_test.py:371 +#: squirrelbattle/tests/game_test.py:374 squirrelbattle/tests/game_test.py:377 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Nuevo partido" @@ -246,6 +255,10 @@ msgstr "Paquete de texturas" msgid "Language" msgstr "Languaje" +#: squirrelbattle/tests/translations_test.py:62 +msgid "player" +msgstr "jugador" + #: squirrelbattle/tests/translations_test.py:64 msgid "hedgehog" msgstr "erizo" @@ -289,3 +302,23 @@ msgstr "corazón" #: squirrelbattle/tests/translations_test.py:75 msgid "sword" msgstr "espada" + +#: squirrelbattle/tests/translations_test.py:76 +msgid "helmet" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:77 +msgid "chestplate" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:78 +msgid "shield" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:79 +msgid "ring_of_critical_damage" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:81 +msgid "ring_of_more_experience" +msgstr "" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 42172c3..18baf0b 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 15:19+0100\n" +"POT-Creation-Date: 2021-01-08 12:03+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -21,40 +21,49 @@ msgstr "" msgid "{name} takes {amount} damage." msgstr "{name} prend {amount} points de dégât." -#: squirrelbattle/display/menudisplay.py:160 +#: squirrelbattle/display/creditsdisplay.py:28 +#: squirrelbattle/display/menudisplay.py:123 +#: squirrelbattle/display/menudisplay.py:148 +msgid "Credits" +msgstr "" + +#: squirrelbattle/display/creditsdisplay.py:32 +msgid "Developers:" +msgstr "" + +#: squirrelbattle/display/creditsdisplay.py:38 +msgid "Translators:" +msgstr "" + +#: squirrelbattle/display/menudisplay.py:168 msgid "INVENTORY" msgstr "INVENTAIRE" -#: squirrelbattle/display/menudisplay.py:202 +#: squirrelbattle/display/menudisplay.py:214 msgid "STALL" msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:23 -#: squirrelbattle/tests/translations_test.py:60 -msgid "player" -msgstr "joueur" - -#: squirrelbattle/display/statsdisplay.py:35 +#: squirrelbattle/display/statsdisplay.py:40 msgid "Inventory:" msgstr "Inventaire :" -#: squirrelbattle/display/statsdisplay.py:52 +#: squirrelbattle/display/statsdisplay.py:57 msgid "Equipped main:" msgstr "Équipement principal :" -#: squirrelbattle/display/statsdisplay.py:56 +#: squirrelbattle/display/statsdisplay.py:61 msgid "Equipped secondary:" msgstr "Équipement secondaire :" -#: squirrelbattle/display/statsdisplay.py:61 +#: squirrelbattle/display/statsdisplay.py:66 msgid "Equipped chestplate:" msgstr "Plastron équipé :" -#: squirrelbattle/display/statsdisplay.py:65 +#: squirrelbattle/display/statsdisplay.py:70 msgid "Equipped helmet:" msgstr "Casque équipé :" -#: squirrelbattle/display/statsdisplay.py:72 +#: squirrelbattle/display/statsdisplay.py:77 msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" @@ -63,41 +72,41 @@ msgstr "VOUS ÊTES MORT" msgid "I don't sell any squirrel" msgstr "Je ne vends pas d'écureuil" -#: squirrelbattle/entities/friendly.py:52 +#: squirrelbattle/entities/friendly.py:55 msgid "Flower power!!" msgstr "Pouvoir des fleurs !!" -#: squirrelbattle/entities/friendly.py:52 +#: squirrelbattle/entities/friendly.py:55 msgid "The sun is warm today" msgstr "Le soleil est chaud aujourd'hui" #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. -#: squirrelbattle/entities/items.py:163 +#: squirrelbattle/entities/items.py:178 msgid "Bomb is exploding." msgstr "La bombe explose." -#: squirrelbattle/entities/items.py:344 +#: squirrelbattle/entities/items.py:365 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} a échangé son corps avec {entity}." -#: squirrelbattle/game.py:182 +#: squirrelbattle/game.py:200 #, python-brace-format msgid "The player climbs down to the floor {floor}." msgstr "Le joueur descend à l'étage {floor}." -#: squirrelbattle/game.py:195 +#: squirrelbattle/game.py:213 #, 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 +#: squirrelbattle/game.py:304 squirrelbattle/tests/game_test.py:603 msgid "The buyer does not have enough money" msgstr "L'acheteur n'a pas assez d'argent" -#: squirrelbattle/game.py:328 +#: squirrelbattle/game.py:347 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -105,7 +114,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:336 +#: squirrelbattle/game.py:355 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -113,7 +122,7 @@ msgstr "" "Aucun joueur n'a été trouvé sur la carte !\n" "Peut-être êtes-vous mort ?" -#: squirrelbattle/game.py:356 +#: squirrelbattle/game.py:375 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -121,26 +130,26 @@ msgstr "" "Le fichier JSON de sauvegarde est incorrect.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/interfaces.py:452 +#: squirrelbattle/interfaces.py:712 msgid "It's a critical hit!" msgstr "C'est un coup critique !" -#: squirrelbattle/interfaces.py:453 +#: squirrelbattle/interfaces.py:713 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} frappe {opponent}." -#: squirrelbattle/interfaces.py:465 +#: squirrelbattle/interfaces.py:727 #, python-brace-format msgid "{name} takes {damage} damage." msgstr "{name} prend {damage} dégâts." -#: squirrelbattle/interfaces.py:467 +#: squirrelbattle/interfaces.py:729 #, python-brace-format msgid "{name} dies." msgstr "{name} meurt." -#: squirrelbattle/interfaces.py:501 +#: squirrelbattle/interfaces.py:763 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} a dit : {message}" @@ -149,8 +158,8 @@ msgstr "{entity} a dit : {message}" msgid "Back" msgstr "Retour" -#: 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/game_test.py:368 squirrelbattle/tests/game_test.py:371 +#: squirrelbattle/tests/game_test.py:374 squirrelbattle/tests/game_test.py:377 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Nouvelle partie" @@ -247,6 +256,10 @@ msgstr "Pack de textures" msgid "Language" msgstr "Langue" +#: squirrelbattle/tests/translations_test.py:62 +msgid "player" +msgstr "joueur" + #: squirrelbattle/tests/translations_test.py:64 msgid "hedgehog" msgstr "hérisson" @@ -290,3 +303,23 @@ msgstr "cœur" #: squirrelbattle/tests/translations_test.py:75 msgid "sword" msgstr "épée" + +#: squirrelbattle/tests/translations_test.py:76 +msgid "helmet" +msgstr "casque" + +#: squirrelbattle/tests/translations_test.py:77 +msgid "chestplate" +msgstr "plastron" + +#: squirrelbattle/tests/translations_test.py:78 +msgid "shield" +msgstr "bouclier" + +#: squirrelbattle/tests/translations_test.py:79 +msgid "ring of critical damage" +msgstr "anneau de coup critique" + +#: squirrelbattle/tests/translations_test.py:81 +msgid "ring of more experience" +msgstr "anneau de plus d'expérience" diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index 4a99e73..1ffa7d3 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -73,3 +73,10 @@ class TestTranslations(unittest.TestCase): self.assertEqual(_("explosion"), "explosion") self.assertEqual(_("heart"), "cœur") self.assertEqual(_("sword"), "épée") + self.assertEqual(_("helmet"), "casque") + self.assertEqual(_("chestplate"), "plastron") + self.assertEqual(_("shield"), "bouclier") + self.assertEqual(_("ring of critical damage"), + "anneau de coup critique") + self.assertEqual(_("ring of more experience"), + "anneau de plus d'expérience") From 120ec82d096e33574a4e72082683c5ec7200fe67 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 12:07:13 +0100 Subject: [PATCH 89/95] Helmet needs only one character in squirrel mode, add a trailing space --- squirrelbattle/display/texturepack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 81302b3..1fa0584 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -118,7 +118,7 @@ TexturePack.SQUIRREL_PACK = TexturePack( HAZELNUT='🌰', HEART='💜', HEDGEHOG='🦔', - HELMET='⛑️', + HELMET='⛑️ ', PLAYER='🐿️ ️', MERCHANT='🦜', RABBIT='🐇', From f48377e055b10c1573b0c0e5285e5974efa901ef Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 14:23:57 +0100 Subject: [PATCH 90/95] Save floors and visibility, fixes #61 --- squirrelbattle/game.py | 8 ++++++-- squirrelbattle/interfaces.py | 20 +++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index ba8493f..837e753 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -335,14 +335,16 @@ class Game: """ Saves the game to a dictionary. """ - return self.map.save_state() + return dict(map_index=self.map_index, + maps=[m.save_state() for m in self.maps]) def load_state(self, d: dict) -> None: """ Loads the game from a dictionary. """ try: - self.map.load_state(d) + self.map_index = d["map_index"] + self.maps = [Map().load_state(map_dict) for map_dict in d["maps"]] except KeyError: self.message = _("Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted.") @@ -359,6 +361,8 @@ class Game: return self.player = players[0] + self.map.compute_visibility(self.player.y, self.player.x, + self.player.vision) self.display_actions(DisplayActions.UPDATE) def load_game(self) -> None: diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index fa43c73..19ee25c 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -80,18 +80,18 @@ class Map: currentx: int currenty: int - def __init__(self, width: int, height: int, tiles: list, - start_y: int, start_x: int): + def __init__(self, width: int = 0, height: int = 0, tiles: list = None, + start_y: int = 0, start_x: int = 0): self.floor = 0 self.width = width self.height = height 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.tiles = tiles or [] + self.visibility = [[False for _ in range(len(self.tiles[0]))] + for _ in range(len(self.tiles))] self.seen_tiles = [[False for _ in range(len(tiles[0]))] - for _ in range(len(tiles))] + for _ in range(len(self.tiles))] self.entities = [] self.logs = Logs() @@ -338,9 +338,10 @@ class Map: for enti in self.entities: d["entities"].append(enti.save_state()) d["map"] = self.draw_string(TexturePack.ASCII_PACK) + d["seen_tiles"] = self.seen_tiles return d - def load_state(self, d: dict) -> None: + def load_state(self, d: dict) -> "Map": """ Loads the map's attributes from a dictionary. """ @@ -351,11 +352,16 @@ class Map: self.currentx = d["currentx"] self.currenty = d["currenty"] self.tiles = self.load_dungeon_from_string(d["map"]) + self.seen_tiles = d["seen_tiles"] + self.visibility = [[False for _ in range(len(self.tiles[0]))] + for _ in range(len(self.tiles))] self.entities = [] dictclasses = Entity.get_all_entity_classes_in_a_dict() for entisave in d["entities"]: self.add_entity(dictclasses[entisave["type"]](**entisave)) + return self + class Tile(Enum): """ From 156e4a7e3aaca95cafc2fe52508193e5991086fb Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 14:51:56 +0100 Subject: [PATCH 91/95] Add a hint to tell the user how to use ladders --- docs/entities/monsters.rst | 2 +- docs/texture-pack.rst | 6 +- squirrelbattle/display/statsdisplay.py | 9 +++ .../locale/de/LC_MESSAGES/squirrelbattle.po | 69 +++++++++++-------- .../locale/es/LC_MESSAGES/squirrelbattle.po | 69 +++++++++++-------- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 59 +++++++++------- squirrelbattle/tests/translations_test.py | 1 + 7 files changed, 132 insertions(+), 83 deletions(-) diff --git a/docs/entities/monsters.rst b/docs/entities/monsters.rst index 31735bd..d648069 100644 --- a/docs/entities/monsters.rst +++ b/docs/entities/monsters.rst @@ -57,7 +57,7 @@ Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``8``. Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🧸``. -Pyguargue +Pygargue --------- Son nom est fixé à `eagle`. Il a par défaut une force à **1000** et **5000** points de vie. diff --git a/docs/texture-pack.rst b/docs/texture-pack.rst index 072b6ff..6dc3b60 100644 --- a/docs/texture-pack.rst +++ b/docs/texture-pack.rst @@ -22,7 +22,7 @@ Pack de textures .. _Bouclier: entities/items.html#bouclier .. _Hazel: ../index.html .. _Plastron: ../entities/items.html#plastron -.. _Pyguargue: ../entities/monsters.html#Pyguargue +.. _Pygargue: ../entities/monsters.html#Pygargue .. _Casque: ../entities/items.html#Casque .. _Anneau: ../entities/items.html#Anneau .. _Trompette: ../entities/items.html#Trompette @@ -64,7 +64,7 @@ Chaque tuile fait un caractère de large. * Bouclier_ : ``D`` * Hazel_ : ``¤`` * Plastron_ : ``(`` - * Pyguargue_ : ``µ`` + * Pygargue_ : ``µ`` * Casque_ : ``0`` * Anneau_ : ``o`` * Trompette_ : ``/`` @@ -95,7 +95,7 @@ Chaque tuile fait 2 caractères de large pour afficher les émojis proprement. * Bouclier_ : ``🛡️`` * Hazel_ : ``🌰`` * Plastron_ : ``🦺`` - * Pyguargue_ : ``🦅`` + * Pygargue_ : ``🦅`` * Casque_ : ``⛑️`` * Anneau_ : ``💍`` * Trompette_ : ``🎺`` diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index 35ff92c..d148c55 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -5,6 +5,7 @@ import curses from ..entities.player import Player from ..game import Game +from ..settings import Settings from ..translations import gettext as _ from .display import Display @@ -14,6 +15,7 @@ class StatsDisplay(Display): A class to handle the display of the stats of the player. """ player: Player + settings: Settings def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -21,6 +23,7 @@ class StatsDisplay(Display): def update(self, game: Game) -> None: self.player = game.player + self.settings = game.settings def update_pad(self) -> None: string2 = f"{_(self.player.name).capitalize()} " \ @@ -77,6 +80,12 @@ class StatsDisplay(Display): self.addstr(self.pad, 15, 0, _("YOU ARE DEAD"), curses.COLOR_RED, bold=True, blink=True, standout=True) + if self.player.map.tiles[self.player.y][self.player.x].is_ladder(): + msg = _("Use {key} to use the ladder") \ + .format(key=self.settings.KEY_LADDER) + self.addstr(self.pad, self.height - 2, 0, msg, + italic=True, reverse=True) + def display(self) -> None: self.pad.erase() self.update_pad() diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 1d6d8df..4130e13 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-08 12:03+0100\n" +"POT-Creation-Date: 2021-01-08 14:47+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,12 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +msgid "ring_of_critical_damage" +msgstr "" + +msgid "ring_of_more_experience" +msgstr "" + #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} nimmt {amount} Schadenspunkte." @@ -43,30 +49,35 @@ msgstr "BESTAND" msgid "STALL" msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:40 +#: squirrelbattle/display/statsdisplay.py:43 msgid "Inventory:" msgstr "Bestand:" -#: squirrelbattle/display/statsdisplay.py:57 +#: squirrelbattle/display/statsdisplay.py:60 msgid "Equipped main:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:61 +#: squirrelbattle/display/statsdisplay.py:64 msgid "Equipped secondary:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:66 +#: squirrelbattle/display/statsdisplay.py:69 msgid "Equipped chestplate:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:70 +#: squirrelbattle/display/statsdisplay.py:73 msgid "Equipped helmet:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:77 +#: squirrelbattle/display/statsdisplay.py:80 msgid "YOU ARE DEAD" msgstr "SIE WURDEN GESTORBEN" +#: squirrelbattle/display/statsdisplay.py:84 +#, python-brace-format +msgid "Use {key} to use the ladder" +msgstr "" + #. TODO #: squirrelbattle/entities/friendly.py:33 msgid "I don't sell any squirrel" @@ -106,7 +117,7 @@ msgstr "Der Spieler klettert auf dem Stock {floor} hinoben." msgid "The buyer does not have enough money" msgstr "Der Kaufer hat nicht genug Geld" -#: squirrelbattle/game.py:347 +#: squirrelbattle/game.py:349 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -114,7 +125,7 @@ msgstr "" "In Ihrer Speicherdatei fehlen einige Schlüssel.\n" "Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht." -#: squirrelbattle/game.py:355 +#: squirrelbattle/game.py:357 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -122,7 +133,7 @@ msgstr "" "Auf dieser Karte wurde kein Spieler gefunden!\n" "Vielleicht sind Sie gestorben?" -#: squirrelbattle/game.py:375 +#: squirrelbattle/game.py:379 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -130,26 +141,26 @@ msgstr "" "Die JSON-Datei ist nicht korrekt.\n" "Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." -#: squirrelbattle/interfaces.py:712 +#: squirrelbattle/interfaces.py:718 msgid "It's a critical hit!" msgstr "" -#: squirrelbattle/interfaces.py:713 +#: squirrelbattle/interfaces.py:719 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} schlägt {opponent}." -#: squirrelbattle/interfaces.py:727 +#: squirrelbattle/interfaces.py:733 #, python-brace-format msgid "{name} takes {damage} damage." msgstr "" -#: squirrelbattle/interfaces.py:729 +#: squirrelbattle/interfaces.py:735 #, python-brace-format msgid "{name} dies." msgstr "{name} stirbt." -#: squirrelbattle/interfaces.py:763 +#: squirrelbattle/interfaces.py:769 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} hat gesagt: {message}" @@ -284,42 +295,46 @@ msgstr "Teddybär" msgid "tiger" msgstr "Tiger" -#: squirrelbattle/tests/translations_test.py:71 +#: squirrelbattle/tests/translations_test.py:70 +msgid "eagle" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:72 msgid "body snatch potion" msgstr "Leichenfleddererzaubertrank" -#: squirrelbattle/tests/translations_test.py:72 +#: squirrelbattle/tests/translations_test.py:73 msgid "bomb" msgstr "Bombe" -#: squirrelbattle/tests/translations_test.py:73 +#: squirrelbattle/tests/translations_test.py:74 msgid "explosion" msgstr "Explosion" -#: squirrelbattle/tests/translations_test.py:74 +#: squirrelbattle/tests/translations_test.py:75 msgid "heart" msgstr "Herz" -#: squirrelbattle/tests/translations_test.py:75 +#: squirrelbattle/tests/translations_test.py:76 msgid "sword" msgstr "schwert" -#: squirrelbattle/tests/translations_test.py:76 +#: squirrelbattle/tests/translations_test.py:77 msgid "helmet" msgstr "" -#: squirrelbattle/tests/translations_test.py:77 +#: squirrelbattle/tests/translations_test.py:78 msgid "chestplate" msgstr "" -#: squirrelbattle/tests/translations_test.py:78 +#: squirrelbattle/tests/translations_test.py:79 msgid "shield" msgstr "" -#: squirrelbattle/tests/translations_test.py:79 -msgid "ring_of_critical_damage" +#: squirrelbattle/tests/translations_test.py:80 +msgid "ring of critical damage" msgstr "" -#: squirrelbattle/tests/translations_test.py:81 -msgid "ring_of_more_experience" +#: squirrelbattle/tests/translations_test.py:82 +msgid "ring of more experience" msgstr "" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index 9c15730..c6e4484 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-08 12:03+0100\n" +"POT-Creation-Date: 2021-01-08 14:47+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,12 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +msgid "ring_of_critical_damage" +msgstr "" + +msgid "ring_of_more_experience" +msgstr "" + #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} recibe {amount} daño." @@ -43,30 +49,35 @@ msgstr "INVENTORIO" msgid "STALL" msgstr "PUESTO" -#: squirrelbattle/display/statsdisplay.py:40 +#: squirrelbattle/display/statsdisplay.py:43 msgid "Inventory:" msgstr "Inventorio :" -#: squirrelbattle/display/statsdisplay.py:57 +#: squirrelbattle/display/statsdisplay.py:60 msgid "Equipped main:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:61 +#: squirrelbattle/display/statsdisplay.py:64 msgid "Equipped secondary:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:66 +#: squirrelbattle/display/statsdisplay.py:69 msgid "Equipped chestplate:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:70 +#: squirrelbattle/display/statsdisplay.py:73 msgid "Equipped helmet:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:77 +#: squirrelbattle/display/statsdisplay.py:80 msgid "YOU ARE DEAD" msgstr "ERES MUERTO" +#: squirrelbattle/display/statsdisplay.py:84 +#, python-brace-format +msgid "Use {key} to use the ladder" +msgstr "" + #: squirrelbattle/entities/friendly.py:33 msgid "I don't sell any squirrel" msgstr "No vendo ninguna ardilla" @@ -105,7 +116,7 @@ msgstr "" msgid "The buyer does not have enough money" msgstr "El comprador no tiene suficiente dinero" -#: squirrelbattle/game.py:347 +#: squirrelbattle/game.py:349 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -113,7 +124,7 @@ msgstr "" "Algunas claves faltan en su archivo de guarda.\n" "Su guarda parece a ser corruptido. Fue eliminado." -#: squirrelbattle/game.py:355 +#: squirrelbattle/game.py:357 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -121,7 +132,7 @@ msgstr "" "No jugador encontrado sobre la carta !\n" "¿ Quizas murió ?" -#: squirrelbattle/game.py:375 +#: squirrelbattle/game.py:379 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -129,26 +140,26 @@ msgstr "" "El JSON archivo no es correcto.\n" "Su guarda parece corrupta. Fue eliminada." -#: squirrelbattle/interfaces.py:712 +#: squirrelbattle/interfaces.py:718 msgid "It's a critical hit!" msgstr "" -#: squirrelbattle/interfaces.py:713 +#: squirrelbattle/interfaces.py:719 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} golpea a {opponent}." -#: squirrelbattle/interfaces.py:727 +#: squirrelbattle/interfaces.py:733 #, python-brace-format msgid "{name} takes {damage} damage." msgstr "" -#: squirrelbattle/interfaces.py:729 +#: squirrelbattle/interfaces.py:735 #, python-brace-format msgid "{name} dies." msgstr "{name} se muere." -#: squirrelbattle/interfaces.py:763 +#: squirrelbattle/interfaces.py:769 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} dijo : {message}" @@ -283,42 +294,46 @@ msgstr "osito de peluche" msgid "tiger" msgstr "tigre" -#: squirrelbattle/tests/translations_test.py:71 +#: squirrelbattle/tests/translations_test.py:70 +msgid "eagle" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:72 msgid "body snatch potion" msgstr "poción de intercambio" -#: squirrelbattle/tests/translations_test.py:72 +#: squirrelbattle/tests/translations_test.py:73 msgid "bomb" msgstr "bomba" -#: squirrelbattle/tests/translations_test.py:73 +#: squirrelbattle/tests/translations_test.py:74 msgid "explosion" msgstr "explosión" -#: squirrelbattle/tests/translations_test.py:74 +#: squirrelbattle/tests/translations_test.py:75 msgid "heart" msgstr "corazón" -#: squirrelbattle/tests/translations_test.py:75 +#: squirrelbattle/tests/translations_test.py:76 msgid "sword" msgstr "espada" -#: squirrelbattle/tests/translations_test.py:76 +#: squirrelbattle/tests/translations_test.py:77 msgid "helmet" msgstr "" -#: squirrelbattle/tests/translations_test.py:77 +#: squirrelbattle/tests/translations_test.py:78 msgid "chestplate" msgstr "" -#: squirrelbattle/tests/translations_test.py:78 +#: squirrelbattle/tests/translations_test.py:79 msgid "shield" msgstr "" -#: squirrelbattle/tests/translations_test.py:79 -msgid "ring_of_critical_damage" +#: squirrelbattle/tests/translations_test.py:80 +msgid "ring of critical damage" msgstr "" -#: squirrelbattle/tests/translations_test.py:81 -msgid "ring_of_more_experience" +#: squirrelbattle/tests/translations_test.py:82 +msgid "ring of more experience" msgstr "" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 18baf0b..fe3f613 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-08 12:03+0100\n" +"POT-Creation-Date: 2021-01-08 14:47+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -43,30 +43,35 @@ msgstr "INVENTAIRE" msgid "STALL" msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:40 +#: squirrelbattle/display/statsdisplay.py:43 msgid "Inventory:" msgstr "Inventaire :" -#: squirrelbattle/display/statsdisplay.py:57 +#: squirrelbattle/display/statsdisplay.py:60 msgid "Equipped main:" msgstr "Équipement principal :" -#: squirrelbattle/display/statsdisplay.py:61 +#: squirrelbattle/display/statsdisplay.py:64 msgid "Equipped secondary:" msgstr "Équipement secondaire :" -#: squirrelbattle/display/statsdisplay.py:66 +#: squirrelbattle/display/statsdisplay.py:69 msgid "Equipped chestplate:" msgstr "Plastron équipé :" -#: squirrelbattle/display/statsdisplay.py:70 +#: squirrelbattle/display/statsdisplay.py:73 msgid "Equipped helmet:" msgstr "Casque équipé :" -#: squirrelbattle/display/statsdisplay.py:77 +#: squirrelbattle/display/statsdisplay.py:80 msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" +#: squirrelbattle/display/statsdisplay.py:84 +#, python-brace-format +msgid "Use {key} to use the ladder" +msgstr "Appuyez sur {key} pour utiliser l'échelle" + #. TODO #: squirrelbattle/entities/friendly.py:33 msgid "I don't sell any squirrel" @@ -106,7 +111,7 @@ msgstr "Le joueur monte à l'étage {floor}." msgid "The buyer does not have enough money" msgstr "L'acheteur n'a pas assez d'argent" -#: squirrelbattle/game.py:347 +#: squirrelbattle/game.py:349 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -114,7 +119,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:355 +#: squirrelbattle/game.py:357 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -122,7 +127,7 @@ msgstr "" "Aucun joueur n'a été trouvé sur la carte !\n" "Peut-être êtes-vous mort ?" -#: squirrelbattle/game.py:375 +#: squirrelbattle/game.py:379 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -130,26 +135,26 @@ msgstr "" "Le fichier JSON de sauvegarde est incorrect.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/interfaces.py:712 +#: squirrelbattle/interfaces.py:718 msgid "It's a critical hit!" msgstr "C'est un coup critique !" -#: squirrelbattle/interfaces.py:713 +#: squirrelbattle/interfaces.py:719 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} frappe {opponent}." -#: squirrelbattle/interfaces.py:727 +#: squirrelbattle/interfaces.py:733 #, python-brace-format msgid "{name} takes {damage} damage." msgstr "{name} prend {damage} dégâts." -#: squirrelbattle/interfaces.py:729 +#: squirrelbattle/interfaces.py:735 #, python-brace-format msgid "{name} dies." msgstr "{name} meurt." -#: squirrelbattle/interfaces.py:763 +#: squirrelbattle/interfaces.py:769 #, python-brace-format msgid "{entity} said: {message}" msgstr "{entity} a dit : {message}" @@ -284,42 +289,46 @@ msgstr "nounours" msgid "tiger" msgstr "tigre" -#: squirrelbattle/tests/translations_test.py:71 +#: squirrelbattle/tests/translations_test.py:70 +msgid "eagle" +msgstr "pygargue" + +#: squirrelbattle/tests/translations_test.py:72 msgid "body snatch potion" msgstr "potion d'arrachage de corps" -#: squirrelbattle/tests/translations_test.py:72 +#: squirrelbattle/tests/translations_test.py:73 msgid "bomb" msgstr "bombe" -#: squirrelbattle/tests/translations_test.py:73 +#: squirrelbattle/tests/translations_test.py:74 msgid "explosion" msgstr "explosion" -#: squirrelbattle/tests/translations_test.py:74 +#: squirrelbattle/tests/translations_test.py:75 msgid "heart" msgstr "cœur" -#: squirrelbattle/tests/translations_test.py:75 +#: squirrelbattle/tests/translations_test.py:76 msgid "sword" msgstr "épée" -#: squirrelbattle/tests/translations_test.py:76 +#: squirrelbattle/tests/translations_test.py:77 msgid "helmet" msgstr "casque" -#: squirrelbattle/tests/translations_test.py:77 +#: squirrelbattle/tests/translations_test.py:78 msgid "chestplate" msgstr "plastron" -#: squirrelbattle/tests/translations_test.py:78 +#: squirrelbattle/tests/translations_test.py:79 msgid "shield" msgstr "bouclier" -#: squirrelbattle/tests/translations_test.py:79 +#: squirrelbattle/tests/translations_test.py:80 msgid "ring of critical damage" msgstr "anneau de coup critique" -#: squirrelbattle/tests/translations_test.py:81 +#: squirrelbattle/tests/translations_test.py:82 msgid "ring of more experience" msgstr "anneau de plus d'expérience" diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index 1ffa7d3..54581f3 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -67,6 +67,7 @@ class TestTranslations(unittest.TestCase): self.assertEqual(_("sunflower"), "tournesol") self.assertEqual(_("teddy bear"), "nounours") self.assertEqual(_("tiger"), "tigre") + self.assertEqual(_("eagle"), "pygargue") self.assertEqual(_("body snatch potion"), "potion d'arrachage de corps") self.assertEqual(_("bomb"), "bombe") From 28a6532a2101ca6fb68a155c675f6f34e2e2f83a Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 14:59:44 +0100 Subject: [PATCH 92/95] Add a hint to tell the user how to talk to friendly entities --- squirrelbattle/display/statsdisplay.py | 12 ++++++++++- .../locale/de/LC_MESSAGES/squirrelbattle.po | 21 ++++++++++++------- .../locale/es/LC_MESSAGES/squirrelbattle.po | 21 ++++++++++++------- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 21 ++++++++++++------- 4 files changed, 50 insertions(+), 25 deletions(-) diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index d148c55..cba8c06 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -5,6 +5,7 @@ import curses from ..entities.player import Player from ..game import Game +from ..interfaces import FriendlyEntity from ..settings import Settings from ..translations import gettext as _ from .display import Display @@ -82,10 +83,19 @@ class StatsDisplay(Display): if self.player.map.tiles[self.player.y][self.player.x].is_ladder(): msg = _("Use {key} to use the ladder") \ - .format(key=self.settings.KEY_LADDER) + .format(key=self.settings.KEY_LADDER.upper()) self.addstr(self.pad, self.height - 2, 0, msg, italic=True, reverse=True) + for dy, dx in [(-1, 0), (0, -1), (0, 1), (1, 0)]: + for entity in self.player.map.find_entities(FriendlyEntity): + if entity.y == self.player.y + dy \ + and entity.x == self.player.x + dx: + msg = _("Use {key} then move to talk to the entity") \ + .format(key=self.settings.KEY_CHAT.upper()) + self.addstr(self.pad, self.height - 1, 0, msg, + italic=True, reverse=True) + def display(self) -> None: self.pad.erase() self.update_pad() diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 4130e13..7bf5e67 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-08 14:47+0100\n" +"POT-Creation-Date: 2021-01-08 14:59+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -49,35 +49,40 @@ msgstr "BESTAND" msgid "STALL" msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:43 +#: squirrelbattle/display/statsdisplay.py:44 msgid "Inventory:" msgstr "Bestand:" -#: squirrelbattle/display/statsdisplay.py:60 +#: squirrelbattle/display/statsdisplay.py:61 msgid "Equipped main:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:64 +#: squirrelbattle/display/statsdisplay.py:65 msgid "Equipped secondary:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:69 +#: squirrelbattle/display/statsdisplay.py:70 msgid "Equipped chestplate:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:73 +#: squirrelbattle/display/statsdisplay.py:74 msgid "Equipped helmet:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:80 +#: squirrelbattle/display/statsdisplay.py:81 msgid "YOU ARE DEAD" msgstr "SIE WURDEN GESTORBEN" -#: squirrelbattle/display/statsdisplay.py:84 +#: squirrelbattle/display/statsdisplay.py:85 #, python-brace-format msgid "Use {key} to use the ladder" msgstr "" +#: squirrelbattle/display/statsdisplay.py:94 +#, python-brace-format +msgid "Use {key} then move to talk to the entity" +msgstr "" + #. TODO #: squirrelbattle/entities/friendly.py:33 msgid "I don't sell any squirrel" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index c6e4484..9ac1e4e 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-08 14:47+0100\n" +"POT-Creation-Date: 2021-01-08 14:59+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -49,35 +49,40 @@ msgstr "INVENTORIO" msgid "STALL" msgstr "PUESTO" -#: squirrelbattle/display/statsdisplay.py:43 +#: squirrelbattle/display/statsdisplay.py:44 msgid "Inventory:" msgstr "Inventorio :" -#: squirrelbattle/display/statsdisplay.py:60 +#: squirrelbattle/display/statsdisplay.py:61 msgid "Equipped main:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:64 +#: squirrelbattle/display/statsdisplay.py:65 msgid "Equipped secondary:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:69 +#: squirrelbattle/display/statsdisplay.py:70 msgid "Equipped chestplate:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:73 +#: squirrelbattle/display/statsdisplay.py:74 msgid "Equipped helmet:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:80 +#: squirrelbattle/display/statsdisplay.py:81 msgid "YOU ARE DEAD" msgstr "ERES MUERTO" -#: squirrelbattle/display/statsdisplay.py:84 +#: squirrelbattle/display/statsdisplay.py:85 #, python-brace-format msgid "Use {key} to use the ladder" msgstr "" +#: squirrelbattle/display/statsdisplay.py:94 +#, python-brace-format +msgid "Use {key} then move to talk to the entity" +msgstr "" + #: squirrelbattle/entities/friendly.py:33 msgid "I don't sell any squirrel" msgstr "No vendo ninguna ardilla" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index fe3f613..c3c4864 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-08 14:47+0100\n" +"POT-Creation-Date: 2021-01-08 14:59+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -43,35 +43,40 @@ msgstr "INVENTAIRE" msgid "STALL" msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:43 +#: squirrelbattle/display/statsdisplay.py:44 msgid "Inventory:" msgstr "Inventaire :" -#: squirrelbattle/display/statsdisplay.py:60 +#: squirrelbattle/display/statsdisplay.py:61 msgid "Equipped main:" msgstr "Équipement principal :" -#: squirrelbattle/display/statsdisplay.py:64 +#: squirrelbattle/display/statsdisplay.py:65 msgid "Equipped secondary:" msgstr "Équipement secondaire :" -#: squirrelbattle/display/statsdisplay.py:69 +#: squirrelbattle/display/statsdisplay.py:70 msgid "Equipped chestplate:" msgstr "Plastron équipé :" -#: squirrelbattle/display/statsdisplay.py:73 +#: squirrelbattle/display/statsdisplay.py:74 msgid "Equipped helmet:" msgstr "Casque équipé :" -#: squirrelbattle/display/statsdisplay.py:80 +#: squirrelbattle/display/statsdisplay.py:81 msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" -#: squirrelbattle/display/statsdisplay.py:84 +#: squirrelbattle/display/statsdisplay.py:85 #, python-brace-format msgid "Use {key} to use the ladder" msgstr "Appuyez sur {key} pour utiliser l'échelle" +#: squirrelbattle/display/statsdisplay.py:94 +#, python-brace-format +msgid "Use {key} then move to talk to the entity" +msgstr "Appuyez sur {key} puis déplacez-vous pour parler à l'entité" + #. TODO #: squirrelbattle/entities/friendly.py:33 msgid "I don't sell any squirrel" From 1270640619065dc738812936a045bab5c3a2c69c Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 15:07:35 +0100 Subject: [PATCH 93/95] Change hint whenever the T key is pressed or not --- squirrelbattle/display/statsdisplay.py | 12 ++++++---- .../locale/de/LC_MESSAGES/squirrelbattle.po | 22 ++++++++++------- .../locale/es/LC_MESSAGES/squirrelbattle.po | 22 ++++++++++------- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 24 +++++++++++-------- 4 files changed, 47 insertions(+), 33 deletions(-) diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index cba8c06..91b85b9 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -15,16 +15,16 @@ class StatsDisplay(Display): """ A class to handle the display of the stats of the player. """ + game: Game player: Player - settings: Settings def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.pad = self.newpad(self.rows, self.cols) def update(self, game: Game) -> None: + self.game = game self.player = game.player - self.settings = game.settings def update_pad(self) -> None: string2 = f"{_(self.player.name).capitalize()} " \ @@ -83,7 +83,7 @@ class StatsDisplay(Display): if self.player.map.tiles[self.player.y][self.player.x].is_ladder(): msg = _("Use {key} to use the ladder") \ - .format(key=self.settings.KEY_LADDER.upper()) + .format(key=self.game.settings.KEY_LADDER.upper()) self.addstr(self.pad, self.height - 2, 0, msg, italic=True, reverse=True) @@ -91,8 +91,10 @@ class StatsDisplay(Display): for entity in self.player.map.find_entities(FriendlyEntity): if entity.y == self.player.y + dy \ and entity.x == self.player.x + dx: - msg = _("Use {key} then move to talk to the entity") \ - .format(key=self.settings.KEY_CHAT.upper()) + msg = _("Move to the friendly entity to talk to it") \ + if self.game.waiting_for_friendly_key else \ + _("Use {key} then move to talk to the entity") \ + .format(key=self.game.settings.KEY_CHAT.upper()) self.addstr(self.pad, self.height - 1, 0, msg, italic=True, reverse=True) diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 7bf5e67..e55e2a1 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-08 14:59+0100\n" +"POT-Creation-Date: 2021-01-08 15:04+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -49,36 +49,40 @@ msgstr "BESTAND" msgid "STALL" msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:44 +#: squirrelbattle/display/statsdisplay.py:46 msgid "Inventory:" msgstr "Bestand:" -#: squirrelbattle/display/statsdisplay.py:61 +#: squirrelbattle/display/statsdisplay.py:63 msgid "Equipped main:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:65 +#: squirrelbattle/display/statsdisplay.py:67 msgid "Equipped secondary:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:70 +#: squirrelbattle/display/statsdisplay.py:72 msgid "Equipped chestplate:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:74 +#: squirrelbattle/display/statsdisplay.py:76 msgid "Equipped helmet:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:81 +#: squirrelbattle/display/statsdisplay.py:83 msgid "YOU ARE DEAD" msgstr "SIE WURDEN GESTORBEN" -#: squirrelbattle/display/statsdisplay.py:85 +#: squirrelbattle/display/statsdisplay.py:87 #, python-brace-format msgid "Use {key} to use the ladder" msgstr "" -#: squirrelbattle/display/statsdisplay.py:94 +#: squirrelbattle/display/statsdisplay.py:96 +msgid "Move to the friendly entity to talk to it" +msgstr "" + +#: squirrelbattle/display/statsdisplay.py:98 #, python-brace-format msgid "Use {key} then move to talk to the entity" msgstr "" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index 9ac1e4e..aaa59cc 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-08 14:59+0100\n" +"POT-Creation-Date: 2021-01-08 15:04+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -49,36 +49,40 @@ msgstr "INVENTORIO" msgid "STALL" msgstr "PUESTO" -#: squirrelbattle/display/statsdisplay.py:44 +#: squirrelbattle/display/statsdisplay.py:46 msgid "Inventory:" msgstr "Inventorio :" -#: squirrelbattle/display/statsdisplay.py:61 +#: squirrelbattle/display/statsdisplay.py:63 msgid "Equipped main:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:65 +#: squirrelbattle/display/statsdisplay.py:67 msgid "Equipped secondary:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:70 +#: squirrelbattle/display/statsdisplay.py:72 msgid "Equipped chestplate:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:74 +#: squirrelbattle/display/statsdisplay.py:76 msgid "Equipped helmet:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:81 +#: squirrelbattle/display/statsdisplay.py:83 msgid "YOU ARE DEAD" msgstr "ERES MUERTO" -#: squirrelbattle/display/statsdisplay.py:85 +#: squirrelbattle/display/statsdisplay.py:87 #, python-brace-format msgid "Use {key} to use the ladder" msgstr "" -#: squirrelbattle/display/statsdisplay.py:94 +#: squirrelbattle/display/statsdisplay.py:96 +msgid "Move to the friendly entity to talk to it" +msgstr "" + +#: squirrelbattle/display/statsdisplay.py:98 #, python-brace-format msgid "Use {key} then move to talk to the entity" msgstr "" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index c3c4864..f55fa7e 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-08 14:59+0100\n" +"POT-Creation-Date: 2021-01-08 15:04+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -43,39 +43,43 @@ msgstr "INVENTAIRE" msgid "STALL" msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:44 +#: squirrelbattle/display/statsdisplay.py:46 msgid "Inventory:" msgstr "Inventaire :" -#: squirrelbattle/display/statsdisplay.py:61 +#: squirrelbattle/display/statsdisplay.py:63 msgid "Equipped main:" msgstr "Équipement principal :" -#: squirrelbattle/display/statsdisplay.py:65 +#: squirrelbattle/display/statsdisplay.py:67 msgid "Equipped secondary:" msgstr "Équipement secondaire :" -#: squirrelbattle/display/statsdisplay.py:70 +#: squirrelbattle/display/statsdisplay.py:72 msgid "Equipped chestplate:" msgstr "Plastron équipé :" -#: squirrelbattle/display/statsdisplay.py:74 +#: squirrelbattle/display/statsdisplay.py:76 msgid "Equipped helmet:" msgstr "Casque équipé :" -#: squirrelbattle/display/statsdisplay.py:81 +#: squirrelbattle/display/statsdisplay.py:83 msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" -#: squirrelbattle/display/statsdisplay.py:85 +#: squirrelbattle/display/statsdisplay.py:87 #, python-brace-format msgid "Use {key} to use the ladder" msgstr "Appuyez sur {key} pour utiliser l'échelle" -#: squirrelbattle/display/statsdisplay.py:94 +#: squirrelbattle/display/statsdisplay.py:96 +msgid "Move to the friendly entity to talk to it" +msgstr "Avancez vers l'entité pour lui parler" + +#: squirrelbattle/display/statsdisplay.py:98 #, python-brace-format msgid "Use {key} then move to talk to the entity" -msgstr "Appuyez sur {key} puis déplacez-vous pour parler à l'entité" +msgstr "Appuyez sur {key} puis déplacez-vous pour parler" #. TODO #: squirrelbattle/entities/friendly.py:33 From a497d08f316a5f2d80758fc0c017599eccc397ec Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 15:48:12 +0100 Subject: [PATCH 94/95] Implement the monocle of truth, closes #62 --- squirrelbattle/display/statsdisplay.py | 42 +++++++++++++++---- squirrelbattle/display/texturepack.py | 2 + squirrelbattle/entities/items.py | 10 ++++- squirrelbattle/game.py | 2 +- squirrelbattle/interfaces.py | 16 +++---- .../locale/de/LC_MESSAGES/squirrelbattle.po | 24 ++++++----- .../locale/es/LC_MESSAGES/squirrelbattle.po | 24 ++++++----- .../locale/fr/LC_MESSAGES/squirrelbattle.po | 24 ++++++----- squirrelbattle/tests/game_test.py | 1 + squirrelbattle/tests/translations_test.py | 1 + 10 files changed, 97 insertions(+), 49 deletions(-) diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index 91b85b9..a60f6a2 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -3,10 +3,10 @@ import curses +from ..entities.items import Monocle from ..entities.player import Player from ..game import Game -from ..interfaces import FriendlyEntity -from ..settings import Settings +from ..interfaces import FightingEntity from ..translations import gettext as _ from .display import Display @@ -87,16 +87,40 @@ class StatsDisplay(Display): self.addstr(self.pad, self.height - 2, 0, msg, italic=True, reverse=True) + self.update_entities_stats() + + def update_entities_stats(self) -> None: + """ + Display information about a near entity if we have a monocle. + """ for dy, dx in [(-1, 0), (0, -1), (0, 1), (1, 0)]: - for entity in self.player.map.find_entities(FriendlyEntity): + for entity in self.player.map.find_entities(FightingEntity): + if entity == self.player: + continue + if entity.y == self.player.y + dy \ and entity.x == self.player.x + dx: - msg = _("Move to the friendly entity to talk to it") \ - if self.game.waiting_for_friendly_key else \ - _("Use {key} then move to talk to the entity") \ - .format(key=self.game.settings.KEY_CHAT.upper()) - self.addstr(self.pad, self.height - 1, 0, msg, - italic=True, reverse=True) + if entity.is_friendly(): + msg = _("Move to the friendly entity to talk to it") \ + if self.game.waiting_for_friendly_key else \ + _("Use {key} then move to talk to the entity") \ + .format(key=self.game.settings.KEY_CHAT.upper()) + self.addstr(self.pad, self.height - 1, 0, msg, + italic=True, reverse=True) + + if isinstance(self.player.equipped_secondary, Monocle): + # Truth monocle + message = f"{entity.translated_name.capitalize()} " \ + f"{self.pack[entity.name.upper()]}\n" \ + f"STR {entity.strength}\n" \ + f"INT {entity.intelligence}\n" \ + f"CHR {entity.charisma}\n" \ + f"DEX {entity.dexterity}\n" \ + f"CON {entity.constitution}\n" \ + f"CRI {entity.critical}%" + self.addstr(self.pad, 17, 0, message) + # Only display one entity + break def display(self) -> None: self.pad.erase() diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 1fa0584..34c76ee 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -84,6 +84,7 @@ TexturePack.ASCII_PACK = TexturePack( HEDGEHOG='*', HELMET='0', MERCHANT='M', + MONOCLE='ô', PLAYER='@', RABBIT='Y', RING_OF_CRITICAL_DAMAGE='o', @@ -121,6 +122,7 @@ TexturePack.SQUIRREL_PACK = TexturePack( HELMET='⛑️ ', PLAYER='🐿️ ️', MERCHANT='🦜', + MONOCLE='🧐', RABBIT='🐇', RING_OF_CRITICAL_DAMAGE='💍', RING_OF_MORE_EXPERIENCE='💍', diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index dab346c..25244cd 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -86,8 +86,8 @@ class Item(Entity): """ Returns the list of all item classes. """ - return [BodySnatchPotion, Bomb, Heart, Shield, Sword, - Chestplate, Helmet, RingCritical, RingXP] + return [BodySnatchPotion, Chestplate, Bomb, Heart, Helmet, Monocle, + Shield, Sword, RingCritical, RingXP] def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder) -> bool: """ @@ -453,3 +453,9 @@ class RingXP(Ring): experience: float = 2, *args, **kwargs): super().__init__(name=name, price=price, experience=experience, *args, **kwargs) + + +class Monocle(Item): + def __init__(self, name: str = "monocle", price: int = 10, + *args, **kwargs): + super().__init__(name=name, price=price, *args, **kwargs) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 837e753..5cb3762 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -345,7 +345,7 @@ class Game: try: self.map_index = d["map_index"] self.maps = [Map().load_state(map_dict) for map_dict in d["maps"]] - except KeyError: + except KeyError as e: self.message = _("Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted.") os.unlink(ResourceManager.get_config_path("save.json")) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 19ee25c..900067c 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -635,24 +635,26 @@ class Entity: from squirrelbattle.entities.friendly import Merchant, Sunflower, \ Trumpet from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \ - Heart, Sword, Shield, Chestplate, Helmet, RingCritical, RingXP + Heart, Monocle, Sword, Shield, Chestplate, Helmet, \ + RingCritical, RingXP return { - "Tiger": Tiger, "Bomb": Bomb, + "Chestplate": Chestplate, "Heart": Heart, "BodySnatchPotion": BodySnatchPotion, + "Eagle": GiantSeaEagle, "Hedgehog": Hedgehog, - "Rabbit": Rabbit, - "TeddyBear": TeddyBear, + "Helmet": Helmet, "Player": Player, "Merchant": Merchant, + "Monocle": Monocle, "Sunflower": Sunflower, "Sword": Sword, "Trumpet": Trumpet, - "Eagle": GiantSeaEagle, "Shield": Shield, - "Chestplate": Chestplate, - "Helmet": Helmet, + "TeddyBear": TeddyBear, + "Tiger": Tiger, + "Rabbit": Rabbit, "RingCritical": RingCritical, "RingXP": RingXP, } diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index e55e2a1..2cea0a3 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-08 15:04+0100\n" +"POT-Creation-Date: 2021-01-08 15:15+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -49,40 +49,40 @@ msgstr "BESTAND" msgid "STALL" msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:46 +#: squirrelbattle/display/statsdisplay.py:44 msgid "Inventory:" msgstr "Bestand:" -#: squirrelbattle/display/statsdisplay.py:63 +#: squirrelbattle/display/statsdisplay.py:61 msgid "Equipped main:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:67 +#: squirrelbattle/display/statsdisplay.py:65 msgid "Equipped secondary:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:72 +#: squirrelbattle/display/statsdisplay.py:70 msgid "Equipped chestplate:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:76 +#: squirrelbattle/display/statsdisplay.py:74 msgid "Equipped helmet:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:83 +#: squirrelbattle/display/statsdisplay.py:81 msgid "YOU ARE DEAD" msgstr "SIE WURDEN GESTORBEN" -#: squirrelbattle/display/statsdisplay.py:87 +#: squirrelbattle/display/statsdisplay.py:85 #, python-brace-format msgid "Use {key} to use the ladder" msgstr "" -#: squirrelbattle/display/statsdisplay.py:96 +#: squirrelbattle/display/statsdisplay.py:94 msgid "Move to the friendly entity to talk to it" msgstr "" -#: squirrelbattle/display/statsdisplay.py:98 +#: squirrelbattle/display/statsdisplay.py:96 #, python-brace-format msgid "Use {key} then move to talk to the entity" msgstr "" @@ -347,3 +347,7 @@ msgstr "" #: squirrelbattle/tests/translations_test.py:82 msgid "ring of more experience" msgstr "" + +#: squirrelbattle/tests/translations_test.py:84 +msgid "monocle" +msgstr "" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index aaa59cc..18868c3 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-08 15:04+0100\n" +"POT-Creation-Date: 2021-01-08 15:15+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -49,40 +49,40 @@ msgstr "INVENTORIO" msgid "STALL" msgstr "PUESTO" -#: squirrelbattle/display/statsdisplay.py:46 +#: squirrelbattle/display/statsdisplay.py:44 msgid "Inventory:" msgstr "Inventorio :" -#: squirrelbattle/display/statsdisplay.py:63 +#: squirrelbattle/display/statsdisplay.py:61 msgid "Equipped main:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:67 +#: squirrelbattle/display/statsdisplay.py:65 msgid "Equipped secondary:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:72 +#: squirrelbattle/display/statsdisplay.py:70 msgid "Equipped chestplate:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:76 +#: squirrelbattle/display/statsdisplay.py:74 msgid "Equipped helmet:" msgstr "" -#: squirrelbattle/display/statsdisplay.py:83 +#: squirrelbattle/display/statsdisplay.py:81 msgid "YOU ARE DEAD" msgstr "ERES MUERTO" -#: squirrelbattle/display/statsdisplay.py:87 +#: squirrelbattle/display/statsdisplay.py:85 #, python-brace-format msgid "Use {key} to use the ladder" msgstr "" -#: squirrelbattle/display/statsdisplay.py:96 +#: squirrelbattle/display/statsdisplay.py:94 msgid "Move to the friendly entity to talk to it" msgstr "" -#: squirrelbattle/display/statsdisplay.py:98 +#: squirrelbattle/display/statsdisplay.py:96 #, python-brace-format msgid "Use {key} then move to talk to the entity" msgstr "" @@ -346,3 +346,7 @@ msgstr "" #: squirrelbattle/tests/translations_test.py:82 msgid "ring of more experience" msgstr "" + +#: squirrelbattle/tests/translations_test.py:84 +msgid "monocle" +msgstr "" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index f55fa7e..4657fe8 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-08 15:04+0100\n" +"POT-Creation-Date: 2021-01-08 15:15+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -43,40 +43,40 @@ msgstr "INVENTAIRE" msgid "STALL" msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:46 +#: squirrelbattle/display/statsdisplay.py:44 msgid "Inventory:" msgstr "Inventaire :" -#: squirrelbattle/display/statsdisplay.py:63 +#: squirrelbattle/display/statsdisplay.py:61 msgid "Equipped main:" msgstr "Équipement principal :" -#: squirrelbattle/display/statsdisplay.py:67 +#: squirrelbattle/display/statsdisplay.py:65 msgid "Equipped secondary:" msgstr "Équipement secondaire :" -#: squirrelbattle/display/statsdisplay.py:72 +#: squirrelbattle/display/statsdisplay.py:70 msgid "Equipped chestplate:" msgstr "Plastron équipé :" -#: squirrelbattle/display/statsdisplay.py:76 +#: squirrelbattle/display/statsdisplay.py:74 msgid "Equipped helmet:" msgstr "Casque équipé :" -#: squirrelbattle/display/statsdisplay.py:83 +#: squirrelbattle/display/statsdisplay.py:81 msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" -#: squirrelbattle/display/statsdisplay.py:87 +#: squirrelbattle/display/statsdisplay.py:85 #, python-brace-format msgid "Use {key} to use the ladder" msgstr "Appuyez sur {key} pour utiliser l'échelle" -#: squirrelbattle/display/statsdisplay.py:96 +#: squirrelbattle/display/statsdisplay.py:94 msgid "Move to the friendly entity to talk to it" msgstr "Avancez vers l'entité pour lui parler" -#: squirrelbattle/display/statsdisplay.py:98 +#: squirrelbattle/display/statsdisplay.py:96 #, python-brace-format msgid "Use {key} then move to talk to the entity" msgstr "Appuyez sur {key} puis déplacez-vous pour parler" @@ -341,3 +341,7 @@ msgstr "anneau de coup critique" #: squirrelbattle/tests/translations_test.py:82 msgid "ring of more experience" msgstr "anneau de plus d'expérience" + +#: squirrelbattle/tests/translations_test.py:84 +msgid "monocle" +msgstr "monocle" diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index dab0e7e..785cd6a 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -66,6 +66,7 @@ class TestGame(unittest.TestCase): new_state = self.game.save_state() self.assertEqual(old_state, new_state) + self.assertIsNone(self.game.message) # Ensure that the bomb is loaded self.assertTrue(self.game.player.inventory) diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index 54581f3..bdf0238 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -81,3 +81,4 @@ class TestTranslations(unittest.TestCase): "anneau de coup critique") self.assertEqual(_("ring of more experience"), "anneau de plus d'expérience") + self.assertEqual(_("monocle"), "monocle") From 7a4936e6a5443d9408e7e62b97f8c3e7cb8b5a37 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 8 Jan 2021 15:58:54 +0100 Subject: [PATCH 95/95] Test monocles --- squirrelbattle/game.py | 2 +- squirrelbattle/tests/entities_test.py | 19 +++++++++++++++++-- squirrelbattle/tests/game_test.py | 23 +++++++++++++---------- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 5cb3762..837e753 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -345,7 +345,7 @@ class Game: try: self.map_index = d["map_index"] self.maps = [Map().load_state(map_dict) for map_dict in d["maps"]] - except KeyError as e: + except KeyError: self.message = _("Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted.") os.unlink(ResourceManager.get_config_path("save.json")) diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index af6f7dd..5729032 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -1,11 +1,12 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later - +import random import unittest from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart, Item, \ Explosion -from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, TeddyBear +from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit,\ + TeddyBear, GiantSeaEagle from squirrelbattle.entities.friendly import Trumpet from squirrelbattle.entities.player import Player from squirrelbattle.interfaces import Entity, Map @@ -264,3 +265,17 @@ class TestEntities(unittest.TestCase): player_state = player.save_state() self.assertEqual(player_state["current_xp"], 10) + + def test_critical_hit(self) -> None: + """ + Ensure that critical hits are working. + """ + random.seed(2) # Next random.randint(1, 100) will output 8 + self.player.critical = 10 + sea_eagle = GiantSeaEagle() + self.map.add_entity(sea_eagle) + sea_eagle.move(2, 6) + old_health = sea_eagle.health + self.player.hit(sea_eagle) + self.assertEqual(sea_eagle.health, + old_health - self.player.strength * 4) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 785cd6a..a5cb8ae 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -1,8 +1,8 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later + import curses import os -import random import unittest from ..bootstrap import Bootstrap @@ -10,7 +10,7 @@ from ..display.display import Display from ..display.display_manager import DisplayManager from ..entities.friendly import Merchant, Sunflower from ..entities.items import Bomb, Heart, Sword, Explosion, Shield, Helmet, \ - Chestplate, RingCritical + Chestplate, RingCritical, Monocle from ..entities.monsters import GiantSeaEagle from ..entities.player import Player from ..enums import DisplayActions @@ -584,6 +584,7 @@ class TestGame(unittest.TestCase): # Buy a heart merchant.inventory[1] = Heart() + self.game.display_actions(DisplayActions.REFRESH) item = self.game.store_menu.validate() self.assertIn(item, merchant.inventory) self.assertEqual(item, merchant.inventory[1]) @@ -702,19 +703,21 @@ class TestGame(unittest.TestCase): self.game.save_state() ring.unequip() - def test_critical_hit(self) -> None: + def test_monocle(self) -> None: """ - Ensure that critical hits are working. + The player is wearing a monocle, then the stats are displayed. """ - random.seed(2) # Next random.randint(1, 100) will output 8 - self.game.player.critical = 10 + self.game.state = GameMode.PLAY + + monocle = Monocle() + monocle.hold(self.game.player) + monocle.equip() + sea_eagle = GiantSeaEagle() self.game.map.add_entity(sea_eagle) sea_eagle.move(2, 6) - old_health = sea_eagle.health - self.game.player.hit(sea_eagle) - self.assertEqual(sea_eagle.health, - old_health - self.game.player.strength * 4) + + self.game.display_actions(DisplayActions.REFRESH) def test_ladders(self) -> None: """