Merge branch 'EvenmoreDoc' into 'moredocs'

# Conflicts:
#   docs/entities/items.rst
This commit is contained in:
ynerant 2021-01-10 18:49:25 +01:00
commit 10a1b59b24
23 changed files with 486 additions and 301 deletions

View File

@ -9,8 +9,63 @@
# Squirrel Battle # Squirrel Battle
Attention aux couteaux des écureuils ! 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
####Via PyPI :
``` pip install --user squirrel-battle
``` to install
``` pip install --user --upgrade squirrel-battle
``` to upgrade
####Via ArchLinux package :
Download one of these two packages on the AUR :
* python-squirrel-battle
* python-squirrel-battle-git
####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
In all cases, execute via command line : ```squirrel-battle```
##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.
Upon starting, the game will display the main menu. To navigate in menus, use zqsd or the keyboard arrows. To validate one of the options, use the Enter key. Mouse click is also supported in most menus, **but not in game**.
The game in itself can have two types of display : using ascii and simple unicode characters, or using emojis. To activate emoji mode, go to the settings menu and select the squirrel texture pack. Emojis will not work if the terminal does not support them, so do tests before to ensure the terminal can display them.
The game is translated (almost entirely) in English, French, German and Spanish. To change the language, go to the settings menu.
Controls in-game are pretty basic : use zqsd or the keyboard arrows to move. To hit an ennemy, simply go in its direction if it is in an adjacent tile.
There are several special control keys, they can be changed in the settings menu :
* To close a store menu or go back to the main menu, use Space
* To open/close the inventory, use i
* To use an object in the inventory, use u
* To equip an object in the inventory, use e
* To use a long range weapon after it has been equipped, use l and then select the direction to shoot in
* To drop an object from the inventory, use r (to pick up an object, simply go on its tile, its automatic)
* To talk to certains entities (or open a chest), use t and then select the direction of the entity
* To wait a turn (rather than moving), use w
* To dance and confuse the ennemies, use y
* To use a ladder, use <
The dungeon consists in empty tiles (you can not go there), walls (which you can not cross) and floor ( :) ). Entities that move are usually monsters, but if you see a trumpet (or a '/'), do not kill it ! It is a familiar that will help you defeat monsters. Entities that do not move are either entities to which you can talk, like merchants and ... chests for some reason, or objects. Differentiating the two is not difficult, trying to go on the same tile as a living entity (or a chest) is impossible. Objects have pretty clear names, so it should not be too difficult determining what they do (if you still don't know, you can either read the docs, or test for yourself (beware of surprises though))
And that is all you need to get started! You can now start your adventure and don't worry, floors are randomly generated, so it won't always be the same boring level.
## Documentation ## Documentation
La documentation du projet est présente sur [squirrel-battle.readthedocs.io](https://squirrel-battle.readthedocs.io). The documentation for the project cen be found at [squirrel-battle.readthedocs.io](https://squirrel-battle.readthedocs.io). It is unfortunately only written in French.
Anyone interested in understanding how the code works can find a few explanations in the documentation.

View File

@ -29,10 +29,10 @@ Bombe
Une bombe est un objet que l'on peut ramasser. Une fois ramassée, elle est placée Une bombe est un objet que l'on peut ramasser. Une fois ramassée, elle est placée
dans l'inventaire. Le joueur peut ensuite utiliser la bombe, via l'inventaire dans l'inventaire. Le joueur peut ensuite utiliser la bombe, via l'inventaire
ou après l'avoir équipée, qui fera alors 3 dégâts à toutes les ou après l'avoir équipée, qui fera alors 5 dégâts à toutes les
`entités attaquantes`_ situées à moins de trois cases au bout de 4 ticks de jeu. `entités attaquantes`_ situées à moins de trois cases au bout de 4 ticks de jeu.
Elle est représentée dans le `pack de textures`_ ASCII par le caractère ``o`` Elle est représentée dans le `pack de textures`_ ASCII par le caractère ``ç``
et dans le `pack de textures`_ écureuil par l'émoji ``💣``. Lors de l'explosion, et dans le `pack de textures`_ écureuil par l'émoji ``💣``. Lors de l'explosion,
la bombe est remplacée par un symbole ``%`` ou l'émoji ``💥`` selon le pack de la bombe est remplacée par un symbole ``%`` ou l'émoji ``💥`` selon le pack de
textures utilisé. textures utilisé.
@ -44,8 +44,7 @@ Cœur
---- ----
Un cœur est un objet que l'on ne peut pas ramasser. Dès que le joueur s'en Un cœur est un objet que l'on ne peut pas ramasser. Dès que le joueur s'en
approche ou qu'il l'achète auprès d'un marchand, il est régénéré automatiquement approche ou qu'il l'achète auprès d'un marchand, il récupère automatiquement 5 points de vie, et le cœur disparaît.
de 3 points de vie, et le cœur disparaît.
Il est représenté dans le `pack de textures`_ ASCII par le caractère ```` Il est représenté dans le `pack de textures`_ ASCII par le caractère ````
et dans le `pack de textures`_ écureuil par l'émoji ``💜``. et dans le `pack de textures`_ écureuil par l'émoji ``💜``.
@ -65,11 +64,21 @@ Elle est représentée par les caractères ``I`` et ``🔀``
Cette potion coûte 14 Hazels auprès des marchands. Cette potion coûte 14 Hazels auprès des marchands.
Règle
-----
La règle est une arme que l'on peut trouver uniquement par achat auprès d'un
marchand pour le coût de 2 Hazels ou dans un coffre. Une fois équipée, la règle ajoute 1 de force
à son porteur.
Elle est représentée par les caractères ``\`` et ``📏``.
Épée Épée
---- ----
L'épée est un objet que l'on peut trouver uniquement par achat auprès d'un L'épée est une arme que l'on peut trouver uniquement par achat auprès d'un
marchand pour le coût de 20 Hazels. Une fois équipée, l'épée ajoute 3 de force marchand pour le coût de 20 Hazels ou dans un coffre. Une fois équipée, l'épée ajoute 3 de force
à son porteur. à son porteur.
Elle est représentée par les caractères ```` et ``🗡️``. Elle est représentée par les caractères ```` et ``🗡️``.
@ -78,38 +87,34 @@ Elle est représentée par les caractères ``†`` et ``🗡️``.
Bouclier Bouclier
-------- --------
Le bouclier est un objet que l'on peut trouver uniquement par achat auprès d'un Le bouclier est un type d'armure que l'on peut trouver uniquement par achat auprès d'un marchand pour le coût de 16 Hazels ou dans un coffre. Il s'équippe dans la main secondaire.
marchand pour le coût de 16 Hazels. Il s'équippe dans la main secondaire. Une fois équipé, le bouclier ajoute 2 de constitution à son porteur, lui permettant de parer mieux les coups.
Une fois équipé, le bouclier ajoute 1 de
constitution à son porteur, lui permettant de parer mieux les coups.
Il est représenté par les caractères ``D`` et ``🛡️``. Il est représenté par les caractères ``D`` et ``🛡️``.
Casque Casque
------ ------
Le casque est un objet que l'on peut trouver uniquement par achat auprès d'un Le casque est un type d'armure que l'on peut trouver uniquement par achat auprès d'un marchand pour le coût de 18 Hazels ou dans un coffre. Il s'équippe sur la tête.
marchand pour le coût de 18 Hazels. Il s'équippe sur la tête. Une fois équipé, le casque ajoute 2 de constitution à son porteur, lui permettant de prendre moins de dégâts.
Une fois équipé, le casque ajoute 2 de
constitution à son porteur, lui permettant de prendre moins de dêgats.
Il est représenté par les caractères ``0`` et ``⛑️``. Il est représenté par les caractères ``0`` et ``⛑️``.
Plastron Plastron
-------- --------
Le plastron est un objet que l'on peut trouver uniquement par achat auprès d'un Le plastron est un type d'armure que l'on peut trouver uniquement par achat
marchand pour le coût de 30 Hazels. Il s'équippe sur le corps. auprès d'un marchand pour le coût de 30 Hazels ou dans un coffre. Il s'équippe sur le corps.
Une fois équipé, le casque ajoute 4 de constitution à son porteur, Une fois équipé, le casque ajoute 4 de constitution à son porteur,
lui permettant de prendre moins de dêgats. lui permettant de prendre moins de dégâts.
Il est représenté par les caractères ``(`` et ``🦺``. Il est représenté par les caractères ``(`` et ``🦺``.
Anneau Anneau
------ ------
L'anneau est un objet que l'on peut trouver uniquement par achat auprès d'un Un anneau est un objet que l'on peut trouver uniquement par achat auprès d'un
marchand. Il s'équippe sur la main secondaire. marchand ou dans un coffre. Il s'équippe sur la main secondaire.
Une fois équipé, l'anneau ajoute un bonus à une ou plusieurs statistiques du Une fois équipé, l'anneau ajoute un bonus à une ou plusieurs statistiques du
joueur, améliorant sa capacité à se débarasser des monstres. joueur, améliorant sa capacité à se débarasser des monstres.
@ -131,3 +136,35 @@ Une fois porté, il permet de voir les caractéristiques des entités voisines
(nom, force, chance de critique, ...). (nom, force, chance de critique, ...).
Un monocle est représenté par les caractères ``ô`` et ``🧐``. Un monocle est représenté par les caractères ``ô`` et ``🧐``.
Parchemin
---------
Un parchemin est un objet consommable qui se trouve chez un marchand ou dans un coffre. Lorsqu'il est utilisé, il a un effet sur les statistiques du joueur ou des autres entités combattantes. L'intensité de l'effet du parchemin dépend de l'intelligence du joueur.
Il y a plusieurs types de parchemins :
* **Parchemin de dégâts**, qui inflige des dégâts à toutes les entités combattantes qui sont à distance moins de 5 du joueur (ça touche aussi les familiers, mais pas le joueur). Le nombre de points de dégâts est directement l'intelligence du joueur. Il coute 18 Hazels.
* **Parchemin de faiblesse**, qui réduit la force de toutes les entités sauf le joueur de min(1, intelligence//2) pendant 3 tours du jeu. Il coûte 13 Hazels.
Un parchemin est représenté par les caractères ``]`` et ``📜``.
Arc
---
Un arc est une arme à distance qui s'équippe dans la main principale. Pour l'utiliser, il faut appuyer sur la touche de lancer (l de base) puis une touche de direction. Une flèche est alors tirée dans cette direction, et touche le premier ennemi qu'elle rencontre, s'il existe, sur les 3 premières cases dans cette direction.
La flèche fait 4 + dextérité du joueur dégâts.
L'arc coûte 22 Hazels chez un marchand. On peut le trouver sinon dans les coffres.
Il est représenté par les caractères ``)`` et ``🏹``.
Baton de boule de feu
---------------------
Un baton est une arme à distance qui s'équippe dans la main principale. Pour l'utiliser, il faut appuyer sur la touche de lancer (l de base) puis une touche de direction. Une boule de feu est alors tirée dans cette direction, et touche le premier ennemi qu'elle rencontre, s'il existe, sur les 4 premières cases dans cette direction. Lorsqu'un ennemi est touché, une explosion est affichée sur sa case.
La boule de feu fait 6 + intelligence du joueur dégâts.
Le baton coûte 36 Hazels chez un marchand. On peut le trouver sinon dans les coffres.
Il est représenté par les caractères ``:`` et ``🪄``.

View File

@ -32,6 +32,10 @@ Déplacement
Selon les paramètres_, il est possible de bouger le joueur dans les 4 directions Selon les paramètres_, il est possible de bouger le joueur dans les 4 directions
en appuyant sur ``z``, ``q``, ``s``, ``d`` ou sur les flèches directionnelles. en appuyant sur ``z``, ``q``, ``s``, ``d`` ou sur les flèches directionnelles.
(ou sur d'autres touches selon ce qui est écrit dans le menu des paramètres)
Le joueur peut aussi ne rien faire pendant son tour, il suffit d'appuyer sur
la touche d'attente (``w`` de base).
Le joueur se retrouvera bloqué s'il avance contre un mur. Si il avance sur un Le joueur se retrouvera bloqué s'il avance contre un mur. Si il avance sur un
objet_, alors il prend l'objet_ et avance sur la case. objet_, alors il prend l'objet_ et avance sur la case.
@ -40,6 +44,11 @@ S'il rencontre une autre `entité attaquante`_, alors il frappe l'entité en
infligeant autant de dégâts qu'il n'a de force. À chaque fois qu'une entité est infligeant autant de dégâts qu'il n'a de force. À chaque fois qu'une entité est
tuée, le joueur gagne aléatoirement entre 3 et 7 points d'expérience. tuée, le joueur gagne aléatoirement entre 3 et 7 points d'expérience.
Outre se déplacer et attaquer, le joueur peut utiliser la touche pour danser
(``y`` de base) durant son tour et danser. Selon son charisme, il a plus ou moins
de chances de rendre confus tous les ennemis à distance moins de 3. Un ennemi confus
ne peut pas attaquer.
Expérience Expérience
---------- ----------
@ -49,4 +58,6 @@ Lorsque le joueur atteint la quantité d'expérience requise pour monter de nive
le joueur gagne un niveau, regagne toute sa vie, consomme son expérience et la le joueur gagne un niveau, regagne toute sa vie, consomme son expérience et la
nouvelle quantité d'expérience requise est 10 fois le niveau actuel. De plus, nouvelle quantité d'expérience requise est 10 fois le niveau actuel. De plus,
entre 5 et 10 fois le niveau actuel entités apparaissent aléatoirement sur la entre 5 et 10 fois le niveau actuel entités apparaissent aléatoirement sur la
carte à la montée de niveau. Enfin, le joueur gagne en force en montant de niveau. carte à la montée de niveau. Enfin, le joueur améliore ses statistiques en augmentant
de niveau. Toutes les caractéristiques ne sont pas incrémentées à chaque niveau
gagné.

View File

@ -27,6 +27,9 @@ Les touches utilisées de base sont :
* **Lacher un objet** : r * **Lacher un objet** : r
* **Parler** : t * **Parler** : t
* **Attendre** : w * **Attendre** : w
* **Utiliser une arme à distance** : l
* **Dancer** : y
* **Utiliser une échelle** : <
Autres Autres
------ ------

View File

@ -1,97 +0,0 @@
# Copyright (C) 2020-2021 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later
import curses
from ..display.display import Box, Display
from ..game import Game
from ..resources import ResourceManager
from ..translations import gettext as _
class CreditsDisplay(Display):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.box = Box(*args, **kwargs)
self.pad = self.newpad(1, 1)
self.ascii_art_displayed = False
def update(self, game: Game) -> None:
return
def display(self) -> None:
self.box.refresh(self.y, self.x, self.height, self.width)
self.box.display()
self.pad.erase()
messages = [
_("Credits"),
"",
"Squirrel Battle",
"",
_("Developers:"),
"Yohann \"ÿnérant\" D'ANELLO",
"Mathilde \"eichhornchen\" DÉPRÉS",
"Nicolas \"nicomarg\" MARGULIES",
"Charles \"charsle\" PEYRAT",
"",
_("Translators:"),
"Hugo \"ifugao\" JACOB (español)",
]
for i, msg in enumerate(messages):
self.addstr(self.pad, i + (self.height - len(messages)) // 2,
(self.width - len(msg)) // 2, msg,
bold=(i == 0), italic=(":" in msg))
if self.ascii_art_displayed:
self.display_ascii_art()
self.refresh_pad(self.pad, 0, 0, self.y + 1, self.x + 1,
self.height + self.y - 2,
self.width + self.x - 2)
def display_ascii_art(self) -> None:
with open(ResourceManager.get_asset_path("ascii-art-ecureuil.txt"))\
as f:
ascii_art = f.read().split("\n")
height, width = len(ascii_art), len(ascii_art[0])
y_offset, x_offset = (self.height - height) // 2,\
(self.width - width) // 2
for i, line in enumerate(ascii_art):
for j, c in enumerate(line):
bg_color = curses.COLOR_WHITE
fg_color = curses.COLOR_BLACK
bold = False
if c == ' ':
bg_color = curses.COLOR_BLACK
elif c == '' or c == '' or c == '':
bold = True
fg_color = curses.COLOR_WHITE
bg_color = curses.COLOR_BLACK
elif c == '|':
bold = True # c = '┃'
fg_color = (100, 700, 1000)
bg_color = curses.COLOR_BLACK
elif c == '':
fg_color = (700, 300, 0)
elif c == '':
fg_color = (700, 300, 0)
bg_color = curses.COLOR_BLACK
elif c == '':
fg_color = (350, 150, 0)
elif c == '':
fg_color = (0, 0, 0)
bg_color = curses.COLOR_BLACK
elif c == '':
c = ''
fg_color = (1000, 1000, 1000)
bg_color = curses.COLOR_BLACK
self.addstr(self.pad, y_offset + i, x_offset + j, c,
fg_color, bg_color, bold=bold)
def handle_click(self, y: int, x: int, attr: int, game: Game) -> None:
if self.pad.inch(y - 1, x - 1) != ord(" "):
self.ascii_art_displayed = True

View File

@ -290,3 +290,29 @@ class Box(Display):
self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.refresh_pad(self.pad, 0, 0, self.y, self.x,
self.y + self.height - 1, self.x + self.width - 1) self.y + self.height - 1, self.x + self.width - 1)
class MessageDisplay(Display):
"""
A class to handle the display of popup messages.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.box = Box(fg_border_color=curses.COLOR_RED, *args, **kwargs)
self.message = ""
self.pad = self.newpad(1, 1)
def update(self, game: Game) -> None:
self.message = game.message
def display(self) -> None:
self.box.refresh(self.y - 1, self.x - 2,
self.height + 2, self.width + 4)
self.box.display()
self.pad.erase()
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

@ -4,14 +4,11 @@
import curses import curses
from typing import Any, List from typing import Any, List
from .creditsdisplay import CreditsDisplay from .display import Display, HorizontalSplit, MessageDisplay, VerticalSplit
from .display import Display, HorizontalSplit, VerticalSplit from .gamedisplay import LogsDisplay, MapDisplay, StatsDisplay
from .logsdisplay import LogsDisplay from .menudisplay import ChestInventoryDisplay, CreditsDisplay, \
from .mapdisplay import MapDisplay MainMenuDisplay, PlayerInventoryDisplay, \
from .menudisplay import ChestInventoryDisplay, MainMenuDisplay, \ SettingsMenuDisplay, StoreInventoryDisplay
PlayerInventoryDisplay, SettingsMenuDisplay, StoreInventoryDisplay
from .messagedisplay import MessageDisplay
from .statsdisplay import StatsDisplay
from .texturepack import TexturePack from .texturepack import TexturePack
from ..enums import DisplayActions from ..enums import DisplayActions
from ..game import Game, GameMode from ..game import Game, GameMode

View File

@ -7,10 +7,116 @@ from .display import Display
from ..entities.items import Monocle from ..entities.items import Monocle
from ..entities.player import Player from ..entities.player import Player
from ..game import Game from ..game import Game
from ..interfaces import FightingEntity from ..interfaces import FightingEntity, Logs, Map
from ..translations import gettext as _ from ..translations import gettext as _
class LogsDisplay(Display):
"""
A class to handle the display of the logs.
"""
logs: Logs
def __init__(self, *args) -> None:
super().__init__(*args)
self.pad = self.newpad(self.rows, self.cols)
def update(self, game: Game) -> None:
self.logs = game.logs
def display(self) -> None:
messages = self.logs.messages[-self.height:]
messages = messages[::-1]
self.pad.erase()
for i in range(min(self.height, len(messages))):
self.addstr(self.pad, self.height - i - 1, self.x,
messages[i][:self.width])
self.refresh_pad(self.pad, 0, 0, self.y, self.x,
self.y + self.height - 1, self.x + self.width - 1)
class MapDisplay(Display):
"""
A class to handle the display of the map.
"""
map: Map
def __init__(self, *args):
super().__init__(*args)
def update(self, game: Game) -> None:
self.map = game.map
self.pad = self.newpad(self.map.height,
self.pack.tile_width * self.map.width + 1)
def update_pad(self) -> None:
for j in range(len(self.map.tiles)):
for i in range(len(self.map.tiles[j])):
if not self.map.seen_tiles[j][i]:
continue
fg, bg = self.map.tiles[j][i].visible_color(self.pack) if \
self.map.visibility[j][i] else \
self.map.tiles[j][i].hidden_color(self.pack)
self.addstr(self.pad, j, self.pack.tile_width * i,
self.map.tiles[j][i].char(self.pack), fg, bg)
for e in self.map.entities:
if self.map.visibility[e.y][e.x]:
self.addstr(self.pad, e.y, self.pack.tile_width * e.x,
self.pack[e.name.upper()],
self.pack.entity_fg_color,
self.pack.entity_bg_color)
# 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
# if player:
# for x in range(self.map.width):
# for y in range(self.map.height):
# if (y,x) in player.paths:
# deltay, deltax = (y - player.paths[(y, x)][0],
# x - player.paths[(y, x)][1])
# if (deltay, deltax) == (-1, 0):
# character = '↓'
# elif (deltay, deltax) == (1, 0):
# character = '↑'
# elif (deltay, deltax) == (0, -1):
# character = '→'
# else:
# character = '←'
# self.addstr(self.pad, y, self.pack.tile_width * x,
# 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
deltay, deltax = (self.height // 2) + 1, (self.width // 2) + 1
pminrow, pmincol = y - deltay, x - deltax
sminrow, smincol = max(-pminrow, 0), max(-pmincol, 0)
deltay, deltax = self.height - deltay, self.width - deltax
smaxrow = self.map.height - (y + deltay) + self.height - 1
smaxrow = min(smaxrow, self.height - 1)
smaxcol = self.pack.tile_width * self.map.width - \
(x + deltax) + self.width - 1
# Wrap perfectly the map according to the width of the tiles
pmincol = self.pack.tile_width * (pmincol // self.pack.tile_width)
smincol = self.pack.tile_width * (smincol // self.pack.tile_width)
smaxcol = self.pack.tile_width \
* (smaxcol // self.pack.tile_width + 1) - 1
smaxcol = min(smaxcol, self.width - 1)
pminrow = max(0, min(self.map.height, pminrow))
pmincol = max(0, min(self.pack.tile_width * self.map.width, pmincol))
self.pad.erase()
self.update_pad()
self.refresh_pad(self.pad, pminrow, pmincol, sminrow, smincol, smaxrow,
smaxcol)
class StatsDisplay(Display): class StatsDisplay(Display):
""" """
A class to handle the display of the stats of the player. A class to handle the display of the stats of the player.

View File

@ -1,31 +0,0 @@
# Copyright (C) 2020-2021 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later
from squirrelbattle.display.display import Display
from squirrelbattle.game import Game
from squirrelbattle.interfaces import Logs
class LogsDisplay(Display):
"""
A class to handle the display of the logs.
"""
logs: Logs
def __init__(self, *args) -> None:
super().__init__(*args)
self.pad = self.newpad(self.rows, self.cols)
def update(self, game: Game) -> None:
self.logs = game.logs
def display(self) -> None:
messages = self.logs.messages[-self.height:]
messages = messages[::-1]
self.pad.erase()
for i in range(min(self.height, len(messages))):
self.addstr(self.pad, self.height - i - 1, self.x,
messages[i][:self.width])
self.refresh_pad(self.pad, 0, 0, self.y, self.x,
self.y + self.height - 1, self.x + self.width - 1)

View File

@ -1,87 +0,0 @@
# Copyright (C) 2020-2021 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later
from .display import Display
from ..game import Game
from ..interfaces import Map
class MapDisplay(Display):
"""
A class to handle the display of the map.
"""
map: Map
def __init__(self, *args):
super().__init__(*args)
def update(self, game: Game) -> None:
self.map = game.map
self.pad = self.newpad(self.map.height,
self.pack.tile_width * self.map.width + 1)
def update_pad(self) -> None:
for j in range(len(self.map.tiles)):
for i in range(len(self.map.tiles[j])):
if not self.map.seen_tiles[j][i]:
continue
fg, bg = self.map.tiles[j][i].visible_color(self.pack) if \
self.map.visibility[j][i] else \
self.map.tiles[j][i].hidden_color(self.pack)
self.addstr(self.pad, j, self.pack.tile_width * i,
self.map.tiles[j][i].char(self.pack), fg, bg)
for e in self.map.entities:
if self.map.visibility[e.y][e.x]:
self.addstr(self.pad, e.y, self.pack.tile_width * e.x,
self.pack[e.name.upper()],
self.pack.entity_fg_color,
self.pack.entity_bg_color)
# 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
# if player:
# for x in range(self.map.width):
# for y in range(self.map.height):
# if (y,x) in player.paths:
# deltay, deltax = (y - player.paths[(y, x)][0],
# x - player.paths[(y, x)][1])
# if (deltay, deltax) == (-1, 0):
# character = '↓'
# elif (deltay, deltax) == (1, 0):
# character = '↑'
# elif (deltay, deltax) == (0, -1):
# character = '→'
# else:
# character = '←'
# self.addstr(self.pad, y, self.pack.tile_width * x,
# 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
deltay, deltax = (self.height // 2) + 1, (self.width // 2) + 1
pminrow, pmincol = y - deltay, x - deltax
sminrow, smincol = max(-pminrow, 0), max(-pmincol, 0)
deltay, deltax = self.height - deltay, self.width - deltax
smaxrow = self.map.height - (y + deltay) + self.height - 1
smaxrow = min(smaxrow, self.height - 1)
smaxcol = self.pack.tile_width * self.map.width - \
(x + deltax) + self.width - 1
# Wrap perfectly the map according to the width of the tiles
pmincol = self.pack.tile_width * (pmincol // self.pack.tile_width)
smincol = self.pack.tile_width * (smincol // self.pack.tile_width)
smaxcol = self.pack.tile_width \
* (smaxcol // self.pack.tile_width + 1) - 1
smaxcol = min(smaxcol, self.width - 1)
pminrow = max(0, min(self.map.height, pminrow))
pmincol = max(0, min(self.pack.tile_width * self.map.width, pmincol))
self.pad.erase()
self.update_pad()
self.refresh_pad(self.pad, pminrow, pmincol, sminrow, smincol, smaxrow,
smaxcol)

View File

@ -104,7 +104,8 @@ class MainMenuDisplay(Display):
super().__init__(*args) super().__init__(*args)
self.menu = menu self.menu = menu
with open(ResourceManager.get_asset_path("ascii_art.txt"), "r") as file: with open(ResourceManager.get_asset_path("ascii_art-title.txt"), "r")\
as file:
self.title = file.read().split("\n") self.title = file.read().split("\n")
self.pad = self.newpad(max(self.rows, len(self.title) + 30), self.pad = self.newpad(max(self.rows, len(self.title) + 30),
@ -281,3 +282,91 @@ class ChestInventoryDisplay(MenuDisplay):
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2)) self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2))
game.is_in_chest_menu = True game.is_in_chest_menu = True
game.handle_key_pressed(KeyValues.ENTER) game.handle_key_pressed(KeyValues.ENTER)
class CreditsDisplay(Display):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.box = Box(*args, **kwargs)
self.pad = self.newpad(1, 1)
self.ascii_art_displayed = False
def update(self, game: Game) -> None:
return
def display(self) -> None:
self.box.refresh(self.y, self.x, self.height, self.width)
self.box.display()
self.pad.erase()
messages = [
_("Credits"),
"",
"Squirrel Battle",
"",
_("Developers:"),
"Yohann \"ÿnérant\" D'ANELLO",
"Mathilde \"eichhornchen\" DÉPRÉS",
"Nicolas \"nicomarg\" MARGULIES",
"Charles \"charsle\" PEYRAT",
"",
_("Translators:"),
"Hugo \"ifugao\" JACOB (español)",
]
for i, msg in enumerate(messages):
self.addstr(self.pad, i + (self.height - len(messages)) // 2,
(self.width - len(msg)) // 2, msg,
bold=(i == 0), italic=(":" in msg))
if self.ascii_art_displayed:
self.display_ascii_art()
self.refresh_pad(self.pad, 0, 0, self.y + 1, self.x + 1,
self.height + self.y - 2,
self.width + self.x - 2)
def display_ascii_art(self) -> None:
with open(ResourceManager.get_asset_path("ascii-art-ecureuil.txt"))\
as f:
ascii_art = f.read().split("\n")
height, width = len(ascii_art), len(ascii_art[0])
y_offset, x_offset = (self.height - height) // 2,\
(self.width - width) // 2
for i, line in enumerate(ascii_art):
for j, c in enumerate(line):
bg_color = curses.COLOR_WHITE
fg_color = curses.COLOR_BLACK
bold = False
if c == ' ':
bg_color = curses.COLOR_BLACK
elif c == '' or c == '' or c == '':
bold = True
fg_color = curses.COLOR_WHITE
bg_color = curses.COLOR_BLACK
elif c == '|':
bold = True # c = '┃'
fg_color = (100, 700, 1000)
bg_color = curses.COLOR_BLACK
elif c == '':
fg_color = (700, 300, 0)
elif c == '':
fg_color = (700, 300, 0)
bg_color = curses.COLOR_BLACK
elif c == '':
fg_color = (350, 150, 0)
elif c == '':
fg_color = (0, 0, 0)
bg_color = curses.COLOR_BLACK
elif c == '':
c = ''
fg_color = (1000, 1000, 1000)
bg_color = curses.COLOR_BLACK
self.addstr(self.pad, y_offset + i, x_offset + j, c,
fg_color, bg_color, bold=bold)
def handle_click(self, y: int, x: int, attr: int, game: Game) -> None:
if self.pad.inch(y - 1, x - 1) != ord(" "):
self.ascii_art_displayed = True

View File

@ -1,32 +0,0 @@
# Copyright (C) 2020-2021 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later
import curses
from squirrelbattle.display.display import Box, Display
from squirrelbattle.game import Game
class MessageDisplay(Display):
"""
A class to handle the display of popup messages.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.box = Box(fg_border_color=curses.COLOR_RED, *args, **kwargs)
self.message = ""
self.pad = self.newpad(1, 1)
def update(self, game: Game) -> None:
self.message = game.message
def display(self) -> None:
self.box.refresh(self.y - 1, self.x - 2,
self.height + 2, self.width + 4)
self.box.display()
self.pad.erase()
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

@ -3,7 +3,7 @@
from random import choice, shuffle from random import choice, shuffle
from .items import Item from .items import Bomb, Item
from .monsters import Monster from .monsters import Monster
from .player import Player from .player import Player
from ..interfaces import Entity, FightingEntity, FriendlyEntity, \ from ..interfaces import Entity, FightingEntity, FriendlyEntity, \
@ -48,11 +48,14 @@ class Chest(InventoryHolder, FriendlyEntity):
""" """
A class of chest inanimate entities which contain objects. A class of chest inanimate entities which contain objects.
""" """
annihilated: bool
def __init__(self, name: str = "chest", inventory: list = None, def __init__(self, name: str = "chest", inventory: list = None,
hazel: int = 0, *args, **kwargs): hazel: int = 0, *args, **kwargs):
super().__init__(name=name, *args, **kwargs) super().__init__(name=name, *args, **kwargs)
self.hazel = hazel self.hazel = hazel
self.inventory = self.translate_inventory(inventory or []) self.inventory = self.translate_inventory(inventory or [])
self.annihilated = False
if not self.inventory: if not self.inventory:
for i in range(3): for i in range(3):
self.inventory.append(choice(Item.get_all_items())()) self.inventory.append(choice(Item.get_all_items())())
@ -68,6 +71,10 @@ class Chest(InventoryHolder, FriendlyEntity):
""" """
A chest is not living, it can not take damage A chest is not living, it can not take damage
""" """
if isinstance(attacker, Bomb):
self.die()
self.annihilated = True
return _("The chest exploded")
return _("It's not really effective") return _("It's not really effective")
@property @property
@ -75,14 +82,14 @@ class Chest(InventoryHolder, FriendlyEntity):
""" """
Chest can not die Chest can not die
""" """
return False return self.annihilated
class Sunflower(FriendlyEntity): class Sunflower(FriendlyEntity):
""" """
A friendly sunflower. A friendly sunflower.
""" """
def __init__(self, maxhealth: int = 15, def __init__(self, maxhealth: int = 20,
*args, **kwargs) -> None: *args, **kwargs) -> None:
super().__init__(name="sunflower", maxhealth=maxhealth, *args, **kwargs) super().__init__(name="sunflower", maxhealth=maxhealth, *args, **kwargs)
@ -162,6 +169,6 @@ class Trumpet(Familiar):
A class of familiars. A class of familiars.
""" """
def __init__(self, name: str = "trumpet", strength: int = 3, def __init__(self, name: str = "trumpet", strength: int = 3,
maxhealth: int = 20, *args, **kwargs) -> None: maxhealth: int = 30, *args, **kwargs) -> None:
super().__init__(name=name, strength=strength, super().__init__(name=name, strength=strength,
maxhealth=maxhealth, *args, **kwargs) maxhealth=maxhealth, *args, **kwargs)

View File

@ -498,7 +498,7 @@ class ScrollofDamage(Item):
class ScrollofWeakening(Item): class ScrollofWeakening(Item):
""" """
A scroll that, when used, reduces the damage of the ennemies for 3 turn. A scroll that, when used, reduces the damage of the ennemies for 3 turns.
""" """
def __init__(self, name: str = "scroll_of_weakening", price: int = 13, def __init__(self, name: str = "scroll_of_weakening", price: int = 13,
*args, **kwargs): *args, **kwargs):
@ -613,7 +613,7 @@ class FireBallStaff(LongRangeWeapon):
@property @property
def stat(self) -> str: def stat(self) -> str:
""" """
Here it is dexterity Here it is intelligence
""" """
return "intelligence" return "intelligence"

View File

@ -76,8 +76,8 @@ class Tiger(Monster):
""" """
A tiger monster. A tiger monster.
""" """
def __init__(self, name: str = "tiger", strength: int = 2, def __init__(self, name: str = "tiger", strength: int = 5,
maxhealth: int = 20, *args, **kwargs) -> None: maxhealth: int = 30, *args, **kwargs) -> None:
super().__init__(name=name, strength=strength, super().__init__(name=name, strength=strength,
maxhealth=maxhealth, *args, **kwargs) maxhealth=maxhealth, *args, **kwargs)
@ -97,7 +97,7 @@ class Rabbit(Monster):
A rabbit monster. A rabbit monster.
""" """
def __init__(self, name: str = "rabbit", strength: int = 1, def __init__(self, name: str = "rabbit", strength: int = 1,
maxhealth: int = 15, critical: int = 30, maxhealth: int = 20, critical: int = 30,
*args, **kwargs) -> None: *args, **kwargs) -> None:
super().__init__(name=name, strength=strength, super().__init__(name=name, strength=strength,
maxhealth=maxhealth, critical=critical, maxhealth=maxhealth, critical=critical,

View File

@ -1,11 +1,13 @@
# Copyright (C) 2020-2021 by ÿnérant, eichhornchen, nicomarg, charlse # Copyright (C) 2020-2021 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from math import log
from random import randint from random import randint
from typing import Dict, Optional, Tuple from typing import Dict, Optional, Tuple
from .items import Item from .items import Item
from ..interfaces import FightingEntity, InventoryHolder from ..interfaces import FightingEntity, InventoryHolder
from ..translations import gettext as _
class Player(InventoryHolder, FightingEntity): class Player(InventoryHolder, FightingEntity):
@ -61,6 +63,31 @@ class Player(InventoryHolder, FightingEntity):
self.recalculate_paths() self.recalculate_paths()
self.map.compute_visibility(self.y, self.x, self.vision) self.map.compute_visibility(self.y, self.x, self.vision)
def dance(self) -> None:
"""
Dancing has a certain probability or making ennemies unable
to fight for 3 turns. That probability depends on the player's
charisma.
"""
diceroll = randint(1, 10)
found = False
if diceroll <= self.charisma:
for entity in self.map.entities:
if entity.is_fighting_entity() and not entity == self \
and entity.distance(self) <= 3:
found = True
entity.confused = 1
entity.effects.append(["confused", 1, 3])
if found:
self.map.logs.add_message(_(
"It worked! Nearby ennemies will be confused for 3 turns."))
else:
self.map.logs.add_message(_(
"It worked, but there is no one nearby..."))
else:
self.map.logs.add_message(
_("The dance was not effective..."))
def level_up(self) -> None: def level_up(self) -> None:
""" """
Add as many levels as possible to the player. Add as many levels as possible to the player.
@ -69,9 +96,19 @@ class Player(InventoryHolder, FightingEntity):
self.level += 1 self.level += 1
self.current_xp -= self.max_xp self.current_xp -= self.max_xp
self.max_xp = self.level * 10 self.max_xp = self.level * 10
self.maxhealth += int(2 * log(self.level) / log(2))
self.health = self.maxhealth self.health = self.maxhealth
self.strength = self.strength + 1 self.strength = self.strength + 1
# TODO Remove it, that's only fun if self.level % 3 == 0:
self.dexterity += 1
self.constitution += 1
if self.level % 4 == 0:
self.intelligence += 1
if self.level % 6 == 0:
self.charisma += 1
if self.level % 10 == 0 and self.critical < 95:
self.critical += (100 - self.charisma) // 30
# TODO Remove it, that's only for fun
self.map.spawn_random_entities(randint(3 * self.level, self.map.spawn_random_entities(randint(3 * self.level,
10 * self.level)) 10 * self.level))

View File

@ -50,6 +50,7 @@ class KeyValues(Enum):
WAIT = auto() WAIT = auto()
LADDER = auto() LADDER = auto()
LAUNCH = auto() LAUNCH = auto()
DANCE = auto()
@staticmethod @staticmethod
def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]: def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]:
@ -88,4 +89,6 @@ class KeyValues(Enum):
return KeyValues.LADDER return KeyValues.LADDER
elif key == settings.KEY_LAUNCH: elif key == settings.KEY_LAUNCH:
return KeyValues.LAUNCH return KeyValues.LAUNCH
elif key == settings.KEY_DANCE:
return KeyValues.DANCE
return None return None

View File

@ -179,6 +179,9 @@ class Game:
self.map.tick(self.player) self.map.tick(self.player)
elif key == KeyValues.LADDER: elif key == KeyValues.LADDER:
self.handle_ladder() self.handle_ladder()
elif key == KeyValues.DANCE:
self.player.dance()
self.map.tick(self.player)
def handle_ladder(self) -> None: def handle_ladder(self) -> None:
""" """

View File

@ -628,8 +628,9 @@ class Entity:
Rabbit, TeddyBear, GiantSeaEagle Rabbit, TeddyBear, GiantSeaEagle
from squirrelbattle.entities.friendly import Merchant, Sunflower, \ from squirrelbattle.entities.friendly import Merchant, Sunflower, \
Trumpet, Chest Trumpet, Chest
return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear, return [BodySnatchPotion, Bomb, Chest, GiantSeaEagle, Heart,
Sunflower, Tiger, Merchant, GiantSeaEagle, Trumpet, Chest] Hedgehog, Merchant, Rabbit, Sunflower, TeddyBear, Tiger,
Trumpet]
@staticmethod @staticmethod
def get_weights() -> list: def get_weights() -> list:
@ -637,7 +638,7 @@ class Entity:
Returns a weigth list associated to the above function, to Returns a weigth list associated to the above function, to
be used to spawn random entities with a certain probability. be used to spawn random entities with a certain probability.
""" """
return [3, 5, 6, 5, 5, 5, 5, 4, 3, 1, 2, 4] return [30, 80, 50, 1, 100, 100, 60, 70, 70, 20, 40, 40]
@staticmethod @staticmethod
def get_all_entity_classes_in_a_dict() -> dict: def get_all_entity_classes_in_a_dict() -> dict:
@ -706,6 +707,7 @@ class FightingEntity(Entity):
constitution: int constitution: int
level: int level: int
critical: int critical: int
confused: int # Seulement 0 ou 1
def __init__(self, maxhealth: int = 0, health: Optional[int] = None, def __init__(self, maxhealth: int = 0, health: Optional[int] = None,
strength: int = 0, intelligence: int = 0, charisma: int = 0, strength: int = 0, intelligence: int = 0, charisma: int = 0,
@ -722,6 +724,7 @@ class FightingEntity(Entity):
self.level = level self.level = level
self.critical = critical self.critical = critical
self.effects = [] # effects = temporary buff or weakening of the stats. self.effects = [] # effects = temporary buff or weakening of the stats.
self.confused = 0
@property @property
def dead(self) -> bool: def dead(self) -> bool:
@ -749,6 +752,10 @@ class FightingEntity(Entity):
The entity deals damage to the opponent The entity deals damage to the opponent
based on their respective stats. based on their respective stats.
""" """
if self.confused:
return _("{name} is confused, it can not hit {opponent}.")\
.format(name=_(self.translated_name.capitalize()),
opponent=_(opponent.translated_name))
diceroll = randint(1, 100) diceroll = randint(1, 100)
damage = max(0, self.strength) damage = max(0, self.strength)
string = " " string = " "
@ -765,7 +772,7 @@ class FightingEntity(Entity):
The entity takes damage from the attacker The entity takes damage from the attacker
based on their respective stats. based on their respective stats.
""" """
damage = max(0, amount - self.constitution) damage = max(1, amount - self.constitution)
self.health -= damage self.health -= damage
if self.health <= 0: if self.health <= 0:
self.die() self.die()

View File

@ -36,6 +36,7 @@ class Settings:
self.KEY_WAIT = ['w', 'Key used to wait'] self.KEY_WAIT = ['w', 'Key used to wait']
self.KEY_LADDER = ['<', 'Key used to use ladders'] self.KEY_LADDER = ['<', 'Key used to use ladders']
self.KEY_LAUNCH = ['l', 'Key used to use a bow'] self.KEY_LAUNCH = ['l', 'Key used to use a bow']
self.KEY_DANCE = ['y', 'Key used to dance']
self.TEXTURE_PACK = ['ascii', 'Texture pack'] self.TEXTURE_PACK = ['ascii', 'Texture pack']
self.LOCALE = [locale.getlocale()[0][:2], 'Language'] self.LOCALE = [locale.getlocale()[0][:2], 'Language']

View File

@ -4,7 +4,7 @@
import random import random
import unittest import unittest
from ..entities.friendly import Trumpet from ..entities.friendly import Chest, Trumpet
from ..entities.items import BodySnatchPotion, Bomb, Explosion, Heart, Item from ..entities.items import BodySnatchPotion, Bomb, Explosion, Heart, Item
from ..entities.monsters import GiantSeaEagle, Hedgehog, Rabbit, \ from ..entities.monsters import GiantSeaEagle, Hedgehog, Rabbit, \
TeddyBear, Tiger TeddyBear, Tiger
@ -45,18 +45,19 @@ class TestEntities(unittest.TestCase):
""" """
entity = Tiger() entity = Tiger()
self.map.add_entity(entity) self.map.add_entity(entity)
self.assertEqual(entity.maxhealth, 20) self.assertEqual(entity.maxhealth, 30)
self.assertEqual(entity.maxhealth, entity.health) self.assertEqual(entity.maxhealth, entity.health)
self.assertEqual(entity.strength, 2) self.assertEqual(entity.strength, 5)
for _ in range(9): for _ in range(5):
self.assertEqual(entity.hit(entity), self.assertEqual(entity.hit(entity),
"Tiger hits tiger. Tiger takes 2 damage.") "Tiger hits tiger. Tiger takes 5 damage.")
self.assertFalse(entity.dead) self.assertFalse(entity.dead)
self.assertEqual(entity.hit(entity), "Tiger hits tiger. " self.assertEqual(entity.hit(entity), "Tiger hits tiger. "
+ "Tiger takes 2 damage. Tiger dies.") + "Tiger takes 5 damage. Tiger dies.")
self.assertTrue(entity.dead) self.assertTrue(entity.dead)
entity = Rabbit() entity = Rabbit()
entity.health = 15
entity.critical = 0 entity.critical = 0
self.map.add_entity(entity) self.map.add_entity(entity)
entity.move(15, 44) entity.move(15, 44)
@ -94,7 +95,20 @@ class TestEntities(unittest.TestCase):
self.assertTrue(entity.dead) self.assertTrue(entity.dead)
self.assertGreaterEqual(self.player.current_xp, 3) self.assertGreaterEqual(self.player.current_xp, 3)
# Test the familiars # Test that a chest is destroyed by a bomb
bomb = Bomb()
bomb.owner = self.player
bomb.move(3, 6)
self.map.add_entity(bomb)
chest = Chest()
chest.move(4, 6)
self.map.add_entity(chest)
bomb.exploding = True
for _ in range(5):
self.map.tick(self.player)
self.assertTrue(chest.annihilated)
def test_familiar(self) -> None:
fam = Trumpet() fam = Trumpet()
entity = Rabbit() entity = Rabbit()
self.map.add_entity(entity) self.map.add_entity(entity)
@ -266,6 +280,15 @@ class TestEntities(unittest.TestCase):
player_state = player.save_state() player_state = player.save_state()
self.assertEqual(player_state["current_xp"], 10) self.assertEqual(player_state["current_xp"], 10)
player = Player()
player.map = self.map
player.add_xp(700)
for _ in range(13):
player.level_up()
self.assertEqual(player.level, 12)
self.assertEqual(player.critical, 5 + 95 // 30)
self.assertEqual(player.charisma, 3)
def test_critical_hit(self) -> None: def test_critical_hit(self) -> None:
""" """
Ensure that critical hits are working. Ensure that critical hits are working.

View File

@ -160,6 +160,9 @@ class TestGame(unittest.TestCase):
KeyValues.SPACE) KeyValues.SPACE)
self.assertEqual(KeyValues.translate_key('plop', self.game.settings), self.assertEqual(KeyValues.translate_key('plop', self.game.settings),
None) None)
self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_DANCE, self.game.settings),
KeyValues.DANCE)
def test_key_press(self) -> None: def test_key_press(self) -> None:
""" """
@ -249,6 +252,30 @@ class TestGame(unittest.TestCase):
self.game.handle_key_pressed(KeyValues.WAIT) self.game.handle_key_pressed(KeyValues.WAIT)
self.assertNotIn(explosion, self.game.map.entities) self.assertNotIn(explosion, self.game.map.entities)
rabbit = Rabbit()
self.game.map.add_entity(rabbit)
self.game.player.move(1, 6)
rabbit.move(3, 6)
self.game.player.charisma = 11
self.game.handle_key_pressed(KeyValues.DANCE)
self.assertEqual(rabbit.confused, 1)
string = rabbit.hit(self.game.player)
self.assertEqual(string,
"{name} is confused, it can not hit {opponent}."
.format(name=_(rabbit.translated_name.capitalize()
), opponent=_(
self.game.player.translated_name
)))
rabbit.confused = 0
self.game.player.charisma = 0
self.game.handle_key_pressed(KeyValues.DANCE)
self.assertEqual(rabbit.confused, 0)
rabbit.die()
self.game.player.charisma = 11
self.game.handle_key_pressed(KeyValues.DANCE)
self.game.player.charisma = 1
self.game.handle_key_pressed(KeyValues.SPACE) self.game.handle_key_pressed(KeyValues.SPACE)
self.assertEqual(self.game.state, GameMode.MAINMENU) self.assertEqual(self.game.state, GameMode.MAINMENU)
@ -350,7 +377,7 @@ class TestGame(unittest.TestCase):
self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a') self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a')
# Navigate to "texture pack" # Navigate to "texture pack"
for ignored in range(13): for ignored in range(14):
self.game.handle_key_pressed(KeyValues.DOWN) self.game.handle_key_pressed(KeyValues.DOWN)
# Change texture pack # Change texture pack