Merge branch 'master' into doc
This commit is contained in:
		
							
								
								
									
										44
									
								
								squirrelbattle/assets/ascii-art-ecureuil.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								squirrelbattle/assets/ascii-art-ecureuil.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
|                                                                                | ||||
|                   ⋀ | ||||
|                  ┃|┃ | ||||
|                  ┃|┃        ▓▓▒            ▓▓ | ||||
|                  ┃|┃         ▓▓           ▓▓▒ | ||||
|                  ┃|┃         ▓▓▓    ▓▓   ▓▓▓            ▒▒▒▒▒▒▒▒▒ | ||||
|                  ┃|┃          ▓▓▓▓▓▓▓▓▓▓▓▓▓           ▒▒▒▒▒▒▒▒▒▒▒▒▒ | ||||
|                  ┃|┃          ▓▓▓▓▓▓▓▓▓▓▓▓▓         ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ | ||||
|                  ┃|┃         ▓▓▓▬█▓▓▓▓▓▓▬█▓▓▓      ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ | ||||
|                  ┃|┃       ▓▓▓▓░██░░▓▓░░██░▓▓▓▓   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ | ||||
|                ━━▓▓▓▓━━   ▓▓░░░░░░░░  ░░░░░░░░▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒ | ||||
|                   ▓▓▓▓▓▓  ▓░░░░░░░░░░░░░░░░░░░░▓▓▒▒▒▒▒▒▒▒▒▒▒▒                  | ||||
|                   ┃ ▓▓▓▓▓  ▓░░░░░░░░▄▄▄▄░░░░░░░▓▒▒▒▒▒▒▒▒▒▒▒▒▒ | ||||
|                   ┃  ▓▓▓▓▓ ▓▓░░░░░░░░░░░░░░░▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ | ||||
|                        ▓▓▓▓   ▓▓▓▓░░░░░░░▓▓   ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒                | ||||
|                         ▓▓▓▓▓▓▒▒░░░░░░░░░▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒                | ||||
|                          ▓▓▓▓▒░░░░░░░░░░░░▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒                | ||||
|                           ▓▓▒░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒               | ||||
|                           ▓▒▒░░░░░░░░░░░░▓▓▓▓▒▒▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒               | ||||
|                          ▓▓▒░░░░░░░░░░░░░░░▓▒▒▒▒▒▒▓▓▓▓▓▒▒▒▒▒▒▒▒▒               | ||||
|                          ▓▓▒▒░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▓▓▒▒▒▒▒▒▒▒▒                | ||||
|                          ▓▓▓▒░░░░░░░░░░░░░▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒                 | ||||
|                        ▓▓▒▓▒▒░░░░░░░░░░░░░░▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒                  | ||||
|                     ▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒                    | ||||
|                   ▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▒▒▒▒▒                      | ||||
|                   ▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▒▒▒                       | ||||
|                  ▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▒                         | ||||
|                  ▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓                           | ||||
|                  ▓▓▓▓▓▓▓▓▓▒░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓                           | ||||
|                   ▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓     ░                      | ||||
|                    ▓▓▓▓▓▓▓▓▓▒░░░░░░░░░░░░▓▓▓▓▓▓▓▓▓    ░░                       | ||||
|                 ▓▓▓▓▓▓▓▓▒▒░░░▒▒▒▒░░░░░░▓▓░▒▒▒▓▓▓▓▓▓▓▓▓▓░░░   ░                 | ||||
|                ▓▓▓▓▓▓▓▒░░░░░░░░░▒░░░░░░░░░░░░▒▒▒▓▓▓▓▓▓▓▓░░ ░░▒                 | ||||
|              ░ ░░░░░▒░░░░░░▒░░░▒░░░░░░░░░░░░░░░░░▒▒▒▒▒▒░░░░░░░▒                | ||||
|            ▒▒░░▓▓░░▒░░░░░░░░▒░░░░░░▒░░░░░░░░▒░░░░░░░░░░▒░░░░░▒   ░░            | ||||
|          ▒▒▒▒▒▓▒▒▓░░░░░░░░░▒░░░░░░░░▒░░░░░░░░▒░░░░░░░░▒░░░░░░░░░░░░            | ||||
|         ▒▒█▒█▒▒▒▓░░▒░░░░░░░░░░░░░░░▒░░░░░░░░▒░░░░░░░░░░░░░░░░░░░░░ | ||||
|        ▒▒▒▒█▒▒▒▒░░░░▒░░░▒░░░░░░░░░░░░░░░░░░░░░░▒░░░░░░░░░░░░▒░░░ | ||||
|     ▓█▒▒▒▒█▒█▒▒▒▒░░▒░░░░░▒░░░░▒░░░░░░░░░░░░░░░░░▒░░░░▒░░░░░░░▒░░░░░▒▒ | ||||
|     ██▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░▒░░░░░░▒░░░░░░░░▒░░░░░░▒░░░░░░▒░░░░░▒░░░░░             | ||||
|         ▒▒▒▒█▒▒▒▒▒▒▒░░░░░░░░░░▒░░░░░░░░░░▒░░░░░░░░░░░▒░░░░░░░░░░░░░░░ | ||||
|          ▒▒█▒▒▒▒▒░▒░▒░░░░▓▓▓░░░░░░░▒░░░░▒░░░▒░░░░░░░▓▓░░░░░░░░░░░░  ░ | ||||
|            ▒▒▒▒▒▒▒░▒░░░▓▓▓▓▓▓░░░░░░░▒░░░░░░░░▒░░░░▓▓▓▓▓▓░░░░░░░░  ░            | ||||
|                       ░▓▓▓▓▓▓░░░░░░▒░░░░░░░░▒░░░░░░▓▓▓▓▓░░░ ░  ░░              | ||||
							
								
								
									
										97
									
								
								squirrelbattle/display/creditsdisplay.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								squirrelbattle/display/creditsdisplay.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| # 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 update(self, game: Game) -> None: | ||||
|         return | ||||
|  | ||||
|     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) -> None: | ||||
|         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 | ||||
|                 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, bold=bold) | ||||
|  | ||||
|     def handle_click(self, y: int, x: int, game: Game) -> None: | ||||
|         if self.pad.inch(y - 1, x - 1) != ord(" "): | ||||
|             self.ascii_art_displayed = True | ||||
| @@ -25,9 +25,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") | ||||
| @@ -37,8 +44,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 | ||||
| @@ -67,9 +74,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 | ||||
| @@ -129,6 +136,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 | ||||
| @@ -139,6 +149,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() | ||||
| @@ -147,10 +160,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) | ||||
| @@ -180,7 +193,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 | ||||
|  | ||||
| @@ -194,7 +207,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) | ||||
| @@ -215,7 +230,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) | ||||
| @@ -236,6 +253,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: | ||||
|   | ||||
| @@ -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,17 +32,21 @@ 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: | ||||
|         """ | ||||
|         Handles the differents values of display action. | ||||
|         """ | ||||
|         if action == DisplayActions.REFRESH: | ||||
|             self.refresh() | ||||
|         elif action == DisplayActions.UPDATE: | ||||
| @@ -58,6 +64,9 @@ class DisplayManager: | ||||
|             d.update(self.game) | ||||
|  | ||||
|     def handle_mouse_click(self, y: int, x: int) -> None: | ||||
|         """ | ||||
|         Handles the mouse clicks. | ||||
|         """ | ||||
|         displays = self.refresh() | ||||
|         display = None | ||||
|         for d in displays: | ||||
| @@ -70,6 +79,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 = [] | ||||
|         pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK) | ||||
|  | ||||
| @@ -118,6 +130,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 | ||||
| @@ -135,7 +150,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, | ||||
| @@ -146,8 +161,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 | ||||
|   | ||||
| @@ -7,6 +7,10 @@ from squirrelbattle.interfaces import Logs | ||||
|  | ||||
|  | ||||
| class LogsDisplay(Display): | ||||
|     """ | ||||
|     A class to handle the display of the logs. | ||||
|     """ | ||||
|  | ||||
|     logs: Logs | ||||
|  | ||||
|     def __init__(self, *args) -> None: | ||||
|   | ||||
| @@ -7,6 +7,10 @@ from ..game import Game | ||||
|  | ||||
|  | ||||
| class MapDisplay(Display): | ||||
|     """ | ||||
|     A class to handle the display of the map. | ||||
|     """ | ||||
|  | ||||
|     map: Map | ||||
|  | ||||
|     def __init__(self, *args): | ||||
|   | ||||
| @@ -16,7 +16,7 @@ from ..translations import gettext as _ | ||||
|  | ||||
| class MenuDisplay(Display): | ||||
|     """ | ||||
|     A class to display the menu objects | ||||
|     A class to display the menu objects. | ||||
|     """ | ||||
|     menu: Menu | ||||
|     position: int | ||||
| @@ -80,7 +80,7 @@ class MenuDisplay(Display): | ||||
|  | ||||
| class SettingsMenuDisplay(MenuDisplay): | ||||
|     """ | ||||
|     A class to display specifically a settingsmenu object | ||||
|     A class to display specifically a settingsmenu object. | ||||
|     """ | ||||
|     menu: SettingsMenu | ||||
|  | ||||
| @@ -98,7 +98,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) | ||||
| @@ -120,6 +120,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) | ||||
| @@ -143,8 +145,14 @@ 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): | ||||
|     """ | ||||
|     A class to handle the display of the player's inventory. | ||||
|     """ | ||||
|     player: Player = None | ||||
|     selected: bool = True | ||||
|     store_mode: bool = False | ||||
| @@ -191,6 +199,9 @@ class PlayerInventoryDisplay(MenuDisplay): | ||||
|  | ||||
|  | ||||
| class StoreInventoryDisplay(MenuDisplay): | ||||
|     """ | ||||
|     A class to handle the display of a merchant's inventory. | ||||
|     """ | ||||
|     menu: StoreMenu | ||||
|     selected: bool = False | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,7 @@ from squirrelbattle.game import Game | ||||
|  | ||||
| class MessageDisplay(Display): | ||||
|     """ | ||||
|     Display a message in a popup. | ||||
|     A class to handle the display of popup messages. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|   | ||||
| @@ -10,6 +10,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): | ||||
|   | ||||
| @@ -6,6 +6,9 @@ from typing import Any | ||||
|  | ||||
|  | ||||
| class TexturePack: | ||||
|     """ | ||||
|     A class to handle displaying several textures. | ||||
|     """ | ||||
|     _packs = dict() | ||||
|  | ||||
|     name: str | ||||
| @@ -29,6 +32,7 @@ class TexturePack: | ||||
|     SWORD: str | ||||
|     TEDDY_BEAR: str | ||||
|     TIGER: str | ||||
|     TRUMPET: str | ||||
|     WALL: str | ||||
|  | ||||
|     ASCII_PACK: "TexturePack" | ||||
| @@ -74,6 +78,7 @@ TexturePack.ASCII_PACK = TexturePack( | ||||
|     SWORD='\u2020', | ||||
|     TEDDY_BEAR='8', | ||||
|     TIGER='n', | ||||
|     TRUMPET='/', | ||||
|     WALL='#', | ||||
| ) | ||||
|  | ||||
| @@ -100,5 +105,6 @@ TexturePack.SQUIRREL_PACK = TexturePack( | ||||
|     SWORD='🗡️ ', | ||||
|     TEDDY_BEAR='🧸', | ||||
|     TIGER='🐅', | ||||
|     TRUMPET='🎺', | ||||
|     WALL='🧱', | ||||
| ) | ||||
|   | ||||
| @@ -1,17 +1,18 @@ | ||||
| from ..interfaces import FriendlyEntity, InventoryHolder | ||||
| from ..interfaces import FriendlyEntity, InventoryHolder, Map, FightingEntity | ||||
| 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): | ||||
|     """ | ||||
|     The class for merchants in the dungeon | ||||
|     The class of merchants in the dungeon. | ||||
|     """ | ||||
|     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())()) | ||||
| @@ -28,20 +28,20 @@ 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 | ||||
|  | ||||
|  | ||||
| class Sunflower(FriendlyEntity): | ||||
|     """ | ||||
|     A friendly sunflower | ||||
|     A friendly sunflower. | ||||
|     """ | ||||
|     def __init__(self, maxhealth: int = 15, | ||||
|                  *args, **kwargs) -> None: | ||||
| @@ -49,4 +49,80 @@ 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")] | ||||
|  | ||||
|  | ||||
| class Familiar(FightingEntity): | ||||
|     """ | ||||
|     A friendly familiar that helps the player defeat monsters. | ||||
|     """ | ||||
|     def __init__(self, maxhealth: int = 25, | ||||
|                  *args, **kwargs) -> None: | ||||
|         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 | ||||
|         player and if a monster comes in range 3, it focuses on the monster | ||||
|         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 (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. | ||||
|                     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)) | ||||
|                     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) | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
| @@ -61,10 +61,17 @@ class Monster(FightingEntity): | ||||
|                 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): | ||||
|     """ | ||||
|     A tiger monster | ||||
|     A tiger monster. | ||||
|     """ | ||||
|     def __init__(self, name: str = "tiger", strength: int = 2, | ||||
|                  maxhealth: int = 20, *args, **kwargs) -> None: | ||||
| @@ -74,7 +81,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 +91,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 +101,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: | ||||
|   | ||||
| @@ -1,21 +1,17 @@ | ||||
| # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from functools import reduce | ||||
| from queue import PriorityQueue | ||||
| from random import randint | ||||
| from typing import Dict, Tuple | ||||
|  | ||||
| from ..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 | ||||
|     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, | ||||
| @@ -45,7 +41,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 +55,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() | ||||
| @@ -87,56 +83,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: | ||||
|         """ | ||||
|         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. | ||||
|         """ | ||||
|         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 | ||||
|   | ||||
| @@ -21,20 +21,20 @@ class DisplayActions(Enum): | ||||
|  | ||||
| class GameMode(Enum): | ||||
|     """ | ||||
|     Game mode options | ||||
|     Game mode options. | ||||
|     """ | ||||
|     MAINMENU = auto() | ||||
|     PLAY = auto() | ||||
|     SETTINGS = auto() | ||||
|     INVENTORY = auto() | ||||
|     STORE = auto() | ||||
|     CREDITS = auto() | ||||
|  | ||||
|  | ||||
| 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() | ||||
| @@ -51,7 +51,7 @@ class KeyValues(Enum): | ||||
|     @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): | ||||
|   | ||||
| @@ -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 | ||||
| @@ -49,7 +49,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_2.txt")) | ||||
| @@ -64,8 +64,8 @@ class Game: | ||||
|     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. | ||||
|         We wait for the player's action, then we do what should be done | ||||
|         when a key gets pressed. | ||||
|         """ | ||||
|         screen.refresh() | ||||
|         while True: | ||||
| @@ -84,7 +84,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: | ||||
| @@ -106,6 +106,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: | ||||
| @@ -114,16 +116,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: | ||||
| @@ -132,12 +134,13 @@ 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: | ||||
|         """ | ||||
|         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 | ||||
| @@ -226,7 +229,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() | ||||
| @@ -251,13 +254,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) | ||||
| @@ -281,7 +284,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): | ||||
| @@ -298,7 +301,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())) | ||||
|   | ||||
| @@ -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 _ | ||||
| @@ -12,7 +14,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 +34,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 +61,17 @@ 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) | ||||
|         if entity.is_familiar(): | ||||
|             self.entities.insert(1, entity) | ||||
|         else: | ||||
|             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 +91,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 +100,8 @@ 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 +110,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 +126,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 +135,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 +143,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 | ||||
| @@ -150,16 +156,19 @@ class Map: | ||||
|             entity.move(y, x) | ||||
|             self.add_entity(entity) | ||||
|  | ||||
|     def tick(self) -> None: | ||||
|     def tick(self, p: Any) -> None: | ||||
|         """ | ||||
|         Trigger all entity events. | ||||
|         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: | ||||
|         """ | ||||
|         Saves the map's attributes to a dictionary | ||||
|         Saves the map's attributes to a dictionary. | ||||
|         """ | ||||
|         d = dict() | ||||
|         d["width"] = self.width | ||||
| @@ -176,7 +185,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 +202,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 +211,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 +221,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,19 +233,20 @@ 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 | ||||
|     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,11 +255,12 @@ 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: | ||||
|         """ | ||||
|         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 +269,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 +277,100 @@ 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 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. | ||||
|         """ | ||||
|         if self.paths is 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: | ||||
|         """ | ||||
|         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)) | ||||
|  | ||||
| @@ -331,6 +393,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? | ||||
| @@ -340,29 +409,34 @@ 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, \ | ||||
|             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: | ||||
|         """ | ||||
|         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, \ | ||||
|             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 { | ||||
| @@ -377,11 +451,12 @@ class Entity: | ||||
|             "Merchant": Merchant, | ||||
|             "Sunflower": Sunflower, | ||||
|             "Sword": Sword, | ||||
|             "Trumpet": Trumpet, | ||||
|         } | ||||
|  | ||||
|     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 +468,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 +495,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 +512,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 +526,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 +549,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 +560,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 +571,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 +581,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 +591,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 +600,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 | ||||
|   | ||||
| @@ -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 = None | ||||
|  | ||||
|     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 if self.merchant else [] | ||||
|   | ||||
| @@ -13,9 +13,10 @@ 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 +51,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 +62,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 +76,7 @@ class Settings: | ||||
|  | ||||
|     def dumps_to_string(self) -> str: | ||||
|         """ | ||||
|         Dump settings | ||||
|         Dumps settings. | ||||
|         """ | ||||
|         d = dict() | ||||
|         for key in self.settings_keys: | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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 | ||||
| @@ -14,7 +15,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 +24,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 +39,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) | ||||
| @@ -57,17 +58,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], | ||||
| @@ -89,9 +90,50 @@ 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) | ||||
|  | ||||
|         # Test fighting | ||||
|         entity.health = 2 | ||||
|         entity.paths = [] | ||||
|         entity.recalculate_paths() | ||||
|         fam.target = entity | ||||
|         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 = 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: | ||||
|         """ | ||||
|         Test some random stuff with items. | ||||
|         Tests some random stuff with items. | ||||
|         """ | ||||
|         item = Item() | ||||
|         self.map.add_entity(item) | ||||
| @@ -112,7 +154,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 +198,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 +213,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 +231,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) | ||||
|   | ||||
| @@ -22,7 +22,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() | ||||
| @@ -36,7 +36,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) | ||||
| @@ -46,6 +46,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, 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) | ||||
| @@ -90,7 +95,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. | ||||
|         """ | ||||
| @@ -99,7 +104,7 @@ class TestGame(unittest.TestCase): | ||||
|  | ||||
|     def test_key_translation(self) -> None: | ||||
|         """ | ||||
|         Test key bindings. | ||||
|         Tests key bindings. | ||||
|         """ | ||||
|         self.game.settings = Settings() | ||||
|  | ||||
| @@ -155,7 +160,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(), | ||||
| @@ -246,7 +251,7 @@ class TestGame(unittest.TestCase): | ||||
|  | ||||
|     def test_mouse_click(self) -> None: | ||||
|         """ | ||||
|         Simulate mouse clicks. | ||||
|         Simulates mouse clicks. | ||||
|         """ | ||||
|         self.game.state = GameMode.MAINMENU | ||||
|  | ||||
| @@ -276,7 +281,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 | ||||
| @@ -299,7 +304,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() | ||||
|  | ||||
| @@ -385,7 +390,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 | ||||
| @@ -401,14 +406,14 @@ 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) | ||||
|         self.assertRaises(NotImplementedError, Display.update, None, self.game) | ||||
|  | ||||
|     def test_messages(self) -> None: | ||||
|         """ | ||||
|         Display error messages. | ||||
|         Displays error messages. | ||||
|         """ | ||||
|         self.game.message = "I am an error" | ||||
|         self.game.display_actions(DisplayActions.UPDATE) | ||||
| @@ -418,7 +423,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 | ||||
| @@ -479,7 +484,7 @@ class TestGame(unittest.TestCase): | ||||
|  | ||||
|     def test_talk_to_sunflowers(self) -> None: | ||||
|         """ | ||||
|         Interact with sunflowers | ||||
|         Interacts with sunflowers. | ||||
|         """ | ||||
|         self.game.state = GameMode.PLAY | ||||
|  | ||||
| @@ -530,7 +535,7 @@ class TestGame(unittest.TestCase): | ||||
|  | ||||
|     def test_talk_to_merchant(self) -> None: | ||||
|         """ | ||||
|         Interact with merchants | ||||
|         Interacts with merchants. | ||||
|         """ | ||||
|         self.game.state = GameMode.PLAY | ||||
|  | ||||
| @@ -609,3 +614,19 @@ 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) | ||||
|  | ||||
|         self.game.state = GameMode.CREDITS | ||||
|         self.game.handle_key_pressed(KeyValues.ENTER) | ||||
|  | ||||
|         self.assertEqual(self.game.state, GameMode.MAINMENU) | ||||
|   | ||||
| @@ -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()) | ||||
|   | ||||
| @@ -24,3 +24,6 @@ class FakePad: | ||||
|  | ||||
|     def getmaxyx(self) -> Tuple[int, int]: | ||||
|         return 42, 42 | ||||
|  | ||||
|     def inch(self, y: int, x: int) -> str: | ||||
|         return "i" | ||||
|   | ||||
| @@ -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') | ||||
|   | ||||
| @@ -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") | ||||
|   | ||||
| @@ -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) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user