Merge remote-tracking branch 'origin/master' into lighting

This commit is contained in:
Nicolas Margulies 2020-12-17 21:24:32 +01:00
commit 62ce2b5c71
23 changed files with 1299 additions and 215 deletions

View File

@ -2,9 +2,10 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import curses import curses
from typing import Any, Optional, Union from typing import Any, Optional, Tuple, Union
from squirrelbattle.display.texturepack import TexturePack from squirrelbattle.display.texturepack import TexturePack
from squirrelbattle.game import Game
from squirrelbattle.tests.screen import FakePad from squirrelbattle.tests.screen import FakePad
@ -15,6 +16,9 @@ class Display:
height: int height: int
pad: Any pad: Any
_color_pairs = {(curses.COLOR_WHITE, curses.COLOR_BLACK): 0}
_colors_rgb = {}
def __init__(self, screen: Any, pack: Optional[TexturePack] = None): def __init__(self, screen: Any, pack: Optional[TexturePack] = None):
self.screen = screen self.screen = screen
self.pack = pack or TexturePack.get_pack("ascii") self.pack = pack or TexturePack.get_pack("ascii")
@ -30,15 +34,84 @@ class Display:
lines = [line[:width] for line in lines] lines = [line[:width] for line in lines]
return "\n".join(lines) return "\n".join(lines)
def addstr(self, pad: Any, y: int, x: int, msg: str, *options) -> None: def translate_color(self, color: Union[int, Tuple[int, int, int]]) -> int:
"""
Translate a tuple (R, G, B) into a curses color index.
If we have already a color index, then nothing is processed.
If this is a tuple, we construct a new color index if non-existing
and we return this index.
The values of R, G and B must be between 0 and 1000, and not
between 0 and 255.
"""
if isinstance(color, tuple):
# The color is a tuple (R, G, B), that is potentially unknown.
# We translate it into a curses color number.
if color not in self._colors_rgb:
# The color does not exist, we create it.
color_nb = len(self._colors_rgb) + 8
self.init_color(color_nb, color[0], color[1], color[2])
self._colors_rgb[color] = color_nb
color = self._colors_rgb[color]
return color
def addstr(self, pad: Any, y: int, x: int, msg: str,
fg_color: Union[int, Tuple[int, int, int]] = curses.COLOR_WHITE,
bg_color: Union[int, Tuple[int, int, int]] = curses.COLOR_BLACK,
*, altcharset: bool = False, blink: bool = False,
bold: bool = False, dim: bool = False, invis: bool = False,
italic: bool = False, normal: bool = False,
protect: bool = False, reverse: bool = False,
standout: bool = False, underline: bool = False,
horizontal: bool = False, left: bool = False,
low: bool = False, right: bool = False, top: bool = False,
vertical: bool = False, chartext: bool = False) -> None:
""" """
Display a message onto the pad. Display a message onto the pad.
If the message is too large, it is truncated vertically and horizontally If the message is too large, it is truncated vertically and horizontally
The text can be bold, italic, blinking, ... if the good parameters are
given. These parameters are translated into curses attributes.
The foreground and background colors can be given as curses constants
(curses.COLOR_*), or by giving a tuple (R, G, B) that corresponds to
the color. R, G, B must be between 0 and 1000, and not 0 and 255.
""" """
height, width = pad.getmaxyx() height, width = pad.getmaxyx()
# Truncate message if it is too large
msg = self.truncate(msg, height - y, width - x - 1) msg = self.truncate(msg, height - y, width - x - 1)
if msg.replace("\n", "") and x >= 0 and y >= 0: if msg.replace("\n", "") and x >= 0 and y >= 0:
return pad.addstr(y, x, msg, *options) fg_color = self.translate_color(fg_color)
bg_color = self.translate_color(bg_color)
# Get the pair number for the tuple (fg, bg)
# If it does not exist, create it and give a new unique id.
if (fg_color, bg_color) in self._color_pairs:
pair_nb = self._color_pairs[(fg_color, bg_color)]
else:
pair_nb = len(self._color_pairs)
self.init_pair(pair_nb, fg_color, bg_color)
self._color_pairs[(fg_color, bg_color)] = pair_nb
# Compute curses attributes from the parameters
attr = self.color_pair(pair_nb)
attr |= curses.A_ALTCHARSET if altcharset else 0
attr |= curses.A_BLINK if blink else 0
attr |= curses.A_BOLD if bold else 0
attr |= curses.A_DIM if dim else 0
attr |= curses.A_INVIS if invis else 0
attr |= curses.A_ITALIC if italic else 0
attr |= curses.A_NORMAL if normal else 0
attr |= curses.A_PROTECT if protect else 0
attr |= curses.A_REVERSE if reverse else 0
attr |= curses.A_STANDOUT if standout else 0
attr |= curses.A_UNDERLINE if underline else 0
attr |= curses.A_HORIZONTAL if horizontal else 0
attr |= curses.A_LEFT if left else 0
attr |= curses.A_LOW if low else 0
attr |= curses.A_RIGHT if right else 0
attr |= curses.A_TOP if top else 0
attr |= curses.A_VERTICAL if vertical else 0
attr |= curses.A_CHARTEXT if chartext else 0
return pad.addstr(y, x, msg, attr)
def init_pair(self, number: int, foreground: int, background: int) -> None: def init_pair(self, number: int, foreground: int, background: int) -> None:
return curses.init_pair(number, foreground, background) \ return curses.init_pair(number, foreground, background) \
@ -47,6 +120,10 @@ class Display:
def color_pair(self, number: int) -> int: def color_pair(self, number: int) -> int:
return curses.color_pair(number) if self.screen else 0 return curses.color_pair(number) if self.screen else 0
def init_color(self, number: int, red: int, green: int, blue: int) -> None:
return curses.init_color(number, red, green, blue) \
if self.screen else None
def resize(self, y: int, x: int, height: int, width: int, def resize(self, y: int, x: int, height: int, width: int,
resize_pad: bool = True) -> None: resize_pad: bool = True) -> None:
self.x = x self.x = x
@ -55,6 +132,7 @@ class Display:
self.height = height self.height = height
if hasattr(self, "pad") and resize_pad and \ if hasattr(self, "pad") and resize_pad and \
self.height >= 0 and self.width >= 0: self.height >= 0 and self.width >= 0:
self.pad.erase()
self.pad.resize(self.height + 1, self.width + 1) self.pad.resize(self.height + 1, self.width + 1)
def refresh(self, *args, resize_pad: bool = True) -> None: def refresh(self, *args, resize_pad: bool = True) -> None:
@ -86,6 +164,13 @@ class Display:
def display(self) -> None: def display(self) -> None:
raise NotImplementedError 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.
"""
pass
@property @property
def rows(self) -> int: def rows(self) -> int:
return curses.LINES if self.screen else 42 return curses.LINES if self.screen else 42
@ -138,23 +223,29 @@ class HorizontalSplit(Display):
class Box(Display): class Box(Display):
title: str = ""
def update_title(self, title: str) -> None:
self.title = title
def __init__(self, *args, fg_border_color: Optional[int] = None, **kwargs): def __init__(self, *args, fg_border_color: Optional[int] = None, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.pad = self.newpad(self.rows, self.cols) self.pad = self.newpad(self.rows, self.cols)
self.fg_border_color = fg_border_color or curses.COLOR_WHITE self.fg_border_color = fg_border_color or curses.COLOR_WHITE
pair_number = 4 + self.fg_border_color
self.init_pair(pair_number, self.fg_border_color, curses.COLOR_BLACK)
self.pair = self.color_pair(pair_number)
def display(self) -> None: def display(self) -> None:
self.addstr(self.pad, 0, 0, "" + "" * (self.width - 2) + "", self.addstr(self.pad, 0, 0, "" + "" * (self.width - 2) + "",
self.pair) self.fg_border_color)
for i in range(1, self.height - 1): for i in range(1, self.height - 1):
self.addstr(self.pad, i, 0, "", self.pair) self.addstr(self.pad, i, 0, "", self.fg_border_color)
self.addstr(self.pad, i, self.width - 1, "", self.pair) self.addstr(self.pad, i, self.width - 1, "", self.fg_border_color)
self.addstr(self.pad, self.height - 1, 0, self.addstr(self.pad, self.height - 1, 0,
"" + "" * (self.width - 2) + "", self.pair) "" + "" * (self.width - 2) + "", self.fg_border_color)
if self.title:
self.addstr(self.pad, 0, (self.width - len(self.title) - 8) // 2,
f" == {self.title} == ", curses.COLOR_GREEN,
italic=True, bold=True)
self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.refresh_pad(self.pad, 0, 0, self.y, self.x,
self.y + self.height - 1, self.x + self.width - 1) self.y + self.height - 1, self.x + self.width - 1)

View File

@ -2,15 +2,16 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import curses import curses
from squirrelbattle.display.display import VerticalSplit, HorizontalSplit from squirrelbattle.display.display import VerticalSplit, HorizontalSplit, \
Display
from squirrelbattle.display.mapdisplay import MapDisplay from squirrelbattle.display.mapdisplay import MapDisplay
from squirrelbattle.display.messagedisplay import MessageDisplay from squirrelbattle.display.messagedisplay import MessageDisplay
from squirrelbattle.display.statsdisplay import StatsDisplay from squirrelbattle.display.statsdisplay import StatsDisplay
from squirrelbattle.display.menudisplay import MainMenuDisplay, \ from squirrelbattle.display.menudisplay import MainMenuDisplay, \
InventoryDisplay, SettingsMenuDisplay PlayerInventoryDisplay, StoreInventoryDisplay, SettingsMenuDisplay
from squirrelbattle.display.logsdisplay import LogsDisplay from squirrelbattle.display.logsdisplay import LogsDisplay
from squirrelbattle.display.texturepack import TexturePack from squirrelbattle.display.texturepack import TexturePack
from typing import Any from typing import Any, List
from squirrelbattle.game import Game, GameMode from squirrelbattle.game import Game, GameMode
from squirrelbattle.enums import DisplayActions from squirrelbattle.enums import DisplayActions
@ -24,7 +25,8 @@ class DisplayManager:
self.mapdisplay = MapDisplay(screen, pack) self.mapdisplay = MapDisplay(screen, pack)
self.statsdisplay = StatsDisplay(screen, pack) self.statsdisplay = StatsDisplay(screen, pack)
self.logsdisplay = LogsDisplay(screen, pack) self.logsdisplay = LogsDisplay(screen, pack)
self.inventorydisplay = InventoryDisplay(screen, pack) self.playerinventorydisplay = PlayerInventoryDisplay(screen, pack)
self.storeinventorydisplay = StoreInventoryDisplay(screen, pack)
self.mainmenudisplay = MainMenuDisplay(self.game.main_menu, self.mainmenudisplay = MainMenuDisplay(self.game.main_menu,
screen, pack) screen, pack)
self.settingsmenudisplay = SettingsMenuDisplay(screen, pack) self.settingsmenudisplay = SettingsMenuDisplay(screen, pack)
@ -33,28 +35,50 @@ class DisplayManager:
self.vbar = VerticalSplit(screen, pack) self.vbar = VerticalSplit(screen, pack)
self.displays = [self.statsdisplay, self.mapdisplay, self.displays = [self.statsdisplay, self.mapdisplay,
self.mainmenudisplay, self.settingsmenudisplay, self.mainmenudisplay, self.settingsmenudisplay,
self.logsdisplay, self.messagedisplay] self.logsdisplay, self.messagedisplay,
self.playerinventorydisplay,
self.storeinventorydisplay]
self.update_game_components() self.update_game_components()
def handle_display_action(self, action: DisplayActions) -> None: def handle_display_action(self, action: DisplayActions, *params) -> None:
if action == DisplayActions.REFRESH: if action == DisplayActions.REFRESH:
self.refresh() self.refresh()
elif action == DisplayActions.UPDATE: elif action == DisplayActions.UPDATE:
self.update_game_components() self.update_game_components()
elif action == DisplayActions.MOUSE:
self.handle_mouse_click(*params)
def update_game_components(self) -> None: def update_game_components(self) -> None:
for d in self.displays: for d in self.displays:
d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK) d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK)
self.mapdisplay.update_map(self.game.map) self.mapdisplay.update_map(self.game.map)
self.statsdisplay.update_player(self.game.player) self.statsdisplay.update_player(self.game.player)
self.inventorydisplay.update_menu(self.game.inventory_menu) self.game.inventory_menu.update_player(self.game.player)
self.game.store_menu.update_merchant(self.game.player)
self.playerinventorydisplay.update_menu(self.game.inventory_menu)
self.storeinventorydisplay.update_menu(self.game.store_menu)
self.settingsmenudisplay.update_menu(self.game.settings_menu) self.settingsmenudisplay.update_menu(self.game.settings_menu)
self.logsdisplay.update_logs(self.game.logs) self.logsdisplay.update_logs(self.game.logs)
self.messagedisplay.update_message(self.game.message) self.messagedisplay.update_message(self.game.message)
def refresh(self) -> None: def handle_mouse_click(self, y: int, x: int) -> None:
displays = self.refresh()
display = None
for d in displays:
top_y, top_x, height, width = d.y, d.x, d.height, d.width
if top_y <= y < top_y + height and top_x <= x < top_x + width:
# The click coordinates correspond to the coordinates
# of that display
display = d
if display:
display.handle_click(y - display.y, x - display.x, self.game)
def refresh(self) -> List[Display]:
displays = []
if self.game.state == GameMode.PLAY \ if self.game.state == GameMode.PLAY \
or self.game.state == GameMode.INVENTORY: or self.game.state == GameMode.INVENTORY \
or self.game.state == GameMode.STORE:
# The map pad has already the good size # The map pad has already the good size
self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, self.mapdisplay.refresh(0, 0, self.rows * 4 // 5,
self.mapdisplay.pack.tile_width self.mapdisplay.pack.tile_width
@ -67,15 +91,26 @@ class DisplayManager:
self.rows // 5 - 1, self.cols * 4 // 5) self.rows // 5 - 1, self.cols * 4 // 5)
self.hbar.refresh(self.rows * 4 // 5, 0, 1, self.cols * 4 // 5) self.hbar.refresh(self.rows * 4 // 5, 0, 1, self.cols * 4 // 5)
self.vbar.refresh(0, self.cols * 4 // 5, self.rows, 1) self.vbar.refresh(0, self.cols * 4 // 5, self.rows, 1)
displays += [self.mapdisplay, self.statsdisplay, self.logsdisplay,
self.hbar, self.vbar]
if self.game.state == GameMode.INVENTORY: if self.game.state == GameMode.INVENTORY:
self.inventorydisplay.refresh(self.rows // 10, self.playerinventorydisplay.refresh(
self.cols // 2, self.rows // 10, self.cols // 2,
8 * self.rows // 10, 8 * self.rows // 10, 2 * self.cols // 5)
2 * self.cols // 5) 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)
displays.append(self.storeinventorydisplay)
elif self.game.state == GameMode.MAINMENU: elif self.game.state == GameMode.MAINMENU:
self.mainmenudisplay.refresh(0, 0, self.rows, self.cols) self.mainmenudisplay.refresh(0, 0, self.rows, self.cols)
displays.append(self.mainmenudisplay)
elif self.game.state == GameMode.SETTINGS: elif self.game.state == GameMode.SETTINGS:
self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols) self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols)
displays.append(self.settingsmenudisplay)
if self.game.message: if self.game.message:
height, width = 0, 0 height, width = 0, 0
@ -84,9 +119,12 @@ class DisplayManager:
width = max(width, len(line)) width = max(width, len(line))
y, x = (self.rows - height) // 2, (self.cols - width) // 2 y, x = (self.rows - height) // 2, (self.cols - width) // 2
self.messagedisplay.refresh(y, x, height, width) self.messagedisplay.refresh(y, x, height, width)
displays.append(self.messagedisplay)
self.resize_window() self.resize_window()
return displays
def resize_window(self) -> bool: def resize_window(self) -> bool:
""" """
If the window got resized, ensure that the screen size got updated. If the window got resized, ensure that the screen size got updated.

View File

@ -15,15 +15,14 @@ class MapDisplay(Display):
self.pad = self.newpad(m.height, self.pack.tile_width * m.width + 1) self.pad = self.newpad(m.height, self.pack.tile_width * m.width + 1)
def update_pad(self) -> None: def update_pad(self) -> None:
self.init_pair(1, self.pack.tile_fg_color, self.pack.tile_bg_color)
self.init_pair(2, self.pack.entity_fg_color, self.pack.entity_bg_color)
self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack),
self.color_pair(1)) self.pack.tile_fg_color, self.pack.tile_bg_color)
for e in self.map.entities: for e in self.map.entities:
self.addstr(self.pad, e.y, self.pack.tile_width * e.x, self.addstr(self.pad, e.y, self.pack.tile_width * e.x,
self.pack[e.name.upper()], self.color_pair(2)) self.pack[e.name.upper()],
self.pack.entity_fg_color, self.pack.entity_bg_color)
# Display Path map for deubg purposes # Display Path map for debug purposes
# from squirrelbattle.entities.player import Player # from squirrelbattle.entities.player import Player
# players = [ p for p in self.map.entities if isinstance(p,Player) ] # players = [ p for p in self.map.entities if isinstance(p,Player) ]
# player = players[0] if len(players) > 0 else None # player = players[0] if len(players) > 0 else None
@ -42,7 +41,8 @@ class MapDisplay(Display):
# else: # else:
# character = '←' # character = '←'
# self.addstr(self.pad, y, self.pack.tile_width * x, # self.addstr(self.pad, y, self.pack.tile_width * x,
# character, self.color_pair(1)) # character, self.pack.tile_fg_color,
# self.pack.tile_bg_color)
def display(self) -> None: def display(self) -> None:
y, x = self.map.currenty, self.pack.tile_width * self.map.currentx y, x = self.map.currenty, self.pack.tile_width * self.map.currentx

View File

@ -1,15 +1,22 @@
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import curses import curses
from random import randint
from typing import List from typing import List
from squirrelbattle.menus import Menu, MainMenu from squirrelbattle.menus import Menu, MainMenu
from .display import Display, Box from .display import Box, Display
from ..enums import KeyValues
from ..game import Game
from ..resources import ResourceManager from ..resources import ResourceManager
from ..translations import gettext as _ from ..translations import gettext as _
class MenuDisplay(Display): class MenuDisplay(Display):
"""
A class to display the menu objects
"""
position: int position: int
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -37,10 +44,17 @@ class MenuDisplay(Display):
self.menubox.refresh(self.y, self.x, self.height, self.width) self.menubox.refresh(self.y, self.x, self.height, self.width)
self.pad.erase() self.pad.erase()
self.update_pad() self.update_pad()
self.refresh_pad(self.pad, cornery, 0, self.y + 1, self.x + 2, self.refresh_pad(self.pad, cornery, 0, self.y + 1, self.x + 1,
self.height - 2 + self.y, self.height - 2 + self.y,
self.width - 2 + self.x) self.width - 2 + self.x)
def handle_click(self, y: int, x: int, game: Game) -> None:
"""
We can select a menu item with the mouse.
"""
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 1))
game.handle_key_pressed(KeyValues.ENTER)
@property @property
def truewidth(self) -> int: def truewidth(self) -> int:
return max([len(str(a)) for a in self.values]) return max([len(str(a)) for a in self.values])
@ -63,6 +77,9 @@ class MenuDisplay(Display):
class SettingsMenuDisplay(MenuDisplay): class SettingsMenuDisplay(MenuDisplay):
"""
A class to display specifically a settingsmenu object
"""
@property @property
def values(self) -> List[str]: def values(self) -> List[str]:
return [_(a[1][1]) + (" : " return [_(a[1][1]) + (" : "
@ -73,6 +90,9 @@ class SettingsMenuDisplay(MenuDisplay):
class MainMenuDisplay(Display): class MainMenuDisplay(Display):
"""
A class to display specifically a mainmenu object
"""
def __init__(self, menu: MainMenu, *args): def __init__(self, menu: MainMenu, *args):
super().__init__(*args) super().__init__(*args)
self.menu = menu self.menu = menu
@ -83,13 +103,16 @@ class MainMenuDisplay(Display):
self.pad = self.newpad(max(self.rows, len(self.title) + 30), self.pad = self.newpad(max(self.rows, len(self.title) + 30),
max(len(self.title[0]) + 5, self.cols)) max(len(self.title[0]) + 5, self.cols))
self.fg_color = curses.COLOR_WHITE
self.menudisplay = MenuDisplay(self.screen, self.pack) self.menudisplay = MenuDisplay(self.screen, self.pack)
self.menudisplay.update_menu(self.menu) self.menudisplay.update_menu(self.menu)
def display(self) -> None: def display(self) -> None:
for i in range(len(self.title)): for i in range(len(self.title)):
self.addstr(self.pad, 4 + i, max(self.width // 2 self.addstr(self.pad, 4 + i, max(self.width // 2
- len(self.title[0]) // 2 - 1, 0), self.title[i]) - len(self.title[0]) // 2 - 1, 0), self.title[i],
self.fg_color)
self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.refresh_pad(self.pad, 0, 0, self.y, self.x,
self.height + self.y - 1, self.height + self.y - 1,
self.width + self.x - 1) self.width + self.x - 1)
@ -99,16 +122,25 @@ class MainMenuDisplay(Display):
menuy, menux, min(self.menudisplay.preferred_height, menuy, menux, min(self.menudisplay.preferred_height,
self.height - menuy), menuwidth) self.height - menuy), menuwidth)
def handle_click(self, y: int, x: int, game: Game) -> None:
menuwidth = min(self.menudisplay.preferred_width, self.width)
menuy, menux = len(self.title) + 8, self.width // 2 - menuwidth // 2 - 1
menuheight = min(self.menudisplay.preferred_height, self.height - menuy)
class InventoryDisplay(MenuDisplay): if menuy <= y < menuy + menuheight and menux <= x < menux + menuwidth:
self.menudisplay.handle_click(y - menuy, x - menux, game)
if y <= len(self.title):
self.fg_color = randint(0, 1000), randint(0, 1000), randint(0, 1000)
class PlayerInventoryDisplay(MenuDisplay):
def update_pad(self) -> None: def update_pad(self) -> None:
message = _("== INVENTORY ==") self.menubox.update_title(_("INVENTORY"))
self.addstr(self.pad, 0, (self.width - len(message)) // 2, message,
curses.A_BOLD | curses.A_ITALIC)
for i, item in enumerate(self.menu.values): for i, item in enumerate(self.menu.values):
rep = self.pack[item.name.upper()] 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 else f" {rep} "
self.addstr(self.pad, 2 + i, 0, selection self.addstr(self.pad, i + 1, 0, selection
+ " " + item.translated_name.capitalize()) + " " + item.translated_name.capitalize())
@property @property
@ -118,3 +150,36 @@ class InventoryDisplay(MenuDisplay):
@property @property
def trueheight(self) -> int: def trueheight(self) -> int:
return 2 + super().trueheight return 2 + super().trueheight
def handle_click(self, y: int, x: int, game: Game) -> None:
"""
We can select a menu item with the mouse.
"""
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2))
game.handle_key_pressed(KeyValues.ENTER)
class StoreInventoryDisplay(MenuDisplay):
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} "
self.addstr(self.pad, i + 1, 0, selection
+ " " + item.translated_name.capitalize()
+ ": " + str(item.price) + " Hazels")
@property
def truewidth(self) -> int:
return max(1, self.height if hasattr(self, "height") else 10)
@property
def trueheight(self) -> int:
return 2 + super().trueheight
def handle_click(self, y: int, x: int, game: Game) -> None:
"""
We can select a menu item with the mouse.
"""
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2))
game.handle_key_pressed(KeyValues.ENTER)

View File

@ -25,7 +25,7 @@ class MessageDisplay(Display):
self.height + 2, self.width + 4) self.height + 2, self.width + 4)
self.box.display() self.box.display()
self.pad.erase() self.pad.erase()
self.addstr(self.pad, 0, 0, self.message, curses.A_BOLD) self.addstr(self.pad, 0, 0, self.message, bold=True)
self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.refresh_pad(self.pad, 0, 0, self.y, self.x,
self.height + self.y - 1, self.height + self.y - 1,
self.width + self.x - 1) self.width + self.x - 1)

View File

@ -14,7 +14,6 @@ class StatsDisplay(Display):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.pad = self.newpad(self.rows, self.cols) self.pad = self.newpad(self.rows, self.cols)
self.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK)
def update_player(self, p: Player) -> None: def update_player(self, p: Player) -> None:
self.player = p self.player = p
@ -46,10 +45,12 @@ class StatsDisplay(Display):
printed_items.append(item) printed_items.append(item)
self.addstr(self.pad, 8, 0, inventory_str) self.addstr(self.pad, 8, 0, inventory_str)
self.addstr(self.pad, 9, 0, f"{self.pack.HAZELNUT} "
f"x{self.player.hazel}")
if self.player.dead: if self.player.dead:
self.addstr(self.pad, 10, 0, _("YOU ARE DEAD"), self.addstr(self.pad, 11, 0, _("YOU ARE DEAD"), curses.COLOR_RED,
curses.A_BOLD | curses.A_BLINK | curses.A_STANDOUT bold=True, blink=True, standout=True)
| self.color_pair(3))
def display(self) -> None: def display(self) -> None:
self.pad.erase() self.pad.erase()

View File

@ -14,10 +14,22 @@ class TexturePack:
tile_bg_color: int tile_bg_color: int
entity_fg_color: int entity_fg_color: int
entity_bg_color: int entity_bg_color: int
BODY_SNATCH_POTION: str
BOMB: str
HEART: str
HEDGEHOG: str
EMPTY: str EMPTY: str
WALL: str
FLOOR: str FLOOR: str
HAZELNUT: str
MERCHANT: str
PLAYER: str PLAYER: str
RABBIT: str
SUNFLOWER: str
SWORD: str
TEDDY_BEAR: str
TIGER: str
WALL: str
ASCII_PACK: "TexturePack" ASCII_PACK: "TexturePack"
SQUIRREL_PACK: "TexturePack" SQUIRREL_PACK: "TexturePack"
@ -46,17 +58,23 @@ TexturePack.ASCII_PACK = TexturePack(
tile_bg_color=curses.COLOR_BLACK, tile_bg_color=curses.COLOR_BLACK,
entity_fg_color=curses.COLOR_WHITE, entity_fg_color=curses.COLOR_WHITE,
entity_bg_color=curses.COLOR_BLACK, entity_bg_color=curses.COLOR_BLACK,
EMPTY=' ',
WALL='#',
FLOOR='.',
PLAYER='@',
HEDGEHOG='*',
HEART='',
BOMB='o',
RABBIT='Y',
TIGER='n',
TEDDY_BEAR='8',
BODY_SNATCH_POTION='S', BODY_SNATCH_POTION='S',
BOMB='o',
EMPTY=' ',
EXPLOSION='%',
FLOOR='.',
HAZELNUT='¤',
HEART='',
HEDGEHOG='*',
MERCHANT='M',
PLAYER='@',
RABBIT='Y',
SUNFLOWER='I',
SWORD='\u2020',
TEDDY_BEAR='8',
TIGER='n',
WALL='#',
) )
TexturePack.SQUIRREL_PACK = TexturePack( TexturePack.SQUIRREL_PACK = TexturePack(
@ -66,15 +84,21 @@ TexturePack.SQUIRREL_PACK = TexturePack(
tile_bg_color=curses.COLOR_BLACK, tile_bg_color=curses.COLOR_BLACK,
entity_fg_color=curses.COLOR_WHITE, entity_fg_color=curses.COLOR_WHITE,
entity_bg_color=curses.COLOR_WHITE, entity_bg_color=curses.COLOR_WHITE,
EMPTY=' ',
WALL='🧱',
FLOOR='██',
PLAYER='🐿️ ',
HEDGEHOG='🦔',
HEART='💜',
BOMB='💣',
RABBIT='🐇',
TIGER='🐅',
TEDDY_BEAR='🧸',
BODY_SNATCH_POTION='🔀', BODY_SNATCH_POTION='🔀',
BOMB='💣',
EMPTY=' ',
EXPLOSION='💥',
FLOOR='██',
HAZELNUT='🌰',
HEART='💜',
HEDGEHOG='🦔',
PLAYER='🐿️ ',
MERCHANT='🦜',
RABBIT='🐇',
SUNFLOWER='🌻',
SWORD='🗡️',
TEDDY_BEAR='🧸',
TIGER='🐅',
WALL='🧱',
) )

View File

@ -0,0 +1,52 @@
from ..interfaces import FriendlyEntity, InventoryHolder
from ..translations import gettext as _
from .player import Player
from .items import Item
from random import choice
class Merchant(InventoryHolder, FriendlyEntity):
"""
The class for merchants in the dungeon
"""
def keys(self) -> list:
"""
Returns a friendly entitie's specific attributes
"""
return super().keys() + ["inventory", "hazel"]
def __init__(self, name: str = "merchant", inventory: list = None,
hazel: int = 75, *args, **kwargs):
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())())
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
"""
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.
"""
self.hazel += hz
class Sunflower(FriendlyEntity):
"""
A friendly sunflower
"""
def __init__(self, maxhealth: int = 15,
*args, **kwargs) -> None:
super().__init__(name="sunflower", maxhealth=maxhealth, *args, **kwargs)
@property
def dialogue_option(self) -> list:
return [_("Flower power!!"), _("The sun is warm today")]

View File

@ -5,7 +5,7 @@ from random import choice, randint
from typing import Optional from typing import Optional
from .player import Player from .player import Player
from ..interfaces import Entity, FightingEntity, Map from ..interfaces import Entity, FightingEntity, Map, InventoryHolder
from ..translations import gettext as _ from ..translations import gettext as _
@ -14,13 +14,16 @@ class Item(Entity):
A class for items A class for items
""" """
held: bool held: bool
held_by: Optional[Player] held_by: Optional[InventoryHolder]
price: int
def __init__(self, held: bool = False, held_by: Optional[Player] = None, def __init__(self, held: bool = False,
*args, **kwargs): held_by: Optional[InventoryHolder] = None,
price: int = 2, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.held = held self.held = held
self.held_by = held_by self.held_by = held_by
self.price = price
def drop(self) -> None: def drop(self) -> None:
""" """
@ -28,7 +31,7 @@ class Item(Entity):
""" """
if self.held: if self.held:
self.held_by.inventory.remove(self) self.held_by.inventory.remove(self)
self.map.add_entity(self) self.held_by.map.add_entity(self)
self.move(self.held_by.y, self.held_by.x) self.move(self.held_by.y, self.held_by.x)
self.held = False self.held = False
self.held_by = None self.held_by = None
@ -43,14 +46,14 @@ class Item(Entity):
Indicates what should be done when the item is equipped. Indicates what should be done when the item is equipped.
""" """
def hold(self, player: "Player") -> None: def hold(self, player: InventoryHolder) -> None:
""" """
The item is taken from the floor and put into the inventory The item is taken from the floor and put into the inventory
""" """
self.held = True self.held = True
self.held_by = player self.held_by = player
self.map.remove_entity(self) self.held_by.map.remove_entity(self)
player.inventory.append(self) player.add_to_inventory(self)
def save_state(self) -> dict: def save_state(self) -> dict:
""" """
@ -60,6 +63,25 @@ class Item(Entity):
d["held"] = self.held d["held"] = self.held
return d return d
@staticmethod
def get_all_items() -> list:
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
"""
if buyer.hazel >= self.price:
self.hold(buyer)
seller.remove_from_inventory(self)
buyer.change_hazel_balance(-self.price)
seller.change_hazel_balance(self.price)
return True
else:
return False
class Heart(Item): class Heart(Item):
""" """
@ -67,16 +89,17 @@ class Heart(Item):
""" """
healing: int healing: int
def __init__(self, name: str = "heart", healing: int = 5, *args, **kwargs): def __init__(self, name: str = "heart", healing: int = 5, price: int = 3,
super().__init__(name=name, *args, **kwargs) *args, **kwargs):
super().__init__(name=name, price=price, *args, **kwargs)
self.healing = healing self.healing = healing
def hold(self, player: "Player") -> None: def hold(self, entity: InventoryHolder) -> None:
""" """
When holding a heart, heal the player and don't put item in inventory. When holding a heart, heal the player and don't put item in inventory.
""" """
player.health = min(player.maxhealth, player.health + self.healing) entity.health = min(entity.maxhealth, entity.health + self.healing)
self.map.remove_entity(self) entity.map.remove_entity(self)
def save_state(self) -> dict: def save_state(self) -> dict:
""" """
@ -97,8 +120,8 @@ class Bomb(Item):
tick: int tick: int
def __init__(self, name: str = "bomb", damage: int = 5, def __init__(self, name: str = "bomb", damage: int = 5,
exploding: bool = False, *args, **kwargs): exploding: bool = False, price: int = 4, *args, **kwargs):
super().__init__(name=name, *args, **kwargs) super().__init__(name=name, price=price, *args, **kwargs)
self.damage = damage self.damage = damage
self.exploding = exploding self.exploding = exploding
self.tick = 4 self.tick = 4
@ -135,6 +158,10 @@ class Bomb(Item):
m.logs.add_message(log_message) m.logs.add_message(log_message)
m.entities.remove(self) m.entities.remove(self)
# Add sparkles where the bomb exploded.
explosion = Explosion(y=self.y, x=self.x)
self.map.add_entity(explosion)
def save_state(self) -> dict: def save_state(self) -> dict:
""" """
Saves the state of the bomb into a dictionary Saves the state of the bomb into a dictionary
@ -145,14 +172,63 @@ class Bomb(Item):
return d return d
class Explosion(Item):
"""
When a bomb explodes, the explosion is displayed.
"""
def __init__(self, *args, **kwargs):
super().__init__(name="explosion", *args, **kwargs)
def act(self, m: Map) -> None:
"""
The explosion instant dies.
"""
m.remove_entity(self)
def hold(self, player: InventoryHolder) -> None:
"""
The player can't hold any explosion.
"""
pass
class Weapon(Item):
"""
Non-throwable items that improve player damage
"""
damage: int
def __init__(self, damage: int = 3, *args, **kwargs):
super().__init__(*args, **kwargs)
self.damage = damage
def save_state(self) -> dict:
"""
Saves the state of the weapon into a dictionary
"""
d = super().save_state()
d["damage"] = self.damage
return d
class Sword(Weapon):
"""
A basic weapon
"""
def __init__(self, name: str = "sword", price: int = 20, *args, **kwargs):
super().__init__(name=name, price=price, *args, **kwargs)
self.name = name
class BodySnatchPotion(Item): class BodySnatchPotion(Item):
""" """
The body-snatch potion allows to exchange all characteristics with a random The body-snatch potion allows to exchange all characteristics with a random
other entity. other entity.
""" """
def __init__(self, name: str = "body_snatch_potion", *args, **kwargs): def __init__(self, name: str = "body_snatch_potion", price: int = 14,
super().__init__(name=name, *args, **kwargs) *args, **kwargs):
super().__init__(name=name, price=price, *args, **kwargs)
def use(self) -> None: def use(self) -> None:
""" """

View File

@ -6,23 +6,22 @@ from queue import PriorityQueue
from random import randint from random import randint
from typing import Dict, Tuple from typing import Dict, Tuple
from ..interfaces import FightingEntity from ..interfaces import FightingEntity, InventoryHolder
class Player(FightingEntity): class Player(InventoryHolder, FightingEntity):
""" """
The class of the player The class of the player
""" """
current_xp: int = 0 current_xp: int = 0
max_xp: int = 10 max_xp: int = 10
inventory: list
paths: Dict[Tuple[int, int], Tuple[int, int]] paths: Dict[Tuple[int, int], Tuple[int, int]]
def __init__(self, name: str = "player", maxhealth: int = 20, def __init__(self, name: str = "player", maxhealth: int = 20,
strength: int = 5, intelligence: int = 1, charisma: int = 1, strength: int = 5, intelligence: int = 1, charisma: int = 1,
dexterity: int = 1, constitution: int = 1, level: int = 1, dexterity: int = 1, constitution: int = 1, level: int = 1,
current_xp: int = 0, max_xp: int = 10, inventory: list = None, current_xp: int = 0, max_xp: int = 10, inventory: list = None,
*args, **kwargs) \ hazel: int = 42, *args, **kwargs) \
-> None: -> None:
super().__init__(name=name, maxhealth=maxhealth, strength=strength, super().__init__(name=name, maxhealth=maxhealth, strength=strength,
intelligence=intelligence, charisma=charisma, intelligence=intelligence, charisma=charisma,
@ -30,13 +29,9 @@ class Player(FightingEntity):
level=level, *args, **kwargs) level=level, *args, **kwargs)
self.current_xp = current_xp self.current_xp = current_xp
self.max_xp = max_xp self.max_xp = max_xp
self.inventory = inventory if inventory else list() self.inventory = self.translate_inventory(inventory or [])
for i in range(len(self.inventory)):
if isinstance(self.inventory[i], dict):
entity_classes = self.get_all_entity_classes_in_a_dict()
item_class = entity_classes[self.inventory[i]["type"]]
self.inventory[i] = item_class(**self.inventory[i])
self.paths = dict() self.paths = dict()
self.hazel = hazel
def move(self, y: int, x: int) -> None: def move(self, y: int, x: int) -> None:
""" """
@ -149,5 +144,4 @@ class Player(FightingEntity):
d = super().save_state() d = super().save_state()
d["current_xp"] = self.current_xp d["current_xp"] = self.current_xp
d["max_xp"] = self.max_xp d["max_xp"] = self.max_xp
d["inventory"] = [item.save_state() for item in self.inventory]
return d return d

View File

@ -16,6 +16,7 @@ class DisplayActions(Enum):
""" """
REFRESH = auto() REFRESH = auto()
UPDATE = auto() UPDATE = auto()
MOUSE = auto()
class GameMode(Enum): class GameMode(Enum):
@ -26,12 +27,14 @@ class GameMode(Enum):
PLAY = auto() PLAY = auto()
SETTINGS = auto() SETTINGS = auto()
INVENTORY = auto() INVENTORY = auto()
STORE = auto()
class KeyValues(Enum): class KeyValues(Enum):
""" """
Key values options used in the game Key values options used in the game
""" """
MOUSE = auto()
UP = auto() UP = auto()
DOWN = auto() DOWN = auto()
LEFT = auto() LEFT = auto()
@ -42,6 +45,8 @@ class KeyValues(Enum):
EQUIP = auto() EQUIP = auto()
DROP = auto() DROP = auto()
SPACE = auto() SPACE = auto()
CHAT = auto()
WAIT = auto()
@staticmethod @staticmethod
def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]: def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]:
@ -72,4 +77,8 @@ class KeyValues(Enum):
return KeyValues.DROP return KeyValues.DROP
elif key == ' ': elif key == ' ':
return KeyValues.SPACE return KeyValues.SPACE
elif key == settings.KEY_CHAT:
return KeyValues.CHAT
elif key == settings.KEY_WAIT:
return KeyValues.WAIT
return None return None

View File

@ -4,6 +4,7 @@
from json import JSONDecodeError from json import JSONDecodeError
from random import randint from random import randint
from typing import Any, Optional from typing import Any, Optional
import curses
import json import json
import os import os
import sys import sys
@ -15,7 +16,6 @@ from .resources import ResourceManager
from .settings import Settings from .settings import Settings
from . import menus from . import menus
from .translations import gettext as _, Translator from .translations import gettext as _, Translator
from typing import Callable
class Game: class Game:
@ -24,14 +24,16 @@ class Game:
""" """
map: Map map: Map
player: Player player: Player
screen: Any
# display_actions is a display interface set by the bootstrapper # display_actions is a display interface set by the bootstrapper
display_actions: Callable[[DisplayActions], None] display_actions: callable
def __init__(self) -> None: def __init__(self) -> None:
""" """
Init the game. Init the game.
""" """
self.state = GameMode.MAINMENU self.state = GameMode.MAINMENU
self.waiting_for_friendly_key = False
self.settings = Settings() self.settings = Settings()
self.settings.load_settings() self.settings.load_settings()
self.settings.write_settings() self.settings.write_settings()
@ -40,6 +42,7 @@ class Game:
self.settings_menu = menus.SettingsMenu() self.settings_menu = menus.SettingsMenu()
self.settings_menu.update_values(self.settings) self.settings_menu.update_values(self.settings)
self.inventory_menu = menus.InventoryMenu() self.inventory_menu = menus.InventoryMenu()
self.store_menu = menus.StoreMenu()
self.logs = Logs() self.logs = Logs()
self.message = None self.message = None
@ -68,6 +71,10 @@ class Game:
screen.refresh() screen.refresh()
self.display_actions(DisplayActions.REFRESH) self.display_actions(DisplayActions.REFRESH)
key = screen.getkey() key = screen.getkey()
if key == "KEY_MOUSE":
_ignored1, x, y, _ignored2, _ignored3 = curses.getmouse()
self.display_actions(DisplayActions.MOUSE, y, x)
else:
self.handle_key_pressed( self.handle_key_pressed(
KeyValues.translate_key(key, self.settings), key) KeyValues.translate_key(key, self.settings), key)
@ -83,6 +90,10 @@ class Game:
return return
if self.state == GameMode.PLAY: if self.state == GameMode.PLAY:
if self.waiting_for_friendly_key:
# The player requested to talk with a friendly entity
self.handle_friendly_entity_chat(key)
else:
self.handle_key_pressed_play(key) self.handle_key_pressed_play(key)
elif self.state == GameMode.INVENTORY: elif self.state == GameMode.INVENTORY:
self.handle_key_pressed_inventory(key) self.handle_key_pressed_inventory(key)
@ -90,6 +101,8 @@ class Game:
self.handle_key_pressed_main_menu(key) self.handle_key_pressed_main_menu(key)
elif self.state == GameMode.SETTINGS: elif self.state == GameMode.SETTINGS:
self.settings_menu.handle_key_pressed(key, raw_key, self) self.settings_menu.handle_key_pressed(key, raw_key, self)
elif self.state == GameMode.STORE:
self.handle_key_pressed_store(key)
self.display_actions(DisplayActions.REFRESH) self.display_actions(DisplayActions.REFRESH)
def handle_key_pressed_play(self, key: KeyValues) -> None: def handle_key_pressed_play(self, key: KeyValues) -> None:
@ -112,6 +125,44 @@ class Game:
self.state = GameMode.INVENTORY self.state = GameMode.INVENTORY
elif key == KeyValues.SPACE: elif key == KeyValues.SPACE:
self.state = GameMode.MAINMENU self.state = GameMode.MAINMENU
elif key == KeyValues.CHAT:
# Wait for the direction of the friendly entity
self.waiting_for_friendly_key = True
elif key == KeyValues.WAIT:
self.map.tick()
def handle_friendly_entity_chat(self, key: KeyValues) -> None:
"""
If the player is talking to a friendly entity, we get the direction
where the entity is, then we interact with it.
"""
if not self.waiting_for_friendly_key:
return
self.waiting_for_friendly_key = False
if key == KeyValues.UP:
xp = self.player.x
yp = self.player.y - 1
elif key == KeyValues.DOWN:
xp = self.player.x
yp = self.player.y + 1
elif key == KeyValues.LEFT:
xp = self.player.x - 1
yp = self.player.y
elif key == KeyValues.RIGHT:
xp = self.player.x + 1
yp = self.player.y
else:
return
if self.map.entity_is_present(yp, xp):
for entity in self.map.entities:
if entity.is_friendly() and entity.x == xp and \
entity.y == yp:
msg = entity.talk_to(self.player)
self.logs.add_message(msg)
if entity.is_merchant():
self.state = GameMode.STORE
self.store_menu.update_merchant(entity)
def handle_key_pressed_inventory(self, key: KeyValues) -> None: def handle_key_pressed_inventory(self, key: KeyValues) -> None:
""" """
@ -136,6 +187,27 @@ class Game:
len(self.inventory_menu.values) len(self.inventory_menu.values)
- 1) - 1)
def handle_key_pressed_store(self, key: KeyValues) -> None:
"""
In a store menu, we can buy items or close the menu.
"""
if key == KeyValues.SPACE:
self.state = GameMode.PLAY
elif key == KeyValues.UP:
self.store_menu.go_up()
elif key == KeyValues.DOWN:
self.store_menu.go_down()
if self.store_menu.values and not self.player.dead:
if key == KeyValues.ENTER:
item = self.store_menu.validate()
flag = item.be_sold(self.player, self.store_menu.merchant)
if not flag:
self.message = _("You do not have enough money")
self.display_actions(DisplayActions.UPDATE)
# Ensure that the cursor has a good position
self.store_menu.position = min(self.store_menu.position,
len(self.store_menu.values) - 1)
def handle_key_pressed_main_menu(self, key: KeyValues) -> None: 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 options.

View File

@ -4,7 +4,7 @@
from enum import Enum, auto from enum import Enum, auto
from math import sqrt from math import sqrt
from random import choice, randint from random import choice, randint
from typing import List, Optional, Union, Tuple from typing import List, Optional, Union, Tuple, Any
from .display.texturepack import TexturePack from .display.texturepack import TexturePack
from .translations import gettext as _ from .translations import gettext as _
@ -93,6 +93,7 @@ class Map:
""" """
Unregister an entity from the map. Unregister an entity from the map.
""" """
if entity in self.entities:
self.entities.remove(entity) self.entities.remove(entity)
def find_entities(self, entity_class: type) -> list: def find_entities(self, entity_class: type) -> list:
@ -101,12 +102,21 @@ class Map:
def is_free(self, y: int, x: int) -> bool: def is_free(self, y: int, x: int) -> bool:
""" """
Indicates that the case at the coordinates (y, x) is empty. Indicates that the tile at the coordinates (y, x) is empty.
""" """
return 0 <= y < self.height and 0 <= x < self.width and \ return 0 <= y < self.height and 0 <= x < self.width and \
self.tiles[y][x].can_walk() and \ self.tiles[y][x].can_walk() and \
not any(entity.x == x and entity.y == y for entity in self.entities) not any(entity.x == x and entity.y == y for entity in self.entities)
def entity_is_present(self, y: int, x: int) -> bool:
"""
Indicates that the tile at the coordinates (y, x) contains a killable
entity
"""
return 0 <= y < self.height and 0 <= x < self.width and \
any(entity.x == x and entity.y == y and entity.is_friendly()
for entity in self.entities)
@staticmethod @staticmethod
def load(filename: str) -> "Map": def load(filename: str) -> "Map":
""" """
@ -152,7 +162,7 @@ class Map:
def spawn_random_entities(self, count: int) -> None: def spawn_random_entities(self, count: int) -> None:
""" """
Put randomly {count} hedgehogs on the map, where it is available. Put randomly {count} entities on the map, where it is available.
""" """
for _ignored in range(count): for _ignored in range(count):
y, x = 0, 0 y, x = 0, 0
@ -459,20 +469,34 @@ class Entity:
from squirrelbattle.entities.items import Item from squirrelbattle.entities.items import Item
return isinstance(self, Item) return isinstance(self, Item)
def is_friendly(self) -> bool:
"""
Is this entity a friendly entity?
"""
return isinstance(self, FriendlyEntity)
def is_merchant(self) -> bool:
"""
Is this entity a merchant?
"""
from squirrelbattle.entities.friendly import Merchant
return isinstance(self, Merchant)
@property @property
def translated_name(self) -> str: def translated_name(self) -> str:
return _(self.name.replace("_", " ")) return _(self.name.replace("_", " "))
@staticmethod @staticmethod
def get_all_entity_classes(): 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.items import BodySnatchPotion, Bomb, Heart
from squirrelbattle.entities.monsters import Tiger, Hedgehog, \ from squirrelbattle.entities.monsters import Tiger, Hedgehog, \
Rabbit, TeddyBear Rabbit, TeddyBear
return [BodySnatchPotion, Bomb, Heart, Hedgehog, from squirrelbattle.entities.friendly import Merchant, Sunflower
Rabbit, TeddyBear, Tiger] return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear,
Sunflower, Tiger, Merchant]
@staticmethod @staticmethod
def get_all_entity_classes_in_a_dict() -> dict: def get_all_entity_classes_in_a_dict() -> dict:
@ -482,7 +506,9 @@ class Entity:
from squirrelbattle.entities.player import Player from squirrelbattle.entities.player import Player
from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \ from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \
TeddyBear TeddyBear
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart from squirrelbattle.entities.friendly import Merchant, Sunflower
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \
Heart, Sword
return { return {
"Tiger": Tiger, "Tiger": Tiger,
"Bomb": Bomb, "Bomb": Bomb,
@ -492,6 +518,9 @@ class Entity:
"Rabbit": Rabbit, "Rabbit": Rabbit,
"TeddyBear": TeddyBear, "TeddyBear": TeddyBear,
"Player": Player, "Player": Player,
"Merchant": Merchant,
"Sunflower": Sunflower,
"Sword": Sword,
} }
def save_state(self) -> dict: def save_state(self) -> dict:
@ -567,7 +596,7 @@ class FightingEntity(Entity):
def keys(self) -> list: def keys(self) -> list:
""" """
Returns a fighting entities specific attributes Returns a fighting entity's specific attributes
""" """
return ["name", "maxhealth", "health", "level", "strength", return ["name", "maxhealth", "health", "level", "strength",
"intelligence", "charisma", "dexterity", "constitution"] "intelligence", "charisma", "dexterity", "constitution"]
@ -580,3 +609,74 @@ class FightingEntity(Entity):
for name in self.keys(): for name in self.keys():
d[name] = getattr(self, name) d[name] = getattr(self, name)
return d return d
class FriendlyEntity(FightingEntity):
"""
Friendly entities are living entities which do not attack the player
"""
dialogue_option: list
def talk_to(self, player: Any) -> str:
return _("{entity} said: {message}").format(
entity=self.translated_name.capitalize(),
message=choice(self.dialogue_option))
def keys(self) -> list:
"""
Returns a friendly entity's specific attributes
"""
return ["maxhealth", "health"]
class InventoryHolder(Entity):
hazel: int # Currency of the game
inventory: list
def translate_inventory(self, inventory: list) -> list:
"""
Translate the JSON-state of the inventory into a list of the items in
the inventory.
"""
for i in range(len(inventory)):
if isinstance(inventory[i], dict):
inventory[i] = self.dict_to_inventory(inventory[i])
return inventory
def dict_to_inventory(self, item_dict: dict) -> Entity:
"""
Translate a dict object that contains the state of an item
into an item object.
"""
entity_classes = self.get_all_entity_classes_in_a_dict()
item_class = entity_classes[item_dict["type"]]
return item_class(**item_dict)
def save_state(self) -> dict:
"""
We save the inventory of the merchant formatted as JSON
"""
d = super().save_state()
d["hazel"] = self.hazel
d["inventory"] = [item.save_state() for item in self.inventory]
return d
def add_to_inventory(self, obj: Any) -> None:
"""
Adds an object to inventory
"""
self.inventory.append(obj)
def remove_from_inventory(self, obj: Any) -> None:
"""
Removes an object from the inventory
"""
self.inventory.remove(obj)
def change_hazel_balance(self, hz: int) -> None:
"""
Change the number of hazel the entity has by hz. hz is negative
when the player loses money and positive when he gains money
"""
self.hazel += hz

View File

@ -1,12 +1,14 @@
# German translation of Squirrel Battle # SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse # Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao
# This file is distributed under the same license as the squirrelbattle package. # This file is distributed under the same license as the squirrelbattle package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
#, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: squirrelbattle 3.14.1\n" "Project-Id-Version: squirrelbattle 3.14.1\n"
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
"POT-Creation-Date: 2020-12-05 14:46+0100\n" "POT-Creation-Date: 2020-12-12 18:02+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -15,31 +17,52 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: squirrelbattle/display/menudisplay.py:105 #: squirrelbattle/display/menudisplay.py:139
msgid "== INVENTORY ==" msgid "INVENTORY"
msgstr "== BESTAND ==" msgstr "BESTAND"
#: squirrelbattle/display/statsdisplay.py:34 #: squirrelbattle/display/menudisplay.py:164
msgid "STALL"
msgstr "STAND"
#: squirrelbattle/display/statsdisplay.py:33
msgid "Inventory:" msgid "Inventory:"
msgstr "Bestand:" msgstr "Bestand:"
#: squirrelbattle/display/statsdisplay.py:50 #: squirrelbattle/display/statsdisplay.py:52
msgid "YOU ARE DEAD" msgid "YOU ARE DEAD"
msgstr "SIE WURDEN GESTORBEN" msgstr "SIE WURDEN GESTORBEN"
#. TODO
#: squirrelbattle/entities/friendly.py:33
msgid "I don't sell any squirrel"
msgstr "Ich verkaufe keinen Eichhörnchen."
#: squirrelbattle/entities/friendly.py:52
msgid "Flower power!!"
msgstr "Blumenmacht!!"
#: squirrelbattle/entities/friendly.py:52
msgid "The sun is warm today"
msgstr "Die Sonne ist warm heute"
#. The bomb is exploding. #. The bomb is exploding.
#. Each entity that is close to the bomb takes damages. #. Each entity that is close to the bomb takes damages.
#. The player earn XP if the entity was killed. #. The player earn XP if the entity was killed.
#: squirrelbattle/entities/items.py:128 #: squirrelbattle/entities/items.py:151
msgid "Bomb is exploding." msgid "Bomb is exploding."
msgstr "Die Bombe explodiert." msgstr "Die Bombe explodiert."
#: squirrelbattle/entities/items.py:172 #: squirrelbattle/entities/items.py:248
#, python-brace-format #, python-brace-format
msgid "{player} exchanged its body with {entity}." msgid "{player} exchanged its body with {entity}."
msgstr "{player} täuscht seinem Körper mit {entity} aus." msgstr "{player} täuscht seinem Körper mit {entity} aus."
#: squirrelbattle/game.py:177 #: 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:249
msgid "" msgid ""
"Some keys are missing in your save file.\n" "Some keys are missing in your save file.\n"
"Your save seems to be corrupt. It got deleted." "Your save seems to be corrupt. It got deleted."
@ -47,7 +70,7 @@ msgstr ""
"In Ihrer Speicherdatei fehlen einige Schlüssel.\n" "In Ihrer Speicherdatei fehlen einige Schlüssel.\n"
"Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht." "Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht."
#: squirrelbattle/game.py:185 #: squirrelbattle/game.py:257
msgid "" msgid ""
"No player was found on this map!\n" "No player was found on this map!\n"
"Maybe you died?" "Maybe you died?"
@ -55,7 +78,7 @@ msgstr ""
"Auf dieser Karte wurde kein Spieler gefunden!\n" "Auf dieser Karte wurde kein Spieler gefunden!\n"
"Vielleicht sind Sie gestorben?" "Vielleicht sind Sie gestorben?"
#: squirrelbattle/game.py:205 #: squirrelbattle/game.py:277
msgid "" msgid ""
"The JSON file is not correct.\n" "The JSON file is not correct.\n"
"Your save seems corrupted. It got deleted." "Your save seems corrupted. It got deleted."
@ -63,27 +86,32 @@ msgstr ""
"Die JSON-Datei ist nicht korrekt.\n" "Die JSON-Datei ist nicht korrekt.\n"
"Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." "Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht."
#: squirrelbattle/interfaces.py:400 #: squirrelbattle/interfaces.py:429
#, python-brace-format #, python-brace-format
msgid "{name} hits {opponent}." msgid "{name} hits {opponent}."
msgstr "{name} schlägt {opponent}." msgstr "{name} schlägt {opponent}."
#: squirrelbattle/interfaces.py:412 #: squirrelbattle/interfaces.py:441
#, python-brace-format #, python-brace-format
msgid "{name} takes {amount} damage." msgid "{name} takes {amount} damage."
msgstr "{name} nimmt {amount} Schadenspunkte." msgstr "{name} nimmt {amount} Schadenspunkte."
#: squirrelbattle/interfaces.py:414 #: squirrelbattle/interfaces.py:443
#, python-brace-format #, python-brace-format
msgid "{name} dies." msgid "{name} dies."
msgstr "{name} stirbt." msgstr "{name} stirbt."
#: squirrelbattle/menus.py:72 #: squirrelbattle/interfaces.py:477
#, python-brace-format
msgid "{entity} said: {message}"
msgstr "{entity} hat gesagt: {message}"
#: squirrelbattle/menus.py:73
msgid "Back" msgid "Back"
msgstr "Zurück" msgstr "Zurück"
#: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303 #: squirrelbattle/tests/game_test.py:344 squirrelbattle/tests/game_test.py:347
#: squirrelbattle/tests/game_test.py:306 #: squirrelbattle/tests/game_test.py:350 squirrelbattle/tests/game_test.py:353
#: squirrelbattle/tests/translations_test.py:16 #: squirrelbattle/tests/translations_test.py:16
msgid "New game" msgid "New game"
msgstr "Neu Spiel" msgstr "Neu Spiel"
@ -161,41 +189,65 @@ msgid "Key used to drop an item in the inventory"
msgstr "Taste um eines Objekts im Bestand zu werfen" msgstr "Taste um eines Objekts im Bestand zu werfen"
#: squirrelbattle/tests/translations_test.py:53 #: squirrelbattle/tests/translations_test.py:53
msgid "Key used to talk to a friendly entity"
msgstr "Taste um mit einer friedlicher Entität zu sprechen"
#: squirrelbattle/tests/translations_test.py:55
msgid "Key used to wait"
msgstr "Wartentaste"
#: squirrelbattle/tests/translations_test.py:56
msgid "Texture pack" msgid "Texture pack"
msgstr "Textur-Packung" msgstr "Textur-Packung"
#: squirrelbattle/tests/translations_test.py:54 #: squirrelbattle/tests/translations_test.py:57
msgid "Language" msgid "Language"
msgstr "Sprache" msgstr "Sprache"
#: squirrelbattle/tests/translations_test.py:57 #: squirrelbattle/tests/translations_test.py:60
msgid "player" msgid "player"
msgstr "Spieler" msgstr "Spieler"
#: squirrelbattle/tests/translations_test.py:59 #: squirrelbattle/tests/translations_test.py:62
msgid "tiger"
msgstr "Tiger"
#: squirrelbattle/tests/translations_test.py:60
msgid "hedgehog" msgid "hedgehog"
msgstr "Igel" msgstr "Igel"
#: squirrelbattle/tests/translations_test.py:61 #: squirrelbattle/tests/translations_test.py:63
msgid "merchant"
msgstr "Kaufmann"
#: squirrelbattle/tests/translations_test.py:64
msgid "rabbit" msgid "rabbit"
msgstr "Kanninchen" msgstr "Kanninchen"
#: squirrelbattle/tests/translations_test.py:62 #: squirrelbattle/tests/translations_test.py:65
msgid "sunflower"
msgstr "Sonnenblume"
#: squirrelbattle/tests/translations_test.py:66
msgid "teddy bear" msgid "teddy bear"
msgstr "Teddybär" msgstr "Teddybär"
#: squirrelbattle/tests/translations_test.py:64 #: squirrelbattle/tests/translations_test.py:67
msgid "tiger"
msgstr "Tiger"
#: squirrelbattle/tests/translations_test.py:69
msgid "body snatch potion" msgid "body snatch potion"
msgstr "Leichenfleddererzaubertrank" msgstr "Leichenfleddererzaubertrank"
#: squirrelbattle/tests/translations_test.py:65 #: squirrelbattle/tests/translations_test.py:70
msgid "bomb" msgid "bomb"
msgstr "Bombe" msgstr "Bombe"
#: squirrelbattle/tests/translations_test.py:66 #: squirrelbattle/tests/translations_test.py:71
msgid "explosion"
msgstr "Explosion"
#: squirrelbattle/tests/translations_test.py:72
msgid "heart" msgid "heart"
msgstr "Herz" msgstr "Herz"
#: squirrelbattle/tests/translations_test.py:73
msgid "sword"
msgstr "schwert"

View File

@ -0,0 +1,207 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse
# This file is distributed under the same license as the squirrelbattle package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: squirrelbattle 3.14.1\n"
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
"POT-Creation-Date: 2020-12-01 17:10+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: squirrelbattle/display/statsdisplay.py:34
msgid "Inventory:"
msgstr ""
#: squirrelbattle/display/statsdisplay.py:39
msgid "YOU ARE DEAD"
msgstr ""
#: squirrelbattle/interfaces.py:394 squirrelbattle/interfaces.py:398
#: squirrelbattle/interfaces.py:408
#, python-brace-format
msgid "{name} hits {opponent}."
msgstr ""
#: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410
#: squirrelbattle/interfaces.py:420
#, python-brace-format
msgid "{name} takes {amount} damage."
msgstr ""
#: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:14
#: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287
#: squirrelbattle/tests/translations_test.py:16
#: squirrelbattle/tests/game_test.py:290
msgid "New game"
msgstr ""
#: squirrelbattle/menus.py:46 squirrelbattle/tests/translations_test.py:15
#: squirrelbattle/tests/translations_test.py:17
msgid "Resume"
msgstr ""
#: squirrelbattle/menus.py:47 squirrelbattle/tests/translations_test.py:17
#: squirrelbattle/tests/translations_test.py:19
msgid "Save"
msgstr ""
#: squirrelbattle/menus.py:48 squirrelbattle/tests/translations_test.py:16
#: squirrelbattle/tests/translations_test.py:18
msgid "Load"
msgstr ""
#: squirrelbattle/menus.py:49 squirrelbattle/tests/translations_test.py:18
#: squirrelbattle/tests/translations_test.py:20
msgid "Settings"
msgstr ""
#: squirrelbattle/menus.py:50 squirrelbattle/tests/translations_test.py:19
#: squirrelbattle/tests/translations_test.py:21
msgid "Exit"
msgstr ""
#: squirrelbattle/menus.py:71
msgid "Back"
msgstr ""
#: squirrelbattle/game.py:147 squirrelbattle/game.py:148
msgid ""
"Some keys are missing in your save file.\n"
"Your save seems to be corrupt. It got deleted."
msgstr ""
#: squirrelbattle/game.py:155 squirrelbattle/game.py:156
msgid ""
"No player was found on this map!\n"
"Maybe you died?"
msgstr ""
#: squirrelbattle/game.py:175 squirrelbattle/game.py:176
msgid ""
"The JSON file is not correct.\n"
"Your save seems corrupted. It got deleted."
msgstr ""
#: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:21
#: squirrelbattle/tests/translations_test.py:25
#: squirrelbattle/tests/translations_test.py:27
msgid "Main key to move up"
msgstr ""
#: squirrelbattle/settings.py:22 squirrelbattle/tests/translations_test.py:23
#: squirrelbattle/tests/translations_test.py:27
#: squirrelbattle/tests/translations_test.py:29
msgid "Secondary key to move up"
msgstr ""
#: squirrelbattle/settings.py:23 squirrelbattle/tests/translations_test.py:25
#: squirrelbattle/tests/translations_test.py:29
#: squirrelbattle/tests/translations_test.py:31
msgid "Main key to move down"
msgstr ""
#: squirrelbattle/settings.py:24 squirrelbattle/tests/translations_test.py:27
#: squirrelbattle/tests/translations_test.py:31
#: squirrelbattle/tests/translations_test.py:33
msgid "Secondary key to move down"
msgstr ""
#: squirrelbattle/settings.py:25 squirrelbattle/tests/translations_test.py:29
#: squirrelbattle/tests/translations_test.py:33
#: squirrelbattle/tests/translations_test.py:35
msgid "Main key to move left"
msgstr ""
#: squirrelbattle/settings.py:26 squirrelbattle/tests/translations_test.py:31
#: squirrelbattle/tests/translations_test.py:35
#: squirrelbattle/tests/translations_test.py:37
msgid "Secondary key to move left"
msgstr ""
#: squirrelbattle/settings.py:27 squirrelbattle/tests/translations_test.py:33
#: squirrelbattle/tests/translations_test.py:37
#: squirrelbattle/tests/translations_test.py:39
msgid "Main key to move right"
msgstr ""
#: squirrelbattle/settings.py:29 squirrelbattle/tests/translations_test.py:35
#: squirrelbattle/tests/translations_test.py:39
#: squirrelbattle/tests/translations_test.py:41
msgid "Secondary key to move right"
msgstr ""
#: squirrelbattle/settings.py:30 squirrelbattle/tests/translations_test.py:37
#: squirrelbattle/tests/translations_test.py:41
#: squirrelbattle/tests/translations_test.py:43
msgid "Key to validate a menu"
msgstr ""
#: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:39
#: squirrelbattle/tests/translations_test.py:43
#: squirrelbattle/tests/translations_test.py:45
msgid "Texture pack"
msgstr ""
#: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40
#: squirrelbattle/tests/translations_test.py:44
#: squirrelbattle/tests/translations_test.py:46
msgid "Language"
msgstr ""
#: squirrelbattle/interfaces.py:407 squirrelbattle/interfaces.py:412
#: squirrelbattle/interfaces.py:422
#, python-brace-format
msgid "{name} dies."
msgstr ""
#: squirrelbattle/tests/translations_test.py:47
#: squirrelbattle/tests/translations_test.py:49
msgid "player"
msgstr ""
#: squirrelbattle/tests/translations_test.py:49
#: squirrelbattle/tests/translations_test.py:51
msgid "tiger"
msgstr ""
#: squirrelbattle/tests/translations_test.py:50
#: squirrelbattle/tests/translations_test.py:52
msgid "hedgehog"
msgstr ""
#: squirrelbattle/tests/translations_test.py:51
#: squirrelbattle/tests/translations_test.py:53
msgid "rabbit"
msgstr ""
#: squirrelbattle/tests/translations_test.py:52
#: squirrelbattle/tests/translations_test.py:54
msgid "teddy bear"
msgstr ""
#: squirrelbattle/tests/translations_test.py:54
#: squirrelbattle/tests/translations_test.py:56
msgid "bomb"
msgstr ""
#: squirrelbattle/tests/translations_test.py:55
#: squirrelbattle/tests/translations_test.py:57
msgid "heart"
msgstr ""
#: squirrelbattle/entities/friendly.py:31
msgid "Flower power!!"
msgstr ""
#: squirrelbattle/entities/friendly.py:31
msgid "The sun is warm today"
msgstr ""

View File

@ -1,49 +1,67 @@
# Spanish translation of Squirrel Battle # SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse # Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao
# This file is distributed under the same license as the squirrelbattle package. # This file is distributed under the same license as the squirrelbattle package.
# Translation by ifugaao # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
#, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: squirrelbattle 3.14.1\n" "Project-Id-Version: squirrelbattle 3.14.1\n"
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
"POT-Creation-Date: 2020-12-05 14:46+0100\n" "POT-Creation-Date: 2020-12-12 18:02+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ifugao\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
# Suggested in Weblate: == INVENTORIO == #: squirrelbattle/display/menudisplay.py:139
#: squirrelbattle/display/menudisplay.py:105 msgid "INVENTORY"
msgid "== INVENTORY ==" msgstr "INVENTORIO"
msgstr "== INVENTORIO =="
# Suggested in Weblate: Inventorio : #: squirrelbattle/display/menudisplay.py:164
#: squirrelbattle/display/statsdisplay.py:34 msgid "STALL"
msgstr "PUESTO"
#: squirrelbattle/display/statsdisplay.py:33
msgid "Inventory:" msgid "Inventory:"
msgstr "Inventorio :" msgstr "Inventorio :"
# Suggested in Weblate: ERES MUERTO #: squirrelbattle/display/statsdisplay.py:52
#: squirrelbattle/display/statsdisplay.py:50
msgid "YOU ARE DEAD" msgid "YOU ARE DEAD"
msgstr "ERES MUERTO" msgstr "ERES MUERTO"
#: squirrelbattle/entities/friendly.py:33
msgid "I don't sell any squirrel"
msgstr "No vendo ninguna ardilla"
#: squirrelbattle/entities/friendly.py:52
msgid "Flower power!!"
msgstr "Poder de las flores!!"
#: squirrelbattle/entities/friendly.py:52
msgid "The sun is warm today"
msgstr "El sol está caliente hoy"
#. The bomb is exploding. #. The bomb is exploding.
#. Each entity that is close to the bomb takes damages. #. Each entity that is close to the bomb takes damages.
#. The player earn XP if the entity was killed. #. The player earn XP if the entity was killed.
#: squirrelbattle/entities/items.py:128 #: squirrelbattle/entities/items.py:151
msgid "Bomb is exploding." msgid "Bomb is exploding."
msgstr "La bomba está explotando." msgstr "La bomba está explotando."
#: squirrelbattle/entities/items.py:172 #: squirrelbattle/entities/items.py:248
#, python-brace-format #, python-brace-format
msgid "{player} exchanged its body with {entity}." msgid "{player} exchanged its body with {entity}."
msgstr "{player} intercambió su cuerpo con {entity}." msgstr "{player} intercambió su cuerpo con {entity}."
#: squirrelbattle/game.py:177 #: 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:249
msgid "" msgid ""
"Some keys are missing in your save file.\n" "Some keys are missing in your save file.\n"
"Your save seems to be corrupt. It got deleted." "Your save seems to be corrupt. It got deleted."
@ -51,7 +69,7 @@ msgstr ""
"Algunas claves faltan en su archivo de guarda.\n" "Algunas claves faltan en su archivo de guarda.\n"
"Su guarda parece a ser corruptido. Fue eliminado." "Su guarda parece a ser corruptido. Fue eliminado."
#: squirrelbattle/game.py:185 #: squirrelbattle/game.py:257
msgid "" msgid ""
"No player was found on this map!\n" "No player was found on this map!\n"
"Maybe you died?" "Maybe you died?"
@ -59,7 +77,7 @@ msgstr ""
"No jugador encontrado sobre la carta !\n" "No jugador encontrado sobre la carta !\n"
"¿ Quizas murió ?" "¿ Quizas murió ?"
#: squirrelbattle/game.py:205 #: squirrelbattle/game.py:277
msgid "" msgid ""
"The JSON file is not correct.\n" "The JSON file is not correct.\n"
"Your save seems corrupted. It got deleted." "Your save seems corrupted. It got deleted."
@ -67,28 +85,32 @@ msgstr ""
"El JSON archivo no es correcto.\n" "El JSON archivo no es correcto.\n"
"Su guarda parece corrupta. Fue eliminada." "Su guarda parece corrupta. Fue eliminada."
#: squirrelbattle/interfaces.py:400 #: squirrelbattle/interfaces.py:429
#, python-brace-format #, python-brace-format
msgid "{name} hits {opponent}." msgid "{name} hits {opponent}."
msgstr "{name} golpea a {opponent}." msgstr "{name} golpea a {opponent}."
#: squirrelbattle/interfaces.py:412 #: squirrelbattle/interfaces.py:441
#, python-brace-format #, python-brace-format
msgid "{name} takes {amount} damage." msgid "{name} takes {amount} damage."
msgstr "{name} recibe {amount} daño." msgstr "{name} recibe {amount} daño."
#: squirrelbattle/interfaces.py:414 #: squirrelbattle/interfaces.py:443
#, python-brace-format #, python-brace-format
msgid "{name} dies." msgid "{name} dies."
msgstr "{name} se muere." msgstr "{name} se muere."
#: squirrelbattle/menus.py:72 #: squirrelbattle/interfaces.py:477
#, python-brace-format
msgid "{entity} said: {message}"
msgstr "{entity} dijo : {message}"
#: squirrelbattle/menus.py:73
msgid "Back" msgid "Back"
msgstr "Volver" msgstr "Volver"
#: squirrelbattle/tests/game_test.py:300, #: squirrelbattle/tests/game_test.py:344 squirrelbattle/tests/game_test.py:347
#: squirrelbattle/tests/game_test.py:303, #: squirrelbattle/tests/game_test.py:350 squirrelbattle/tests/game_test.py:353
#: squirrelbattle/tests/game_test.py:306,
#: squirrelbattle/tests/translations_test.py:16 #: squirrelbattle/tests/translations_test.py:16
msgid "New game" msgid "New game"
msgstr "Nuevo partido" msgstr "Nuevo partido"
@ -166,41 +188,65 @@ msgid "Key used to drop an item in the inventory"
msgstr "Tecla para dejar un objeto del inventorio" msgstr "Tecla para dejar un objeto del inventorio"
#: squirrelbattle/tests/translations_test.py:53 #: squirrelbattle/tests/translations_test.py:53
msgid "Key used to talk to a friendly entity"
msgstr "Tecla para hablar con una entidad amiga"
#: squirrelbattle/tests/translations_test.py:55
msgid "Key used to wait"
msgstr "Tecla para espera"
#: squirrelbattle/tests/translations_test.py:56
msgid "Texture pack" msgid "Texture pack"
msgstr "Paquete de texturas" msgstr "Paquete de texturas"
#: squirrelbattle/tests/translations_test.py:54 #: squirrelbattle/tests/translations_test.py:57
msgid "Language" msgid "Language"
msgstr "Languaje" msgstr "Languaje"
#: squirrelbattle/tests/translations_test.py:57 #: squirrelbattle/tests/translations_test.py:60
msgid "player" msgid "player"
msgstr "jugador" msgstr "jugador"
#: squirrelbattle/tests/translations_test.py:59 #: squirrelbattle/tests/translations_test.py:62
msgid "tiger"
msgstr "tigre"
#: squirrelbattle/tests/translations_test.py:60
msgid "hedgehog" msgid "hedgehog"
msgstr "erizo" msgstr "erizo"
#: squirrelbattle/tests/translations_test.py:61 #: squirrelbattle/tests/translations_test.py:63
msgid "merchant"
msgstr "comerciante"
#: squirrelbattle/tests/translations_test.py:64
msgid "rabbit" msgid "rabbit"
msgstr "conejo" msgstr "conejo"
#: squirrelbattle/tests/translations_test.py:62 #: squirrelbattle/tests/translations_test.py:65
msgid "sunflower"
msgstr "girasol"
#: squirrelbattle/tests/translations_test.py:66
msgid "teddy bear" msgid "teddy bear"
msgstr "osito de peluche" msgstr "osito de peluche"
#: squirrelbattle/tests/translations_test.py:64 #: squirrelbattle/tests/translations_test.py:67
msgid "tiger"
msgstr "tigre"
#: squirrelbattle/tests/translations_test.py:69
msgid "body snatch potion" msgid "body snatch potion"
msgstr "poción de intercambio" msgstr "poción de intercambio"
#: squirrelbattle/tests/translations_test.py:65 #: squirrelbattle/tests/translations_test.py:70
msgid "bomb" msgid "bomb"
msgstr "bomba" msgstr "bomba"
#: squirrelbattle/tests/translations_test.py:66 #: squirrelbattle/tests/translations_test.py:71
msgid "explosion"
msgstr "explosión"
#: squirrelbattle/tests/translations_test.py:72
msgid "heart" msgid "heart"
msgstr "corazón" msgstr "corazón"
#: squirrelbattle/tests/translations_test.py:73
msgid "sword"
msgstr "espada"

View File

@ -1,46 +1,68 @@
# French translation of Squirrel Battle # SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse # Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao
# This file is distributed under the same license as the squirrelbattle package. # This file is distributed under the same license as the squirrelbattle package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: squirrelbattle 3.14.1\n" "Project-Id-Version: squirrelbattle 3.14.1\n"
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
"POT-Creation-Date: 2020-12-05 14:46+0100\n" "POT-Creation-Date: 2020-12-12 18:02+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: fr\n" "Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: squirrelbattle/display/menudisplay.py:105 #: squirrelbattle/display/menudisplay.py:139
msgid "== INVENTORY ==" msgid "INVENTORY"
msgstr "== INVENTAIRE ==" msgstr "INVENTAIRE"
#: squirrelbattle/display/statsdisplay.py:34 #: squirrelbattle/display/menudisplay.py:164
msgid "STALL"
msgstr "STAND"
#: squirrelbattle/display/statsdisplay.py:33
msgid "Inventory:" msgid "Inventory:"
msgstr "Inventaire :" msgstr "Inventaire :"
#: squirrelbattle/display/statsdisplay.py:50 #: squirrelbattle/display/statsdisplay.py:52
msgid "YOU ARE DEAD" msgid "YOU ARE DEAD"
msgstr "VOUS ÊTES MORT" msgstr "VOUS ÊTES MORT"
#. TODO
#: squirrelbattle/entities/friendly.py:33
msgid "I don't sell any squirrel"
msgstr "Je ne vends pas d'écureuil"
#: squirrelbattle/entities/friendly.py:52
msgid "Flower power!!"
msgstr "Pouvoir des fleurs !!"
#: squirrelbattle/entities/friendly.py:52
msgid "The sun is warm today"
msgstr "Le soleil est chaud aujourd'hui"
#. The bomb is exploding. #. The bomb is exploding.
#. Each entity that is close to the bomb takes damages. #. Each entity that is close to the bomb takes damages.
#. The player earn XP if the entity was killed. #. The player earn XP if the entity was killed.
#: squirrelbattle/entities/items.py:128 #: squirrelbattle/entities/items.py:151
msgid "Bomb is exploding." msgid "Bomb is exploding."
msgstr "La bombe explose." msgstr "La bombe explose."
#: squirrelbattle/entities/items.py:172 #: squirrelbattle/entities/items.py:248
#, python-brace-format #, python-brace-format
msgid "{player} exchanged its body with {entity}." msgid "{player} exchanged its body with {entity}."
msgstr "{player} a échangé son corps avec {entity}." msgstr "{player} a échangé son corps avec {entity}."
#: squirrelbattle/game.py:177 #: 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:249
msgid "" msgid ""
"Some keys are missing in your save file.\n" "Some keys are missing in your save file.\n"
"Your save seems to be corrupt. It got deleted." "Your save seems to be corrupt. It got deleted."
@ -48,7 +70,7 @@ msgstr ""
"Certaines clés de votre ficher de sauvegarde sont manquantes.\n" "Certaines clés de votre ficher de sauvegarde sont manquantes.\n"
"Votre sauvegarde semble corrompue. Elle a été supprimée." "Votre sauvegarde semble corrompue. Elle a été supprimée."
#: squirrelbattle/game.py:185 #: squirrelbattle/game.py:257
msgid "" msgid ""
"No player was found on this map!\n" "No player was found on this map!\n"
"Maybe you died?" "Maybe you died?"
@ -56,7 +78,7 @@ msgstr ""
"Aucun joueur n'a été trouvé sur la carte !\n" "Aucun joueur n'a été trouvé sur la carte !\n"
"Peut-être êtes-vous mort ?" "Peut-être êtes-vous mort ?"
#: squirrelbattle/game.py:205 #: squirrelbattle/game.py:277
msgid "" msgid ""
"The JSON file is not correct.\n" "The JSON file is not correct.\n"
"Your save seems corrupted. It got deleted." "Your save seems corrupted. It got deleted."
@ -64,27 +86,32 @@ msgstr ""
"Le fichier JSON de sauvegarde est incorrect.\n" "Le fichier JSON de sauvegarde est incorrect.\n"
"Votre sauvegarde semble corrompue. Elle a été supprimée." "Votre sauvegarde semble corrompue. Elle a été supprimée."
#: squirrelbattle/interfaces.py:400 #: squirrelbattle/interfaces.py:429
#, python-brace-format #, python-brace-format
msgid "{name} hits {opponent}." msgid "{name} hits {opponent}."
msgstr "{name} frappe {opponent}." msgstr "{name} frappe {opponent}."
#: squirrelbattle/interfaces.py:412 #: squirrelbattle/interfaces.py:441
#, python-brace-format #, python-brace-format
msgid "{name} takes {amount} damage." msgid "{name} takes {amount} damage."
msgstr "{name} prend {amount} points de dégât." msgstr "{name} prend {amount} points de dégât."
#: squirrelbattle/interfaces.py:414 #: squirrelbattle/interfaces.py:443
#, python-brace-format #, python-brace-format
msgid "{name} dies." msgid "{name} dies."
msgstr "{name} meurt." msgstr "{name} meurt."
#: squirrelbattle/menus.py:72 #: squirrelbattle/interfaces.py:477
#, python-brace-format
msgid "{entity} said: {message}"
msgstr "{entity} a dit : {message}"
#: squirrelbattle/menus.py:73
msgid "Back" msgid "Back"
msgstr "Retour" msgstr "Retour"
#: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303 #: squirrelbattle/tests/game_test.py:344 squirrelbattle/tests/game_test.py:347
#: squirrelbattle/tests/game_test.py:306 #: squirrelbattle/tests/game_test.py:350 squirrelbattle/tests/game_test.py:353
#: squirrelbattle/tests/translations_test.py:16 #: squirrelbattle/tests/translations_test.py:16
msgid "New game" msgid "New game"
msgstr "Nouvelle partie" msgstr "Nouvelle partie"
@ -162,41 +189,65 @@ msgid "Key used to drop an item in the inventory"
msgstr "Touche pour jeter un objet de l'inventaire" msgstr "Touche pour jeter un objet de l'inventaire"
#: squirrelbattle/tests/translations_test.py:53 #: squirrelbattle/tests/translations_test.py:53
msgid "Key used to talk to a friendly entity"
msgstr "Touche pour parler à une entité pacifique"
#: squirrelbattle/tests/translations_test.py:55
msgid "Key used to wait"
msgstr "Touche pour attendre"
#: squirrelbattle/tests/translations_test.py:56
msgid "Texture pack" msgid "Texture pack"
msgstr "Pack de textures" msgstr "Pack de textures"
#: squirrelbattle/tests/translations_test.py:54 #: squirrelbattle/tests/translations_test.py:57
msgid "Language" msgid "Language"
msgstr "Langue" msgstr "Langue"
#: squirrelbattle/tests/translations_test.py:57 #: squirrelbattle/tests/translations_test.py:60
msgid "player" msgid "player"
msgstr "joueur" msgstr "joueur"
#: squirrelbattle/tests/translations_test.py:59 #: squirrelbattle/tests/translations_test.py:62
msgid "tiger"
msgstr "tigre"
#: squirrelbattle/tests/translations_test.py:60
msgid "hedgehog" msgid "hedgehog"
msgstr "hérisson" msgstr "hérisson"
#: squirrelbattle/tests/translations_test.py:61 #: squirrelbattle/tests/translations_test.py:63
msgid "merchant"
msgstr "marchand"
#: squirrelbattle/tests/translations_test.py:64
msgid "rabbit" msgid "rabbit"
msgstr "lapin" msgstr "lapin"
#: squirrelbattle/tests/translations_test.py:62 #: squirrelbattle/tests/translations_test.py:65
msgid "sunflower"
msgstr "tournesol"
#: squirrelbattle/tests/translations_test.py:66
msgid "teddy bear" msgid "teddy bear"
msgstr "nounours" msgstr "nounours"
#: squirrelbattle/tests/translations_test.py:64 #: squirrelbattle/tests/translations_test.py:67
msgid "tiger"
msgstr "tigre"
#: squirrelbattle/tests/translations_test.py:69
msgid "body snatch potion" msgid "body snatch potion"
msgstr "potion d'arrachage de corps" msgstr "potion d'arrachage de corps"
#: squirrelbattle/tests/translations_test.py:65 #: squirrelbattle/tests/translations_test.py:70
msgid "bomb" msgid "bomb"
msgstr "bombe" msgstr "bombe"
#: squirrelbattle/tests/translations_test.py:66 #: squirrelbattle/tests/translations_test.py:71
msgid "explosion"
msgstr ""
#: squirrelbattle/tests/translations_test.py:72
msgid "heart" msgid "heart"
msgstr "cœur" msgstr "cœur"
#: squirrelbattle/tests/translations_test.py:73
msgid "sword"
msgstr "épée"

View File

@ -6,6 +6,7 @@ from typing import Any, Optional
from .display.texturepack import TexturePack from .display.texturepack import TexturePack
from .entities.player import Player from .entities.player import Player
from .entities.friendly import Merchant
from .enums import GameMode, KeyValues, DisplayActions from .enums import GameMode, KeyValues, DisplayActions
from .settings import Settings from .settings import Settings
from .translations import gettext as _, Translator from .translations import gettext as _, Translator
@ -128,3 +129,14 @@ class InventoryMenu(Menu):
@property @property
def values(self) -> list: def values(self) -> list:
return self.player.inventory return self.player.inventory
class StoreMenu(Menu):
merchant: Merchant
def update_merchant(self, merchant: Merchant) -> None:
self.merchant = merchant
@property
def values(self) -> list:
return self.merchant.inventory

View File

@ -31,6 +31,8 @@ class Settings:
self.KEY_USE = ['u', 'Key used to use an item in the inventory'] self.KEY_USE = ['u', 'Key used to use an item in the inventory']
self.KEY_EQUIP = ['e', 'Key used to equip an item in the inventory'] self.KEY_EQUIP = ['e', 'Key used to equip an item in the inventory']
self.KEY_DROP = ['r', 'Key used to drop an item in the inventory'] self.KEY_DROP = ['r', 'Key used to drop an item in the inventory']
self.KEY_CHAT = ['t', 'Key used to talk to a friendly entity']
self.KEY_WAIT = ['w', 'Key used to wait']
self.TEXTURE_PACK = ['ascii', 'Texture pack'] self.TEXTURE_PACK = ['ascii', 'Texture pack']
self.LOCALE = [locale.getlocale()[0][:2], 'Language'] self.LOCALE = [locale.getlocale()[0][:2], 'Language']

View File

@ -20,6 +20,8 @@ class TermManager: # pragma: no cover
curses.cbreak() curses.cbreak()
# make cursor invisible # make cursor invisible
curses.curs_set(False) curses.curs_set(False)
# Catch mouse events
curses.mousemask(True)
# Enable colors # Enable colors
curses.start_color() curses.start_color()

View File

@ -3,7 +3,8 @@
import unittest import unittest
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart, Item from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart, Item, \
Explosion
from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, TeddyBear from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, TeddyBear
from squirrelbattle.entities.player import Player from squirrelbattle.entities.player import Player
from squirrelbattle.interfaces import Entity, Map from squirrelbattle.interfaces import Entity, Map
@ -138,6 +139,20 @@ class TestEntities(unittest.TestCase):
self.assertTrue(teddy_bear.dead) self.assertTrue(teddy_bear.dead)
bomb_state = item.save_state() bomb_state = item.save_state()
self.assertEqual(bomb_state["damage"], item.damage) self.assertEqual(bomb_state["damage"], item.damage)
explosions = self.map.find_entities(Explosion)
self.assertTrue(explosions)
explosion = explosions[0]
self.assertEqual(explosion.y, item.y)
self.assertEqual(explosion.x, item.x)
# The player can't hold the explosion
explosion.hold(self.player)
self.assertNotIn(explosion, self.player.inventory)
self.assertFalse(explosion.held)
# The explosion disappears after one tick
explosion.act(self.map)
self.assertNotIn(explosion, self.map.entities)
def test_hearts(self) -> None: def test_hearts(self) -> None:
""" """

View File

@ -7,7 +7,8 @@ import unittest
from ..bootstrap import Bootstrap from ..bootstrap import Bootstrap
from ..display.display import Display from ..display.display import Display
from ..display.display_manager import DisplayManager from ..display.display_manager import DisplayManager
from ..entities.items import Bomb from ..entities.friendly import Merchant, Sunflower
from ..entities.items import Bomb, Heart, Sword, Explosion
from ..entities.player import Player from ..entities.player import Player
from ..enums import DisplayActions from ..enums import DisplayActions
from ..game import Game, KeyValues, GameMode from ..game import Game, KeyValues, GameMode
@ -34,7 +35,17 @@ class TestGame(unittest.TestCase):
""" """
bomb = Bomb() bomb = Bomb()
self.game.map.add_entity(bomb) self.game.map.add_entity(bomb)
sword = Sword()
self.game.map.add_entity(sword)
# Add items in the inventory to check that it is well loaded
bomb.hold(self.game.player) bomb.hold(self.game.player)
sword.hold(self.game.player)
# Ensure that merchants can be saved
merchant = Merchant()
merchant.move(3, 6)
self.game.map.add_entity(merchant)
old_state = self.game.save_state() old_state = self.game.save_state()
self.game.handle_key_pressed(KeyValues.DOWN) self.game.handle_key_pressed(KeyValues.DOWN)
@ -117,6 +128,9 @@ class TestGame(unittest.TestCase):
self.assertEqual(KeyValues.translate_key( self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_INVENTORY, self.game.settings), self.game.settings.KEY_INVENTORY, self.game.settings),
KeyValues.INVENTORY) KeyValues.INVENTORY)
self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_CHAT, self.game.settings),
KeyValues.CHAT)
self.assertEqual(KeyValues.translate_key( self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_USE, self.game.settings), self.game.settings.KEY_USE, self.game.settings),
KeyValues.USE) KeyValues.USE)
@ -126,6 +140,9 @@ class TestGame(unittest.TestCase):
self.assertEqual(KeyValues.translate_key( self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_DROP, self.game.settings), self.game.settings.KEY_DROP, self.game.settings),
KeyValues.DROP) KeyValues.DROP)
self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_WAIT, self.game.settings),
KeyValues.WAIT)
self.assertEqual(KeyValues.translate_key(' ', self.game.settings), self.assertEqual(KeyValues.translate_key(' ', self.game.settings),
KeyValues.SPACE) KeyValues.SPACE)
self.assertEqual(KeyValues.translate_key('plop', self.game.settings), self.assertEqual(KeyValues.translate_key('plop', self.game.settings),
@ -213,9 +230,45 @@ class TestGame(unittest.TestCase):
self.assertEqual(new_y, y) self.assertEqual(new_y, y)
self.assertEqual(new_x, x - 1) self.assertEqual(new_x, x - 1)
explosion = Explosion()
self.game.map.add_entity(explosion)
self.assertIn(explosion, self.game.map.entities)
self.game.handle_key_pressed(KeyValues.WAIT)
self.assertNotIn(explosion, self.game.map.entities)
self.game.handle_key_pressed(KeyValues.SPACE) self.game.handle_key_pressed(KeyValues.SPACE)
self.assertEqual(self.game.state, GameMode.MAINMENU) self.assertEqual(self.game.state, GameMode.MAINMENU)
def test_mouse_click(self) -> None:
"""
Simulate mouse clicks.
"""
self.game.state = GameMode.MAINMENU
# Change the color of the artwork
self.game.display_actions(DisplayActions.MOUSE, 0, 10)
# Settings menu
self.game.display_actions(DisplayActions.MOUSE, 25, 21)
self.assertEqual(self.game.main_menu.position, 4)
self.assertEqual(self.game.state, GameMode.SETTINGS)
bomb = Bomb()
bomb.hold(self.game.player)
bomb2 = Bomb()
bomb2.hold(self.game.player)
self.game.state = GameMode.INVENTORY
# Click nowhere
self.game.display_actions(DisplayActions.MOUSE, 0, 0)
self.assertEqual(self.game.state, GameMode.INVENTORY)
# Click on the second item
self.game.display_actions(DisplayActions.MOUSE, 8, 25)
self.assertEqual(self.game.state, GameMode.INVENTORY)
self.assertEqual(self.game.inventory_menu.position, 1)
def test_new_game(self) -> None: def test_new_game(self) -> None:
""" """
Ensure that the start button starts a new game. Ensure that the start button starts a new game.
@ -253,13 +306,13 @@ class TestGame(unittest.TestCase):
self.game.handle_key_pressed(KeyValues.ENTER) self.game.handle_key_pressed(KeyValues.ENTER)
self.assertEqual(self.game.state, GameMode.SETTINGS) self.assertEqual(self.game.state, GameMode.SETTINGS)
# Define the "move up" key to 'w' # Define the "move up" key to 'h'
self.assertFalse(self.game.settings_menu.waiting_for_key) self.assertFalse(self.game.settings_menu.waiting_for_key)
self.game.handle_key_pressed(KeyValues.ENTER) self.game.handle_key_pressed(KeyValues.ENTER)
self.assertTrue(self.game.settings_menu.waiting_for_key) self.assertTrue(self.game.settings_menu.waiting_for_key)
self.game.handle_key_pressed(None, 'w') self.game.handle_key_pressed(None, 'h')
self.assertFalse(self.game.settings_menu.waiting_for_key) self.assertFalse(self.game.settings_menu.waiting_for_key)
self.assertEqual(self.game.settings.KEY_UP_PRIMARY, 'w') self.assertEqual(self.game.settings.KEY_UP_PRIMARY, 'h')
# Navigate to "move left" # Navigate to "move left"
self.game.handle_key_pressed(KeyValues.DOWN) self.game.handle_key_pressed(KeyValues.DOWN)
@ -280,7 +333,7 @@ class TestGame(unittest.TestCase):
self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a') self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a')
# Navigate to "texture pack" # Navigate to "texture pack"
for ignored in range(9): for ignored in range(11):
self.game.handle_key_pressed(KeyValues.DOWN) self.game.handle_key_pressed(KeyValues.DOWN)
# Change texture pack # Change texture pack
@ -417,3 +470,118 @@ class TestGame(unittest.TestCase):
self.assertTrue(bomb.exploding) self.assertTrue(bomb.exploding)
self.assertEqual(bomb.y, self.game.player.y) self.assertEqual(bomb.y, self.game.player.y)
self.assertEqual(bomb.x, self.game.player.x) self.assertEqual(bomb.x, self.game.player.x)
def test_talk_to_sunflowers(self) -> None:
"""
Interact with sunflowers
"""
self.game.state = GameMode.PLAY
sunflower = Sunflower()
sunflower.move(2, 6)
self.game.map.add_entity(sunflower)
# Does nothing
self.assertIsNone(self.game.handle_friendly_entity_chat(KeyValues.UP))
# Talk to sunflower... or not
self.game.handle_key_pressed(KeyValues.CHAT)
self.assertTrue(self.game.waiting_for_friendly_key)
# Wrong key
self.game.handle_key_pressed(KeyValues.EQUIP)
self.assertFalse(self.game.waiting_for_friendly_key)
self.game.handle_key_pressed(KeyValues.CHAT)
self.assertTrue(self.game.waiting_for_friendly_key)
self.game.handle_key_pressed(KeyValues.UP)
self.assertFalse(self.game.waiting_for_friendly_key)
self.assertEqual(self.game.state, GameMode.PLAY)
self.assertFalse(len(self.game.logs.messages) > 1)
# Talk to sunflower
self.game.handle_key_pressed(KeyValues.CHAT)
self.assertTrue(self.game.waiting_for_friendly_key)
self.game.handle_key_pressed(KeyValues.DOWN)
self.assertFalse(self.game.waiting_for_friendly_key)
self.assertEqual(self.game.state, GameMode.PLAY)
self.assertTrue(self.game.logs.messages)
# Ensure that the message is a good message
self.assertTrue(any(self.game.logs.messages[1].endswith(msg)
for msg in Sunflower().dialogue_option))
# Test all directions to detect the friendly entity
self.game.player.move(3, 6)
self.game.handle_key_pressed(KeyValues.CHAT)
self.game.handle_key_pressed(KeyValues.UP)
self.assertEqual(len(self.game.logs.messages), 3)
self.game.player.move(2, 7)
self.game.handle_key_pressed(KeyValues.CHAT)
self.game.handle_key_pressed(KeyValues.LEFT)
self.assertEqual(len(self.game.logs.messages), 4)
self.game.player.move(2, 5)
self.game.handle_key_pressed(KeyValues.CHAT)
self.game.handle_key_pressed(KeyValues.RIGHT)
self.assertEqual(len(self.game.logs.messages), 5)
def test_talk_to_merchant(self) -> None:
"""
Interact with merchants
"""
self.game.state = GameMode.PLAY
merchant = Merchant()
merchant.move(2, 6)
self.game.map.add_entity(merchant)
# Does nothing
self.assertIsNone(self.game.handle_friendly_entity_chat(KeyValues.UP))
# Talk to merchant
self.game.handle_key_pressed(KeyValues.CHAT)
self.assertTrue(self.game.waiting_for_friendly_key)
self.game.handle_key_pressed(KeyValues.DOWN)
self.assertFalse(self.game.waiting_for_friendly_key)
self.assertEqual(self.game.state, GameMode.STORE)
self.assertTrue(self.game.logs.messages)
# Navigate in the menu
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.UP)
self.assertEqual(self.game.store_menu.position, 1)
self.game.player.hazel = 0x7ffff42ff
# The second item is not a heart
merchant.inventory[1] = 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)
# Buy a heart
merchant.inventory[1] = Heart()
item = self.game.store_menu.validate()
self.assertIn(item, merchant.inventory)
self.assertEqual(item, merchant.inventory[1])
self.game.player.health = self.game.player.maxhealth - 1 - item.healing
self.game.handle_key_pressed(KeyValues.ENTER)
self.assertNotIn(item, self.game.player.inventory)
self.assertNotIn(item, merchant.inventory)
self.assertEqual(self.game.player.health,
self.game.player.maxhealth - 1)
# We don't have enough of money
self.game.player.hazel = 0
item = self.game.store_menu.validate()
self.game.handle_key_pressed(KeyValues.ENTER)
self.assertNotIn(item, self.game.player.inventory)
self.assertIn(item, merchant.inventory)
self.assertEqual(self.game.message, _("You do not have enough money"))
self.game.handle_key_pressed(KeyValues.ENTER)
# Exit the menu
self.game.handle_key_pressed(KeyValues.SPACE)
self.assertEqual(self.game.state, GameMode.PLAY)

View File

@ -50,17 +50,24 @@ class TestTranslations(unittest.TestCase):
"Touche pour équiper un objet de l'inventaire") "Touche pour équiper un objet de l'inventaire")
self.assertEqual(_("Key used to drop an item in the inventory"), self.assertEqual(_("Key used to drop an item in the inventory"),
"Touche pour jeter un objet de l'inventaire") "Touche pour jeter un objet de l'inventaire")
self.assertEqual(_("Key used to talk to a friendly entity"),
"Touche pour parler à une entité pacifique")
self.assertEqual(_("Key used to wait"), "Touche pour attendre")
self.assertEqual(_("Texture pack"), "Pack de textures") self.assertEqual(_("Texture pack"), "Pack de textures")
self.assertEqual(_("Language"), "Langue") self.assertEqual(_("Language"), "Langue")
def test_entities_translation(self) -> None: def test_entities_translation(self) -> None:
self.assertEqual(_("player"), "joueur") self.assertEqual(_("player"), "joueur")
self.assertEqual(_("tiger"), "tigre")
self.assertEqual(_("hedgehog"), "hérisson") self.assertEqual(_("hedgehog"), "hérisson")
self.assertEqual(_("merchant"), "marchand")
self.assertEqual(_("rabbit"), "lapin") self.assertEqual(_("rabbit"), "lapin")
self.assertEqual(_("sunflower"), "tournesol")
self.assertEqual(_("teddy bear"), "nounours") self.assertEqual(_("teddy bear"), "nounours")
self.assertEqual(_("tiger"), "tigre")
self.assertEqual(_("body snatch potion"), "potion d'arrachage de corps") self.assertEqual(_("body snatch potion"), "potion d'arrachage de corps")
self.assertEqual(_("bomb"), "bombe") self.assertEqual(_("bomb"), "bombe")
self.assertEqual(_("explosion"), "explosion")
self.assertEqual(_("heart"), "cœur") self.assertEqual(_("heart"), "cœur")
self.assertEqual(_("sword"), "épée")