Compare commits

...

33 Commits

Author SHA1 Message Date
ynerant 4c274bebb9 Merge branch 'md' into 'master'
Fix Readme

See merge request ynerant/squirrel-battle!82
2021-01-25 15:04:47 +01:00
ynerant a28909bb70 Fix Readme 2021-01-25 15:04:47 +01:00
ynerant 6566f5140a Merge branch 'fix-ladders' into 'master'
Fix ladders

Closes #81

See merge request ynerant/squirrel-battle!81
2021-01-25 14:38:58 +01:00
Yohann D'ANELLO 776f8ed88c
Fixes #81 2021-01-25 14:31:37 +01:00
ynerant 284a22c92e Merge branch 'fix-yay' into 'master'
Don't create an english translation file

See merge request ynerant/squirrel-battle!80
2021-01-21 01:14:17 +01:00
Yohann D'ANELLO 3d019d3ca8
Don't create an english translation file 2021-01-21 01:12:55 +01:00
ynerant 99b749aaa2 Merge branch 'fix-explosions' into 'master'
Fix explosions

Closes #80

See merge request ynerant/squirrel-battle!79
2021-01-16 00:46:04 +01:00
Yohann D'ANELLO d978d319bc
Entities are living during two ticks, fixes #80 2021-01-16 00:40:32 +01:00
Yohann D'ANELLO 87e896bd06
Item owners are correctly set 2021-01-16 00:26:33 +01:00
ynerant e233243b81 Merge branch 'es-translation' into 'master'
Fix spanish translation

See merge request ynerant/squirrel-battle!78
2021-01-11 01:48:10 +01:00
Yohann D'ANELLO 7ce3b8cd5d
Fix spanish translation 2021-01-11 01:40:55 +01:00
ynerant fa0a0a79ea Merge branch 'map_generation' into 'master'
Add new room type : chunk rooms

See merge request ynerant/squirrel-battle!77
2021-01-11 01:28:54 +01:00
Yohann D'ANELLO d839356b2a
Merge remote-tracking branch 'origin/map_generation' into map_generation
# Conflicts:
#	squirrelbattle/mapgeneration/broguelike.py
2021-01-11 01:23:26 +01:00
Yohann D'ANELLO 7b019ce149
Linting 2021-01-11 01:21:52 +01:00
Charles Peyrat 03c45a970c
Fix merging mistakes and chunk rooms 2021-01-11 01:19:54 +01:00
Charles Peyrat 79d8ef3a44
Add new room type : chunk rooms 2021-01-11 01:19:54 +01:00
ynerant 6294f9c07f Merge branch 'v23.14' into 'master'
Bump to version 23.14

See merge request ynerant/squirrel-battle!76
2021-01-10 23:58:31 +01:00
Yohann D'ANELLO 57605c969f
Bump to version 23.14 2021-01-10 23:57:39 +01:00
ynerant e9374c5e6b Merge branch 'map_generation' into 'master'
Map generation

Closes #5

See merge request ynerant/squirrel-battle!35
2021-01-10 23:54:28 +01:00
ynerant b72e41d14d Merge branch 'doors' into 'map_generation'
Doors

See merge request ynerant/squirrel-battle!75
2021-01-10 23:54:13 +01:00
Yohann D'ANELLO 588357e5bf
Linting 2021-01-10 23:49:43 +01:00
ynerant 2031d7fa67 Merge branch 'map_generation' into 'doors'
# Conflicts:
#   squirrelbattle/mapgeneration/broguelike.py
2021-01-10 23:43:54 +01:00
Yohann D'ANELLO 65ae99a26d
The logs of the map was not updated 2021-01-10 23:41:51 +01:00
Yohann D'ANELLO 60675d7859
Cover doors code 2021-01-10 23:21:28 +01:00
Yohann D'ANELLO b0ca1d4edf
Cover everytime the map generation test 2021-01-10 23:05:49 +01:00
Yohann D'ANELLO 8f845d1e4c
Doors don't break the connexity of map 2021-01-10 23:03:24 +01:00
Yohann D'ANELLO 11daa8573c
The players can open doors 2021-01-10 22:59:34 +01:00
Yohann D'ANELLO 6c0aaffd77
Doors are walls 2021-01-10 22:53:27 +01:00
Yohann D'ANELLO e744310861
Place doors at the beginning of the corridor 2021-01-10 22:51:01 +01:00
nicomarg 96e9612d16 Merge branch 'equipment-in-inventory' into 'master'
Resolve "Display equipment in inventory"

Closes #75

See merge request ynerant/squirrel-battle!71
2021-01-10 22:45:04 +01:00
Nicolas Margulies f05652d9b8 Fixed tests and reached 100% coverage 2021-01-10 22:39:52 +01:00
Nicolas Margulies 519504fc32 Mark equipped items and allow unequipping 2021-01-10 22:26:43 +01:00
Nicolas Margulies 88471f4361 Changed equipment behaviour, now equipped items stay in the inventory 2021-01-10 22:15:32 +01:00
21 changed files with 199 additions and 175 deletions

View File

@ -11,31 +11,37 @@
Squirrel Battle is an infinite rogue-like game with randomly generated levels, in which the player controls a squirrel in its quest down in a dungeon, using diverse items to defeat monsters, and trying not to die.
##Installation
## Installation
####Via PyPI :
``` pip install --user squirrel-battle
``` to install
#### Via PyPI :
```
$ pip install --user squirrel-battle
```
to install
``` pip install --user --upgrade squirrel-battle
``` to upgrade
```
$ pip install --user --upgrade squirrel-battle
```
to upgrade
####Via ArchLinux package :
#### Via ArchLinux package :
Download one of these two packages on the AUR :
* python-squirrel-battle
* python-squirrel-battle-git
####Via Debian package :
#### Via Debian package :
Available on our git repository, has a dependency on fonts-noto-color-emoji (to be found in the official Debian repositories).
Run ```
dpkg -i python3-squirrelbattle_3.14.1_all.deb
``` after downloading
Run
```
$ dpkg -i python3-squirrelbattle_23.14_all.deb
```
after downloading
In all cases, execute via command line : ```squirrel-battle```
In all cases, execute via command line : `squirrel-battle`
##For first-time players
## For first-time players
The game is played in a terminal only, preferably one that supports color, markdown and emojis, but it can be played with only grey levels and relatively classic unicode characters.

6
debian/changelog vendored
View File

@ -1,3 +1,9 @@
python3-squirrel-battle (23.14) beta; urgency=low
* Big update
-- Yohann D'ANELLO <squirrel-battle@crans.org> Sun, 10 Jan 2021 23:56:42 +0100
python3-squirrel-battle (3.14.1) beta; urgency=low
* Some graphical improvements.

View File

@ -3,7 +3,7 @@ Déploiement du projet
.. _PyPI: https://pypi.org/project/squirrel-battle/
.. _AUR: https://aur.archlinux.org/packages/python-squirrel-battle/
.. _Debian: https://gitlab.crans.org/ynerant/squirrel-battle/-/jobs/artifacts/master/raw/build/python3-squirrelbattle_3.14.1_all.deb?job=build-deb
.. _Debian: https://gitlab.crans.org/ynerant/squirrel-battle/-/jobs/artifacts/master/raw/build/python3-squirrelbattle_23.14_all.deb?job=build-deb
.. _installation: install.html
À chaque nouvelle version du projet, il est compilé et déployé dans PyPI_, dans
@ -46,7 +46,7 @@ paquet ainsi que des détails à fournir à PyPI :
setup(
name="squirrel-battle",
version="3.14.1",
version="23.14",
author="ÿnérant, eichhornchen, nicomarg, charlse",
author_email="squirrel-battle@crans.org",
description="Watch out for squirrel's knives!",
@ -172,7 +172,7 @@ du dépôt Git. Le fichier ``PKGBUILD`` dispose de cette structure :
pkgbase=squirrel-battle
pkgname=python-squirrel-battle-git
pkgver=3.14.1
pkgver=23.14
pkgrel=1
pkgdesc="Watch out for squirrel's knives!"
arch=('any')
@ -222,7 +222,7 @@ les releases, est plus ou moins similaire :
pkgbase=squirrel-battle
pkgname=python-squirrel-battle
pkgver=3.14.1
pkgver=23.14
pkgrel=1
pkgdesc="Watch out for squirrel's knives!"
arch=('any')
@ -232,7 +232,7 @@ les releases, est plus ou moins similaire :
makedepends=('gettext' 'python-setuptools')
depends=('noto-fonts-emoji')
checkdepends=('python-tox')
source=("https://gitlab.crans.org/ynerant/squirrel-battle/-/archive/v3.14.1/$pkgbase-v$pkgver.tar.gz")
source=("https://gitlab.crans.org/ynerant/squirrel-battle/-/archive/v23.14/$pkgbase-v$pkgver.tar.gz")
sha256sums=("6090534d598c0b3a8f5acdb553c12908ba8107d62d08e17747d1dbb397bddef0")
build() {
@ -317,7 +317,7 @@ On peut ensuite construire le paquet :
dpkg-buildpackage
mkdir build && cp ../*.deb build/
Le paquet sera installé dans ``build/python3-squirrel-battle_3.14.1_all.deb``.
Le paquet sera installé dans ``build/python3-squirrel-battle_23.14_all.deb``.
Le paquet Debian_ est construit par l'intégration continue Gitlab et ajouté
à chaque release.

View File

@ -61,7 +61,7 @@ Le jeu peut être ensuite lancé via la commande ``squirrel-battle``.
Sur Ubuntu/Debian
~~~~~~~~~~~~~~~~~
.. _paquet: https://gitlab.crans.org/ynerant/squirrel-battle/-/jobs/artifacts/master/raw/build/python3-squirrelbattle_3.14.1_all.deb?job=build-deb
.. _paquet: https://gitlab.crans.org/ynerant/squirrel-battle/-/jobs/artifacts/master/raw/build/python3-squirrelbattle_23.14_all.deb?job=build-deb
Un paquet_ est généré par l'intégration continue de Gitlab à chaque commit.
Ils sont également attachés à chaque nouvelle release.
@ -73,7 +73,7 @@ Pour installer ce paquet, il suffit de le télécharger et d'appeler ``dpkg`` :
.. code:: bash
dpkg -i python3-squirrelbattle_3.14.1_all.deb
dpkg -i python3-squirrelbattle_23.14_all.deb
Ce paquet inclut un patch pour afficher les émojis écureuil correctement.

View File

@ -65,7 +65,7 @@ d'exécuter pour chaque langue ``<LANG>`` :
find squirrelbattle -iname '*.py' | xargs xgettext --from-code utf-8
--add-comments
--package-name=squirrelbattle
--package-version=3.14.1
--package-version=23.14
"--copyright-holder=ÿnérant, eichhornchen, nicomarg, charlse"
--msgid-bugs-address=squirrel-battle@crans.org
-o squirrelbattle/locale/<LANG>/LC_MESSAGES/squirrelbattle.po

View File

@ -22,7 +22,7 @@ for language in ["de", "es", "fr"]:
setup(
name="squirrel-battle",
version="3.14.1",
version="23.14",
author="ÿnérant, eichhornchen, nicomarg, charlse",
author_email="squirrel-battle@crans.org",
description="Watch out for squirrel's knives!",

View File

@ -2,17 +2,17 @@
####### #############
#.H...# #...........#
#.....# #####...........#
#.....# #............H..#
#.....# #...&........H..#
#.##### #.###...........#
#.# #.# #...........#
#.# #.# #############
#.# #.#
#.#### #.#
#....# #.#
####.###################.#
####&###################&#
#.....................# #################
#.....................# #...............#
#.....................#######...............#
#...........................................#
#.....................&.....&...............#
#.....................#######...............#
####################### #################

View File

@ -176,7 +176,8 @@ class PlayerInventoryDisplay(MenuDisplay):
selection = f"[{rep}]" if i == self.menu.position \
and self.selected else f" {rep} "
self.addstr(self.pad, i + 1, 0, selection
+ " " + item.translated_name.capitalize()
+ " " + ("[E]" if item.equipped else "")
+ item.translated_name.capitalize()
+ (f" ({item.description})" if item.description else "")
+ (": " + str(item.price) + " Hazels"
if self.store_mode else ""))

View File

@ -82,6 +82,7 @@ TexturePack.ASCII_PACK = TexturePack(
BOW=')',
CHEST='',
CHESTPLATE='(',
DOOR='&',
EAGLE='µ',
EMPTY=' ',
EXPLOSION='%',
@ -124,6 +125,8 @@ TexturePack.SQUIRREL_PACK = TexturePack(
BOW='🏹',
CHEST='🧰',
CHESTPLATE='🦺',
DOOR=('🚪', curses.COLOR_WHITE, (1000, 1000, 1000),
curses.COLOR_WHITE, (1000, 1000, 1000)),
EAGLE='🦅',
EMPTY=' ',
EXPLOSION='💥',

View File

@ -12,16 +12,19 @@ class Item(Entity):
"""
A class for items.
"""
held: bool
held_by: Optional[InventoryHolder]
price: int
def __init__(self, held: bool = False,
def __init__(self, equipped: bool = False,
held_by: Optional[InventoryHolder] = None,
hold_slot: str = "equipped_secondary",
price: int = 2, *args, **kwargs):
super().__init__(*args, **kwargs)
self.held = held
self.held_by = held_by
self.equipped = equipped
self.hold_slot = hold_slot
if equipped:
self.equip()
self.price = price
@property
@ -35,11 +38,11 @@ class Item(Entity):
"""
The item is dropped from the inventory onto the floor.
"""
if self.held:
if self.held_by is not None:
self.unequip()
self.held_by.remove_from_inventory(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
def use(self) -> None:
@ -52,28 +55,41 @@ class Item(Entity):
Indicates what should be done when the item is thrown.
"""
def on_equip(self) -> None:
"""
Indicates a special behaviour when equipping
"""
def on_unequip(self) -> None:
"""
Indicates a special behaviour when unequipping
"""
def equip(self) -> None:
"""
Indicates what should be done when the item is equipped.
"""
# Other objects are only equipped as secondary.
if self.held_by.equipped_secondary:
self.held_by.equipped_secondary.unequip()
self.held_by.remove_from_inventory(self)
self.held_by.equipped_secondary = self
if not self.equipped:
if getattr(self.held_by, self.hold_slot):
getattr(self.held_by, self.hold_slot).unequip()
self.equipped = True
setattr(self.held_by, self.hold_slot, self)
self.on_equip()
def unequip(self) -> None:
"""
Indicates what should be done when the item is unequipped.
"""
self.held_by.remove_from_inventory(self)
self.held_by.add_to_inventory(self)
if self.equipped:
setattr(self.held_by, self.hold_slot, None)
self.equipped = False
self.on_unequip()
def hold(self, holder: InventoryHolder) -> None:
"""
The item is taken from the floor and put into the inventory.
"""
self.held = True
self.held_by = holder
self.held_by.map.remove_entity(self)
holder.add_to_inventory(self)
@ -83,7 +99,7 @@ class Item(Entity):
Saves the state of the item into a dictionary.
"""
d = super().save_state()
d["held"] = self.held
d["equipped"] = self.equipped
return d
@staticmethod
@ -103,10 +119,12 @@ class Item(Entity):
inventory.
"""
if for_free:
self.unequip() if self.equipped else None
self.hold(buyer)
seller.remove_from_inventory(self)
return True
elif buyer.hazel >= self.price:
self.unequip() if self.equipped else None
self.hold(buyer)
seller.remove_from_inventory(self)
buyer.change_hazel_balance(-self.price)
@ -169,7 +187,7 @@ class Bomb(Item):
"""
When the bomb is used, it is thrown and then it explodes.
"""
if self.held:
if self.held_by is not None:
self.owner = self.held_by
super().drop()
self.exploding = True
@ -214,14 +232,19 @@ class Explosion(Item):
"""
When a bomb explodes, the explosion is displayed.
"""
def __init__(self, *args, **kwargs):
living_ticks: int
def __init__(self, living_ticks: int = 2, *args, **kwargs):
super().__init__(name="explosion", *args, **kwargs)
self.living_ticks = living_ticks
def act(self, m: Map) -> None:
"""
The bomb disappears after exploding.
"""
m.remove_entity(self)
self.living_ticks -= 1
if self.living_ticks <= 0:
m.remove_entity(self)
def hold(self, player: InventoryHolder) -> None:
"""
@ -236,7 +259,7 @@ class Weapon(Item):
damage: int
def __init__(self, damage: int = 3, *args, **kwargs):
super().__init__(*args, **kwargs)
super().__init__(hold_slot="equipped_main", *args, **kwargs)
self.damage = damage
@property
@ -251,20 +274,17 @@ class Weapon(Item):
d["damage"] = self.damage
return d
def equip(self) -> None:
def on_equip(self) -> None:
"""
When a weapon is equipped, the player gains strength.
"""
self.held_by.remove_from_inventory(self)
self.held_by.equipped_main = self
self.held_by.strength += self.damage
def unequip(self) -> None:
def on_unequip(self) -> None:
"""
Remove the strength earned by the weapon.
:return:
"""
super().unequip()
self.held_by.strength -= self.damage
@ -301,12 +321,10 @@ class Armor(Item):
return f"CON+{self.constitution}" if self.constitution \
else super().description
def equip(self) -> None:
super().equip()
def on_equip(self) -> None:
self.held_by.constitution += self.constitution
def unequip(self) -> None:
super().unequip()
def on_unequip(self) -> None:
self.held_by.constitution -= self.constitution
def save_state(self) -> dict:
@ -332,13 +350,7 @@ class Helmet(Armor):
def __init__(self, name: str = "helmet", constitution: int = 2,
price: int = 18, *args, **kwargs):
super().__init__(name=name, constitution=constitution, price=price,
*args, **kwargs)
def equip(self) -> None:
if self.held_by.equipped_helmet:
self.held_by.equipped_helmet.unequip()
self.held_by.remove_from_inventory(self)
self.held_by.equipped_helmet = self
hold_slot="equipped_helmet", *args, **kwargs)
class Chestplate(Armor):
@ -348,13 +360,7 @@ class Chestplate(Armor):
def __init__(self, name: str = "chestplate", constitution: int = 4,
price: int = 30, *args, **kwargs):
super().__init__(name=name, constitution=constitution, price=price,
*args, **kwargs)
def equip(self) -> None:
if self.held_by.equipped_armor:
self.held_by.equipped_armor.unequip()
self.held_by.remove_from_inventory(self)
self.held_by.equipped_armor = self
hold_slot="equipped_armor", *args, **kwargs)
class BodySnatchPotion(Item):
@ -426,8 +432,7 @@ class Ring(Item):
("CRI", self.critical), ("XP", self.experience)]
return ", ".join(f"{key}+{value}" for key, value in fields if value)
def equip(self) -> None:
super().equip()
def on_equip(self) -> None:
self.held_by.maxhealth += self.maxhealth
self.held_by.strength += self.strength
self.held_by.intelligence += self.intelligence
@ -437,8 +442,7 @@ class Ring(Item):
self.held_by.critical += self.critical
self.held_by.xp_buff += self.experience
def unequip(self) -> None:
super().unequip()
def on_unequip(self) -> None:
self.held_by.maxhealth -= self.maxhealth
self.held_by.strength -= self.strength
self.held_by.intelligence -= self.intelligence
@ -557,13 +561,6 @@ class LongRangeWeapon(Weapon):
self.held_by.map.logs.add_message(line)
return (to_kill.y, to_kill.x) if to_kill else None
def equip(self) -> None:
"""
Equip the weapon.
"""
self.held_by.remove_from_inventory(self)
self.held_by.equipped_main = self
@property
def stat(self) -> str:
"""

View File

@ -6,7 +6,7 @@ from random import randint
from typing import Dict, Optional, Tuple
from .items import Item
from ..interfaces import FightingEntity, InventoryHolder
from ..interfaces import FightingEntity, InventoryHolder, Tile
from ..translations import gettext as _
@ -117,21 +117,6 @@ class Player(InventoryHolder, FightingEntity):
self.current_xp += int(xp * self.xp_buff)
self.level_up()
def remove_from_inventory(self, obj: Item) -> None:
"""
Remove the given item from the inventory, even if the item is equipped.
"""
if obj == self.equipped_main:
self.equipped_main = None
elif obj == self.equipped_armor:
self.equipped_armor = None
elif obj == self.equipped_secondary:
self.equipped_secondary = None
elif obj == self.equipped_helmet:
self.equipped_helmet = None
else:
return super().remove_from_inventory(obj)
# noinspection PyTypeChecker,PyUnresolvedReferences
def check_move(self, y: int, x: int, move_if_possible: bool = False) \
-> bool:
@ -152,6 +137,12 @@ class Player(InventoryHolder, FightingEntity):
return True
elif entity.is_item():
entity.hold(self)
tile = self.map.tiles[y][x]
if tile == Tile.DOOR and move_if_possible:
# Open door
self.map.tiles[y][x] = Tile.FLOOR
self.map.compute_visibility(y, x, self.vision)
return super().check_move(y, x, move_if_possible)
return super().check_move(y, x, move_if_possible)
def save_state(self) -> dict:

View File

@ -192,14 +192,16 @@ class Game:
# We move up on the ladder of the beginning,
# down at the end of the stage
move_down = y != self.map.start_y and x != self.map.start_x
move_down = y != self.map.start_y or x != self.map.start_x
old_map = self.map
self.map_index += 1 if move_down else -1
if self.map_index == -1:
self.map_index = 0
return
while self.map_index >= len(self.maps):
self.maps.append(broguelike.Generator().run())
m = broguelike.Generator().run()
m.logs = self.logs
self.maps.append(m)
new_map = self.map
new_map.floor = self.map_index
old_map.remove_entity(self.player)
@ -305,7 +307,8 @@ class Game:
if key == KeyValues.USE:
self.inventory_menu.validate().use()
elif key == KeyValues.EQUIP:
self.inventory_menu.validate().equip()
item = self.inventory_menu.validate()
item.unequip() if item.equipped else item.equip()
elif key == KeyValues.DROP:
self.inventory_menu.validate().drop()
@ -417,6 +420,7 @@ class Game:
self.maps = [Map().load_state(map_dict) for map_dict in d["maps"]]
for i, m in enumerate(self.maps):
m.floor = i
m.logs = self.logs
except KeyError as error:
self.message = _("Some keys are missing in your save file.\n"
"Your save seems to be corrupt. It got deleted.")\

View File

@ -390,6 +390,7 @@ class Tile(Enum):
WALL = auto()
FLOOR = auto()
LADDER = auto()
DOOR = auto()
@staticmethod
def from_ascii_char(ch: str) -> "Tile":
@ -430,7 +431,7 @@ class Tile(Enum):
"""
Is this Tile a wall?
"""
return self == Tile.WALL
return self == Tile.WALL or self == Tile.DOOR
def is_ladder(self) -> bool:
"""
@ -857,7 +858,9 @@ class InventoryHolder(Entity):
entity_classes = self.get_all_entity_classes_in_a_dict()
item_class = entity_classes[item_dict["type"]]
return item_class(**item_dict)
item = item_class(**item_dict)
item.held_by = self
return item
def save_state(self) -> dict:
"""
@ -873,6 +876,7 @@ class InventoryHolder(Entity):
Adds an object to the inventory.
"""
if obj not in self.inventory:
obj.held_by = self
self.inventory.append(obj)
def remove_from_inventory(self, obj: Any) -> None:

View File

@ -6,7 +6,7 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: squirrelbattle 3.14.1\n"
"Project-Id-Version: squirrelbattle 23.14\n"
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
"POT-Creation-Date: 2021-01-10 21:30+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"

View File

@ -6,7 +6,7 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: squirrelbattle 3.14.1\n"
"Project-Id-Version: squirrelbattle 23.14\n"
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
"POT-Creation-Date: 2021-01-10 21:30+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
@ -23,19 +23,19 @@ msgstr "Inventorio :"
#: squirrelbattle/display/gamedisplay.py:167
msgid "Equipped main:"
msgstr "Principal equipado:"
msgstr "Equipado principal :"
#: squirrelbattle/display/gamedisplay.py:171
msgid "Equipped secondary:"
msgstr "Equipado secundario:"
msgstr "Equipado segundario :"
#: squirrelbattle/display/gamedisplay.py:176
msgid "Equipped chestplate:"
msgstr "Pechera equipada:"
msgstr "Pechera equipada :"
#: squirrelbattle/display/gamedisplay.py:180
msgid "Equipped helmet:"
msgstr "Casco equipado:"
msgstr "Casco equipado :"
#: squirrelbattle/display/gamedisplay.py:187
msgid "YOU ARE DEAD"
@ -44,22 +44,22 @@ msgstr "ERES MUERTO"
#: squirrelbattle/display/gamedisplay.py:191
#, python-brace-format
msgid "Use {key} to use the ladder"
msgstr "Usa {key} para usar la escalera"
msgstr "Presiona {key} para utilizar la escala"
#: squirrelbattle/display/gamedisplay.py:210
msgid "Move to the friendly entity to talk to it"
msgstr "Muévete hacia la entidad amiga para hablar con ella."
msgstr "Moverse hasta la entitad amistosa para hablar con ella"
#: squirrelbattle/display/gamedisplay.py:212
#, python-brace-format
msgid "Use {key} then move to talk to the entity"
msgstr "Usa {key} y luego muévete para hablar con la entidad"
msgstr "Presionar {key} pues moverse para hablar con la entitad"
#: squirrelbattle/display/menudisplay.py:124
#: squirrelbattle/display/menudisplay.py:149
#: squirrelbattle/display/menudisplay.py:304
msgid "Credits"
msgstr "Creditos"
msgstr "Créditos"
#: squirrelbattle/display/menudisplay.py:173
msgid "INVENTORY"
@ -75,11 +75,11 @@ msgstr "COFRE"
#: squirrelbattle/display/menudisplay.py:308
msgid "Developers:"
msgstr "Desarrollador:"
msgstr "Desarrolladores :"
#: squirrelbattle/display/menudisplay.py:314
msgid "Translators:"
msgstr "Traductores:"
msgstr "Traductores :"
#: squirrelbattle/entities/friendly.py:38
msgid "I don't sell any squirrel"
@ -153,12 +153,12 @@ msgstr "El baile no fue efectivo ..."
#: squirrelbattle/game.py:214
#, python-brace-format
msgid "The player climbs down to the floor {floor}."
msgstr "El jugador desciende alla planta {floor}."
msgstr "El jugador baja a la planta {floor}."
#: squirrelbattle/game.py:227
#, python-brace-format
msgid "The player climbs up the floor {floor}."
msgstr "El jugador sube por la planta {floor}."
msgstr "El jugador sube a la planta {floor}."
#: squirrelbattle/game.py:348 squirrelbattle/tests/game_test.py:631
msgid "The buyer does not have enough money"
@ -417,7 +417,7 @@ msgstr "anillo de daño crítico"
#: squirrelbattle/tests/translations_test.py:91
msgid "ring of more experience"
msgstr "anillo de más experiencia"
msgstr "anillo de mejorada experiencia"
#: squirrelbattle/tests/translations_test.py:93
msgid "monocle"

View File

@ -10,7 +10,7 @@ msgstr "{name} prend {amount} points de dégât."
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: squirrelbattle 3.14.1\n"
"Project-Id-Version: squirrelbattle 23.14\n"
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
"POT-Creation-Date: 2021-01-10 21:30+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"

View File

@ -24,15 +24,17 @@ DEFAULT_PARAMS = {
"loop_max": 5,
"loop_threshold": 15,
"spawn_per_region": [1, 2],
"room_chances" : {
"circular" : 5,
"chunks" : 1,
},
"room_chances": {
"circular": 5,
"chunks": 1,
},
}
def dist(level, y1, x1, y2, x2):
def dist(level: List[List[Tile]], y1: int, x1: int, y2: int, x2: int) -> int:
"""
Compute the minimum walking distance between points (y1, x1) and (y2, x2) on a Tile grid
Compute the minimum walking distance between points (y1, x1) and (y2, x2)
on a Tile grid
"""
# simple breadth first search
copy = [[t for t in row] for row in level]
@ -64,9 +66,9 @@ class Generator:
room: List[List[Tile]], door_y: int, door_x: int,
dy: int, dx: int) -> bool:
"""
Using point (door_y, door_x) in the room as a reference and placing it
Using point (door_y, door_x) in the room as a reference and placing it
over point (y, x) in the level, returns whether or not the room fits
here
here
"""
lh, lw = len(level), len(level[0])
rh, rw = len(room), len(room[0])
@ -97,12 +99,11 @@ class Generator:
def place_room(level: List[List[Tile]], y: int, x: int,
room: List[List[Tile]], door_y: int, door_x: int) -> None:
"""
Mutates level in place to add the room. Placement is determined by
Mutates level in place to add the room. Placement is determined by
making (door_y, door_x) in the room correspond with (y, x) in the level
"""
rh, rw = len(room), len(room[0])
# maybe place Tile.DOOR here instead ?
level[y][x] = Tile.FLOOR
level[y][x] = Tile.DOOR
for ry in range(rh):
for rx in range(rw):
if room[ry][rx] == Tile.FLOOR:
@ -111,11 +112,11 @@ class Generator:
@staticmethod
def add_loop(level: List[List[Tile]], y: int, x: int) -> bool:
"""
Try to add a corridor between two far apart floor tiles, passing
Try to add a corridor between two far apart floor tiles, passing
through point (y, x).
"""
h, w = len(level), len(level[0])
if level[y][x] != Tile.EMPTY:
return False
@ -133,8 +134,8 @@ class Generator:
continue
def verify_sides() -> bool:
# switching up dy and dx here pivots the axis, so
# (y+dx, x+dy) and (y-dx, x-dy) are the tiles adjacent to
# switching up dy and dx here pivots the axis, so
# (y+dx, x+dy) and (y-dx, x-dy) are the tiles adjacent to
# (y, x), but not on the original axis
for delta_x, delta_y in [[dy, dx], [-dy, -dx]]:
for i in range(1, y2 - y1 + x2 - x1):
@ -199,8 +200,8 @@ class Generator:
dy: int, dx: int, length: int) -> bool:
"""
Tries to build the exit from the room at given coordinates
Depending on parameter length, it will either attempt to build a
simple door, or a long corridor. Return value is a boolean
Depending on parameter length, it will either attempt to build a
simple door, or a long corridor. Return value is a boolean
signifying whether or not the exit was successfully built
"""
rh, rw = len(room), len(room[0])
@ -252,8 +253,8 @@ class Generator:
if room[y][x] == Tile.EMPTY and \
Generator.build_door(room, y, x, dy, dx, length):
break
else:
return None, None
else: # pragma: no cover
return None, None, None, None
return y + length * dy, x + length * dx, dy, dx
@ -268,19 +269,19 @@ class Generator:
nb_chunks, r = 6, 3
h_sup, w_sup, h_off, w_off = self.corr_meta_info()
room = [[Tile.EMPTY] * (width + w_sup) \
room = [[Tile.EMPTY] * (width + w_sup)
for _dummy in range(height + h_sup)]
def draw_chunk(room, y, x):
def draw_chunk(room: List[List[Tile]], y: int, x: int) -> None:
for i in range(y - r, y + r + 1):
d = (y - i)**2
for j in range(x - r, x + r + 1):
if d + (x - j)**2 < r**2:
if d + (x - j) ** 2 < r ** 2:
room[i][j] = Tile.FLOOR
draw_chunk(room, h_off + height//2 + 1, w_off + width//2 + 1)
draw_chunk(room, h_off + height // 2 + 1, w_off + width // 2 + 1)
min_w, max_w = w_off + r + 1, width + w_off - r -1
min_w, max_w = w_off + r + 1, width + w_off - r - 1
min_h, max_h = h_off + r + 1, height + h_off - r - 1
for i in range(nb_chunks):
y, x = randint(min_h, max_h), randint(min_w, max_w)
@ -301,7 +302,7 @@ class Generator:
def create_circular_room(self, spawnable: bool = True) \
-> Tuple[List[List[Tile]], int, int, int, int]:
"""
Create and return as a tile grid a room that is circular in shape, and
Create and return as a tile grid a room that is circular in shape, and
may have a center, also circular hole
Also return door info so we know how to place the room in the level
"""
@ -344,7 +345,7 @@ class Generator:
def create_random_room(self, spawnable: bool = True) \
-> Tuple[List[list], int, int, int, int]:
"""
Randomly select a room shape and return one such room along with its
Randomly select a room shape and return one such room along with its
door info. Set spawnable to False is the room should be marked as a
potential spawning region on the map
"""
@ -358,9 +359,9 @@ class Generator:
break
if key == "circular":
return self.create_circular_room(spawnable = spawnable)
return self.create_circular_room(spawnable=spawnable)
elif key == "chunks":
return self.create_chunk_room(spawnable = spawnable)
return self.create_chunk_room(spawnable=spawnable)
def register_spawn_area(self, area: List[List[Tile]]) -> None:
"""
@ -377,8 +378,8 @@ class Generator:
def update_spawnable(self, y: int, x: int) -> None:
"""
Convert previous spawn positions relative to the room grid to actual
actual spawn positions on the level grid, using the position of the
top left corner of the room on the level, then log them as a
actual spawn positions on the level grid, using the position of the
top left corner of the room on the level, then log them as a
spawnable region
"""
if self.queued_area is not None:

View File

@ -134,13 +134,13 @@ class TestEntities(unittest.TestCase):
self.map.remove_entity(entity2)
# Test following the player and finding the player as target
self.player.move(5, 5)
fam.move(4, 5)
self.player.move(6, 5)
fam.move(5, 5)
fam.target = None
self.player.move_down()
self.map.tick(self.player)
self.assertTrue(fam.target == self.player)
self.assertEqual(fam.y, 5)
self.assertEqual(fam.y, 6)
self.assertEqual(fam.x, 5)
# Test random move
@ -155,9 +155,9 @@ class TestEntities(unittest.TestCase):
"""
item = Item()
self.map.add_entity(item)
self.assertFalse(item.held)
self.assertIsNone(item.held_by)
item.hold(self.player)
self.assertTrue(item.held)
self.assertEqual(item.held_by, self.player)
item.drop()
self.assertEqual(item.y, 1)
self.assertEqual(item.x, 6)
@ -165,7 +165,6 @@ class TestEntities(unittest.TestCase):
# Pick up item
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)
self.assertNotIn(item, self.map.entities)
@ -208,9 +207,10 @@ class TestEntities(unittest.TestCase):
# The player can't hold the explosion
explosion.hold(self.player)
self.assertNotIn(explosion, self.player.inventory)
self.assertFalse(explosion.held)
self.assertIsNone(explosion.held_by)
# The explosion disappears after one tick
# The explosion disappears after two ticks
explosion.act(self.map)
explosion.act(self.map)
self.assertNotIn(explosion, self.map.entities)

View File

@ -2,7 +2,6 @@
# SPDX-License-Identifier: GPL-3.0-or-later
import curses
import os
import unittest
from ..bootstrap import Bootstrap
@ -49,6 +48,7 @@ class TestGame(unittest.TestCase):
# Add items in the inventory to check that it is well loaded
bomb.hold(self.game.player)
sword.hold(self.game.player)
sword.equip()
# Ensure that merchants can be saved
merchant = Merchant()
@ -100,7 +100,6 @@ class TestGame(unittest.TestCase):
Yeah, that's only for coverage.
"""
self.assertRaises(Exception, Bootstrap.run_game)
self.assertEqual(os.getenv("TERM", "unknown"), "unknown")
def test_key_translation(self) -> None:
"""
@ -256,6 +255,7 @@ class TestGame(unittest.TestCase):
self.game.map.add_entity(explosion)
self.assertIn(explosion, self.game.map.entities)
self.game.handle_key_pressed(KeyValues.WAIT)
self.game.handle_key_pressed(KeyValues.WAIT)
self.assertNotIn(explosion, self.game.map.entities)
rabbit = Rabbit()
@ -497,10 +497,8 @@ class TestGame(unittest.TestCase):
# 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)
@ -510,10 +508,8 @@ class TestGame(unittest.TestCase):
# 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)
@ -666,42 +662,37 @@ class TestGame(unittest.TestCase):
sword.hold(self.game.player)
self.game.handle_key_pressed(KeyValues.EQUIP)
self.assertEqual(self.game.player.equipped_main, sword)
self.assertFalse(self.game.player.inventory)
# shield goes into the secondary equipment slot
shield = Shield()
shield.hold(self.game.player)
self.game.handle_key_pressed(KeyValues.EQUIP)
shield.equip()
self.assertEqual(self.game.player.equipped_secondary, shield)
self.assertFalse(self.game.player.inventory)
# helmet goes into the helmet slot
helmet = Helmet()
helmet.hold(self.game.player)
self.game.handle_key_pressed(KeyValues.EQUIP)
helmet.equip()
self.assertEqual(self.game.player.equipped_helmet, helmet)
self.assertFalse(self.game.player.inventory)
# helmet goes into the armor slot
chestplate = Chestplate()
chestplate.hold(self.game.player)
self.game.handle_key_pressed(KeyValues.EQUIP)
chestplate.equip()
self.assertEqual(self.game.player.equipped_armor, chestplate)
self.assertFalse(self.game.player.inventory)
# Use bomb
bomb = Bomb()
bomb.hold(self.game.player)
self.game.handle_key_pressed(KeyValues.EQUIP)
bomb.equip()
self.assertEqual(self.game.player.equipped_secondary, bomb)
self.assertIn(shield, self.game.player.inventory)
self.assertFalse(shield.equipped)
self.game.state = GameMode.PLAY
self.game.handle_key_pressed(KeyValues.USE)
self.assertIsNone(self.game.player.equipped_secondary)
self.game.state = GameMode.INVENTORY
self.game.handle_key_pressed(KeyValues.EQUIP)
shield.equip()
self.assertEqual(self.game.player.equipped_secondary, shield)
self.assertFalse(self.game.player.inventory)
# Reequip, which is useless but covers code
sword.equip()
@ -723,11 +714,13 @@ class TestGame(unittest.TestCase):
self.assertIn(shield, self.game.player.inventory)
self.assertIn(helmet, self.game.player.inventory)
self.assertIn(chestplate, self.game.player.inventory)
self.game.display_actions(DisplayActions.REFRESH)
# Test rings
self.game.player.inventory.clear()
ring = RingCritical()
ring.hold(self.game.player)
self.game.display_actions(DisplayActions.REFRESH)
old_critical = self.game.player.critical
self.game.handle_key_pressed(KeyValues.EQUIP)
self.assertEqual(self.game.player.critical,
@ -951,3 +944,18 @@ class TestGame(unittest.TestCase):
# Exit the menu
self.game.handle_key_pressed(KeyValues.SPACE)
self.assertEqual(self.game.state, GameMode.PLAY)
def test_doors(self) -> None:
"""
Check that the user can open doors.
"""
self.game.state = GameMode.PLAY
self.game.player.move(9, 8)
self.assertEqual(self.game.map.tiles[10][8], Tile.DOOR)
# Open door
self.game.handle_key_pressed(KeyValues.DOWN)
self.assertEqual(self.game.map.tiles[10][8], Tile.FLOOR)
self.assertEqual(self.game.player.y, 10)
self.assertEqual(self.game.player.x, 8)
self.game.display_actions(DisplayActions.REFRESH)

View File

@ -26,16 +26,17 @@ class TestBroguelike(unittest.TestCase):
def is_connex(self, grid: List[List[Tile]]) -> bool:
h, w = len(grid), len(grid[0])
y, x = randint(0, h - 1), randint(0, w - 1)
while not (grid[y][x].can_walk()):
y, x = -1, -1
while not grid[y][x].can_walk():
y, x = randint(0, h - 1), randint(0, w - 1)
queue = Map.neighbourhood(grid, y, x)
while queue:
y, x = queue.pop()
if grid[y][x].can_walk():
if grid[y][x].can_walk() or grid[y][x] == Tile.DOOR:
grid[y][x] = Tile.WALL
queue += Map.neighbourhood(grid, y, x)
return not any([t.can_walk() for row in grid for t in row])
return not any([t.can_walk() or t == Tile.DOOR
for row in grid for t in row])
def test_build_doors(self) -> None:
m = self.stom(". .\n. .\n. .\n")

View File

@ -25,6 +25,8 @@ class Translator:
Loads compiled translations.
"""
for language in cls.SUPPORTED_LOCALES:
if language == "en":
continue
rep = Path(__file__).parent / "locale" / language / "LC_MESSAGES"
rep.mkdir(parents=True) if not rep.is_dir() else None
if os.path.isfile(rep / "squirrelbattle.mo"):
@ -65,7 +67,7 @@ class Translator:
args = ["xargs", "xgettext", "--from-code", "utf-8",
"--add-comments",
"--package-name=squirrelbattle",
"--package-version=3.14.1",
"--package-version=23.14",
"--copyright-holder=ÿnérant, eichhornchen, "
"nicomarg, charlse, ifugao",
"--msgid-bugs-address=squirrel-battle@crans.org",