Merging master into village, conflicts were solved

This commit is contained in:
eichhornchen 2020-12-06 11:43:48 +01:00
parent 38842cee68
commit 866af98fe4
20 changed files with 574 additions and 168 deletions

View File

@ -11,7 +11,7 @@ with open("README.md", "r") as f:
long_description = f.read()
# Compile messages
for language in ["de", "en", "fr"]:
for language in ["de", "fr"]:
args = ["msgfmt", "--check-format",
"-o", f"squirrelbattle/locale/{language}/LC_MESSAGES"
"/squirrelbattle.mo",

View File

@ -6,8 +6,8 @@ from squirrelbattle.display.display import VerticalSplit, HorizontalSplit
from squirrelbattle.display.mapdisplay import MapDisplay
from squirrelbattle.display.messagedisplay import MessageDisplay
from squirrelbattle.display.statsdisplay import StatsDisplay
from squirrelbattle.display.menudisplay import SettingsMenuDisplay, \
MainMenuDisplay
from squirrelbattle.display.menudisplay import MainMenuDisplay, \
InventoryDisplay, SettingsMenuDisplay
from squirrelbattle.display.logsdisplay import LogsDisplay
from squirrelbattle.display.texturepack import TexturePack
from typing import Any
@ -23,10 +23,11 @@ class DisplayManager:
pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK)
self.mapdisplay = MapDisplay(screen, pack)
self.statsdisplay = StatsDisplay(screen, pack)
self.logsdisplay = LogsDisplay(screen, pack)
self.inventorydisplay = InventoryDisplay(screen, pack)
self.mainmenudisplay = MainMenuDisplay(self.game.main_menu,
screen, pack)
self.settingsmenudisplay = SettingsMenuDisplay(screen, pack)
self.logsdisplay = LogsDisplay(screen, pack)
self.messagedisplay = MessageDisplay(screen=screen, pack=None)
self.hbar = HorizontalSplit(screen, pack)
self.vbar = VerticalSplit(screen, pack)
@ -46,12 +47,14 @@ class DisplayManager:
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.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:
if self.game.state == GameMode.PLAY:
if self.game.state == GameMode.PLAY \
or self.game.state == GameMode.INVENTORY:
# The map pad has already the good size
self.mapdisplay.refresh(0, 0, self.rows * 4 // 5,
self.mapdisplay.pack.tile_width
@ -64,10 +67,15 @@ 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)
if self.game.state == GameMode.MAINMENU:
if self.game.state == GameMode.INVENTORY:
self.inventorydisplay.refresh(self.rows // 10,
self.cols // 2,
8 * self.rows // 10,
2 * self.cols // 5)
elif self.game.state == GameMode.MAINMENU:
self.mainmenudisplay.refresh(0, 0, self.rows, self.cols)
if self.game.state == GameMode.SETTINGS:
self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols - 1)
elif self.game.state == GameMode.SETTINGS:
self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols)
if self.game.message:
height, width = 0, 0

View File

@ -1,6 +1,6 @@
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later
import curses
from typing import List
from squirrelbattle.menus import Menu, MainMenu
@ -24,8 +24,6 @@ class MenuDisplay(Display):
# Menu values are printed in pad
self.pad = self.newpad(self.trueheight, self.truewidth + 2)
for i in range(self.trueheight):
self.addstr(self.pad, i, 0, " " + self.values[i])
def update_pad(self) -> None:
for i in range(self.trueheight):
@ -110,12 +108,22 @@ class MainMenuDisplay(Display):
menuy, menux, min(self.menudisplay.preferred_height,
self.height - menuy), menuwidth)
class VariableMenuDisplay(MenuDisplay):
"""
A class to display a menu in which each value is associated to a parameter
"""
class InventoryDisplay(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)
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
+ " " + item.translated_name.capitalize())
@property
def values(self) -> List[str]:
return [a[1][1] + (" : "
+ (a[1][0])
if a[1][0] else "") for a in self.menu.values]
def truewidth(self) -> int:
return max(1, self.height if hasattr(self, "height") else 10)
@property
def trueheight(self) -> int:
return 2 + super().trueheight

View File

@ -31,8 +31,19 @@ class StatsDisplay(Display):
self.player.dexterity, self.player.constitution)
self.addstr(self.pad, 3, 0, string3)
inventory_str = _("Inventory:") + " " + "".join(
self.pack[item.name.upper()] for item in self.player.inventory)
inventory_str = _("Inventory:") + " "
# Stack items by type instead of displaying each item
item_types = [item.name for item in self.player.inventory]
item_types.sort(key=item_types.count, reverse=True)
printed_items = []
for item in item_types:
if item in printed_items:
continue
count = item_types.count(item)
inventory_str += self.pack[item.upper()]
if count > 1:
inventory_str += f"x{count} "
printed_items.append(item)
self.addstr(self.pad, 8, 0, inventory_str)
if self.player.dead:

View File

@ -58,6 +58,7 @@ TexturePack.ASCII_PACK = TexturePack(
TEDDY_BEAR='8',
MERCHANT='M',
SUNFLOWER='I',
BODY_SNATCH_POTION='S',
)
TexturePack.SQUIRREL_PACK = TexturePack(
@ -79,4 +80,5 @@ TexturePack.SQUIRREL_PACK = TexturePack(
TEDDY_BEAR='🧸',
MERCHANT='🦜',
SUNFLOWER='🌻',
BODY_SNATCH_POTION='🔀',
)

View File

@ -1,10 +1,12 @@
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later
from random import choice, randint
from typing import Optional
from .player import Player
from ..interfaces import Entity, FightingEntity, Map
from ..translations import gettext as _
class Item(Entity):
@ -20,16 +22,26 @@ class Item(Entity):
self.held = held
self.held_by = held_by
def drop(self, y: int, x: int) -> None:
def drop(self) -> None:
"""
The item is dropped from the inventory onto the floor
"""
if self.held:
self.held_by.inventory.remove(self)
self.map.add_entity(self)
self.move(self.held_by.y, self.held_by.x)
self.held = False
self.held_by = None
self.map.add_entity(self)
self.move(y, x)
def use(self) -> None:
"""
Indicates what should be done when the item is used.
"""
def equip(self) -> None:
"""
Indicates what should be done when the item is equipped.
"""
def hold(self, player: "Player") -> None:
"""
@ -55,8 +67,8 @@ class Heart(Item):
"""
healing: int
def __init__(self, healing: int = 5, *args, **kwargs):
super().__init__(name="heart", *args, **kwargs)
def __init__(self, name: str = "heart", healing: int = 5, *args, **kwargs):
super().__init__(name=name, *args, **kwargs)
self.healing = healing
def hold(self, player: "Player") -> None:
@ -81,26 +93,47 @@ class Bomb(Item):
"""
damage: int = 5
exploding: bool
owner: Optional["Player"]
tick: int
def __init__(self, damage: int = 5, exploding: bool = False,
*args, **kwargs):
super().__init__(name="bomb", *args, **kwargs)
def __init__(self, name: str = "bomb", damage: int = 5,
exploding: bool = False, *args, **kwargs):
super().__init__(name=name, *args, **kwargs)
self.damage = damage
self.exploding = exploding
self.tick = 4
self.owner = None
def drop(self, x: int, y: int) -> None:
super().drop(x, y)
self.exploding = True
def use(self) -> None:
"""
When the bomb is used, throw it and explodes it.
"""
if self.held:
self.owner = self.held_by
super().drop()
self.exploding = True
def act(self, m: Map) -> None:
"""
Special exploding action of the bomb
"""
if self.exploding:
for e in m.entities.copy():
if abs(e.x - self.x) + abs(e.y - self.y) <= 1 and \
isinstance(e, FightingEntity):
e.take_damage(self, self.damage)
if self.tick > 0:
# The bomb will explode in <tick> moves
self.tick -= 1
else:
# The bomb is exploding.
# Each entity that is close to the bomb takes damages.
# The player earn XP if the entity was killed.
log_message = _("Bomb is exploding.")
for e in m.entities.copy():
if abs(e.x - self.x) + abs(e.y - self.y) <= 3 and \
isinstance(e, FightingEntity):
log_message += " " + e.take_damage(self, self.damage)
if e.dead:
self.owner.add_xp(randint(3, 7))
m.logs.add_message(log_message)
m.entities.remove(self)
def save_state(self) -> dict:
"""
@ -137,3 +170,34 @@ class Sword(Weapon) :
super().__init__(*args, **kwargs)
self.name = "sword"
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 use(self) -> None:
"""
Find a valid random entity, then exchange characteristics.
"""
valid_entities = self.held_by.map.find_entities(FightingEntity)
valid_entities.remove(self.held_by)
entity = choice(valid_entities)
entity_state = entity.save_state()
player_state = self.held_by.save_state()
self.held_by.__dict__.update(entity_state)
entity.__dict__.update(player_state)
self.held_by.map.currenty, self.held_by.map.currentx = self.held_by.y,\
self.held_by.x
self.held_by.map.logs.add_message(
_("{player} exchanged its body with {entity}.").format(
player=self.held_by.translated_name.capitalize(),
entity=entity.translated_name))
self.held_by.recalculate_paths()
self.held_by.inventory.remove(self)

View File

@ -1,7 +1,7 @@
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later
from random import choice
from random import shuffle
from .player import Player
from ..interfaces import FightingEntity, Map
@ -49,9 +49,13 @@ class Monster(FightingEntity):
if not moved and self.distance_squared(target) <= 1:
self.map.logs.add_message(self.hit(target))
else:
for _ in range(100):
if choice([self.move_up, self.move_down,
self.move_left, self.move_right])():
# Move in a random direction
# If the direction is not available, try another one
moves = [self.move_up, self.move_down,
self.move_left, self.move_right]
shuffle(moves)
for move in moves:
if move():
break
@ -59,9 +63,9 @@ class Tiger(Monster):
"""
A tiger monster
"""
def __init__(self, strength: int = 2, maxhealth: int = 20,
*args, **kwargs) -> None:
super().__init__(name="tiger", strength=strength,
def __init__(self, name: str = "tiger", strength: int = 2,
maxhealth: int = 20, *args, **kwargs) -> None:
super().__init__(name=name, strength=strength,
maxhealth=maxhealth, *args, **kwargs)
@ -69,9 +73,9 @@ class Hedgehog(Monster):
"""
A really mean hedgehog monster
"""
def __init__(self, strength: int = 3, maxhealth: int = 10,
*args, **kwargs) -> None:
super().__init__(name="hedgehog", strength=strength,
def __init__(self, name: str = "hedgehog", strength: int = 3,
maxhealth: int = 10, *args, **kwargs) -> None:
super().__init__(name=name, strength=strength,
maxhealth=maxhealth, *args, **kwargs)
@ -79,9 +83,9 @@ class Rabbit(Monster):
"""
A rabbit monster
"""
def __init__(self, strength: int = 1, maxhealth: int = 15,
*args, **kwargs) -> None:
super().__init__(name="rabbit", strength=strength,
def __init__(self, name: str = "rabbit", strength: int = 1,
maxhealth: int = 15, *args, **kwargs) -> None:
super().__init__(name=name, strength=strength,
maxhealth=maxhealth, *args, **kwargs)
@ -89,7 +93,7 @@ class TeddyBear(Monster):
"""
A cute teddybear monster
"""
def __init__(self, strength: int = 0, maxhealth: int = 50,
*args, **kwargs) -> None:
super().__init__(name="teddy_bear", strength=strength,
def __init__(self, name: str = "teddy_bear", strength: int = 0,
maxhealth: int = 50, *args, **kwargs) -> None:
super().__init__(name=name, strength=strength,
maxhealth=maxhealth, *args, **kwargs)

View File

@ -17,17 +17,24 @@ class Player(FightingEntity):
paths: Dict[Tuple[int, int], Tuple[int, int]]
hazel: int #It is the currency of this game
def __init__(self, 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, hazel: int = 42, *args, **kwargs) -> None:
super().__init__(name="player", maxhealth=maxhealth, strength=strength,
def __init__(self, name: str = "player", maxhealth: int = 20,
strength: int = 5, intelligence: int = 1, charisma: int = 1,
dexterity: int = 1, constitution: int = 1, level: int = 1,
current_xp: int = 0, max_xp: int = 10, inventory: list = None,
hazel: int = 42, *args, **kwargs) \
-> None:
super().__init__(name=name, maxhealth=maxhealth, strength=strength,
intelligence=intelligence, charisma=charisma,
dexterity=dexterity, constitution=constitution,
level=level, *args, **kwargs)
self.current_xp = current_xp
self.max_xp = max_xp
self.inventory = list()
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.paths = dict()
self.hazel = hazel
@ -130,4 +137,5 @@ 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

@ -38,6 +38,10 @@ class KeyValues(Enum):
LEFT = auto()
RIGHT = auto()
ENTER = auto()
INVENTORY = auto()
USE = auto()
EQUIP = auto()
DROP = auto()
SPACE = auto()
T = auto()
@ -60,6 +64,14 @@ class KeyValues(Enum):
return KeyValues.UP
elif key == settings.KEY_ENTER:
return KeyValues.ENTER
elif key == settings.KEY_INVENTORY:
return KeyValues.INVENTORY
elif key == settings.KEY_USE:
return KeyValues.USE
elif key == settings.KEY_EQUIP:
return KeyValues.EQUIP
elif key == settings.KEY_DROP:
return KeyValues.DROP
elif key == ' ':
return KeyValues.SPACE
elif key == 't':

View File

@ -40,6 +40,7 @@ class Game:
self.main_menu = menus.MainMenu()
self.settings_menu = menus.SettingsMenu()
self.settings_menu.update_values(self.settings)
self.inventory_menu = menus.InventoryMenu()
self.logs = Logs()
self.message = None
@ -48,13 +49,14 @@ class Game:
Create a new game on the screen.
"""
# TODO generate a new map procedurally
self.map = Map.load(ResourceManager.get_asset_path("example_map_2.txt"))
self.map = Map.load(ResourceManager.get_asset_path("example_map.txt"))
self.map.logs = self.logs
self.logs.clear()
self.player = Player()
self.map.add_entity(self.player)
self.player.move(self.map.start_y, self.map.start_x)
self.map.spawn_random_entities(randint(3, 10))
self.inventory_menu.update_player(self.player)
def run(self, screen: Any) -> None:
"""
@ -84,6 +86,8 @@ class Game:
if self.state == GameMode.PLAY:
self.handle_key_pressed_play(key)
elif self.state == GameMode.INVENTORY:
self.handle_key_pressed_inventory(key)
elif self.state == GameMode.MAINMENU:
self.handle_key_pressed_main_menu(key)
elif self.state == GameMode.SETTINGS:
@ -106,6 +110,8 @@ class Game:
elif key == KeyValues.RIGHT:
if self.player.move_right():
self.map.tick()
elif key == KeyValues.INVENTORY:
self.state = GameMode.INVENTORY
elif key == KeyValues.SPACE:
self.state = GameMode.MAINMENU
elif key == KeyValues.T :
@ -134,6 +140,29 @@ class Game:
self.state = GameMode.STORE
def handle_key_pressed_inventory(self, key: KeyValues) -> None:
"""
In the inventory menu, we can interact with items or close the menu.
"""
if key == KeyValues.SPACE or key == KeyValues.INVENTORY:
self.state = GameMode.PLAY
elif key == KeyValues.UP:
self.inventory_menu.go_up()
elif key == KeyValues.DOWN:
self.inventory_menu.go_down()
if self.inventory_menu.values and not self.player.dead:
if key == KeyValues.USE:
self.inventory_menu.validate().use()
elif key == KeyValues.EQUIP:
self.inventory_menu.validate().equip()
elif key == KeyValues.DROP:
self.inventory_menu.validate().drop()
# Ensure that the cursor has a good position
self.inventory_menu.position = min(self.inventory_menu.position,
len(self.inventory_menu.values)
- 1)
def handle_key_pressed_main_menu(self, key: KeyValues) -> None:
"""
In the main menu, we can navigate through options.

View File

@ -344,11 +344,11 @@ class Entity:
"""
Returns all entities subclasses
"""
from squirrelbattle.entities.items import Heart, Bomb
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart
from squirrelbattle.entities.monsters import Tiger, Hedgehog, \
Rabbit, TeddyBear
from squirrelbattle.entities.friendly import Merchant,Sunflower
return [Tiger, Bomb, Heart, Hedgehog, Rabbit, TeddyBear,Sunflower]
return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear,Sunflower,Tiger]
@staticmethod
def get_all_entity_classes_in_a_dict() -> dict:
@ -358,12 +358,13 @@ class Entity:
from squirrelbattle.entities.player import Player
from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \
TeddyBear
from squirrelbattle.entities.items import Bomb, Heart
from squirrelbattle.entities.friendly import Merchant,Sunflower
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart
return {
"Tiger": Tiger,
"Bomb": Bomb,
"Heart": Heart,
"BodySnatchPotion": BodySnatchPotion,
"Hedgehog": Hedgehog,
"Rabbit": Rabbit,
"TeddyBear": TeddyBear,
@ -447,7 +448,7 @@ class FightingEntity(Entity):
"""
Returns a fighting entity's specific attributes
"""
return ["maxhealth", "health", "level", "strength",
return ["name", "maxhealth", "health", "level", "strength",
"intelligence", "charisma", "dexterity", "constitution"]
def save_state(self) -> dict:

View File

@ -8,7 +8,11 @@ msgid ""
msgstr ""
"Project-Id-Version: squirrelbattle 3.14.1\n"
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
<<<<<<< HEAD
"POT-Creation-Date: 2020-12-01 17:10+0100\n"
=======
"POT-Creation-Date: 2020-12-05 14:46+0100\n"
>>>>>>> master
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,7 +21,75 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287
#: squirrelbattle/display/menudisplay.py:105
msgid "== INVENTORY =="
msgstr "== BESTAND =="
#: squirrelbattle/display/statsdisplay.py:34
msgid "Inventory:"
msgstr "Bestand:"
#: squirrelbattle/display/statsdisplay.py:50
msgid "YOU ARE DEAD"
msgstr "SIE WURDEN GESTORBEN"
#. 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
msgid "Bomb is exploding."
msgstr "Die Bombe explodiert."
#: squirrelbattle/entities/items.py:172
#, python-brace-format
msgid "{player} exchanged its body with {entity}."
msgstr "{player} täuscht seinem Körper mit {entity} aus."
#: squirrelbattle/game.py:177
msgid ""
"Some keys are missing in your save file.\n"
"Your save seems to be corrupt. It got deleted."
msgstr ""
"In Ihrer Speicherdatei fehlen einige Schlüssel.\n"
"Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht."
#: squirrelbattle/game.py:185
msgid ""
"No player was found on this map!\n"
"Maybe you died?"
msgstr ""
"Auf dieser Karte wurde kein Spieler gefunden!\n"
"Vielleicht sind Sie gestorben?"
#: squirrelbattle/game.py:205
msgid ""
"The JSON file is not correct.\n"
"Your save seems corrupted. It got deleted."
msgstr ""
"Die JSON-Datei ist nicht korrekt.\n"
"Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht."
#: squirrelbattle/interfaces.py:400
#, python-brace-format
msgid "{name} hits {opponent}."
msgstr "{name} schlägt {opponent}."
#: squirrelbattle/interfaces.py:412
#, python-brace-format
msgid "{name} takes {amount} damage."
msgstr "{name} nimmt {amount} Schadenspunkte."
#: squirrelbattle/interfaces.py:414
#, python-brace-format
msgid "{name} dies."
msgstr "{name} stirbt."
#: squirrelbattle/menus.py:72
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/translations_test.py:16
#: squirrelbattle/tests/game_test.py:290
msgid "New game"
@ -80,40 +152,61 @@ msgid "Key to validate a menu"
msgstr "Menütaste"
#: squirrelbattle/tests/translations_test.py:45
msgid "Key used to open the inventory"
msgstr "Bestandtaste"
#: squirrelbattle/tests/translations_test.py:47
msgid "Key used to use an item in the inventory"
msgstr "Taste um eines Objekts im Bestand zu verwenden"
#: squirrelbattle/tests/translations_test.py:49
msgid "Key used to equip an item in the inventory"
msgstr "Taste um eines Objekts im Bestand auszurüsten"
#: squirrelbattle/tests/translations_test.py:51
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 "Texture pack"
msgstr "Textur-Packung"
#: squirrelbattle/tests/translations_test.py:46
#: squirrelbattle/tests/translations_test.py:54
msgid "Language"
msgstr "Sprache"
#: squirrelbattle/tests/translations_test.py:49
#: squirrelbattle/tests/translations_test.py:57
msgid "player"
msgstr "Spieler"
#: squirrelbattle/tests/translations_test.py:51
#: squirrelbattle/tests/translations_test.py:59
msgid "tiger"
msgstr "Tiger"
#: squirrelbattle/tests/translations_test.py:52
#: squirrelbattle/tests/translations_test.py:60
msgid "hedgehog"
msgstr "Igel"
#: squirrelbattle/tests/translations_test.py:53
#: squirrelbattle/tests/translations_test.py:61
msgid "rabbit"
msgstr "Kanninchen"
#: squirrelbattle/tests/translations_test.py:54
#: squirrelbattle/tests/translations_test.py:62
msgid "teddy bear"
msgstr "Teddybär"
#: squirrelbattle/tests/translations_test.py:56
#: squirrelbattle/tests/translations_test.py:64
msgid "body snatch potion"
msgstr "Leichenfleddererzaubertrank"
#: squirrelbattle/tests/translations_test.py:65
msgid "bomb"
msgstr "Bombe"
#: squirrelbattle/tests/translations_test.py:57
#: squirrelbattle/tests/translations_test.py:66
msgid "heart"
msgstr "Herz"
<<<<<<< HEAD
#: squirrelbattle/display/statsdisplay.py:34
msgid "Inventory:"
@ -173,3 +266,5 @@ msgstr "Blumenmacht!!"
#: squirrelbattle/entities/friendly.py:31
msgid "The sun is warm today"
msgstr "Die Sonne ist warm heute"
=======
>>>>>>> master

View File

@ -8,7 +8,11 @@ msgid ""
msgstr ""
"Project-Id-Version: squirrelbattle 3.14.1\n"
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
<<<<<<< HEAD
"POT-Creation-Date: 2020-12-01 17:10+0100\n"
=======
"POT-Creation-Date: 2020-12-05 14:46+0100\n"
>>>>>>> master
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,63 +21,35 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: squirrelbattle/display/menudisplay.py:105
msgid "== INVENTORY =="
msgstr "== INVENTAIRE =="
#: squirrelbattle/display/statsdisplay.py:34
msgid "Inventory:"
msgstr "Inventaire :"
#: squirrelbattle/display/statsdisplay.py:39
#: squirrelbattle/display/statsdisplay.py:50
msgid "YOU ARE DEAD"
msgstr "VOUS ÊTES MORT"
<<<<<<< HEAD
#: squirrelbattle/interfaces.py:394 squirrelbattle/interfaces.py:398
#: squirrelbattle/interfaces.py:408
=======
#. 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
msgid "Bomb is exploding."
msgstr "La bombe explose."
#: squirrelbattle/entities/items.py:172
#, python-brace-format
msgid "{name} hits {opponent}."
msgstr "{name} frappe {opponent}."
msgid "{player} exchanged its body with {entity}."
msgstr "{player} a échangé son corps avec {entity}."
#: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410
#: squirrelbattle/interfaces.py:420
#, python-brace-format
msgid "{name} takes {amount} damage."
msgstr "{name} prend {amount} points de dégât."
#: 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 "Nouvelle partie"
#: squirrelbattle/menus.py:46 squirrelbattle/tests/translations_test.py:15
#: squirrelbattle/tests/translations_test.py:17
msgid "Resume"
msgstr "Continuer"
#: squirrelbattle/menus.py:47 squirrelbattle/tests/translations_test.py:17
#: squirrelbattle/tests/translations_test.py:19
msgid "Save"
msgstr "Sauvegarder"
#: squirrelbattle/menus.py:48 squirrelbattle/tests/translations_test.py:16
#: squirrelbattle/tests/translations_test.py:18
msgid "Load"
msgstr "Charger"
#: squirrelbattle/menus.py:49 squirrelbattle/tests/translations_test.py:18
#: squirrelbattle/tests/translations_test.py:20
msgid "Settings"
msgstr "Paramètres"
#: squirrelbattle/menus.py:50 squirrelbattle/tests/translations_test.py:19
#: squirrelbattle/tests/translations_test.py:21
msgid "Exit"
msgstr "Quitter"
#: squirrelbattle/menus.py:71
msgid "Back"
msgstr "Retour"
#: squirrelbattle/game.py:147 squirrelbattle/game.py:148
#: squirrelbattle/game.py:177
msgid ""
"Some keys are missing in your save file.\n"
"Your save seems to be corrupt. It got deleted."
@ -81,7 +57,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:155 squirrelbattle/game.py:156
#: squirrelbattle/game.py:185
msgid ""
"No player was found on this map!\n"
"Maybe you died?"
@ -89,7 +65,7 @@ msgstr ""
"Aucun joueur n'a été trouvé sur la carte !\n"
"Peut-être êtes-vous mort ?"
#: squirrelbattle/game.py:175 squirrelbattle/game.py:176
#: squirrelbattle/game.py:205
msgid ""
"The JSON file is not correct.\n"
"Your save seems corrupted. It got deleted."
@ -97,72 +73,119 @@ msgstr ""
"Le fichier JSON de sauvegarde est incorrect.\n"
"Votre sauvegarde semble corrompue. Elle a été supprimée."
#: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:21
#: squirrelbattle/tests/translations_test.py:25
#: squirrelbattle/interfaces.py:400
>>>>>>> master
#, python-brace-format
msgid "{name} hits {opponent}."
msgstr "{name} frappe {opponent}."
<<<<<<< HEAD
#: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410
#: squirrelbattle/interfaces.py:420
=======
#: squirrelbattle/interfaces.py:412
>>>>>>> master
#, python-brace-format
msgid "{name} takes {amount} damage."
msgstr "{name} prend {amount} points de dégât."
#: squirrelbattle/interfaces.py:414
#, python-brace-format
msgid "{name} dies."
msgstr "{name} meurt."
#: squirrelbattle/menus.py:72
msgid "Back"
msgstr "Retour"
#: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303
#: squirrelbattle/tests/game_test.py:306
#: squirrelbattle/tests/translations_test.py:16
#: squirrelbattle/tests/game_test.py:290
msgid "New game"
msgstr "Nouvelle partie"
#: squirrelbattle/tests/translations_test.py:17
msgid "Resume"
msgstr "Continuer"
#: squirrelbattle/tests/translations_test.py:18
msgid "Load"
msgstr "Charger"
#: squirrelbattle/tests/translations_test.py:19
msgid "Save"
msgstr "Sauvegarder"
#: squirrelbattle/tests/translations_test.py:20
msgid "Settings"
msgstr "Paramètres"
#: squirrelbattle/tests/translations_test.py:21
msgid "Exit"
msgstr "Quitter"
#: squirrelbattle/tests/translations_test.py:27
msgid "Main key to move up"
msgstr "Touche principale pour aller vers le haut"
#: 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 "Touche secondaire pour aller vers le haut"
#: 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 "Touche principale pour aller vers le bas"
#: 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 "Touche secondaire pour aller vers le bas"
#: 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 "Touche principale pour aller vers la gauche"
#: 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 "Touche secondaire pour aller vers la gauche"
#: 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 "Touche principale pour aller vers la droite"
#: 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 "Touche secondaire pour aller vers la droite"
#: 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 "Touche pour valider un menu"
#: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:39
#: squirrelbattle/tests/translations_test.py:43
#: squirrelbattle/tests/translations_test.py:45
msgid "Key used to open the inventory"
msgstr "Touche utilisée pour ouvrir l'inventaire"
#: squirrelbattle/tests/translations_test.py:47
msgid "Key used to use an item in the inventory"
msgstr "Touche pour utiliser un objet de l'inventaire"
#: squirrelbattle/tests/translations_test.py:49
msgid "Key used to equip an item in the inventory"
msgstr "Touche pour équiper un objet de l'inventaire"
#: squirrelbattle/tests/translations_test.py:51
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 "Texture pack"
msgstr "Pack de textures"
#: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40
#: squirrelbattle/tests/translations_test.py:44
#: squirrelbattle/tests/translations_test.py:46
#: squirrelbattle/tests/translations_test.py:54
msgid "Language"
msgstr "Langue"
<<<<<<< HEAD
#: squirrelbattle/interfaces.py:407 squirrelbattle/interfaces.py:412
#: squirrelbattle/interfaces.py:422
#, python-brace-format
@ -171,36 +194,37 @@ msgstr "{name} meurt."
#: squirrelbattle/tests/translations_test.py:47
#: squirrelbattle/tests/translations_test.py:49
=======
#: squirrelbattle/tests/translations_test.py:57
>>>>>>> master
msgid "player"
msgstr "joueur"
#: squirrelbattle/tests/translations_test.py:49
#: squirrelbattle/tests/translations_test.py:51
#: squirrelbattle/tests/translations_test.py:59
msgid "tiger"
msgstr "tigre"
#: squirrelbattle/tests/translations_test.py:50
#: squirrelbattle/tests/translations_test.py:52
#: squirrelbattle/tests/translations_test.py:60
msgid "hedgehog"
msgstr "hérisson"
#: squirrelbattle/tests/translations_test.py:51
#: squirrelbattle/tests/translations_test.py:53
#: squirrelbattle/tests/translations_test.py:61
msgid "rabbit"
msgstr "lapin"
#: squirrelbattle/tests/translations_test.py:52
#: squirrelbattle/tests/translations_test.py:54
#: squirrelbattle/tests/translations_test.py:62
msgid "teddy bear"
msgstr "nounours"
#: squirrelbattle/tests/translations_test.py:54
#: squirrelbattle/tests/translations_test.py:56
#: squirrelbattle/tests/translations_test.py:64
msgid "body snatch potion"
msgstr "potion d'arrachage de corps"
#: squirrelbattle/tests/translations_test.py:65
msgid "bomb"
msgstr "bombe"
#: squirrelbattle/tests/translations_test.py:55
#: squirrelbattle/tests/translations_test.py:57
#: squirrelbattle/tests/translations_test.py:66
msgid "heart"
msgstr "cœur"

View File

@ -5,6 +5,7 @@ from enum import Enum
from typing import Any, Optional
from .display.texturepack import TexturePack
from .entities.player import Player
from .enums import GameMode, KeyValues, DisplayActions
from .settings import Settings
from .translations import gettext as _, Translator
@ -115,3 +116,14 @@ class SettingsMenu(Menu):
game.settings.write_settings()
self.waiting_for_key = False
self.update_values(game.settings)
class InventoryMenu(Menu):
player: Player
def update_player(self, player: Player) -> None:
self.player = player
@property
def values(self) -> list:
return self.player.inventory

View File

@ -27,6 +27,10 @@ class Settings:
self.KEY_RIGHT_PRIMARY = ['d', 'Main key to move right']
self.KEY_RIGHT_SECONDARY = ['KEY_RIGHT', 'Secondary key to move right']
self.KEY_ENTER = ['\n', 'Key to validate a menu']
self.KEY_INVENTORY = ['i', 'Key used to open 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_DROP = ['r', 'Key used to drop an item in the inventory']
self.TEXTURE_PACK = ['ascii', 'Texture pack']
self.LOCALE = [locale.getlocale()[0][:2], 'Language']

View File

@ -3,7 +3,7 @@
import unittest
from squirrelbattle.entities.items import Bomb, Heart, Item
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart, Item
from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, TeddyBear
from squirrelbattle.entities.player import Player
from squirrelbattle.interfaces import Entity, Map
@ -97,12 +97,13 @@ class TestEntities(unittest.TestCase):
self.assertFalse(item.held)
item.hold(self.player)
self.assertTrue(item.held)
item.drop(2, 6)
self.assertEqual(item.y, 2)
item.drop()
self.assertEqual(item.y, 1)
self.assertEqual(item.x, 6)
# Pick up item
self.player.move_down()
self.player.move_left()
self.player.move_right()
self.assertTrue(item.held)
self.assertEqual(item.held_by, self.player)
self.assertIn(item, self.player.inventory)
@ -125,10 +126,14 @@ class TestEntities(unittest.TestCase):
item.act(self.map)
self.assertFalse(hedgehog.dead)
self.assertFalse(teddy_bear.dead)
item.drop(42, 42)
self.player.move(42, 42)
item.hold(self.player)
item.use()
self.assertEqual(item.y, 42)
self.assertEqual(item.x, 42)
item.act(self.map)
# Wait for the explosion
for ignored in range(5):
item.act(self.map)
self.assertTrue(hedgehog.dead)
self.assertTrue(teddy_bear.dead)
bomb_state = item.save_state()
@ -149,6 +154,24 @@ class TestEntities(unittest.TestCase):
heart_state = item.save_state()
self.assertEqual(heart_state["healing"], item.healing)
def test_body_snatch_potion(self) -> None:
"""
Test some random stuff with body snatch potions.
"""
item = BodySnatchPotion()
self.map.add_entity(item)
item.hold(self.player)
tiger = Tiger(y=42, x=42)
self.map.add_entity(tiger)
# The player becomes a tiger, and the tiger becomes a squirrel
item.use()
self.assertEqual(self.player.name, "tiger")
self.assertEqual(tiger.name, "player")
self.assertEqual(self.player.y, 42)
self.assertEqual(self.player.x, 42)
def test_players(self) -> None:
"""
Test some random stuff with players.

View File

@ -7,6 +7,7 @@ import unittest
from ..bootstrap import Bootstrap
from ..display.display import Display
from ..display.display_manager import DisplayManager
from ..entities.items import Bomb
from ..entities.player import Player
from ..enums import DisplayActions
from ..game import Game, KeyValues, GameMode
@ -31,6 +32,9 @@ class TestGame(unittest.TestCase):
"""
Save a game and reload it.
"""
bomb = Bomb()
self.game.map.add_entity(bomb)
bomb.hold(self.game.player)
old_state = self.game.save_state()
self.game.handle_key_pressed(KeyValues.DOWN)
@ -44,6 +48,9 @@ class TestGame(unittest.TestCase):
new_state = self.game.save_state()
self.assertEqual(old_state, new_state)
# Ensure that the bomb is loaded
self.assertTrue(self.game.player.inventory)
# Error on loading save
with open(ResourceManager.get_config_path("save.json"), "w") as f:
f.write("I am not a JSON file")
@ -107,6 +114,18 @@ class TestGame(unittest.TestCase):
self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_ENTER, self.game.settings),
KeyValues.ENTER)
self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_INVENTORY, self.game.settings),
KeyValues.INVENTORY)
self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_USE, self.game.settings),
KeyValues.USE)
self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_EQUIP, self.game.settings),
KeyValues.EQUIP)
self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_DROP, self.game.settings),
KeyValues.DROP)
self.assertEqual(KeyValues.translate_key(' ', self.game.settings),
KeyValues.SPACE)
self.assertEqual(KeyValues.translate_key('plop', self.game.settings),
@ -261,11 +280,8 @@ class TestGame(unittest.TestCase):
self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a')
# Navigate to "texture pack"
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.DOWN)
for ignored in range(9):
self.game.handle_key_pressed(KeyValues.DOWN)
# Change texture pack
self.assertEqual(self.game.settings.TEXTURE_PACK, "ascii")
@ -337,3 +353,64 @@ class TestGame(unittest.TestCase):
self.game.display_actions(DisplayActions.REFRESH)
self.game.handle_key_pressed(None, "random key")
self.assertIsNone(self.game.message)
def test_inventory_menu(self) -> None:
"""
Open the inventory menu and interact with items.
"""
self.game.state = GameMode.PLAY
# Open and close the inventory
self.game.handle_key_pressed(KeyValues.INVENTORY)
self.assertEqual(self.game.state, GameMode.INVENTORY)
self.game.handle_key_pressed(KeyValues.SPACE)
self.assertEqual(self.game.state, GameMode.PLAY)
# Add five bombs in the inventory
for ignored in range(5):
bomb = Bomb()
bomb.map = self.game.map
bomb.map.add_entity(bomb)
bomb.hold(self.game.player)
self.game.handle_key_pressed(KeyValues.INVENTORY)
self.assertEqual(self.game.state, GameMode.INVENTORY)
# Navigate in the menu
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.DOWN)
self.assertEqual(self.game.inventory_menu.position, 3)
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.UP)
self.game.handle_key_pressed(KeyValues.DOWN)
self.assertEqual(self.game.inventory_menu.position, 4)
# Equip key does nothing
self.game.handle_key_pressed(KeyValues.EQUIP)
# Drop an item
bomb = self.game.player.inventory[-1]
self.assertEqual(self.game.inventory_menu.validate(), bomb)
self.assertTrue(bomb.held)
self.assertEqual(bomb.held_by, self.game.player)
self.game.handle_key_pressed(KeyValues.DROP)
self.assertFalse(bomb.held)
self.assertIsNone(bomb.held_by)
self.assertIsNone(bomb.owner)
self.assertFalse(bomb.exploding)
self.assertEqual(bomb.y, self.game.player.y)
self.assertEqual(bomb.x, self.game.player.x)
# Use the bomb
bomb = self.game.player.inventory[-1]
self.assertEqual(self.game.inventory_menu.validate(), bomb)
self.assertTrue(bomb.held)
self.assertEqual(bomb.held_by, self.game.player)
self.game.handle_key_pressed(KeyValues.USE)
self.assertFalse(bomb.held)
self.assertIsNone(bomb.held_by)
self.assertEqual(bomb.owner, self.game.player)
self.assertTrue(bomb.exploding)
self.assertEqual(bomb.y, self.game.player.y)
self.assertEqual(bomb.x, self.game.player.x)

View File

@ -4,9 +4,13 @@
import unittest
from squirrelbattle.settings import Settings
from squirrelbattle.translations import Translator
class TestSettings(unittest.TestCase):
def setUp(self) -> None:
Translator.setlocale("en")
def test_settings(self) -> None:
"""
Ensure that settings are well loaded.

View File

@ -42,6 +42,14 @@ class TestTranslations(unittest.TestCase):
"Touche secondaire pour aller vers la droite")
self.assertEqual(_("Key to validate a menu"),
"Touche pour valider un menu")
self.assertEqual(_("Key used to open the inventory"),
"Touche utilisée pour ouvrir l'inventaire")
self.assertEqual(_("Key used to use an item in the inventory"),
"Touche pour utiliser un objet de l'inventaire")
self.assertEqual(_("Key used to equip an item in the inventory"),
"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(_("Texture pack"), "Pack de textures")
self.assertEqual(_("Language"), "Langue")
@ -53,5 +61,6 @@ class TestTranslations(unittest.TestCase):
self.assertEqual(_("rabbit"), "lapin")
self.assertEqual(_("teddy bear"), "nounours")
self.assertEqual(_("body snatch potion"), "potion d'arrachage de corps")
self.assertEqual(_("bomb"), "bombe")
self.assertEqual(_("heart"), "cœur")

View File

@ -3,6 +3,7 @@
import gettext as gt
import os
import re
import subprocess
from pathlib import Path
from typing import Any, List
@ -53,6 +54,9 @@ class Translator:
Analyse all strings in the project and extract them.
"""
for language in cls.SUPPORTED_LOCALES:
if language == "en":
# Don't translate the main language
continue
file_name = Path(__file__).parent / "locale" / language \
/ "LC_MESSAGES" / "squirrelbattle.po"
args = ["find", "squirrelbattle", "-iname", "*.py"]
@ -65,9 +69,14 @@ class Translator:
"--copyright-holder=ÿnérant, eichhornchen, "
"nicomarg, charlse",
"--msgid-bugs-address=squirrel-battle@crans.org",
"--sort-by-file",
"-o", file_name]
if file_name.is_file():
args.append("--join-existing")
with open(file_name, "r") as f:
content = f.read()
with open(file_name, "w") as f:
f.write(re.sub("#:.*\n", "", content))
print(f"Make {language} messages...")
subprocess.Popen(args, stdin=find.stdout).wait()
@ -77,6 +86,8 @@ class Translator:
Compile translation messages from source files.
"""
for language in cls.SUPPORTED_LOCALES:
if language == "en":
continue
args = ["msgfmt", "--check-format",
"-o", Path(__file__).parent / "locale" / language
/ "LC_MESSAGES" / "squirrelbattle.mo",