Merge branch 'master' into 'lighting'
# Conflicts: # squirrelbattle/display/mapdisplay.py # squirrelbattle/interfaces.py
This commit is contained in:
commit
4acf6804d4
44
squirrelbattle/assets/ascii-art-ecureuil.txt
Normal file
44
squirrelbattle/assets/ascii-art-ecureuil.txt
Normal file
@ -0,0 +1,44 @@
|
||||
|
||||
⋀
|
||||
┃|┃
|
||||
┃|┃ ▓▓▒ ▓▓
|
||||
┃|┃ ▓▓ ▓▓▒
|
||||
┃|┃ ▓▓▓ ▓▓ ▓▓▓ ▒▒▒▒▒▒▒▒▒
|
||||
┃|┃ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||
┃|┃ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||
┃|┃ ▓▓▓▬█▓▓▓▓▓▓▬█▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||
┃|┃ ▓▓▓▓░██░░▓▓░░██░▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||
━━▓▓▓▓━━ ▓▓░░░░░░░░ ░░░░░░░░▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||
▓▓▓▓▓▓ ▓░░░░░░░░░░░░░░░░░░░░▓▓▒▒▒▒▒▒▒▒▒▒▒▒
|
||||
┃ ▓▓▓▓▓ ▓░░░░░░░░▄▄▄▄░░░░░░░▓▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||
┃ ▓▓▓▓▓ ▓▓░░░░░░░░░░░░░░░▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||
▓▓▓▓ ▓▓▓▓░░░░░░░▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||
▓▓▓▓▓▓▒▒░░░░░░░░░▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||
▓▓▓▓▒░░░░░░░░░░░░▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||
▓▓▒░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒
|
||||
▓▒▒░░░░░░░░░░░░▓▓▓▓▒▒▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒
|
||||
▓▓▒░░░░░░░░░░░░░░░▓▒▒▒▒▒▒▓▓▓▓▓▒▒▒▒▒▒▒▒▒
|
||||
▓▓▒▒░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▓▓▒▒▒▒▒▒▒▒▒
|
||||
▓▓▓▒░░░░░░░░░░░░░▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||
▓▓▒▓▒▒░░░░░░░░░░░░░░▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒
|
||||
▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒
|
||||
▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▒▒▒▒▒
|
||||
▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▒▒▒
|
||||
▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▒
|
||||
▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓
|
||||
▓▓▓▓▓▓▓▓▓▒░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓
|
||||
▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓ ░
|
||||
▓▓▓▓▓▓▓▓▓▒░░░░░░░░░░░░▓▓▓▓▓▓▓▓▓ ░░
|
||||
▓▓▓▓▓▓▓▓▒▒░░░▒▒▒▒░░░░░░▓▓░▒▒▒▓▓▓▓▓▓▓▓▓▓░░░ ░
|
||||
▓▓▓▓▓▓▓▒░░░░░░░░░▒░░░░░░░░░░░░▒▒▒▓▓▓▓▓▓▓▓░░ ░░▒
|
||||
░ ░░░░░▒░░░░░░▒░░░▒░░░░░░░░░░░░░░░░░▒▒▒▒▒▒░░░░░░░▒
|
||||
▒▒░░▓▓░░▒░░░░░░░░▒░░░░░░▒░░░░░░░░▒░░░░░░░░░░▒░░░░░▒ ░░
|
||||
▒▒▒▒▒▓▒▒▓░░░░░░░░░▒░░░░░░░░▒░░░░░░░░▒░░░░░░░░▒░░░░░░░░░░░░
|
||||
▒▒█▒█▒▒▒▓░░▒░░░░░░░░░░░░░░░▒░░░░░░░░▒░░░░░░░░░░░░░░░░░░░░░
|
||||
▒▒▒▒█▒▒▒▒░░░░▒░░░▒░░░░░░░░░░░░░░░░░░░░░░▒░░░░░░░░░░░░▒░░░
|
||||
▓█▒▒▒▒█▒█▒▒▒▒░░▒░░░░░▒░░░░▒░░░░░░░░░░░░░░░░░▒░░░░▒░░░░░░░▒░░░░░▒▒
|
||||
██▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░▒░░░░░░▒░░░░░░░░▒░░░░░░▒░░░░░░▒░░░░░▒░░░░░
|
||||
▒▒▒▒█▒▒▒▒▒▒▒░░░░░░░░░░▒░░░░░░░░░░▒░░░░░░░░░░░▒░░░░░░░░░░░░░░░
|
||||
▒▒█▒▒▒▒▒░▒░▒░░░░▓▓▓░░░░░░░▒░░░░▒░░░▒░░░░░░░▓▓░░░░░░░░░░░░ ░
|
||||
▒▒▒▒▒▒▒░▒░░░▓▓▓▓▓▓░░░░░░░▒░░░░░░░░▒░░░░▓▓▓▓▓▓░░░░░░░░ ░
|
||||
░▓▓▓▓▓▓░░░░░░▒░░░░░░░░▒░░░░░░▓▓▓▓▓░░░ ░ ░░
|
@ -1,8 +1,8 @@
|
||||
1 6
|
||||
####### #############
|
||||
#.....# #...........#
|
||||
#.H...# #...........#
|
||||
#.....# #####...........#
|
||||
#.....# #...............#
|
||||
#.....# #............H..#
|
||||
#.##### #.###...........#
|
||||
#.# #.# #...........#
|
||||
#.# #.# #############
|
||||
|
@ -1,6 +1,6 @@
|
||||
1 17
|
||||
########### #########
|
||||
#.........# #.......#
|
||||
#....H....# #.......#
|
||||
#.........# ############.......#
|
||||
#.........###############..........#.......##############
|
||||
#.........#........................#....................#
|
||||
@ -13,7 +13,7 @@
|
||||
########.##########......# #.........# #.........#
|
||||
#...........##......# #.........# #.........#
|
||||
#...........##......# #.........# #.........#
|
||||
#...........##......# #.........# ################.######
|
||||
#...........##..H...# #.........# ################.######
|
||||
#...........##......# #.........# #.................############
|
||||
#...........##......# ########.########.......#.........#..........#
|
||||
#...........##......# #...............#.......#.........#..........#
|
||||
|
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
|
@ -24,9 +24,16 @@ class Display:
|
||||
self.pack = pack or TexturePack.get_pack("ascii")
|
||||
|
||||
def newpad(self, height: int, width: int) -> Union[FakePad, Any]:
|
||||
"""
|
||||
Overwrites the native curses function of the same name.
|
||||
"""
|
||||
return curses.newpad(height, width) if self.screen else FakePad()
|
||||
|
||||
def truncate(self, msg: str, height: int, width: int) -> str:
|
||||
"""
|
||||
Truncates a string into a string adapted to the width and height of
|
||||
the screen.
|
||||
"""
|
||||
height = max(0, height)
|
||||
width = max(0, width)
|
||||
lines = msg.split("\n")
|
||||
@ -36,8 +43,8 @@ class Display:
|
||||
|
||||
def translate_color(self, color: Union[int, Tuple[int, int, int]]) -> int:
|
||||
"""
|
||||
Translate a tuple (R, G, B) into a curses color index.
|
||||
If we have already a color index, then nothing is processed.
|
||||
Translates a tuple (R, G, B) into a curses color index.
|
||||
If we already have a color index, then nothing is processed.
|
||||
If this is a tuple, we construct a new color index if non-existing
|
||||
and we return this index.
|
||||
The values of R, G and B must be between 0 and 1000, and not
|
||||
@ -66,9 +73,9 @@ class Display:
|
||||
low: bool = False, right: bool = False, top: bool = False,
|
||||
vertical: bool = False, chartext: bool = False) -> None:
|
||||
"""
|
||||
Display a message onto the pad.
|
||||
Displays a message onto the pad.
|
||||
If the message is too large, it is truncated vertically and horizontally
|
||||
The text can be bold, italic, blinking, ... if the good parameters are
|
||||
The text can be bold, italic, blinking, ... if the right parameters are
|
||||
given. These parameters are translated into curses attributes.
|
||||
The foreground and background colors can be given as curses constants
|
||||
(curses.COLOR_*), or by giving a tuple (R, G, B) that corresponds to
|
||||
@ -126,6 +133,9 @@ class Display:
|
||||
|
||||
def resize(self, y: int, x: int, height: int, width: int,
|
||||
resize_pad: bool = True) -> None:
|
||||
"""
|
||||
Resizes a pad.
|
||||
"""
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.width = width
|
||||
@ -136,6 +146,9 @@ class Display:
|
||||
self.pad.resize(self.height + 1, self.width + 1)
|
||||
|
||||
def refresh(self, *args, resize_pad: bool = True) -> None:
|
||||
"""
|
||||
Refreshes a pad
|
||||
"""
|
||||
if len(args) == 4:
|
||||
self.resize(*args, resize_pad)
|
||||
self.display()
|
||||
@ -144,10 +157,10 @@ class Display:
|
||||
window_y: int, window_x: int,
|
||||
last_y: int, last_x: int) -> None:
|
||||
"""
|
||||
Refresh a pad on a part of the window.
|
||||
Refreshes a pad on a part of the window.
|
||||
The refresh starts at coordinates (top_y, top_x) from the pad,
|
||||
and is drawn from (window_y, window_x) to (last_y, last_x).
|
||||
If coordinates are invalid (negative indexes/length..., then nothing
|
||||
If coordinates are invalid (negative indexes/length...), then nothing
|
||||
is drawn and no error is raised.
|
||||
"""
|
||||
top_y, top_x = max(0, top_y), max(0, top_x)
|
||||
@ -159,15 +172,25 @@ class Display:
|
||||
|
||||
if last_y >= window_y and last_x >= window_x:
|
||||
# Refresh the pad only if coordinates are valid
|
||||
pad.refresh(top_y, top_x, window_y, window_x, last_y, last_x)
|
||||
pad.noutrefresh(top_y, top_x, window_y, window_x, last_y, last_x)
|
||||
|
||||
def display(self) -> None:
|
||||
"""
|
||||
Draw the content of the display and refresh pads.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def update(self, game: Game) -> None:
|
||||
"""
|
||||
The game state was updated.
|
||||
Indicate what to do with the new state.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def handle_click(self, y: int, x: int, game: Game) -> None:
|
||||
"""
|
||||
A mouse click was performed on the coordinates (y, x) of the pad.
|
||||
Maybe it can do something.
|
||||
Maybe it should do something.
|
||||
"""
|
||||
pass
|
||||
|
||||
@ -181,7 +204,9 @@ class Display:
|
||||
|
||||
|
||||
class VerticalSplit(Display):
|
||||
|
||||
"""
|
||||
A class to split the screen in two vertically with a pretty line.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.pad = self.newpad(self.rows, 1)
|
||||
@ -202,7 +227,9 @@ class VerticalSplit(Display):
|
||||
|
||||
|
||||
class HorizontalSplit(Display):
|
||||
|
||||
"""
|
||||
A class to split the screen in two horizontally with a pretty line.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.pad = self.newpad(1, self.cols)
|
||||
@ -223,6 +250,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:
|
||||
@ -49,19 +55,18 @@ class DisplayManager:
|
||||
self.handle_mouse_click(*params)
|
||||
|
||||
def update_game_components(self) -> None:
|
||||
"""
|
||||
The game state was updated.
|
||||
Trigger all displays of these modifications.
|
||||
"""
|
||||
for d in self.displays:
|
||||
d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK)
|
||||
self.mapdisplay.update_map(self.game.map)
|
||||
self.statsdisplay.update_player(self.game.player)
|
||||
self.game.inventory_menu.update_player(self.game.player)
|
||||
self.game.store_menu.update_merchant(self.game.player)
|
||||
self.playerinventorydisplay.update_menu(self.game.inventory_menu)
|
||||
self.storeinventorydisplay.update_menu(self.game.store_menu)
|
||||
self.settingsmenudisplay.update_menu(self.game.settings_menu)
|
||||
self.logsdisplay.update_logs(self.game.logs)
|
||||
self.messagedisplay.update_message(self.game.message)
|
||||
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:
|
||||
@ -74,7 +79,11 @@ 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)
|
||||
|
||||
if self.game.state == GameMode.PLAY \
|
||||
or self.game.state == GameMode.INVENTORY \
|
||||
@ -97,27 +106,41 @@ class DisplayManager:
|
||||
|
||||
if self.game.state == GameMode.INVENTORY:
|
||||
self.playerinventorydisplay.refresh(
|
||||
self.rows // 10, self.cols // 2,
|
||||
8 * self.rows // 10, 2 * self.cols // 5)
|
||||
self.rows // 10,
|
||||
pack.tile_width * (self.cols // (2 * pack.tile_width)),
|
||||
8 * self.rows // 10,
|
||||
pack.tile_width * (2 * self.cols // (5 * pack.tile_width)))
|
||||
displays.append(self.playerinventorydisplay)
|
||||
elif self.game.state == GameMode.STORE:
|
||||
self.storeinventorydisplay.refresh(
|
||||
self.rows // 10, self.cols // 2,
|
||||
8 * self.rows // 10, 2 * self.cols // 5)
|
||||
self.rows // 10,
|
||||
pack.tile_width * (self.cols // (2 * pack.tile_width)),
|
||||
8 * self.rows // 10,
|
||||
pack.tile_width * (2 * self.cols // (5 * pack.tile_width)))
|
||||
self.playerinventorydisplay.refresh(
|
||||
self.rows // 10,
|
||||
pack.tile_width * (self.cols // (10 * pack.tile_width)),
|
||||
8 * self.rows // 10,
|
||||
pack.tile_width * (2 * self.cols // (5 * pack.tile_width)))
|
||||
displays.append(self.storeinventorydisplay)
|
||||
displays.append(self.playerinventorydisplay)
|
||||
elif self.game.state == GameMode.MAINMENU:
|
||||
self.mainmenudisplay.refresh(0, 0, self.rows, self.cols)
|
||||
displays.append(self.mainmenudisplay)
|
||||
elif self.game.state == GameMode.SETTINGS:
|
||||
self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols)
|
||||
displays.append(self.settingsmenudisplay)
|
||||
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
|
||||
for line in self.game.message.split("\n"):
|
||||
height += 1
|
||||
width = max(width, len(line))
|
||||
y, x = (self.rows - height) // 2, (self.cols - width) // 2
|
||||
y = pack.tile_width * (self.rows - height) // (2 * pack.tile_width)
|
||||
x = pack.tile_width * ((self.cols - width) // (2 * pack.tile_width))
|
||||
self.messagedisplay.refresh(y, x, height, width)
|
||||
displays.append(self.messagedisplay)
|
||||
|
||||
@ -127,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,
|
||||
@ -138,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
|
||||
|
@ -2,17 +2,23 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from squirrelbattle.display.display import Display
|
||||
from squirrelbattle.game import Game
|
||||
from squirrelbattle.interfaces import Logs
|
||||
|
||||
|
||||
class LogsDisplay(Display):
|
||||
"""
|
||||
A class to handle the display of the logs.
|
||||
"""
|
||||
|
||||
logs: Logs
|
||||
|
||||
def __init__(self, *args) -> None:
|
||||
super().__init__(*args)
|
||||
self.pad = self.newpad(self.rows, self.cols)
|
||||
|
||||
def update_logs(self, logs: Logs) -> None:
|
||||
self.logs = logs
|
||||
def update(self, game: Game) -> None:
|
||||
self.logs = game.logs
|
||||
|
||||
def display(self) -> None:
|
||||
messages = self.logs.messages[-self.height:]
|
||||
|
@ -3,16 +3,23 @@
|
||||
|
||||
from squirrelbattle.interfaces import Map
|
||||
from .display import Display
|
||||
from ..game import Game
|
||||
|
||||
|
||||
class MapDisplay(Display):
|
||||
"""
|
||||
A class to handle the display of the map.
|
||||
"""
|
||||
|
||||
map: Map
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
|
||||
def update_map(self, m: Map) -> None:
|
||||
self.map = m
|
||||
self.pad = self.newpad(m.height, self.pack.tile_width * m.width + 1)
|
||||
def update(self, game: Game) -> None:
|
||||
self.map = game.map
|
||||
self.pad = self.newpad(self.map.height,
|
||||
self.pack.tile_width * self.map.width + 1)
|
||||
|
||||
def update_pad(self) -> None:
|
||||
for j in range(len(self.map.tiles)):
|
||||
|
@ -5,9 +5,10 @@ import curses
|
||||
from random import randint
|
||||
from typing import List
|
||||
|
||||
from squirrelbattle.menus import Menu, MainMenu
|
||||
from squirrelbattle.menus import Menu, MainMenu, SettingsMenu, StoreMenu
|
||||
from .display import Box, Display
|
||||
from ..enums import KeyValues
|
||||
from ..entities.player import Player
|
||||
from ..enums import KeyValues, GameMode
|
||||
from ..game import Game
|
||||
from ..resources import ResourceManager
|
||||
from ..translations import gettext as _
|
||||
@ -15,8 +16,9 @@ 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
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -78,8 +80,13 @@ class MenuDisplay(Display):
|
||||
|
||||
class SettingsMenuDisplay(MenuDisplay):
|
||||
"""
|
||||
A class to display specifically a settingsmenu object
|
||||
A class to display specifically a settingsmenu object.
|
||||
"""
|
||||
menu: SettingsMenu
|
||||
|
||||
def update(self, game: Game) -> None:
|
||||
self.update_menu(game.settings_menu)
|
||||
|
||||
@property
|
||||
def values(self) -> List[str]:
|
||||
return [_(a[1][1]) + (" : "
|
||||
@ -91,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)
|
||||
@ -113,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)
|
||||
@ -122,6 +131,9 @@ class MainMenuDisplay(Display):
|
||||
menuy, menux, min(self.menudisplay.preferred_height,
|
||||
self.height - menuy), menuwidth)
|
||||
|
||||
def update(self, game: Game) -> None:
|
||||
self.menudisplay.update_menu(game.main_menu)
|
||||
|
||||
def handle_click(self, y: int, x: int, game: Game) -> None:
|
||||
menuwidth = min(self.menudisplay.preferred_width, self.width)
|
||||
menuy, menux = len(self.title) + 8, self.width // 2 - menuwidth // 2 - 1
|
||||
@ -133,15 +145,41 @@ 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
|
||||
|
||||
def update(self, game: Game) -> None:
|
||||
self.player = game.player
|
||||
self.update_menu(game.inventory_menu)
|
||||
self.store_mode = game.state == GameMode.STORE
|
||||
self.selected = game.state == GameMode.INVENTORY \
|
||||
or (self.store_mode and not game.is_in_store_menu)
|
||||
|
||||
def update_pad(self) -> None:
|
||||
self.menubox.update_title(_("INVENTORY"))
|
||||
for i, item in enumerate(self.menu.values):
|
||||
rep = self.pack[item.name.upper()]
|
||||
selection = f"[{rep}]" if i == self.menu.position else f" {rep} "
|
||||
selection = f"[{rep}]" if i == self.menu.position \
|
||||
and self.selected else f" {rep} "
|
||||
self.addstr(self.pad, i + 1, 0, selection
|
||||
+ " " + item.translated_name.capitalize())
|
||||
+ " " + item.translated_name.capitalize()
|
||||
+ (": " + str(item.price) + " Hazels"
|
||||
if self.store_mode else ""))
|
||||
|
||||
if self.store_mode:
|
||||
price = f"{self.pack.HAZELNUT} {self.player.hazel} Hazels"
|
||||
width = len(price) + (self.pack.tile_width - 1)
|
||||
self.addstr(self.pad, self.height - 3, self.width - width - 2,
|
||||
price, italic=True)
|
||||
|
||||
@property
|
||||
def truewidth(self) -> int:
|
||||
@ -156,19 +194,36 @@ class PlayerInventoryDisplay(MenuDisplay):
|
||||
We can select a menu item with the mouse.
|
||||
"""
|
||||
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2))
|
||||
game.is_in_store_menu = False
|
||||
game.handle_key_pressed(KeyValues.ENTER)
|
||||
|
||||
|
||||
class StoreInventoryDisplay(MenuDisplay):
|
||||
"""
|
||||
A class to handle the display of a merchant's inventory.
|
||||
"""
|
||||
menu: StoreMenu
|
||||
selected: bool = False
|
||||
|
||||
def update(self, game: Game) -> None:
|
||||
self.update_menu(game.store_menu)
|
||||
self.selected = game.is_in_store_menu
|
||||
|
||||
def update_pad(self) -> None:
|
||||
self.menubox.update_title(_("STALL"))
|
||||
for i, item in enumerate(self.menu.values):
|
||||
rep = self.pack[item.name.upper()]
|
||||
selection = f"[{rep}]" if i == self.menu.position else f" {rep} "
|
||||
selection = f"[{rep}]" if i == self.menu.position \
|
||||
and self.selected else f" {rep} "
|
||||
self.addstr(self.pad, i + 1, 0, selection
|
||||
+ " " + item.translated_name.capitalize()
|
||||
+ ": " + str(item.price) + " Hazels")
|
||||
|
||||
price = f"{self.pack.HAZELNUT} {self.menu.merchant.hazel} Hazels"
|
||||
width = len(price) + (self.pack.tile_width - 1)
|
||||
self.addstr(self.pad, self.height - 3, self.width - width - 2, price,
|
||||
italic=True)
|
||||
|
||||
@property
|
||||
def truewidth(self) -> int:
|
||||
return max(1, self.height if hasattr(self, "height") else 10)
|
||||
@ -182,4 +237,5 @@ class StoreInventoryDisplay(MenuDisplay):
|
||||
We can select a menu item with the mouse.
|
||||
"""
|
||||
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2))
|
||||
game.is_in_store_menu = True
|
||||
game.handle_key_pressed(KeyValues.ENTER)
|
||||
|
@ -3,11 +3,12 @@
|
||||
import curses
|
||||
|
||||
from squirrelbattle.display.display import Box, Display
|
||||
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):
|
||||
@ -17,8 +18,8 @@ class MessageDisplay(Display):
|
||||
self.message = ""
|
||||
self.pad = self.newpad(1, 1)
|
||||
|
||||
def update_message(self, msg: str) -> None:
|
||||
self.message = msg
|
||||
def update(self, game: Game) -> None:
|
||||
self.message = game.message
|
||||
|
||||
def display(self) -> None:
|
||||
self.box.refresh(self.y - 1, self.x - 2,
|
||||
|
@ -4,30 +4,36 @@
|
||||
import curses
|
||||
|
||||
from ..entities.player import Player
|
||||
from ..game import Game
|
||||
from ..translations import gettext as _
|
||||
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):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.pad = self.newpad(self.rows, self.cols)
|
||||
|
||||
def update_player(self, p: Player) -> None:
|
||||
self.player = p
|
||||
def update(self, game: Game) -> None:
|
||||
self.player = game.player
|
||||
|
||||
def update_pad(self) -> None:
|
||||
string2 = "Player -- LVL {}\nEXP {}/{}\nHP {}/{}"\
|
||||
.format(self.player.level, self.player.current_xp,
|
||||
self.player.max_xp, self.player.health,
|
||||
self.player.maxhealth)
|
||||
string2 = f"{_(self.player.name).capitalize()} " \
|
||||
f"-- LVL {self.player.level} -- " \
|
||||
f"FLOOR {-self.player.map.floor}\n" \
|
||||
f"EXP {self.player.current_xp}/{self.player.max_xp}\n" \
|
||||
f"HP {self.player.health}/{self.player.maxhealth}"
|
||||
self.addstr(self.pad, 0, 0, string2)
|
||||
string3 = "STR {}\nINT {}\nCHR {}\nDEX {}\nCON {}"\
|
||||
.format(self.player.strength,
|
||||
self.player.intelligence, self.player.charisma,
|
||||
self.player.dexterity, self.player.constitution)
|
||||
string3 = f"STR {self.player.strength}\n" \
|
||||
f"INT {self.player.intelligence}\n" \
|
||||
f"CHR {self.player.charisma}\n" \
|
||||
f"DEX {self.player.dexterity}\n" \
|
||||
f"CON {self.player.constitution}"
|
||||
self.addstr(self.pad, 3, 0, string3)
|
||||
|
||||
inventory_str = _("Inventory:") + " "
|
||||
|
@ -6,6 +6,9 @@ from typing import Any, Union, Tuple
|
||||
|
||||
|
||||
class TexturePack:
|
||||
"""
|
||||
A class to handle displaying several textures.
|
||||
"""
|
||||
_packs = dict()
|
||||
|
||||
name: str
|
||||
@ -30,6 +33,7 @@ class TexturePack:
|
||||
SWORD: str
|
||||
TEDDY_BEAR: str
|
||||
TIGER: str
|
||||
TRUMPET: str
|
||||
WALL: str
|
||||
|
||||
ASCII_PACK: "TexturePack"
|
||||
@ -66,6 +70,7 @@ TexturePack.ASCII_PACK = TexturePack(
|
||||
EMPTY=' ',
|
||||
EXPLOSION='%',
|
||||
FLOOR='.',
|
||||
LADDER='H',
|
||||
HAZELNUT='¤',
|
||||
HEART='❤',
|
||||
HEDGEHOG='*',
|
||||
@ -76,6 +81,7 @@ TexturePack.ASCII_PACK = TexturePack(
|
||||
SWORD='\u2020',
|
||||
TEDDY_BEAR='8',
|
||||
TIGER='n',
|
||||
TRUMPET='/',
|
||||
WALL='#',
|
||||
)
|
||||
|
||||
@ -93,6 +99,7 @@ TexturePack.SQUIRREL_PACK = TexturePack(
|
||||
EMPTY=' ',
|
||||
EXPLOSION='💥',
|
||||
FLOOR='██',
|
||||
LADDER=('🪜', curses.COLOR_WHITE, curses.COLOR_WHITE),
|
||||
HAZELNUT='🌰',
|
||||
HEART='💜',
|
||||
HEDGEHOG='🦔',
|
||||
@ -100,8 +107,9 @@ TexturePack.SQUIRREL_PACK = TexturePack(
|
||||
MERCHANT='🦜',
|
||||
RABBIT='🐇',
|
||||
SUNFLOWER='🌻',
|
||||
SWORD='🗡️',
|
||||
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,
|
||||
@ -47,7 +43,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
|
||||
@ -61,8 +57,8 @@ class Player(InventoryHolder, FightingEntity):
|
||||
|
||||
def add_xp(self, xp: int) -> None:
|
||||
"""
|
||||
Add some experience to the player.
|
||||
If the required amount is reached, level up.
|
||||
Adds some experience to the player.
|
||||
If the required amount is reached, the player levels up.
|
||||
"""
|
||||
self.current_xp += xp
|
||||
self.level_up()
|
||||
@ -89,56 +85,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()
|
||||
@ -47,11 +47,12 @@ class KeyValues(Enum):
|
||||
SPACE = auto()
|
||||
CHAT = auto()
|
||||
WAIT = auto()
|
||||
LADDER = auto()
|
||||
|
||||
@staticmethod
|
||||
def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]:
|
||||
"""
|
||||
Translate the raw string key into an enum value that we can use.
|
||||
Translates the raw string key into an enum value that we can use.
|
||||
"""
|
||||
if key in (settings.KEY_DOWN_SECONDARY,
|
||||
settings.KEY_DOWN_PRIMARY):
|
||||
@ -81,4 +82,6 @@ class KeyValues(Enum):
|
||||
return KeyValues.CHAT
|
||||
elif key == settings.KEY_WAIT:
|
||||
return KeyValues.WAIT
|
||||
elif key == settings.KEY_LADDER:
|
||||
return KeyValues.LADDER
|
||||
return None
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
from json import JSONDecodeError
|
||||
from random import randint
|
||||
from typing import Any, Optional
|
||||
from typing import Any, Optional, List
|
||||
import curses
|
||||
import json
|
||||
import os
|
||||
@ -22,7 +22,8 @@ class Game:
|
||||
"""
|
||||
The game object controls all actions in the game.
|
||||
"""
|
||||
map: Map
|
||||
maps: List[Map]
|
||||
map_index: int
|
||||
player: Player
|
||||
screen: Any
|
||||
# display_actions is a display interface set by the bootstrapper
|
||||
@ -30,10 +31,11 @@ class Game:
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
Init the game.
|
||||
Initiates the game.
|
||||
"""
|
||||
self.state = GameMode.MAINMENU
|
||||
self.waiting_for_friendly_key = False
|
||||
self.is_in_store_menu = True
|
||||
self.settings = Settings()
|
||||
self.settings.load_settings()
|
||||
self.settings.write_settings()
|
||||
@ -48,9 +50,11 @@ 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.maps = []
|
||||
self.map_index = 0
|
||||
self.map = Map.load(ResourceManager.get_asset_path("example_map.txt"))
|
||||
self.map.logs = self.logs
|
||||
self.logs.clear()
|
||||
@ -60,16 +64,36 @@ class Game:
|
||||
self.map.spawn_random_entities(randint(3, 10))
|
||||
self.inventory_menu.update_player(self.player)
|
||||
|
||||
def run(self, screen: Any) -> None:
|
||||
@property
|
||||
def map(self) -> Map:
|
||||
"""
|
||||
Return the current map where the user is.
|
||||
"""
|
||||
return self.maps[self.map_index]
|
||||
|
||||
@map.setter
|
||||
def map(self, m: Map) -> None:
|
||||
"""
|
||||
Redefine the current map.
|
||||
"""
|
||||
if len(self.maps) == self.map_index:
|
||||
# Insert new map
|
||||
self.maps.append(m)
|
||||
# Redefine the current map
|
||||
self.maps[self.map_index] = m
|
||||
|
||||
def run(self, screen: Any) -> None: # pragma no cover
|
||||
"""
|
||||
Main infinite loop.
|
||||
We wait for the player's action, then we do what that should be done
|
||||
when the given key gets pressed.
|
||||
We wait for the player's action, then we do what should be done
|
||||
when a key gets pressed.
|
||||
"""
|
||||
while True: # pragma no cover
|
||||
screen.refresh()
|
||||
while True:
|
||||
screen.erase()
|
||||
screen.refresh()
|
||||
screen.noutrefresh()
|
||||
self.display_actions(DisplayActions.REFRESH)
|
||||
curses.doupdate()
|
||||
key = screen.getkey()
|
||||
if key == "KEY_MOUSE":
|
||||
_ignored1, x, y, _ignored2, _ignored3 = curses.getmouse()
|
||||
@ -81,7 +105,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:
|
||||
@ -103,6 +127,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:
|
||||
@ -111,16 +137,16 @@ class Game:
|
||||
"""
|
||||
if key == KeyValues.UP:
|
||||
if self.player.move_up():
|
||||
self.map.tick()
|
||||
self.map.tick(self.player)
|
||||
elif key == KeyValues.DOWN:
|
||||
if self.player.move_down():
|
||||
self.map.tick()
|
||||
self.map.tick(self.player)
|
||||
elif key == KeyValues.LEFT:
|
||||
if self.player.move_left():
|
||||
self.map.tick()
|
||||
self.map.tick(self.player)
|
||||
elif key == KeyValues.RIGHT:
|
||||
if self.player.move_right():
|
||||
self.map.tick()
|
||||
self.map.tick(self.player)
|
||||
elif key == KeyValues.INVENTORY:
|
||||
self.state = GameMode.INVENTORY
|
||||
elif key == KeyValues.SPACE:
|
||||
@ -129,12 +155,61 @@ class Game:
|
||||
# Wait for the direction of the friendly entity
|
||||
self.waiting_for_friendly_key = True
|
||||
elif key == KeyValues.WAIT:
|
||||
self.map.tick()
|
||||
self.map.tick(self.player)
|
||||
elif key == KeyValues.LADDER:
|
||||
self.handle_ladder()
|
||||
|
||||
def handle_ladder(self) -> None:
|
||||
"""
|
||||
The player pressed the ladder key to switch map
|
||||
"""
|
||||
# On a ladder, we switch level
|
||||
y, x = self.player.y, self.player.x
|
||||
if not self.map.tiles[y][x].is_ladder():
|
||||
return
|
||||
|
||||
# We move up on the ladder of the beginning,
|
||||
# down at the end of the stage
|
||||
move_down = y != self.map.start_y and x != self.map.start_x
|
||||
old_map = self.map
|
||||
self.map_index += 1 if move_down else -1
|
||||
if self.map_index == -1:
|
||||
self.map_index = 0
|
||||
return
|
||||
while self.map_index >= len(self.maps):
|
||||
# TODO: generate a new map
|
||||
self.maps.append(Map.load(ResourceManager.get_asset_path(
|
||||
"example_map_2.txt")))
|
||||
new_map = self.map
|
||||
new_map.floor = self.map_index
|
||||
old_map.remove_entity(self.player)
|
||||
new_map.add_entity(self.player)
|
||||
if move_down:
|
||||
self.player.move(self.map.start_y, self.map.start_x)
|
||||
self.logs.add_message(
|
||||
_("The player climbs down to the floor {floor}.")
|
||||
.format(floor=-self.map_index))
|
||||
else:
|
||||
# Find the ladder of the end of the game
|
||||
ladder_y, ladder_x = -1, -1
|
||||
for y in range(self.map.height):
|
||||
for x in range(self.map.width):
|
||||
if (y, x) != (self.map.start_y, self.map.start_x) \
|
||||
and self.map.tiles[y][x].is_ladder():
|
||||
ladder_y, ladder_x = y, x
|
||||
break
|
||||
self.player.move(ladder_y, ladder_x)
|
||||
self.logs.add_message(
|
||||
_("The player climbs up the floor {floor}.")
|
||||
.format(floor=-self.map_index))
|
||||
|
||||
self.display_actions(DisplayActions.UPDATE)
|
||||
|
||||
def handle_friendly_entity_chat(self, key: KeyValues) -> None:
|
||||
"""
|
||||
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
|
||||
@ -162,7 +237,9 @@ class Game:
|
||||
self.logs.add_message(msg)
|
||||
if entity.is_merchant():
|
||||
self.state = GameMode.STORE
|
||||
self.is_in_store_menu = True
|
||||
self.store_menu.update_merchant(entity)
|
||||
self.display_actions(DisplayActions.UPDATE)
|
||||
|
||||
def handle_key_pressed_inventory(self, key: KeyValues) -> None:
|
||||
"""
|
||||
@ -191,26 +268,37 @@ class Game:
|
||||
"""
|
||||
In a store menu, we can buy items or close the menu.
|
||||
"""
|
||||
if key == KeyValues.SPACE:
|
||||
menu = self.store_menu if self.is_in_store_menu else self.inventory_menu
|
||||
|
||||
if key == KeyValues.SPACE or key == KeyValues.INVENTORY:
|
||||
self.state = GameMode.PLAY
|
||||
elif key == KeyValues.UP:
|
||||
self.store_menu.go_up()
|
||||
menu.go_up()
|
||||
elif key == KeyValues.DOWN:
|
||||
self.store_menu.go_down()
|
||||
if self.store_menu.values and not self.player.dead:
|
||||
menu.go_down()
|
||||
elif key == KeyValues.LEFT:
|
||||
self.is_in_store_menu = False
|
||||
self.display_actions(DisplayActions.UPDATE)
|
||||
elif key == KeyValues.RIGHT:
|
||||
self.is_in_store_menu = True
|
||||
self.display_actions(DisplayActions.UPDATE)
|
||||
if menu.values and not self.player.dead:
|
||||
if key == KeyValues.ENTER:
|
||||
item = self.store_menu.validate()
|
||||
flag = item.be_sold(self.player, self.store_menu.merchant)
|
||||
item = menu.validate()
|
||||
owner = self.store_menu.merchant if self.is_in_store_menu \
|
||||
else self.player
|
||||
buyer = self.player if self.is_in_store_menu \
|
||||
else self.store_menu.merchant
|
||||
flag = item.be_sold(buyer, owner)
|
||||
if not flag:
|
||||
self.message = _("You do not have enough money")
|
||||
self.display_actions(DisplayActions.UPDATE)
|
||||
self.message = _("The buyer does not have enough money")
|
||||
self.display_actions(DisplayActions.UPDATE)
|
||||
# Ensure that the cursor has a good position
|
||||
self.store_menu.position = min(self.store_menu.position,
|
||||
len(self.store_menu.values) - 1)
|
||||
menu.position = min(menu.position, len(menu.values) - 1)
|
||||
|
||||
def handle_key_pressed_main_menu(self, key: KeyValues) -> None:
|
||||
"""
|
||||
In the main menu, we can navigate through options.
|
||||
In the main menu, we can navigate through different options.
|
||||
"""
|
||||
if key == KeyValues.DOWN:
|
||||
self.main_menu.go_down()
|
||||
@ -235,13 +323,13 @@ class Game:
|
||||
|
||||
def save_state(self) -> dict:
|
||||
"""
|
||||
Saves the game to a dictionary
|
||||
Saves the game to a dictionary.
|
||||
"""
|
||||
return self.map.save_state()
|
||||
|
||||
def load_state(self, d: dict) -> None:
|
||||
"""
|
||||
Loads the game from a dictionary
|
||||
Loads the game from a dictionary.
|
||||
"""
|
||||
try:
|
||||
self.map.load_state(d)
|
||||
@ -265,7 +353,7 @@ class Game:
|
||||
|
||||
def load_game(self) -> None:
|
||||
"""
|
||||
Loads the game from a file
|
||||
Loads the game from a file.
|
||||
"""
|
||||
file_path = ResourceManager.get_config_path("save.json")
|
||||
if os.path.isfile(file_path):
|
||||
@ -282,7 +370,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()))
|
||||
|
@ -2,10 +2,11 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from enum import Enum, auto
|
||||
from math import sqrt
|
||||
from math import ceil, sqrt
|
||||
from random import choice, randint
|
||||
from typing import List, Optional, Tuple, Any
|
||||
from math import ceil
|
||||
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 _
|
||||
@ -13,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.
|
||||
"""
|
||||
@ -61,9 +62,10 @@ class Slope():
|
||||
|
||||
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.
|
||||
"""
|
||||
floor: int
|
||||
width: int
|
||||
height: int
|
||||
start_y: int
|
||||
@ -80,6 +82,7 @@ class Map:
|
||||
|
||||
def __init__(self, width: int, height: int, tiles: list,
|
||||
start_y: int, start_x: int):
|
||||
self.floor = 0
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.start_y = start_y
|
||||
@ -94,14 +97,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)
|
||||
@ -121,7 +127,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()
|
||||
@ -130,7 +136,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()
|
||||
@ -139,7 +146,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]
|
||||
@ -155,7 +162,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)
|
||||
@ -164,7 +171,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)
|
||||
@ -172,7 +179,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
|
||||
@ -305,16 +312,19 @@ class Map:
|
||||
self.visibility[y][x] = True
|
||||
self.seen_tiles[y][x] = True
|
||||
|
||||
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
|
||||
@ -331,7 +341,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"]
|
||||
@ -348,16 +358,17 @@ 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()
|
||||
FLOOR = auto()
|
||||
LADDER = auto()
|
||||
|
||||
@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:
|
||||
@ -367,9 +378,18 @@ 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)
|
||||
val = getattr(pack, self.name)
|
||||
return val[0] if isinstance(val, tuple) else val
|
||||
|
||||
def color(self, pack: TexturePack) -> Tuple[int, int]:
|
||||
"""
|
||||
Retrieve the tuple (fg_color, bg_color) of the current Tile.
|
||||
"""
|
||||
val = getattr(pack, self.name)
|
||||
return (val[1], val[2]) if isinstance(val, tuple) else \
|
||||
(pack.tile_fg_color, pack.tile_bg_color)
|
||||
|
||||
def is_wall(self) -> bool:
|
||||
"""
|
||||
@ -377,21 +397,28 @@ class Tile(Enum):
|
||||
"""
|
||||
return self == Tile.WALL
|
||||
|
||||
def is_ladder(self) -> bool:
|
||||
"""
|
||||
Is this Tile a ladder?
|
||||
"""
|
||||
return self == Tile.LADDER
|
||||
|
||||
def can_walk(self) -> bool:
|
||||
"""
|
||||
Check if an entity (player or not) can move in this tile.
|
||||
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,
|
||||
@ -400,11 +427,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:
|
||||
@ -413,7 +441,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
|
||||
@ -421,49 +449,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))
|
||||
|
||||
@ -486,6 +565,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?
|
||||
@ -495,29 +581,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 {
|
||||
@ -532,11 +623,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
|
||||
@ -548,7 +640,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
|
||||
@ -575,11 +667,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()),
|
||||
@ -588,7 +684,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:
|
||||
@ -601,20 +698,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():
|
||||
@ -624,7 +721,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
|
||||
|
||||
@ -635,7 +732,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"]
|
||||
|
||||
@ -646,7 +743,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)):
|
||||
@ -656,7 +753,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()
|
||||
@ -666,7 +763,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
|
||||
@ -675,19 +772,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
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: squirrelbattle 3.14.1\n"
|
||||
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
|
||||
"POT-Creation-Date: 2020-12-12 18:02+0100\n"
|
||||
"POT-Creation-Date: 2021-01-06 15:19+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -17,19 +17,19 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: squirrelbattle/display/menudisplay.py:139
|
||||
#: squirrelbattle/display/menudisplay.py:160
|
||||
msgid "INVENTORY"
|
||||
msgstr "BESTAND"
|
||||
|
||||
#: squirrelbattle/display/menudisplay.py:164
|
||||
#: squirrelbattle/display/menudisplay.py:202
|
||||
msgid "STALL"
|
||||
msgstr "STAND"
|
||||
|
||||
#: squirrelbattle/display/statsdisplay.py:33
|
||||
#: squirrelbattle/display/statsdisplay.py:36
|
||||
msgid "Inventory:"
|
||||
msgstr "Bestand:"
|
||||
|
||||
#: squirrelbattle/display/statsdisplay.py:52
|
||||
#: squirrelbattle/display/statsdisplay.py:55
|
||||
msgid "YOU ARE DEAD"
|
||||
msgstr "SIE WURDEN GESTORBEN"
|
||||
|
||||
@ -58,11 +58,21 @@ msgstr "Die Bombe explodiert."
|
||||
msgid "{player} exchanged its body with {entity}."
|
||||
msgstr "{player} täuscht seinem Körper mit {entity} aus."
|
||||
|
||||
#: squirrelbattle/game.py:205 squirrelbattle/tests/game_test.py:573
|
||||
msgid "You do not have enough money"
|
||||
msgstr "Sie haben nicht genug Geld"
|
||||
#: squirrelbattle/game.py:182
|
||||
#, python-brace-format
|
||||
msgid "The player climbs down to the floor {floor}."
|
||||
msgstr "Der Spieler klettert auf dem Stock {floor} hinunter."
|
||||
|
||||
#: squirrelbattle/game.py:249
|
||||
#: squirrelbattle/game.py:195
|
||||
#, python-brace-format
|
||||
msgid "The player climbs up the floor {floor}."
|
||||
msgstr "Der Spieler klettert auf dem Stock {floor} hinoben."
|
||||
|
||||
#: squirrelbattle/game.py:285 squirrelbattle/tests/game_test.py:592
|
||||
msgid "The buyer does not have enough money"
|
||||
msgstr "Der Kaufer hat nicht genug Geld"
|
||||
|
||||
#: squirrelbattle/game.py:328
|
||||
msgid ""
|
||||
"Some keys are missing in your save file.\n"
|
||||
"Your save seems to be corrupt. It got deleted."
|
||||
@ -70,7 +80,7 @@ msgstr ""
|
||||
"In Ihrer Speicherdatei fehlen einige Schlüssel.\n"
|
||||
"Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht."
|
||||
|
||||
#: squirrelbattle/game.py:257
|
||||
#: squirrelbattle/game.py:336
|
||||
msgid ""
|
||||
"No player was found on this map!\n"
|
||||
"Maybe you died?"
|
||||
@ -78,7 +88,7 @@ msgstr ""
|
||||
"Auf dieser Karte wurde kein Spieler gefunden!\n"
|
||||
"Vielleicht sind Sie gestorben?"
|
||||
|
||||
#: squirrelbattle/game.py:277
|
||||
#: squirrelbattle/game.py:356
|
||||
msgid ""
|
||||
"The JSON file is not correct.\n"
|
||||
"Your save seems corrupted. It got deleted."
|
||||
@ -86,22 +96,22 @@ msgstr ""
|
||||
"Die JSON-Datei ist nicht korrekt.\n"
|
||||
"Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht."
|
||||
|
||||
#: squirrelbattle/interfaces.py:429
|
||||
#: squirrelbattle/interfaces.py:453
|
||||
#, python-brace-format
|
||||
msgid "{name} hits {opponent}."
|
||||
msgstr "{name} schlägt {opponent}."
|
||||
|
||||
#: squirrelbattle/interfaces.py:441
|
||||
#: squirrelbattle/interfaces.py:465
|
||||
#, python-brace-format
|
||||
msgid "{name} takes {amount} damage."
|
||||
msgstr "{name} nimmt {amount} Schadenspunkte."
|
||||
|
||||
#: squirrelbattle/interfaces.py:443
|
||||
#: squirrelbattle/interfaces.py:467
|
||||
#, python-brace-format
|
||||
msgid "{name} dies."
|
||||
msgstr "{name} stirbt."
|
||||
|
||||
#: squirrelbattle/interfaces.py:477
|
||||
#: squirrelbattle/interfaces.py:501
|
||||
#, python-brace-format
|
||||
msgid "{entity} said: {message}"
|
||||
msgstr "{entity} hat gesagt: {message}"
|
||||
@ -110,8 +120,8 @@ msgstr "{entity} hat gesagt: {message}"
|
||||
msgid "Back"
|
||||
msgstr "Zurück"
|
||||
|
||||
#: squirrelbattle/tests/game_test.py:344 squirrelbattle/tests/game_test.py:347
|
||||
#: squirrelbattle/tests/game_test.py:350 squirrelbattle/tests/game_test.py:353
|
||||
#: squirrelbattle/tests/game_test.py:358 squirrelbattle/tests/game_test.py:361
|
||||
#: squirrelbattle/tests/game_test.py:364 squirrelbattle/tests/game_test.py:367
|
||||
#: squirrelbattle/tests/translations_test.py:16
|
||||
msgid "New game"
|
||||
msgstr "Neu Spiel"
|
||||
@ -197,57 +207,61 @@ msgid "Key used to wait"
|
||||
msgstr "Wartentaste"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:56
|
||||
msgid "Key used to use ladders"
|
||||
msgstr "Leitertaste"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:58
|
||||
msgid "Texture pack"
|
||||
msgstr "Textur-Packung"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:57
|
||||
#: squirrelbattle/tests/translations_test.py:59
|
||||
msgid "Language"
|
||||
msgstr "Sprache"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:60
|
||||
#: squirrelbattle/tests/translations_test.py:62
|
||||
msgid "player"
|
||||
msgstr "Spieler"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:62
|
||||
#: squirrelbattle/tests/translations_test.py:64
|
||||
msgid "hedgehog"
|
||||
msgstr "Igel"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:63
|
||||
#: squirrelbattle/tests/translations_test.py:65
|
||||
msgid "merchant"
|
||||
msgstr "Kaufmann"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:64
|
||||
#: squirrelbattle/tests/translations_test.py:66
|
||||
msgid "rabbit"
|
||||
msgstr "Kanninchen"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:65
|
||||
#: squirrelbattle/tests/translations_test.py:67
|
||||
msgid "sunflower"
|
||||
msgstr "Sonnenblume"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:66
|
||||
#: squirrelbattle/tests/translations_test.py:68
|
||||
msgid "teddy bear"
|
||||
msgstr "Teddybär"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:67
|
||||
#: squirrelbattle/tests/translations_test.py:69
|
||||
msgid "tiger"
|
||||
msgstr "Tiger"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:69
|
||||
#: squirrelbattle/tests/translations_test.py:71
|
||||
msgid "body snatch potion"
|
||||
msgstr "Leichenfleddererzaubertrank"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:70
|
||||
#: squirrelbattle/tests/translations_test.py:72
|
||||
msgid "bomb"
|
||||
msgstr "Bombe"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:71
|
||||
#: squirrelbattle/tests/translations_test.py:73
|
||||
msgid "explosion"
|
||||
msgstr "Explosion"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:72
|
||||
#: squirrelbattle/tests/translations_test.py:74
|
||||
msgid "heart"
|
||||
msgstr "Herz"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:73
|
||||
#: squirrelbattle/tests/translations_test.py:75
|
||||
msgid "sword"
|
||||
msgstr "schwert"
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: squirrelbattle 3.14.1\n"
|
||||
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
|
||||
"POT-Creation-Date: 2020-12-12 18:02+0100\n"
|
||||
"POT-Creation-Date: 2021-01-06 15:19+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -17,19 +17,19 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: squirrelbattle/display/menudisplay.py:139
|
||||
#: squirrelbattle/display/menudisplay.py:160
|
||||
msgid "INVENTORY"
|
||||
msgstr "INVENTORIO"
|
||||
|
||||
#: squirrelbattle/display/menudisplay.py:164
|
||||
#: squirrelbattle/display/menudisplay.py:202
|
||||
msgid "STALL"
|
||||
msgstr "PUESTO"
|
||||
|
||||
#: squirrelbattle/display/statsdisplay.py:33
|
||||
#: squirrelbattle/display/statsdisplay.py:36
|
||||
msgid "Inventory:"
|
||||
msgstr "Inventorio :"
|
||||
|
||||
#: squirrelbattle/display/statsdisplay.py:52
|
||||
#: squirrelbattle/display/statsdisplay.py:55
|
||||
msgid "YOU ARE DEAD"
|
||||
msgstr "ERES MUERTO"
|
||||
|
||||
@ -57,11 +57,21 @@ msgstr "La bomba está explotando."
|
||||
msgid "{player} exchanged its body with {entity}."
|
||||
msgstr "{player} intercambió su cuerpo con {entity}."
|
||||
|
||||
#: squirrelbattle/game.py:205 squirrelbattle/tests/game_test.py:573
|
||||
msgid "You do not have enough money"
|
||||
msgstr "No tienes suficiente dinero"
|
||||
#: squirrelbattle/game.py:182
|
||||
#, python-brace-format
|
||||
msgid "The player climbs down to the floor {floor}."
|
||||
msgstr ""
|
||||
|
||||
#: squirrelbattle/game.py:249
|
||||
#: squirrelbattle/game.py:195
|
||||
#, python-brace-format
|
||||
msgid "The player climbs up the floor {floor}."
|
||||
msgstr ""
|
||||
|
||||
#: squirrelbattle/game.py:285 squirrelbattle/tests/game_test.py:592
|
||||
msgid "The buyer does not have enough money"
|
||||
msgstr "El comprador no tiene suficiente dinero"
|
||||
|
||||
#: squirrelbattle/game.py:328
|
||||
msgid ""
|
||||
"Some keys are missing in your save file.\n"
|
||||
"Your save seems to be corrupt. It got deleted."
|
||||
@ -69,7 +79,7 @@ msgstr ""
|
||||
"Algunas claves faltan en su archivo de guarda.\n"
|
||||
"Su guarda parece a ser corruptido. Fue eliminado."
|
||||
|
||||
#: squirrelbattle/game.py:257
|
||||
#: squirrelbattle/game.py:336
|
||||
msgid ""
|
||||
"No player was found on this map!\n"
|
||||
"Maybe you died?"
|
||||
@ -77,7 +87,7 @@ msgstr ""
|
||||
"No jugador encontrado sobre la carta !\n"
|
||||
"¿ Quizas murió ?"
|
||||
|
||||
#: squirrelbattle/game.py:277
|
||||
#: squirrelbattle/game.py:356
|
||||
msgid ""
|
||||
"The JSON file is not correct.\n"
|
||||
"Your save seems corrupted. It got deleted."
|
||||
@ -85,22 +95,22 @@ msgstr ""
|
||||
"El JSON archivo no es correcto.\n"
|
||||
"Su guarda parece corrupta. Fue eliminada."
|
||||
|
||||
#: squirrelbattle/interfaces.py:429
|
||||
#: squirrelbattle/interfaces.py:453
|
||||
#, python-brace-format
|
||||
msgid "{name} hits {opponent}."
|
||||
msgstr "{name} golpea a {opponent}."
|
||||
|
||||
#: squirrelbattle/interfaces.py:441
|
||||
#: squirrelbattle/interfaces.py:465
|
||||
#, python-brace-format
|
||||
msgid "{name} takes {amount} damage."
|
||||
msgstr "{name} recibe {amount} daño."
|
||||
|
||||
#: squirrelbattle/interfaces.py:443
|
||||
#: squirrelbattle/interfaces.py:467
|
||||
#, python-brace-format
|
||||
msgid "{name} dies."
|
||||
msgstr "{name} se muere."
|
||||
|
||||
#: squirrelbattle/interfaces.py:477
|
||||
#: squirrelbattle/interfaces.py:501
|
||||
#, python-brace-format
|
||||
msgid "{entity} said: {message}"
|
||||
msgstr "{entity} dijo : {message}"
|
||||
@ -109,8 +119,8 @@ msgstr "{entity} dijo : {message}"
|
||||
msgid "Back"
|
||||
msgstr "Volver"
|
||||
|
||||
#: squirrelbattle/tests/game_test.py:344 squirrelbattle/tests/game_test.py:347
|
||||
#: squirrelbattle/tests/game_test.py:350 squirrelbattle/tests/game_test.py:353
|
||||
#: squirrelbattle/tests/game_test.py:358 squirrelbattle/tests/game_test.py:361
|
||||
#: squirrelbattle/tests/game_test.py:364 squirrelbattle/tests/game_test.py:367
|
||||
#: squirrelbattle/tests/translations_test.py:16
|
||||
msgid "New game"
|
||||
msgstr "Nuevo partido"
|
||||
@ -196,57 +206,61 @@ msgid "Key used to wait"
|
||||
msgstr "Tecla para espera"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:56
|
||||
msgid "Key used to use ladders"
|
||||
msgstr "Tecla para el uso de las escaleras"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:58
|
||||
msgid "Texture pack"
|
||||
msgstr "Paquete de texturas"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:57
|
||||
#: squirrelbattle/tests/translations_test.py:59
|
||||
msgid "Language"
|
||||
msgstr "Languaje"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:60
|
||||
#: squirrelbattle/tests/translations_test.py:62
|
||||
msgid "player"
|
||||
msgstr "jugador"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:62
|
||||
#: squirrelbattle/tests/translations_test.py:64
|
||||
msgid "hedgehog"
|
||||
msgstr "erizo"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:63
|
||||
#: squirrelbattle/tests/translations_test.py:65
|
||||
msgid "merchant"
|
||||
msgstr "comerciante"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:64
|
||||
#: squirrelbattle/tests/translations_test.py:66
|
||||
msgid "rabbit"
|
||||
msgstr "conejo"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:65
|
||||
#: squirrelbattle/tests/translations_test.py:67
|
||||
msgid "sunflower"
|
||||
msgstr "girasol"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:66
|
||||
#: squirrelbattle/tests/translations_test.py:68
|
||||
msgid "teddy bear"
|
||||
msgstr "osito de peluche"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:67
|
||||
#: squirrelbattle/tests/translations_test.py:69
|
||||
msgid "tiger"
|
||||
msgstr "tigre"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:69
|
||||
#: squirrelbattle/tests/translations_test.py:71
|
||||
msgid "body snatch potion"
|
||||
msgstr "poción de intercambio"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:70
|
||||
#: squirrelbattle/tests/translations_test.py:72
|
||||
msgid "bomb"
|
||||
msgstr "bomba"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:71
|
||||
#: squirrelbattle/tests/translations_test.py:73
|
||||
msgid "explosion"
|
||||
msgstr "explosión"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:72
|
||||
#: squirrelbattle/tests/translations_test.py:74
|
||||
msgid "heart"
|
||||
msgstr "corazón"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:73
|
||||
#: squirrelbattle/tests/translations_test.py:75
|
||||
msgid "sword"
|
||||
msgstr "espada"
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: squirrelbattle 3.14.1\n"
|
||||
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
|
||||
"POT-Creation-Date: 2020-12-12 18:02+0100\n"
|
||||
"POT-Creation-Date: 2021-01-06 15:19+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -17,19 +17,19 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: squirrelbattle/display/menudisplay.py:139
|
||||
#: squirrelbattle/display/menudisplay.py:160
|
||||
msgid "INVENTORY"
|
||||
msgstr "INVENTAIRE"
|
||||
|
||||
#: squirrelbattle/display/menudisplay.py:164
|
||||
#: squirrelbattle/display/menudisplay.py:202
|
||||
msgid "STALL"
|
||||
msgstr "STAND"
|
||||
|
||||
#: squirrelbattle/display/statsdisplay.py:33
|
||||
#: squirrelbattle/display/statsdisplay.py:36
|
||||
msgid "Inventory:"
|
||||
msgstr "Inventaire :"
|
||||
|
||||
#: squirrelbattle/display/statsdisplay.py:52
|
||||
#: squirrelbattle/display/statsdisplay.py:55
|
||||
msgid "YOU ARE DEAD"
|
||||
msgstr "VOUS ÊTES MORT"
|
||||
|
||||
@ -58,11 +58,21 @@ msgstr "La bombe explose."
|
||||
msgid "{player} exchanged its body with {entity}."
|
||||
msgstr "{player} a échangé son corps avec {entity}."
|
||||
|
||||
#: squirrelbattle/game.py:205 squirrelbattle/tests/game_test.py:573
|
||||
msgid "You do not have enough money"
|
||||
msgstr "Vous n'avez pas assez d'argent"
|
||||
#: squirrelbattle/game.py:182
|
||||
#, python-brace-format
|
||||
msgid "The player climbs down to the floor {floor}."
|
||||
msgstr "Le joueur descend à l'étage {floor}."
|
||||
|
||||
#: squirrelbattle/game.py:249
|
||||
#: squirrelbattle/game.py:195
|
||||
#, python-brace-format
|
||||
msgid "The player climbs up the floor {floor}."
|
||||
msgstr "Le joueur monte à l'étage {floor}."
|
||||
|
||||
#: squirrelbattle/game.py:285 squirrelbattle/tests/game_test.py:592
|
||||
msgid "The buyer does not have enough money"
|
||||
msgstr "L'acheteur n'a pas assez d'argent"
|
||||
|
||||
#: squirrelbattle/game.py:328
|
||||
msgid ""
|
||||
"Some keys are missing in your save file.\n"
|
||||
"Your save seems to be corrupt. It got deleted."
|
||||
@ -70,7 +80,7 @@ msgstr ""
|
||||
"Certaines clés de votre ficher de sauvegarde sont manquantes.\n"
|
||||
"Votre sauvegarde semble corrompue. Elle a été supprimée."
|
||||
|
||||
#: squirrelbattle/game.py:257
|
||||
#: squirrelbattle/game.py:336
|
||||
msgid ""
|
||||
"No player was found on this map!\n"
|
||||
"Maybe you died?"
|
||||
@ -78,7 +88,7 @@ msgstr ""
|
||||
"Aucun joueur n'a été trouvé sur la carte !\n"
|
||||
"Peut-être êtes-vous mort ?"
|
||||
|
||||
#: squirrelbattle/game.py:277
|
||||
#: squirrelbattle/game.py:356
|
||||
msgid ""
|
||||
"The JSON file is not correct.\n"
|
||||
"Your save seems corrupted. It got deleted."
|
||||
@ -86,22 +96,22 @@ msgstr ""
|
||||
"Le fichier JSON de sauvegarde est incorrect.\n"
|
||||
"Votre sauvegarde semble corrompue. Elle a été supprimée."
|
||||
|
||||
#: squirrelbattle/interfaces.py:429
|
||||
#: squirrelbattle/interfaces.py:453
|
||||
#, python-brace-format
|
||||
msgid "{name} hits {opponent}."
|
||||
msgstr "{name} frappe {opponent}."
|
||||
|
||||
#: squirrelbattle/interfaces.py:441
|
||||
#: squirrelbattle/interfaces.py:465
|
||||
#, python-brace-format
|
||||
msgid "{name} takes {amount} damage."
|
||||
msgstr "{name} prend {amount} points de dégât."
|
||||
|
||||
#: squirrelbattle/interfaces.py:443
|
||||
#: squirrelbattle/interfaces.py:467
|
||||
#, python-brace-format
|
||||
msgid "{name} dies."
|
||||
msgstr "{name} meurt."
|
||||
|
||||
#: squirrelbattle/interfaces.py:477
|
||||
#: squirrelbattle/interfaces.py:501
|
||||
#, python-brace-format
|
||||
msgid "{entity} said: {message}"
|
||||
msgstr "{entity} a dit : {message}"
|
||||
@ -110,8 +120,8 @@ msgstr "{entity} a dit : {message}"
|
||||
msgid "Back"
|
||||
msgstr "Retour"
|
||||
|
||||
#: squirrelbattle/tests/game_test.py:344 squirrelbattle/tests/game_test.py:347
|
||||
#: squirrelbattle/tests/game_test.py:350 squirrelbattle/tests/game_test.py:353
|
||||
#: squirrelbattle/tests/game_test.py:358 squirrelbattle/tests/game_test.py:361
|
||||
#: squirrelbattle/tests/game_test.py:364 squirrelbattle/tests/game_test.py:367
|
||||
#: squirrelbattle/tests/translations_test.py:16
|
||||
msgid "New game"
|
||||
msgstr "Nouvelle partie"
|
||||
@ -197,57 +207,61 @@ msgid "Key used to wait"
|
||||
msgstr "Touche pour attendre"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:56
|
||||
msgid "Key used to use ladders"
|
||||
msgstr "Touche pour utiliser les échelles"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:58
|
||||
msgid "Texture pack"
|
||||
msgstr "Pack de textures"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:57
|
||||
#: squirrelbattle/tests/translations_test.py:59
|
||||
msgid "Language"
|
||||
msgstr "Langue"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:60
|
||||
#: squirrelbattle/tests/translations_test.py:62
|
||||
msgid "player"
|
||||
msgstr "joueur"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:62
|
||||
#: squirrelbattle/tests/translations_test.py:64
|
||||
msgid "hedgehog"
|
||||
msgstr "hérisson"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:63
|
||||
#: squirrelbattle/tests/translations_test.py:65
|
||||
msgid "merchant"
|
||||
msgstr "marchand"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:64
|
||||
#: squirrelbattle/tests/translations_test.py:66
|
||||
msgid "rabbit"
|
||||
msgstr "lapin"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:65
|
||||
#: squirrelbattle/tests/translations_test.py:67
|
||||
msgid "sunflower"
|
||||
msgstr "tournesol"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:66
|
||||
#: squirrelbattle/tests/translations_test.py:68
|
||||
msgid "teddy bear"
|
||||
msgstr "nounours"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:67
|
||||
#: squirrelbattle/tests/translations_test.py:69
|
||||
msgid "tiger"
|
||||
msgstr "tigre"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:69
|
||||
#: squirrelbattle/tests/translations_test.py:71
|
||||
msgid "body snatch potion"
|
||||
msgstr "potion d'arrachage de corps"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:70
|
||||
#: squirrelbattle/tests/translations_test.py:72
|
||||
msgid "bomb"
|
||||
msgstr "bombe"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:71
|
||||
#: squirrelbattle/tests/translations_test.py:73
|
||||
msgid "explosion"
|
||||
msgstr ""
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:72
|
||||
#: squirrelbattle/tests/translations_test.py:74
|
||||
msgid "heart"
|
||||
msgstr "cœur"
|
||||
|
||||
#: squirrelbattle/tests/translations_test.py:73
|
||||
#: squirrelbattle/tests/translations_test.py:75
|
||||
msgid "sword"
|
||||
msgstr "épée"
|
||||
|
@ -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):
|
||||
merchant: Merchant
|
||||
"""
|
||||
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:
|
||||
return self.merchant.inventory
|
||||
"""
|
||||
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']
|
||||
@ -33,6 +34,7 @@ class Settings:
|
||||
self.KEY_DROP = ['r', 'Key used to drop an item in the inventory']
|
||||
self.KEY_CHAT = ['t', 'Key used to talk to a friendly entity']
|
||||
self.KEY_WAIT = ['w', 'Key used to wait']
|
||||
self.KEY_LADDER = ['<', 'Key used to use ladders']
|
||||
self.TEXTURE_PACK = ['ascii', 'Texture pack']
|
||||
self.LOCALE = [locale.getlocale()[0][:2], 'Language']
|
||||
|
||||
@ -50,7 +52,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 +63,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 +77,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)
|
||||
|
@ -12,6 +12,7 @@ from ..entities.items import Bomb, Heart, Sword, Explosion
|
||||
from ..entities.player import Player
|
||||
from ..enums import DisplayActions
|
||||
from ..game import Game, KeyValues, GameMode
|
||||
from ..interfaces import Map
|
||||
from ..menus import MainMenuValues
|
||||
from ..resources import ResourceManager
|
||||
from ..settings import Settings
|
||||
@ -21,17 +22,21 @@ 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()
|
||||
self.game.map = Map.load(
|
||||
ResourceManager.get_asset_path("example_map.txt"))
|
||||
self.game.map.add_entity(self.game.player)
|
||||
self.game.player.move(self.game.map.start_y, self.game.map.start_x)
|
||||
self.game.logs.add_message("Hello World !")
|
||||
display = DisplayManager(None, self.game)
|
||||
self.game.display_actions = display.handle_display_action
|
||||
|
||||
def test_load_game(self) -> None:
|
||||
"""
|
||||
Save a game and reload it.
|
||||
Saves a game and reloads it.
|
||||
"""
|
||||
bomb = Bomb()
|
||||
self.game.map.add_entity(bomb)
|
||||
@ -85,7 +90,7 @@ class TestGame(unittest.TestCase):
|
||||
|
||||
def test_bootstrap_fail(self) -> None:
|
||||
"""
|
||||
Ensure that the test can't play the game,
|
||||
Ensures that the test can't play the game,
|
||||
because there is no associated shell.
|
||||
Yeah, that's only for coverage.
|
||||
"""
|
||||
@ -94,7 +99,7 @@ class TestGame(unittest.TestCase):
|
||||
|
||||
def test_key_translation(self) -> None:
|
||||
"""
|
||||
Test key bindings.
|
||||
Tests key bindings.
|
||||
"""
|
||||
self.game.settings = Settings()
|
||||
|
||||
@ -143,6 +148,9 @@ class TestGame(unittest.TestCase):
|
||||
self.assertEqual(KeyValues.translate_key(
|
||||
self.game.settings.KEY_WAIT, self.game.settings),
|
||||
KeyValues.WAIT)
|
||||
self.assertEqual(KeyValues.translate_key(
|
||||
self.game.settings.KEY_LADDER, self.game.settings),
|
||||
KeyValues.LADDER)
|
||||
self.assertEqual(KeyValues.translate_key(' ', self.game.settings),
|
||||
KeyValues.SPACE)
|
||||
self.assertEqual(KeyValues.translate_key('plop', self.game.settings),
|
||||
@ -150,7 +158,7 @@ class TestGame(unittest.TestCase):
|
||||
|
||||
def test_key_press(self) -> None:
|
||||
"""
|
||||
Press a key and see what is done.
|
||||
Presses a key and asserts what is done is correct.
|
||||
"""
|
||||
self.assertEqual(self.game.state, GameMode.MAINMENU)
|
||||
self.assertEqual(self.game.main_menu.validate(),
|
||||
@ -241,7 +249,7 @@ class TestGame(unittest.TestCase):
|
||||
|
||||
def test_mouse_click(self) -> None:
|
||||
"""
|
||||
Simulate mouse clicks.
|
||||
Simulates mouse clicks.
|
||||
"""
|
||||
self.game.state = GameMode.MAINMENU
|
||||
|
||||
@ -271,7 +279,7 @@ class TestGame(unittest.TestCase):
|
||||
|
||||
def test_new_game(self) -> None:
|
||||
"""
|
||||
Ensure that the start button starts a new game.
|
||||
Ensures that the start button starts a new game.
|
||||
"""
|
||||
old_map = self.game.map
|
||||
old_player = self.game.player
|
||||
@ -294,7 +302,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()
|
||||
|
||||
@ -333,7 +341,7 @@ class TestGame(unittest.TestCase):
|
||||
self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a')
|
||||
|
||||
# Navigate to "texture pack"
|
||||
for ignored in range(11):
|
||||
for ignored in range(12):
|
||||
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||
|
||||
# Change texture pack
|
||||
@ -380,7 +388,7 @@ class TestGame(unittest.TestCase):
|
||||
|
||||
def test_dead_screen(self) -> None:
|
||||
"""
|
||||
Kill player and render dead screen.
|
||||
Kills the player and renders the dead message on the fake screen.
|
||||
"""
|
||||
self.game.state = GameMode.PLAY
|
||||
# Kill player
|
||||
@ -396,13 +404,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)
|
||||
@ -412,7 +421,7 @@ class TestGame(unittest.TestCase):
|
||||
|
||||
def test_inventory_menu(self) -> None:
|
||||
"""
|
||||
Open the inventory menu and interact with items.
|
||||
Opens the inventory menu and interacts with items.
|
||||
"""
|
||||
self.game.state = GameMode.PLAY
|
||||
# Open and close the inventory
|
||||
@ -473,7 +482,7 @@ class TestGame(unittest.TestCase):
|
||||
|
||||
def test_talk_to_sunflowers(self) -> None:
|
||||
"""
|
||||
Interact with sunflowers
|
||||
Interacts with sunflowers.
|
||||
"""
|
||||
self.game.state = GameMode.PLAY
|
||||
|
||||
@ -524,7 +533,7 @@ class TestGame(unittest.TestCase):
|
||||
|
||||
def test_talk_to_merchant(self) -> None:
|
||||
"""
|
||||
Interact with merchants
|
||||
Interacts with merchants.
|
||||
"""
|
||||
self.game.state = GameMode.PLAY
|
||||
|
||||
@ -546,18 +555,21 @@ class TestGame(unittest.TestCase):
|
||||
# Navigate in the menu
|
||||
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||
self.game.handle_key_pressed(KeyValues.LEFT)
|
||||
self.assertFalse(self.game.is_in_store_menu)
|
||||
self.game.handle_key_pressed(KeyValues.RIGHT)
|
||||
self.assertTrue(self.game.is_in_store_menu)
|
||||
self.game.handle_key_pressed(KeyValues.UP)
|
||||
self.assertEqual(self.game.store_menu.position, 1)
|
||||
|
||||
self.game.player.hazel = 0x7ffff42ff
|
||||
|
||||
# The second item is not a heart
|
||||
merchant.inventory[1] = Sword()
|
||||
merchant.inventory[1] = sword = Sword()
|
||||
# Buy the second item by clicking on it
|
||||
item = self.game.store_menu.validate()
|
||||
self.assertIn(item, merchant.inventory)
|
||||
self.game.display_actions(DisplayActions.MOUSE, 7, 25)
|
||||
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||
self.assertIn(item, self.game.player.inventory)
|
||||
self.assertNotIn(item, merchant.inventory)
|
||||
|
||||
@ -579,9 +591,71 @@ class TestGame(unittest.TestCase):
|
||||
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||
self.assertNotIn(item, self.game.player.inventory)
|
||||
self.assertIn(item, merchant.inventory)
|
||||
self.assertEqual(self.game.message, _("You do not have enough money"))
|
||||
self.assertEqual(self.game.message,
|
||||
_("The buyer does not have enough money"))
|
||||
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||
|
||||
# Sell an item
|
||||
self.game.inventory_menu.position = len(self.game.player.inventory) - 1
|
||||
self.game.handle_key_pressed(KeyValues.LEFT)
|
||||
self.assertFalse(self.game.is_in_store_menu)
|
||||
self.assertIn(sword, self.game.player.inventory)
|
||||
self.assertEqual(self.game.inventory_menu.validate(), sword)
|
||||
old_player_money, old_merchant_money = self.game.player.hazel,\
|
||||
merchant.hazel
|
||||
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||
self.assertNotIn(sword, self.game.player.inventory)
|
||||
self.assertIn(sword, merchant.inventory)
|
||||
self.assertEqual(self.game.player.hazel, old_player_money + sword.price)
|
||||
self.assertEqual(merchant.hazel, old_merchant_money - sword.price)
|
||||
|
||||
# Exit the menu
|
||||
self.game.handle_key_pressed(KeyValues.SPACE)
|
||||
self.assertEqual(self.game.state, GameMode.PLAY)
|
||||
|
||||
def test_ladders(self) -> None:
|
||||
"""
|
||||
Ensure that the player can climb on ladders.
|
||||
"""
|
||||
self.game.state = GameMode.PLAY
|
||||
|
||||
self.assertEqual(self.game.player.map.floor, 0)
|
||||
self.game.handle_key_pressed(KeyValues.LADDER)
|
||||
self.assertEqual(self.game.player.map.floor, 0)
|
||||
|
||||
# Move nowhere
|
||||
self.game.player.move(10, 10)
|
||||
self.game.handle_key_pressed(KeyValues.LADDER)
|
||||
self.assertEqual(self.game.player.map.floor, 0)
|
||||
|
||||
# Move down
|
||||
self.game.player.move(3, 40) # Move on a ladder
|
||||
self.game.handle_key_pressed(KeyValues.LADDER)
|
||||
self.assertEqual(self.game.map_index, 1)
|
||||
self.assertEqual(self.game.player.map.floor, 1)
|
||||
self.assertEqual(self.game.player.y, 1)
|
||||
self.assertEqual(self.game.player.x, 17)
|
||||
self.game.display_actions(DisplayActions.UPDATE)
|
||||
|
||||
# Move up
|
||||
self.game.handle_key_pressed(KeyValues.LADDER)
|
||||
self.assertEqual(self.game.player.map.floor, 0)
|
||||
self.assertEqual(self.game.player.y, 3)
|
||||
self.assertEqual(self.game.player.x, 40)
|
||||
self.game.display_actions(DisplayActions.UPDATE)
|
||||
|
||||
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())
|
||||
|
@ -12,8 +12,8 @@ class FakePad:
|
||||
def addstr(self, y: int, x: int, message: str, color: int = 0) -> None:
|
||||
pass
|
||||
|
||||
def refresh(self, pminrow: int, pmincol: int, sminrow: int,
|
||||
smincol: int, smaxrow: int, smaxcol: int) -> None:
|
||||
def noutrefresh(self, pminrow: int, pmincol: int, sminrow: int,
|
||||
smincol: int, smaxrow: int, smaxcol: int) -> None:
|
||||
pass
|
||||
|
||||
def erase(self) -> None:
|
||||
@ -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")
|
||||
@ -53,6 +53,8 @@ class TestTranslations(unittest.TestCase):
|
||||
self.assertEqual(_("Key used to talk to a friendly entity"),
|
||||
"Touche pour parler à une entité pacifique")
|
||||
self.assertEqual(_("Key used to wait"), "Touche pour attendre")
|
||||
self.assertEqual(_("Key used to use ladders"),
|
||||
"Touche pour utiliser les échelles")
|
||||
self.assertEqual(_("Texture pack"), "Pack de textures")
|
||||
self.assertEqual(_("Language"), "Langue")
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user