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

View File

@ -2,15 +2,16 @@
# SPDX-License-Identifier: GPL-3.0-or-later
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.messagedisplay import MessageDisplay
from squirrelbattle.display.statsdisplay import StatsDisplay
from squirrelbattle.display.menudisplay import MainMenuDisplay, \
InventoryDisplay, SettingsMenuDisplay
PlayerInventoryDisplay, StoreInventoryDisplay, SettingsMenuDisplay
from squirrelbattle.display.logsdisplay import LogsDisplay
from squirrelbattle.display.texturepack import TexturePack
from typing import Any
from typing import Any, List
from squirrelbattle.game import Game, GameMode
from squirrelbattle.enums import DisplayActions
@ -24,7 +25,8 @@ class DisplayManager:
self.mapdisplay = MapDisplay(screen, pack)
self.statsdisplay = StatsDisplay(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,
screen, pack)
self.settingsmenudisplay = SettingsMenuDisplay(screen, pack)
@ -33,28 +35,50 @@ class DisplayManager:
self.vbar = VerticalSplit(screen, pack)
self.displays = [self.statsdisplay, self.mapdisplay,
self.mainmenudisplay, self.settingsmenudisplay,
self.logsdisplay, self.messagedisplay]
self.logsdisplay, self.messagedisplay,
self.playerinventorydisplay,
self.storeinventorydisplay]
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:
self.refresh()
elif action == DisplayActions.UPDATE:
self.update_game_components()
elif action == DisplayActions.MOUSE:
self.handle_mouse_click(*params)
def update_game_components(self) -> None:
for d in self.displays:
d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK)
self.mapdisplay.update_map(self.game.map)
self.statsdisplay.update_player(self.game.player)
self.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.logsdisplay.update_logs(self.game.logs)
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 \
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
self.mapdisplay.refresh(0, 0, self.rows * 4 // 5,
self.mapdisplay.pack.tile_width
@ -67,15 +91,26 @@ class DisplayManager:
self.rows // 5 - 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)
displays += [self.mapdisplay, self.statsdisplay, self.logsdisplay,
self.hbar, self.vbar]
if self.game.state == GameMode.INVENTORY:
self.inventorydisplay.refresh(self.rows // 10,
self.cols // 2,
8 * self.rows // 10,
2 * self.cols // 5)
self.playerinventorydisplay.refresh(
self.rows // 10, self.cols // 2,
8 * self.rows // 10, 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:
self.mainmenudisplay.refresh(0, 0, self.rows, self.cols)
displays.append(self.mainmenudisplay)
elif self.game.state == GameMode.SETTINGS:
self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols)
displays.append(self.settingsmenudisplay)
if self.game.message:
height, width = 0, 0
@ -84,9 +119,12 @@ class DisplayManager:
width = max(width, len(line))
y, x = (self.rows - height) // 2, (self.cols - width) // 2
self.messagedisplay.refresh(y, x, height, width)
displays.append(self.messagedisplay)
self.resize_window()
return displays
def resize_window(self) -> bool:
"""
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)
def update_pad(self) -> None:
self.init_pair(1, self.pack.tile_fg_color, self.pack.tile_bg_color)
self.init_pair(2, self.pack.entity_fg_color, self.pack.entity_bg_color)
self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack),
self.color_pair(1))
self.pack.tile_fg_color, self.pack.tile_bg_color)
for e in self.map.entities:
self.addstr(self.pad, e.y, self.pack.tile_width * e.x,
self.pack[e.name.upper()], self.color_pair(2))
self.pack[e.name.upper()],
self.pack.entity_fg_color, self.pack.entity_bg_color)
# Display Path map for deubg purposes
# Display Path map for debug purposes
# from squirrelbattle.entities.player import Player
# players = [ p for p in self.map.entities if isinstance(p,Player) ]
# player = players[0] if len(players) > 0 else None
@ -42,7 +41,8 @@ class MapDisplay(Display):
# else:
# character = '←'
# self.addstr(self.pad, y, self.pack.tile_width * x,
# character, self.color_pair(1))
# character, self.pack.tile_fg_color,
# self.pack.tile_bg_color)
def display(self) -> None:
y, x = self.map.currenty, self.pack.tile_width * self.map.currentx

View File

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

View File

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

View File

@ -14,10 +14,22 @@ class TexturePack:
tile_bg_color: int
entity_fg_color: int
entity_bg_color: int
BODY_SNATCH_POTION: str
BOMB: str
HEART: str
HEDGEHOG: str
EMPTY: str
WALL: str
FLOOR: str
HAZELNUT: str
MERCHANT: str
PLAYER: str
RABBIT: str
SUNFLOWER: str
SWORD: str
TEDDY_BEAR: str
TIGER: str
WALL: str
ASCII_PACK: "TexturePack"
SQUIRREL_PACK: "TexturePack"
@ -46,17 +58,23 @@ TexturePack.ASCII_PACK = TexturePack(
tile_bg_color=curses.COLOR_BLACK,
entity_fg_color=curses.COLOR_WHITE,
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',
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(
@ -66,15 +84,21 @@ TexturePack.SQUIRREL_PACK = TexturePack(
tile_bg_color=curses.COLOR_BLACK,
entity_fg_color=curses.COLOR_WHITE,
entity_bg_color=curses.COLOR_WHITE,
EMPTY=' ',
WALL='🧱',
FLOOR='██',
PLAYER='🐿️ ',
HEDGEHOG='🦔',
HEART='💜',
BOMB='💣',
RABBIT='🐇',
TIGER='🐅',
TEDDY_BEAR='🧸',
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 .player import Player
from ..interfaces import Entity, FightingEntity, Map
from ..interfaces import Entity, FightingEntity, Map, InventoryHolder
from ..translations import gettext as _
@ -14,13 +14,16 @@ class Item(Entity):
A class for items
"""
held: bool
held_by: Optional[Player]
held_by: Optional[InventoryHolder]
price: int
def __init__(self, held: bool = False, held_by: Optional[Player] = None,
*args, **kwargs):
def __init__(self, held: bool = False,
held_by: Optional[InventoryHolder] = None,
price: int = 2, *args, **kwargs):
super().__init__(*args, **kwargs)
self.held = held
self.held_by = held_by
self.price = price
def drop(self) -> None:
"""
@ -28,7 +31,7 @@ class Item(Entity):
"""
if self.held:
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.held = False
self.held_by = None
@ -43,14 +46,14 @@ class Item(Entity):
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
"""
self.held = True
self.held_by = player
self.map.remove_entity(self)
player.inventory.append(self)
self.held_by.map.remove_entity(self)
player.add_to_inventory(self)
def save_state(self) -> dict:
"""
@ -60,6 +63,25 @@ class Item(Entity):
d["held"] = self.held
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):
"""
@ -67,16 +89,17 @@ class Heart(Item):
"""
healing: int
def __init__(self, name: str = "heart", healing: int = 5, *args, **kwargs):
super().__init__(name=name, *args, **kwargs)
def __init__(self, name: str = "heart", healing: int = 5, price: int = 3,
*args, **kwargs):
super().__init__(name=name, price=price, *args, **kwargs)
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.
"""
player.health = min(player.maxhealth, player.health + self.healing)
self.map.remove_entity(self)
entity.health = min(entity.maxhealth, entity.health + self.healing)
entity.map.remove_entity(self)
def save_state(self) -> dict:
"""
@ -97,8 +120,8 @@ class Bomb(Item):
tick: int
def __init__(self, name: str = "bomb", damage: int = 5,
exploding: bool = False, *args, **kwargs):
super().__init__(name=name, *args, **kwargs)
exploding: bool = False, price: int = 4, *args, **kwargs):
super().__init__(name=name, price=price, *args, **kwargs)
self.damage = damage
self.exploding = exploding
self.tick = 4
@ -135,6 +158,10 @@ class Bomb(Item):
m.logs.add_message(log_message)
m.entities.remove(self)
# Add sparkles where the bomb exploded.
explosion = Explosion(y=self.y, x=self.x)
self.map.add_entity(explosion)
def save_state(self) -> dict:
"""
Saves the state of the bomb into a dictionary
@ -145,14 +172,63 @@ class Bomb(Item):
return d
class Explosion(Item):
"""
When a bomb explodes, the explosion is displayed.
"""
def __init__(self, *args, **kwargs):
super().__init__(name="explosion", *args, **kwargs)
def act(self, m: Map) -> None:
"""
The explosion instant dies.
"""
m.remove_entity(self)
def hold(self, player: InventoryHolder) -> None:
"""
The player can't hold any explosion.
"""
pass
class Weapon(Item):
"""
Non-throwable items that improve player damage
"""
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):
"""
The body-snatch potion allows to exchange all characteristics with a random
other entity.
"""
def __init__(self, name: str = "body_snatch_potion", *args, **kwargs):
super().__init__(name=name, *args, **kwargs)
def __init__(self, name: str = "body_snatch_potion", price: int = 14,
*args, **kwargs):
super().__init__(name=name, price=price, *args, **kwargs)
def use(self) -> None:
"""

View File

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

View File

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

View File

@ -4,6 +4,7 @@
from json import JSONDecodeError
from random import randint
from typing import Any, Optional
import curses
import json
import os
import sys
@ -15,7 +16,6 @@ from .resources import ResourceManager
from .settings import Settings
from . import menus
from .translations import gettext as _, Translator
from typing import Callable
class Game:
@ -24,14 +24,16 @@ class Game:
"""
map: Map
player: Player
screen: Any
# display_actions is a display interface set by the bootstrapper
display_actions: Callable[[DisplayActions], None]
display_actions: callable
def __init__(self) -> None:
"""
Init the game.
"""
self.state = GameMode.MAINMENU
self.waiting_for_friendly_key = False
self.settings = Settings()
self.settings.load_settings()
self.settings.write_settings()
@ -40,6 +42,7 @@ class Game:
self.settings_menu = menus.SettingsMenu()
self.settings_menu.update_values(self.settings)
self.inventory_menu = menus.InventoryMenu()
self.store_menu = menus.StoreMenu()
self.logs = Logs()
self.message = None
@ -68,6 +71,10 @@ class Game:
screen.refresh()
self.display_actions(DisplayActions.REFRESH)
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(
KeyValues.translate_key(key, self.settings), key)
@ -83,6 +90,10 @@ class Game:
return
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)
elif self.state == GameMode.INVENTORY:
self.handle_key_pressed_inventory(key)
@ -90,6 +101,8 @@ class Game:
self.handle_key_pressed_main_menu(key)
elif self.state == GameMode.SETTINGS:
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)
def handle_key_pressed_play(self, key: KeyValues) -> None:
@ -112,6 +125,44 @@ class Game:
self.state = GameMode.INVENTORY
elif key == KeyValues.SPACE:
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:
"""
@ -136,6 +187,27 @@ class Game:
len(self.inventory_menu.values)
- 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:
"""
In the main menu, we can navigate through options.

View File

@ -4,7 +4,7 @@
from enum import Enum, auto
from math import sqrt
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 .translations import gettext as _
@ -93,6 +93,7 @@ class Map:
"""
Unregister an entity from the map.
"""
if entity in self.entities:
self.entities.remove(entity)
def find_entities(self, entity_class: type) -> list:
@ -101,12 +102,21 @@ class Map:
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 \
self.tiles[y][x].can_walk() and \
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
def load(filename: str) -> "Map":
"""
@ -152,7 +162,7 @@ class Map:
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):
y, x = 0, 0
@ -459,20 +469,34 @@ class Entity:
from squirrelbattle.entities.items import 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
def translated_name(self) -> str:
return _(self.name.replace("_", " "))
@staticmethod
def get_all_entity_classes():
def get_all_entity_classes() -> list:
"""
Returns all entities subclasses
"""
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart
from squirrelbattle.entities.monsters import Tiger, Hedgehog, \
Rabbit, TeddyBear
return [BodySnatchPotion, Bomb, Heart, Hedgehog,
Rabbit, TeddyBear, Tiger]
from squirrelbattle.entities.friendly import Merchant, Sunflower
return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear,
Sunflower, Tiger, Merchant]
@staticmethod
def get_all_entity_classes_in_a_dict() -> dict:
@ -482,7 +506,9 @@ class Entity:
from squirrelbattle.entities.player import Player
from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \
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 {
"Tiger": Tiger,
"Bomb": Bomb,
@ -492,6 +518,9 @@ class Entity:
"Rabbit": Rabbit,
"TeddyBear": TeddyBear,
"Player": Player,
"Merchant": Merchant,
"Sunflower": Sunflower,
"Sword": Sword,
}
def save_state(self) -> dict:
@ -567,7 +596,7 @@ class FightingEntity(Entity):
def keys(self) -> list:
"""
Returns a fighting entities specific attributes
Returns a fighting entity's specific attributes
"""
return ["name", "maxhealth", "health", "level", "strength",
"intelligence", "charisma", "dexterity", "constitution"]
@ -580,3 +609,74 @@ class FightingEntity(Entity):
for name in self.keys():
d[name] = getattr(self, name)
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
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao
# This file is distributed under the same license as the squirrelbattle package.
# FIRST AUTHOR <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-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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -15,31 +17,52 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: squirrelbattle/display/menudisplay.py:105
msgid "== INVENTORY =="
msgstr "== BESTAND =="
#: squirrelbattle/display/menudisplay.py:139
msgid "INVENTORY"
msgstr "BESTAND"
#: squirrelbattle/display/statsdisplay.py:34
#: squirrelbattle/display/menudisplay.py:164
msgid "STALL"
msgstr "STAND"
#: squirrelbattle/display/statsdisplay.py:33
msgid "Inventory:"
msgstr "Bestand:"
#: squirrelbattle/display/statsdisplay.py:50
#: squirrelbattle/display/statsdisplay.py:52
msgid "YOU ARE DEAD"
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.
#. Each entity that is close to the bomb takes damages.
#. The player earn XP if the entity was killed.
#: squirrelbattle/entities/items.py:128
#: squirrelbattle/entities/items.py:151
msgid "Bomb is exploding."
msgstr "Die Bombe explodiert."
#: squirrelbattle/entities/items.py:172
#: squirrelbattle/entities/items.py:248
#, python-brace-format
msgid "{player} exchanged its body with {entity}."
msgstr "{player} täuscht seinem Körper mit {entity} aus."
#: squirrelbattle/game.py: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 ""
"Some keys are missing in your save file.\n"
"Your save seems to be corrupt. It got deleted."
@ -47,7 +70,7 @@ msgstr ""
"In Ihrer Speicherdatei fehlen einige Schlüssel.\n"
"Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht."
#: squirrelbattle/game.py:185
#: squirrelbattle/game.py:257
msgid ""
"No player was found on this map!\n"
"Maybe you died?"
@ -55,7 +78,7 @@ msgstr ""
"Auf dieser Karte wurde kein Spieler gefunden!\n"
"Vielleicht sind Sie gestorben?"
#: squirrelbattle/game.py:205
#: squirrelbattle/game.py:277
msgid ""
"The JSON file is not correct.\n"
"Your save seems corrupted. It got deleted."
@ -63,27 +86,32 @@ msgstr ""
"Die JSON-Datei ist nicht korrekt.\n"
"Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht."
#: squirrelbattle/interfaces.py:400
#: squirrelbattle/interfaces.py:429
#, python-brace-format
msgid "{name} hits {opponent}."
msgstr "{name} schlägt {opponent}."
#: squirrelbattle/interfaces.py:412
#: squirrelbattle/interfaces.py:441
#, python-brace-format
msgid "{name} takes {amount} damage."
msgstr "{name} nimmt {amount} Schadenspunkte."
#: squirrelbattle/interfaces.py:414
#: squirrelbattle/interfaces.py:443
#, python-brace-format
msgid "{name} dies."
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"
msgstr "Zurück"
#: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303
#: squirrelbattle/tests/game_test.py:306
#: squirrelbattle/tests/game_test.py:344 squirrelbattle/tests/game_test.py:347
#: squirrelbattle/tests/game_test.py:350 squirrelbattle/tests/game_test.py:353
#: squirrelbattle/tests/translations_test.py:16
msgid "New game"
msgstr "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"
#: 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"
msgstr "Textur-Packung"
#: squirrelbattle/tests/translations_test.py:54
#: squirrelbattle/tests/translations_test.py:57
msgid "Language"
msgstr "Sprache"
#: squirrelbattle/tests/translations_test.py:57
#: squirrelbattle/tests/translations_test.py:60
msgid "player"
msgstr "Spieler"
#: squirrelbattle/tests/translations_test.py:59
msgid "tiger"
msgstr "Tiger"
#: squirrelbattle/tests/translations_test.py:60
#: squirrelbattle/tests/translations_test.py:62
msgid "hedgehog"
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"
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"
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"
msgstr "Leichenfleddererzaubertrank"
#: squirrelbattle/tests/translations_test.py:65
#: squirrelbattle/tests/translations_test.py:70
msgid "bomb"
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"
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
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao
# This file is distributed under the same license as the squirrelbattle package.
# Translation by ifugaao
# FIRST AUTHOR <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-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"
"Last-Translator: ifugao\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"
# Suggested in Weblate: == INVENTORIO ==
#: squirrelbattle/display/menudisplay.py:105
msgid "== INVENTORY =="
msgstr "== INVENTORIO =="
#: squirrelbattle/display/menudisplay.py:139
msgid "INVENTORY"
msgstr "INVENTORIO"
# Suggested in Weblate: Inventorio :
#: squirrelbattle/display/statsdisplay.py:34
#: squirrelbattle/display/menudisplay.py:164
msgid "STALL"
msgstr "PUESTO"
#: squirrelbattle/display/statsdisplay.py:33
msgid "Inventory:"
msgstr "Inventorio :"
# Suggested in Weblate: ERES MUERTO
#: squirrelbattle/display/statsdisplay.py:50
#: squirrelbattle/display/statsdisplay.py:52
msgid "YOU ARE DEAD"
msgstr "ERES MUERTO"
#: squirrelbattle/entities/friendly.py:33
msgid "I don't sell any squirrel"
msgstr "No vendo ninguna ardilla"
#: squirrelbattle/entities/friendly.py: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.
#. Each entity that is close to the bomb takes damages.
#. The player earn XP if the entity was killed.
#: squirrelbattle/entities/items.py:128
#: squirrelbattle/entities/items.py:151
msgid "Bomb is exploding."
msgstr "La bomba está explotando."
#: squirrelbattle/entities/items.py:172
#: squirrelbattle/entities/items.py:248
#, python-brace-format
msgid "{player} exchanged its body with {entity}."
msgstr "{player} intercambió su cuerpo con {entity}."
#: squirrelbattle/game.py:177
#: squirrelbattle/game.py:205 squirrelbattle/tests/game_test.py:573
msgid "You do not have enough money"
msgstr "No tienes suficiente dinero"
#: squirrelbattle/game.py:249
msgid ""
"Some keys are missing in your save file.\n"
"Your save seems to be corrupt. It got deleted."
@ -51,7 +69,7 @@ msgstr ""
"Algunas claves faltan en su archivo de guarda.\n"
"Su guarda parece a ser corruptido. Fue eliminado."
#: squirrelbattle/game.py:185
#: squirrelbattle/game.py:257
msgid ""
"No player was found on this map!\n"
"Maybe you died?"
@ -59,7 +77,7 @@ msgstr ""
"No jugador encontrado sobre la carta !\n"
"¿ Quizas murió ?"
#: squirrelbattle/game.py:205
#: squirrelbattle/game.py:277
msgid ""
"The JSON file is not correct.\n"
"Your save seems corrupted. It got deleted."
@ -67,28 +85,32 @@ msgstr ""
"El JSON archivo no es correcto.\n"
"Su guarda parece corrupta. Fue eliminada."
#: squirrelbattle/interfaces.py:400
#: squirrelbattle/interfaces.py:429
#, python-brace-format
msgid "{name} hits {opponent}."
msgstr "{name} golpea a {opponent}."
#: squirrelbattle/interfaces.py:412
#: squirrelbattle/interfaces.py:441
#, python-brace-format
msgid "{name} takes {amount} damage."
msgstr "{name} recibe {amount} daño."
#: squirrelbattle/interfaces.py:414
#: squirrelbattle/interfaces.py:443
#, python-brace-format
msgid "{name} dies."
msgstr "{name} se muere."
#: squirrelbattle/menus.py:72
#: squirrelbattle/interfaces.py:477
#, python-brace-format
msgid "{entity} said: {message}"
msgstr "{entity} dijo : {message}"
#: squirrelbattle/menus.py:73
msgid "Back"
msgstr "Volver"
#: squirrelbattle/tests/game_test.py:300,
#: squirrelbattle/tests/game_test.py:303,
#: squirrelbattle/tests/game_test.py:306,
#: squirrelbattle/tests/game_test.py:344 squirrelbattle/tests/game_test.py:347
#: squirrelbattle/tests/game_test.py:350 squirrelbattle/tests/game_test.py:353
#: squirrelbattle/tests/translations_test.py:16
msgid "New game"
msgstr "Nuevo partido"
@ -166,41 +188,65 @@ msgid "Key used to drop an item in the inventory"
msgstr "Tecla para dejar un objeto del inventorio"
#: squirrelbattle/tests/translations_test.py:53
msgid "Key used to talk to a friendly entity"
msgstr "Tecla para hablar con una entidad amiga"
#: squirrelbattle/tests/translations_test.py:55
msgid "Key used to wait"
msgstr "Tecla para espera"
#: squirrelbattle/tests/translations_test.py:56
msgid "Texture pack"
msgstr "Paquete de texturas"
#: squirrelbattle/tests/translations_test.py:54
#: squirrelbattle/tests/translations_test.py:57
msgid "Language"
msgstr "Languaje"
#: squirrelbattle/tests/translations_test.py:57
#: squirrelbattle/tests/translations_test.py:60
msgid "player"
msgstr "jugador"
#: squirrelbattle/tests/translations_test.py:59
msgid "tiger"
msgstr "tigre"
#: squirrelbattle/tests/translations_test.py:60
#: squirrelbattle/tests/translations_test.py:62
msgid "hedgehog"
msgstr "erizo"
#: squirrelbattle/tests/translations_test.py:61
#: squirrelbattle/tests/translations_test.py:63
msgid "merchant"
msgstr "comerciante"
#: squirrelbattle/tests/translations_test.py:64
msgid "rabbit"
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"
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"
msgstr "poción de intercambio"
#: squirrelbattle/tests/translations_test.py:65
#: squirrelbattle/tests/translations_test.py:70
msgid "bomb"
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"
msgstr "corazón"
#: squirrelbattle/tests/translations_test.py:73
msgid "sword"
msgstr "espada"

View File

@ -1,46 +1,68 @@
# French translation of Squirrel Battle
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao
# This file is distributed under the same license as the squirrelbattle package.
# FIRST AUTHOR <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-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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: fr\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: squirrelbattle/display/menudisplay.py:105
msgid "== INVENTORY =="
msgstr "== INVENTAIRE =="
#: squirrelbattle/display/menudisplay.py:139
msgid "INVENTORY"
msgstr "INVENTAIRE"
#: squirrelbattle/display/statsdisplay.py:34
#: squirrelbattle/display/menudisplay.py:164
msgid "STALL"
msgstr "STAND"
#: squirrelbattle/display/statsdisplay.py:33
msgid "Inventory:"
msgstr "Inventaire :"
#: squirrelbattle/display/statsdisplay.py:50
#: squirrelbattle/display/statsdisplay.py:52
msgid "YOU ARE DEAD"
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.
#. Each entity that is close to the bomb takes damages.
#. The player earn XP if the entity was killed.
#: squirrelbattle/entities/items.py:128
#: squirrelbattle/entities/items.py:151
msgid "Bomb is exploding."
msgstr "La bombe explose."
#: squirrelbattle/entities/items.py:172
#: squirrelbattle/entities/items.py:248
#, python-brace-format
msgid "{player} exchanged its body with {entity}."
msgstr "{player} a échangé son corps avec {entity}."
#: squirrelbattle/game.py: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 ""
"Some keys are missing in your save file.\n"
"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"
"Votre sauvegarde semble corrompue. Elle a été supprimée."
#: squirrelbattle/game.py:185
#: squirrelbattle/game.py:257
msgid ""
"No player was found on this map!\n"
"Maybe you died?"
@ -56,7 +78,7 @@ msgstr ""
"Aucun joueur n'a été trouvé sur la carte !\n"
"Peut-être êtes-vous mort ?"
#: squirrelbattle/game.py:205
#: squirrelbattle/game.py:277
msgid ""
"The JSON file is not correct.\n"
"Your save seems corrupted. It got deleted."
@ -64,27 +86,32 @@ msgstr ""
"Le fichier JSON de sauvegarde est incorrect.\n"
"Votre sauvegarde semble corrompue. Elle a été supprimée."
#: squirrelbattle/interfaces.py:400
#: squirrelbattle/interfaces.py:429
#, python-brace-format
msgid "{name} hits {opponent}."
msgstr "{name} frappe {opponent}."
#: squirrelbattle/interfaces.py:412
#: squirrelbattle/interfaces.py:441
#, python-brace-format
msgid "{name} takes {amount} damage."
msgstr "{name} prend {amount} points de dégât."
#: squirrelbattle/interfaces.py:414
#: squirrelbattle/interfaces.py:443
#, python-brace-format
msgid "{name} dies."
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"
msgstr "Retour"
#: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303
#: squirrelbattle/tests/game_test.py:306
#: squirrelbattle/tests/game_test.py:344 squirrelbattle/tests/game_test.py:347
#: squirrelbattle/tests/game_test.py:350 squirrelbattle/tests/game_test.py:353
#: squirrelbattle/tests/translations_test.py:16
msgid "New game"
msgstr "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"
#: 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"
msgstr "Pack de textures"
#: squirrelbattle/tests/translations_test.py:54
#: squirrelbattle/tests/translations_test.py:57
msgid "Language"
msgstr "Langue"
#: squirrelbattle/tests/translations_test.py:57
#: squirrelbattle/tests/translations_test.py:60
msgid "player"
msgstr "joueur"
#: squirrelbattle/tests/translations_test.py:59
msgid "tiger"
msgstr "tigre"
#: squirrelbattle/tests/translations_test.py:60
#: squirrelbattle/tests/translations_test.py:62
msgid "hedgehog"
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"
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"
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"
msgstr "potion d'arrachage de corps"
#: squirrelbattle/tests/translations_test.py:65
#: squirrelbattle/tests/translations_test.py:70
msgid "bomb"
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"
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 .entities.player import Player
from .entities.friendly import Merchant
from .enums import GameMode, KeyValues, DisplayActions
from .settings import Settings
from .translations import gettext as _, Translator
@ -128,3 +129,14 @@ class InventoryMenu(Menu):
@property
def values(self) -> list:
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_EQUIP = ['e', 'Key used to equip an item in the inventory']
self.KEY_DROP = ['r', 'Key used to drop an item in the inventory']
self.KEY_CHAT = ['t', 'Key used to talk to a friendly entity']
self.KEY_WAIT = ['w', 'Key used to wait']
self.TEXTURE_PACK = ['ascii', 'Texture pack']
self.LOCALE = [locale.getlocale()[0][:2], 'Language']

View File

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

View File

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

View File

@ -7,7 +7,8 @@ import unittest
from ..bootstrap import Bootstrap
from ..display.display import Display
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 ..enums import DisplayActions
from ..game import Game, KeyValues, GameMode
@ -34,7 +35,17 @@ class TestGame(unittest.TestCase):
"""
bomb = 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)
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()
self.game.handle_key_pressed(KeyValues.DOWN)
@ -117,6 +128,9 @@ class TestGame(unittest.TestCase):
self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_INVENTORY, self.game.settings),
KeyValues.INVENTORY)
self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_CHAT, self.game.settings),
KeyValues.CHAT)
self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_USE, self.game.settings),
KeyValues.USE)
@ -126,6 +140,9 @@ class TestGame(unittest.TestCase):
self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_DROP, self.game.settings),
KeyValues.DROP)
self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_WAIT, self.game.settings),
KeyValues.WAIT)
self.assertEqual(KeyValues.translate_key(' ', self.game.settings),
KeyValues.SPACE)
self.assertEqual(KeyValues.translate_key('plop', self.game.settings),
@ -213,9 +230,45 @@ class TestGame(unittest.TestCase):
self.assertEqual(new_y, y)
self.assertEqual(new_x, x - 1)
explosion = Explosion()
self.game.map.add_entity(explosion)
self.assertIn(explosion, self.game.map.entities)
self.game.handle_key_pressed(KeyValues.WAIT)
self.assertNotIn(explosion, self.game.map.entities)
self.game.handle_key_pressed(KeyValues.SPACE)
self.assertEqual(self.game.state, GameMode.MAINMENU)
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:
"""
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.assertEqual(self.game.state, GameMode.SETTINGS)
# Define the "move up" key to 'w'
# Define the "move up" key to 'h'
self.assertFalse(self.game.settings_menu.waiting_for_key)
self.game.handle_key_pressed(KeyValues.ENTER)
self.assertTrue(self.game.settings_menu.waiting_for_key)
self.game.handle_key_pressed(None, 'w')
self.game.handle_key_pressed(None, 'h')
self.assertFalse(self.game.settings_menu.waiting_for_key)
self.assertEqual(self.game.settings.KEY_UP_PRIMARY, 'w')
self.assertEqual(self.game.settings.KEY_UP_PRIMARY, 'h')
# Navigate to "move left"
self.game.handle_key_pressed(KeyValues.DOWN)
@ -280,7 +333,7 @@ class TestGame(unittest.TestCase):
self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a')
# Navigate to "texture pack"
for ignored in range(9):
for ignored in range(11):
self.game.handle_key_pressed(KeyValues.DOWN)
# Change texture pack
@ -417,3 +470,118 @@ class TestGame(unittest.TestCase):
self.assertTrue(bomb.exploding)
self.assertEqual(bomb.y, self.game.player.y)
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")
self.assertEqual(_("Key used to drop an item in the inventory"),
"Touche pour jeter un objet de l'inventaire")
self.assertEqual(_("Key used to talk to a friendly entity"),
"Touche pour parler à une entité pacifique")
self.assertEqual(_("Key used to wait"), "Touche pour attendre")
self.assertEqual(_("Texture pack"), "Pack de textures")
self.assertEqual(_("Language"), "Langue")
def test_entities_translation(self) -> None:
self.assertEqual(_("player"), "joueur")
self.assertEqual(_("tiger"), "tigre")
self.assertEqual(_("hedgehog"), "hérisson")
self.assertEqual(_("merchant"), "marchand")
self.assertEqual(_("rabbit"), "lapin")
self.assertEqual(_("sunflower"), "tournesol")
self.assertEqual(_("teddy bear"), "nounours")
self.assertEqual(_("tiger"), "tigre")
self.assertEqual(_("body snatch potion"), "potion d'arrachage de corps")
self.assertEqual(_("bomb"), "bombe")
self.assertEqual(_("explosion"), "explosion")
self.assertEqual(_("heart"), "cœur")
self.assertEqual(_("sword"), "épée")