From dd37c2f62f29c0cf320dc5a5cba6cb4ec5c1206c Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 11:54:49 +0100 Subject: [PATCH 01/12] Renamed the title's ascii art file. --- squirrelbattle/display/menudisplay.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 64d69b7..024ba1c 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -105,7 +105,8 @@ class MainMenuDisplay(Display): super().__init__(*args) 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.pad = self.newpad(max(self.rows, len(self.title) + 30), From 5eb769930154263d9060d98bef2b4553d882c52a Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 12:35:50 +0100 Subject: [PATCH 02/12] Rearranged the display class files, related to issue #56. --- .../{ascii_art.txt => ascii_art-title.txt} | 0 squirrelbattle/display/creditsdisplay.py | 97 ---------------- squirrelbattle/display/display.py | 26 +++++ squirrelbattle/display/display_manager.py | 13 +-- .../{statsdisplay.py => gamedisplay.py} | 108 +++++++++++++++++- squirrelbattle/display/logsdisplay.py | 31 ----- squirrelbattle/display/mapdisplay.py | 87 -------------- squirrelbattle/display/menudisplay.py | 88 ++++++++++++++ squirrelbattle/display/messagedisplay.py | 32 ------ 9 files changed, 226 insertions(+), 256 deletions(-) rename squirrelbattle/assets/{ascii_art.txt => ascii_art-title.txt} (100%) delete mode 100644 squirrelbattle/display/creditsdisplay.py rename squirrelbattle/display/{statsdisplay.py => gamedisplay.py} (55%) delete mode 100644 squirrelbattle/display/logsdisplay.py delete mode 100644 squirrelbattle/display/mapdisplay.py delete mode 100644 squirrelbattle/display/messagedisplay.py diff --git a/squirrelbattle/assets/ascii_art.txt b/squirrelbattle/assets/ascii_art-title.txt similarity index 100% rename from squirrelbattle/assets/ascii_art.txt rename to squirrelbattle/assets/ascii_art-title.txt diff --git a/squirrelbattle/display/creditsdisplay.py b/squirrelbattle/display/creditsdisplay.py deleted file mode 100644 index e005c5b..0000000 --- a/squirrelbattle/display/creditsdisplay.py +++ /dev/null @@ -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 diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 9b6e97c..3bf06c5 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -290,3 +290,29 @@ class Box(Display): self.refresh_pad(self.pad, 0, 0, self.y, self.x, 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) diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 81cba4f..897617a 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -4,14 +4,11 @@ import curses from typing import Any, List -from .creditsdisplay import CreditsDisplay -from .display import Display, HorizontalSplit, VerticalSplit -from .logsdisplay import LogsDisplay -from .mapdisplay import MapDisplay -from .menudisplay import ChestInventoryDisplay, MainMenuDisplay, \ - PlayerInventoryDisplay, SettingsMenuDisplay, StoreInventoryDisplay -from .messagedisplay import MessageDisplay -from .statsdisplay import StatsDisplay +from .display import Display, HorizontalSplit, MessageDisplay, VerticalSplit +from .gamedisplay import LogsDisplay, MapDisplay, StatsDisplay +from .menudisplay import ChestInventoryDisplay, CreditsDisplay, \ + MainMenuDisplay, PlayerInventoryDisplay, \ + SettingsMenuDisplay, StoreInventoryDisplay from .texturepack import TexturePack from ..enums import DisplayActions from ..game import Game, GameMode diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/gamedisplay.py similarity index 55% rename from squirrelbattle/display/statsdisplay.py rename to squirrelbattle/display/gamedisplay.py index b80fa7b..5efaf56 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/gamedisplay.py @@ -7,10 +7,116 @@ from .display import Display from ..entities.items import Monocle from ..entities.player import Player from ..game import Game -from ..interfaces import FightingEntity +from ..interfaces import FightingEntity, Logs, Map 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): """ A class to handle the display of the stats of the player. diff --git a/squirrelbattle/display/logsdisplay.py b/squirrelbattle/display/logsdisplay.py deleted file mode 100644 index 1c323af..0000000 --- a/squirrelbattle/display/logsdisplay.py +++ /dev/null @@ -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) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py deleted file mode 100644 index 80e6ada..0000000 --- a/squirrelbattle/display/mapdisplay.py +++ /dev/null @@ -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) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index a502b63..54acaed 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -282,3 +282,91 @@ class ChestInventoryDisplay(MenuDisplay): self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2)) game.is_in_chest_menu = True 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 diff --git a/squirrelbattle/display/messagedisplay.py b/squirrelbattle/display/messagedisplay.py deleted file mode 100644 index 2b1ec30..0000000 --- a/squirrelbattle/display/messagedisplay.py +++ /dev/null @@ -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) From 951623089373ede620c26aaae20adcbbaf311afc Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 15:38:39 +0100 Subject: [PATCH 03/12] We now have a useful Readme with a getting started manual. --- README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d6898db..36482de 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,60 @@ # 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 use a ladder, use < + +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 -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. From 97ecd13c778abfb610013ef037f98b86a9fe8639 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 15:45:26 +0100 Subject: [PATCH 04/12] Readme got even better ! --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 36482de..4c1356c 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ The game is translated (almost entirely) in English, French, German and Spanish. 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. +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 @@ -59,6 +59,8 @@ There are several special control keys. They can be changed in the settings menu * To wait a turn (rather than moving), use w * 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 From 841c7b9f90dc19b75870d8b3a1b0016a99d3643a Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 16:31:16 +0100 Subject: [PATCH 05/12] Chest can be destroyed by bombs. --- squirrelbattle/entities/friendly.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 36f9db3..ee4f7cd 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -3,7 +3,7 @@ from random import choice, shuffle -from .items import Item +from .items import Item, Bomb from .monsters import Monster from .player import Player from ..interfaces import Entity, FightingEntity, FriendlyEntity, \ @@ -68,6 +68,9 @@ class Chest(InventoryHolder, FriendlyEntity): """ A chest is not living, it can not take damage """ + if isinstance(attacker, Bomb) : + self.die() + return _("The chest exploded") return _("It's not really effective") @property @@ -82,7 +85,7 @@ class Sunflower(FriendlyEntity): """ A friendly sunflower. """ - def __init__(self, maxhealth: int = 15, + def __init__(self, maxhealth: int = 20, *args, **kwargs) -> None: super().__init__(name="sunflower", maxhealth=maxhealth, *args, **kwargs) @@ -162,6 +165,6 @@ class Trumpet(Familiar): A class of familiars. """ 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, maxhealth=maxhealth, *args, **kwargs) From dfb591d410eff4c0e126aef58c700440c6619109 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 16:31:46 +0100 Subject: [PATCH 06/12] The player's stats now get better when levelling up. The strength level and frequency of appearance of entities have been changed to offer bettter game experience. --- squirrelbattle/entities/monsters.py | 6 +++--- squirrelbattle/entities/player.py | 13 ++++++++++++- squirrelbattle/interfaces.py | 9 +++++---- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/squirrelbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py index c654428..8f3c2b5 100644 --- a/squirrelbattle/entities/monsters.py +++ b/squirrelbattle/entities/monsters.py @@ -76,8 +76,8 @@ class Tiger(Monster): """ A tiger monster. """ - def __init__(self, name: str = "tiger", strength: int = 2, - maxhealth: int = 20, *args, **kwargs) -> None: + def __init__(self, name: str = "tiger", strength: int = 5, + maxhealth: int = 30, *args, **kwargs) -> None: super().__init__(name=name, strength=strength, maxhealth=maxhealth, *args, **kwargs) @@ -97,7 +97,7 @@ class Rabbit(Monster): A rabbit monster. """ def __init__(self, name: str = "rabbit", strength: int = 1, - maxhealth: int = 15, critical: int = 30, + maxhealth: int = 20, critical: int = 30, *args, **kwargs) -> None: super().__init__(name=name, strength=strength, maxhealth=maxhealth, critical=critical, diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 8257f85..5b788b2 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -3,6 +3,7 @@ from random import randint from typing import Dict, Optional, Tuple +from math import log from .items import Item from ..interfaces import FightingEntity, InventoryHolder @@ -69,9 +70,19 @@ class Player(InventoryHolder, FightingEntity): self.level += 1 self.current_xp -= self.max_xp self.max_xp = self.level * 10 + self.maxhealth += int(2*log(self.level)/log(2)) self.health = self.maxhealth 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, 10 * self.level)) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 2280909..60a7c82 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -628,8 +628,9 @@ class Entity: Rabbit, TeddyBear, GiantSeaEagle from squirrelbattle.entities.friendly import Merchant, Sunflower, \ Trumpet, Chest - return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear, - Sunflower, Tiger, Merchant, GiantSeaEagle, Trumpet, Chest] + return [BodySnatchPotion, Bomb, Chest, GiantSeaEagle, Heart, + Hedgehog, Merchant, Rabbit, Sunflower, TeddyBear, Tiger, + Trumpet] @staticmethod def get_weights() -> list: @@ -637,7 +638,7 @@ class Entity: Returns a weigth list associated to the above function, to 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 def get_all_entity_classes_in_a_dict() -> dict: @@ -765,7 +766,7 @@ class FightingEntity(Entity): The entity takes damage from the attacker based on their respective stats. """ - damage = max(0, amount - self.constitution) + damage = max(1, amount - self.constitution) self.health -= damage if self.health <= 0: self.die() From 3d48c43886223503587407e0b857d85816e113f2 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 17:10:00 +0100 Subject: [PATCH 07/12] Player can now dance! Closes #69. --- squirrelbattle/entities/items.py | 2 +- squirrelbattle/entities/player.py | 27 +++++++++++++++++++++++++++ squirrelbattle/enums.py | 3 +++ squirrelbattle/game.py | 4 ++++ squirrelbattle/interfaces.py | 6 ++++++ squirrelbattle/settings.py | 1 + 6 files changed, 42 insertions(+), 1 deletion(-) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 94a9e36..ac502ea 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -498,7 +498,7 @@ class ScrollofDamage(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, *args, **kwargs): diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 5b788b2..b3cb986 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -7,6 +7,7 @@ from math import log from .items import Item from ..interfaces import FightingEntity, InventoryHolder +from ..translations import gettext as _, Translator class Player(InventoryHolder, FightingEntity): @@ -62,6 +63,32 @@ class Player(InventoryHolder, FightingEntity): self.recalculate_paths() 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 2 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: """ Add as many levels as possible to the player. diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index b6b4bcd..42bd643 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -50,6 +50,7 @@ class KeyValues(Enum): WAIT = auto() LADDER = auto() LAUNCH = auto() + DANCE = auto() @staticmethod def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]: @@ -88,4 +89,6 @@ class KeyValues(Enum): return KeyValues.LADDER elif key == settings.KEY_LAUNCH: return KeyValues.LAUNCH + elif key == settings.KEY_DANCE: + return KeyValues.DANCE return None diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 56ab6ed..73d45eb 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -179,6 +179,9 @@ class Game: self.map.tick(self.player) elif key == KeyValues.LADDER: self.handle_ladder() + elif key == KeyValues.DANCE: + self.player.dance() + self.map.tick(self.player) def handle_ladder(self) -> None: """ @@ -291,6 +294,7 @@ class Game: if self.player.equipped_main: self.player.equipped_main.throw(direction) + def handle_key_pressed_inventory(self, key: KeyValues) -> None: """ In the inventory menu, we can interact with items or close the menu. diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 60a7c82..3832cef 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -707,6 +707,7 @@ class FightingEntity(Entity): constitution: int level: int critical: int + confused: int # Seulement 0 ou 1 def __init__(self, maxhealth: int = 0, health: Optional[int] = None, strength: int = 0, intelligence: int = 0, charisma: int = 0, @@ -723,6 +724,7 @@ class FightingEntity(Entity): self.level = level self.critical = critical self.effects = [] # effects = temporary buff or weakening of the stats. + self.confused = 0 @property def dead(self) -> bool: @@ -750,6 +752,10 @@ class FightingEntity(Entity): The entity deals damage to the opponent 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) damage = max(0, self.strength) string = " " diff --git a/squirrelbattle/settings.py b/squirrelbattle/settings.py index 92a8b37..3fd27c5 100644 --- a/squirrelbattle/settings.py +++ b/squirrelbattle/settings.py @@ -36,6 +36,7 @@ class Settings: self.KEY_WAIT = ['w', 'Key used to wait'] self.KEY_LADDER = ['<', 'Key used to use ladders'] 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.LOCALE = [locale.getlocale()[0][:2], 'Language'] From afaa12d86bb7e9c612481944c782bd467375bf09 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 17:13:07 +0100 Subject: [PATCH 08/12] Updated doc and README conderning dancing --- README.md | 1 + docs/settings.rst | 3 +++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 4c1356c..ae7be80 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ There are several special control keys, they can be changed in the settings menu * 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)) diff --git a/docs/settings.rst b/docs/settings.rst index 60fa5c1..de17fca 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -27,6 +27,9 @@ Les touches utilisées de base sont : * **Lacher un objet** : r * **Parler** : t * **Attendre** : w +* **Utiliser une arme à distance** : l +* **Dancer** : y +* **Utiliser une échelle** : < Autres ------ From 93e51d9240940011f2d31d8971ace581b189215e Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 18:04:33 +0100 Subject: [PATCH 09/12] Testing + linting (yes there remains two linting errors, i don't know what to do. --- squirrelbattle/entities/friendly.py | 10 +++++--- squirrelbattle/entities/player.py | 23 ++++++++--------- squirrelbattle/game.py | 1 - squirrelbattle/interfaces.py | 6 ++--- squirrelbattle/tests/entities_test.py | 37 ++++++++++++++++++++++----- squirrelbattle/tests/game_test.py | 29 ++++++++++++++++++++- 6 files changed, 79 insertions(+), 27 deletions(-) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index ee4f7cd..57506e9 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -3,7 +3,7 @@ from random import choice, shuffle -from .items import Item, Bomb +from .items import Bomb, Item from .monsters import Monster from .player import Player from ..interfaces import Entity, FightingEntity, FriendlyEntity, \ @@ -48,11 +48,14 @@ class Chest(InventoryHolder, FriendlyEntity): """ A class of chest inanimate entities which contain objects. """ + annihilated: bool + def __init__(self, name: str = "chest", inventory: list = None, hazel: int = 0, *args, **kwargs): super().__init__(name=name, *args, **kwargs) self.hazel = hazel self.inventory = self.translate_inventory(inventory or []) + self.annihilated = False if not self.inventory: for i in range(3): self.inventory.append(choice(Item.get_all_items())()) @@ -68,8 +71,9 @@ class Chest(InventoryHolder, FriendlyEntity): """ A chest is not living, it can not take damage """ - if isinstance(attacker, Bomb) : + if isinstance(attacker, Bomb): self.die() + self.annihilated = True return _("The chest exploded") return _("It's not really effective") @@ -78,7 +82,7 @@ class Chest(InventoryHolder, FriendlyEntity): """ Chest can not die """ - return False + return self.annihilated class Sunflower(FriendlyEntity): diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index b3cb986..7648639 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -1,13 +1,13 @@ # Copyright (C) 2020-2021 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later +from math import log from random import randint from typing import Dict, Optional, Tuple -from math import log from .items import Item from ..interfaces import FightingEntity, InventoryHolder -from ..translations import gettext as _, Translator +from ..translations import gettext as _ class Player(InventoryHolder, FightingEntity): @@ -74,10 +74,10 @@ class Player(InventoryHolder, FightingEntity): 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]) + 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.")) @@ -88,7 +88,6 @@ class Player(InventoryHolder, FightingEntity): self.map.logs.add_message( _("The dance was not effective...")) - def level_up(self) -> None: """ Add as many levels as possible to the player. @@ -97,18 +96,18 @@ class Player(InventoryHolder, FightingEntity): self.level += 1 self.current_xp -= self.max_xp self.max_xp = self.level * 10 - self.maxhealth += int(2*log(self.level)/log(2)) + self.maxhealth += int(2 * log(self.level) / log(2)) self.health = self.maxhealth self.strength = self.strength + 1 - if self.level % 3 == 0 : + if self.level % 3 == 0: self.dexterity += 1 self.constitution += 1 - if self.level % 4 == 0 : + if self.level % 4 == 0: self.intelligence += 1 - if self.level % 6 == 0 : + if self.level % 6 == 0: self.charisma += 1 if self.level % 10 == 0 and self.critical < 95: - self.critical += (100-self.charisma)//30 + self.critical += (100 - self.charisma) // 30 # TODO Remove it, that's only for fun self.map.spawn_random_entities(randint(3 * self.level, 10 * self.level)) diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 73d45eb..bde825f 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -294,7 +294,6 @@ class Game: if self.player.equipped_main: self.player.equipped_main.throw(direction) - def handle_key_pressed_inventory(self, key: KeyValues) -> None: """ In the inventory menu, we can interact with items or close the menu. diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 3832cef..bf8ddbe 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -629,7 +629,7 @@ class Entity: from squirrelbattle.entities.friendly import Merchant, Sunflower, \ Trumpet, Chest return [BodySnatchPotion, Bomb, Chest, GiantSeaEagle, Heart, - Hedgehog, Merchant, Rabbit, Sunflower, TeddyBear, Tiger, + Hedgehog, Merchant, Rabbit, Sunflower, TeddyBear, Tiger, Trumpet] @staticmethod @@ -754,8 +754,8 @@ class FightingEntity(Entity): """ if self.confused: return _("{name} is confused, it can not hit {opponent}.")\ - .format(name=_(self.translated_name.capitalize()), - opponent=_(opponent.translated_name)) + .format(name=_(self.translated_name.capitalize()), + opponent=_(opponent.translated_name)) diceroll = randint(1, 100) damage = max(0, self.strength) string = " " diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index db32877..a0e2548 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -4,7 +4,7 @@ import random import unittest -from ..entities.friendly import Trumpet +from ..entities.friendly import Chest, Trumpet from ..entities.items import BodySnatchPotion, Bomb, Explosion, Heart, Item from ..entities.monsters import GiantSeaEagle, Hedgehog, Rabbit, \ TeddyBear, Tiger @@ -45,18 +45,19 @@ class TestEntities(unittest.TestCase): """ entity = Tiger() self.map.add_entity(entity) - self.assertEqual(entity.maxhealth, 20) + self.assertEqual(entity.maxhealth, 30) self.assertEqual(entity.maxhealth, entity.health) - self.assertEqual(entity.strength, 2) - for _ in range(9): + self.assertEqual(entity.strength, 5) + for _ in range(5): self.assertEqual(entity.hit(entity), - "Tiger hits tiger. Tiger takes 2 damage.") + "Tiger hits tiger. Tiger takes 5 damage.") self.assertFalse(entity.dead) self.assertEqual(entity.hit(entity), "Tiger hits tiger. " - + "Tiger takes 2 damage. Tiger dies.") + + "Tiger takes 5 damage. Tiger dies.") self.assertTrue(entity.dead) entity = Rabbit() + entity.health = 15 entity.critical = 0 self.map.add_entity(entity) entity.move(15, 44) @@ -94,7 +95,20 @@ class TestEntities(unittest.TestCase): self.assertTrue(entity.dead) 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() entity = Rabbit() self.map.add_entity(entity) @@ -266,6 +280,15 @@ class TestEntities(unittest.TestCase): player_state = player.save_state() 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: """ Ensure that critical hits are working. diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index b9de160..a3c3a87 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -160,6 +160,9 @@ class TestGame(unittest.TestCase): KeyValues.SPACE) self.assertEqual(KeyValues.translate_key('plop', self.game.settings), None) + self.assertEqual(KeyValues.translate_key( + self.game.settings.KEY_DANCE, self.game.settings), + KeyValues.DANCE) def test_key_press(self) -> None: """ @@ -249,6 +252,30 @@ class TestGame(unittest.TestCase): self.game.handle_key_pressed(KeyValues.WAIT) 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.assertEqual(self.game.state, GameMode.MAINMENU) @@ -350,7 +377,7 @@ class TestGame(unittest.TestCase): self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a') # Navigate to "texture pack" - for ignored in range(13): + for ignored in range(14): self.game.handle_key_pressed(KeyValues.DOWN) # Change texture pack From e96c50e81a3833060005d514a31eb999b3373c4e Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 18:36:46 +0100 Subject: [PATCH 10/12] Doc for new items. --- docs/entities/items.rst | 73 +++++++++++++++++++++++--------- squirrelbattle/entities/items.py | 2 +- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/docs/entities/items.rst b/docs/entities/items.rst index e4f1c0a..768c8dd 100644 --- a/docs/entities/items.rst +++ b/docs/entities/items.rst @@ -29,10 +29,10 @@ Bombe 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 -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. -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, la bombe est remplacée par un symbole ``%`` ou l'émoji ``💥`` selon le pack de 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 -approche ou qu'il l'achète auprès d'un marchand, il est régénéré automatiquement -de 3 points de vie, et le cœur disparaît. +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. Il est représenté dans le `pack de textures`_ ASCII par le caractère ``❤`` 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. +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 ---- -L'épée est un objet 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 +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 ou dans un coffre. Une fois équipée, l'épée ajoute 3 de force à son porteur. Elle est représentée par les caractères ``†`` et ``🗡️``. @@ -78,38 +87,32 @@ Elle est représentée par les caractères ``†`` et ``🗡️``. Bouclier -------- -Le bouclier est un objet que l'on peut trouver uniquement par achat auprès d'un -marchand pour le coût de 16 Hazels. Il s'équippe dans la main secondaire. -Une fois équipé, le bouclier ajoute 1 de -constitution à son porteur, lui permettant de parer mieux les coups. +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. +Une fois équipé, le bouclier ajoute 2 de constitution à son porteur, lui permettant de parer mieux les coups. Il est représenté par les caractères ``D`` et ``🛡️``. Casque ------ -Le casque est un objet que l'on peut trouver uniquement par achat auprès d'un -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êgats. +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. +Une fois équipé, le casque ajoute 2 de constitution à son porteur, lui permettant de prendre moins de dégâts. Il est représenté par les caractères ``0`` et ``⛑️``. Plastron -------- -Le plastron est un objet que l'on peut trouver uniquement par achat auprès d'un -marchand pour le coût de 30 Hazels. Il s'équippe sur le corps. -Une fois équipé, le casque ajoute 4 de -constitution à son porteur, lui permettant de prendre moins de dêgats. +Le plastron est un type d'armure que l'on peut trouver uniquement par achat 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, lui permettant de prendre moins de dégâts. Il est représenté par les caractères ``(`` et ``🦺``. Anneau ------ -L'anneau est un objet que l'on peut trouver uniquement par achat auprès d'un -marchand. Il s'équippe sur la main secondaire. +Un anneau est un objet que l'on peut trouver uniquement par achat auprès d'un +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 joueur, améliorant sa capacité à se débarasser des monstres. @@ -118,4 +121,32 @@ Il y a plusieurs types d'anneaux : * **Anneau de coup critique**, qui augmente la chance de coup critique de 20%. Il coute 15 Hazels. * **Anneau de gain d'expérience amélioré**, qui multiplie le gain d'expérience du joueur par 2. Il coûte 25 Hazels. -Un anneau est représenté par les caractères ``o`` et ``💍``. \ No newline at end of file +Un anneau est représenté par les caractères ``o`` 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. + +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 flèche 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. \ No newline at end of file diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index ac502ea..2741033 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -613,7 +613,7 @@ class FireBallStaff(LongRangeWeapon): @property def stat(self) -> str: """ - Here it is dexterity + Here it is intelligence """ return "intelligence" From 258bd0d81626e8429497937f53aa8c48abc358f2 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 18:38:56 +0100 Subject: [PATCH 11/12] re-itme doc --- docs/entities/items.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/entities/items.rst b/docs/entities/items.rst index 768c8dd..c9565e1 100644 --- a/docs/entities/items.rst +++ b/docs/entities/items.rst @@ -143,10 +143,14 @@ Un arc est une arme à distance qui s'équippe dans la main principale. Pour l'u 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 flèche 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. \ No newline at end of file +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 ``🪄``. \ No newline at end of file From c9994423cb8685df2ec9f58fe61a77bb7eb1e6b2 Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Sun, 10 Jan 2021 18:44:17 +0100 Subject: [PATCH 12/12] Added dance to doc. --- docs/entities/player.rst | 13 ++++++++++++- squirrelbattle/entities/player.py | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/entities/player.rst b/docs/entities/player.rst index d3a644a..fc0d12c 100644 --- a/docs/entities/player.rst +++ b/docs/entities/player.rst @@ -32,6 +32,10 @@ Déplacement 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. +(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 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 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 ---------- @@ -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 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 -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é. diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 7648639..a241fef 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -66,7 +66,7 @@ class Player(InventoryHolder, FightingEntity): def dance(self) -> None: """ Dancing has a certain probability or making ennemies unable - to fight for 2 turns. That probability depends on the player's + to fight for 3 turns. That probability depends on the player's charisma. """ diceroll = randint(1, 10)