Merge branch 'entities' into 'master'
Entities See merge request ynerant/dungeon-battle!10
This commit is contained in:
commit
eca6b9af1f
|
@ -2,12 +2,19 @@ stages:
|
||||||
- test
|
- test
|
||||||
- quality-assurance
|
- quality-assurance
|
||||||
|
|
||||||
|
py37:
|
||||||
|
stage: test
|
||||||
|
image: python:3.7-alpine
|
||||||
|
before_script:
|
||||||
|
- pip install tox
|
||||||
|
script: tox -e py3
|
||||||
|
|
||||||
py38:
|
py38:
|
||||||
stage: test
|
stage: test
|
||||||
image: python:3.8-alpine
|
image: python:3.8-alpine
|
||||||
before_script:
|
before_script:
|
||||||
- pip install tox
|
- pip install tox
|
||||||
script: tox -e py38
|
script: tox -e py3
|
||||||
|
|
||||||
|
|
||||||
py39:
|
py39:
|
||||||
|
@ -15,7 +22,7 @@ py39:
|
||||||
image: python:3.9-alpine
|
image: python:3.9-alpine
|
||||||
before_script:
|
before_script:
|
||||||
- pip install tox
|
- pip install tox
|
||||||
script: tox -e py39
|
script: tox -e py3
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
stage: quality-assurance
|
stage: quality-assurance
|
||||||
|
|
|
@ -11,5 +11,5 @@ class Bootstrap:
|
||||||
game = Game()
|
game = Game()
|
||||||
game.new_game()
|
game.new_game()
|
||||||
display = DisplayManager(term_manager.screen, game)
|
display = DisplayManager(term_manager.screen, game)
|
||||||
game.display_refresh = display.refresh
|
game.display_actions = display.handle_display_action
|
||||||
game.run(term_manager.screen)
|
game.run(term_manager.screen)
|
||||||
|
|
|
@ -19,17 +19,25 @@ class Display:
|
||||||
def newpad(self, height: int, width: int) -> Union[FakePad, Any]:
|
def newpad(self, height: int, width: int) -> Union[FakePad, Any]:
|
||||||
return curses.newpad(height, width) if self.screen else FakePad()
|
return curses.newpad(height, width) if self.screen else FakePad()
|
||||||
|
|
||||||
def resize(self, y: int, x: int, height: int, width: int) -> None:
|
def init_pair(self, number: int, foreground: int, background: int) -> None:
|
||||||
|
return curses.init_pair(number, foreground, background) \
|
||||||
|
if self.screen else None
|
||||||
|
|
||||||
|
def color_pair(self, number: int) -> int:
|
||||||
|
return curses.color_pair(number) if self.screen else 0
|
||||||
|
|
||||||
|
def resize(self, y: int, x: int, height: int, width: int,
|
||||||
|
resize_pad: bool = True) -> None:
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
self.y = y
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
if self.pad:
|
if hasattr(self, "pad") and resize_pad:
|
||||||
self.pad.resize(height - 1, width - 1)
|
self.pad.resize(self.height - 1, self.width - 1)
|
||||||
|
|
||||||
def refresh(self, *args) -> None:
|
def refresh(self, *args, resize_pad: bool = True) -> None:
|
||||||
if len(args) == 4:
|
if len(args) == 4:
|
||||||
self.resize(*args)
|
self.resize(*args, resize_pad)
|
||||||
self.display()
|
self.display()
|
||||||
|
|
||||||
def display(self) -> None:
|
def display(self) -> None:
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import curses
|
import curses
|
||||||
from dungeonbattle.display.mapdisplay import MapDisplay
|
from dungeonbattle.display.mapdisplay import MapDisplay
|
||||||
from dungeonbattle.display.statsdisplay import StatsDisplay
|
from dungeonbattle.display.statsdisplay import StatsDisplay
|
||||||
from dungeonbattle.display.menudisplay import MainMenuDisplay
|
from dungeonbattle.display.menudisplay import MenuDisplay, MainMenuDisplay
|
||||||
from dungeonbattle.display.texturepack import TexturePack
|
from dungeonbattle.display.texturepack import TexturePack
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from dungeonbattle.game import Game, GameMode
|
from dungeonbattle.game import Game, GameMode
|
||||||
|
from dungeonbattle.enums import DisplayActions
|
||||||
|
|
||||||
|
|
||||||
class DisplayManager:
|
class DisplayManager:
|
||||||
|
@ -17,23 +18,35 @@ class DisplayManager:
|
||||||
self.statsdisplay = StatsDisplay(screen, pack)
|
self.statsdisplay = StatsDisplay(screen, pack)
|
||||||
self.mainmenudisplay = MainMenuDisplay(self.game.main_menu,
|
self.mainmenudisplay = MainMenuDisplay(self.game.main_menu,
|
||||||
screen, pack)
|
screen, pack)
|
||||||
|
self.settingsmenudisplay = MenuDisplay(screen, pack)
|
||||||
self.displays = [self.statsdisplay, self.mapdisplay,
|
self.displays = [self.statsdisplay, self.mapdisplay,
|
||||||
self.mainmenudisplay]
|
self.mainmenudisplay, self.settingsmenudisplay]
|
||||||
self.update_game_components()
|
self.update_game_components()
|
||||||
|
|
||||||
|
def handle_display_action(self, action: DisplayActions) -> None:
|
||||||
|
if action == DisplayActions.REFRESH:
|
||||||
|
self.refresh()
|
||||||
|
elif action == DisplayActions.UPDATE:
|
||||||
|
self.update_game_components()
|
||||||
|
|
||||||
def update_game_components(self) -> None:
|
def update_game_components(self) -> None:
|
||||||
for d in self.displays:
|
for d in self.displays:
|
||||||
d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK)
|
d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK)
|
||||||
self.mapdisplay.update_map(self.game.map)
|
self.mapdisplay.update_map(self.game.map)
|
||||||
self.statsdisplay.update_player(self.game.player)
|
self.statsdisplay.update_player(self.game.player)
|
||||||
|
self.settingsmenudisplay.update_menu(self.game.settings_menu)
|
||||||
|
|
||||||
def refresh(self) -> None:
|
def refresh(self) -> None:
|
||||||
if self.game.state == GameMode.PLAY:
|
if self.game.state == GameMode.PLAY:
|
||||||
self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, self.cols)
|
# The map pad has already the good size
|
||||||
|
self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, self.cols,
|
||||||
|
resize_pad=False)
|
||||||
self.statsdisplay.refresh(self.rows * 4 // 5, 0,
|
self.statsdisplay.refresh(self.rows * 4 // 5, 0,
|
||||||
self.rows // 5, self.cols)
|
self.rows // 5, self.cols)
|
||||||
if self.game.state == GameMode.MAINMENU:
|
if self.game.state == GameMode.MAINMENU:
|
||||||
self.mainmenudisplay.refresh(0, 0, self.rows, self.cols)
|
self.mainmenudisplay.refresh(0, 0, self.rows, self.cols)
|
||||||
|
if self.game.state == GameMode.SETTINGS:
|
||||||
|
self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols - 1)
|
||||||
self.resize_window()
|
self.resize_window()
|
||||||
|
|
||||||
def resize_window(self) -> bool:
|
def resize_window(self) -> bool:
|
||||||
|
|
|
@ -12,25 +12,30 @@ class MapDisplay(Display):
|
||||||
|
|
||||||
def update_map(self, m: Map) -> None:
|
def update_map(self, m: Map) -> None:
|
||||||
self.map = m
|
self.map = m
|
||||||
self.pad = self.newpad(m.height, m.width + 1)
|
self.pad = self.newpad(m.height, self.pack.tile_width * m.width + 1)
|
||||||
|
|
||||||
def update_pad(self) -> None:
|
def update_pad(self) -> None:
|
||||||
self.pad.addstr(0, 0, self.map.draw_string(self.pack))
|
self.init_pair(1, self.pack.tile_fg_color, self.pack.tile_bg_color)
|
||||||
|
self.init_pair(2, self.pack.entity_fg_color, self.pack.entity_bg_color)
|
||||||
|
self.pad.addstr(0, 0, self.map.draw_string(self.pack),
|
||||||
|
self.color_pair(1))
|
||||||
for e in self.map.entities:
|
for e in self.map.entities:
|
||||||
self.pad.addstr(e.y, e.x, self.pack.PLAYER)
|
self.pad.addstr(e.y, self.pack.tile_width * e.x,
|
||||||
|
self.pack[e.name.upper()], self.color_pair(2))
|
||||||
|
|
||||||
def display(self) -> None:
|
def display(self) -> None:
|
||||||
y, x = self.map.currenty, self.map.currentx
|
y, x = self.map.currenty, self.pack.tile_width * self.map.currentx
|
||||||
deltay, deltax = (self.height // 2) + 1, (self.width // 2) + 1
|
deltay, deltax = (self.height // 2) + 1, (self.width // 2) + 1
|
||||||
pminrow, pmincol = y - deltay, x - deltax
|
pminrow, pmincol = y - deltay, x - deltax
|
||||||
sminrow, smincol = max(-pminrow, 0), max(-pmincol, 0)
|
sminrow, smincol = max(-pminrow, 0), max(-pmincol, 0)
|
||||||
deltay, deltax = self.height - deltay, self.width - deltax
|
deltay, deltax = self.height - deltay, self.width - deltax
|
||||||
smaxrow = self.map.height - (y + deltay) + self.height - 1
|
smaxrow = self.map.height - (y + deltay) + self.height - 1
|
||||||
smaxrow = min(smaxrow, self.height - 1)
|
smaxrow = min(smaxrow, self.height - 1)
|
||||||
smaxcol = self.map.width - (x + deltax) + self.width - 1
|
smaxcol = self.pack.tile_width * self.map.width - \
|
||||||
|
(x + deltax) + self.width - 1
|
||||||
smaxcol = min(smaxcol, self.width - 1)
|
smaxcol = min(smaxcol, self.width - 1)
|
||||||
pminrow = max(0, min(self.map.height, pminrow))
|
pminrow = max(0, min(self.map.height, pminrow))
|
||||||
pmincol = max(0, min(self.map.width, pmincol))
|
pmincol = max(0, min(self.pack.tile_width * self.map.width, pmincol))
|
||||||
self.pad.clear()
|
self.pad.clear()
|
||||||
self.update_pad()
|
self.update_pad()
|
||||||
self.pad.refresh(pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol)
|
self.pad.refresh(pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from dungeonbattle.menus import Menu, MainMenu
|
from dungeonbattle.menus import Menu, MainMenu
|
||||||
from .display import Display
|
from .display import Display
|
||||||
|
|
||||||
|
@ -11,7 +13,6 @@ class MenuDisplay(Display):
|
||||||
|
|
||||||
def update_menu(self, menu: Menu) -> None:
|
def update_menu(self, menu: Menu) -> None:
|
||||||
self.menu = menu
|
self.menu = menu
|
||||||
self.values = [str(a) for a in menu.values]
|
|
||||||
self.trueheight = len(self.values)
|
self.trueheight = len(self.values)
|
||||||
self.truewidth = max([len(a) for a in self.values])
|
self.truewidth = max([len(a) for a in self.values])
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@ class MenuDisplay(Display):
|
||||||
|
|
||||||
def update_pad(self) -> None:
|
def update_pad(self) -> None:
|
||||||
for i in range(self.trueheight):
|
for i in range(self.trueheight):
|
||||||
self.pad.addstr(i, 0, " ")
|
self.pad.addstr(i, 0, " " + self.values[i])
|
||||||
# set a marker on the selected line
|
# set a marker on the selected line
|
||||||
self.pad.addstr(self.menu.position, 0, ">")
|
self.pad.addstr(self.menu.position, 0, ">")
|
||||||
|
|
||||||
|
@ -54,6 +55,10 @@ class MenuDisplay(Display):
|
||||||
def preferred_height(self) -> int:
|
def preferred_height(self) -> int:
|
||||||
return self.trueheight + 2
|
return self.trueheight + 2
|
||||||
|
|
||||||
|
@property
|
||||||
|
def values(self) -> List[str]:
|
||||||
|
return [str(a) for a in self.menu.values]
|
||||||
|
|
||||||
|
|
||||||
class MainMenuDisplay(Display):
|
class MainMenuDisplay(Display):
|
||||||
def __init__(self, menu: MainMenu, *args):
|
def __init__(self, menu: MainMenu, *args):
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import curses
|
||||||
|
|
||||||
from .display import Display
|
from .display import Display
|
||||||
|
|
||||||
from dungeonbattle.entities.player import Player
|
from dungeonbattle.entities.player import Player
|
||||||
|
@ -9,6 +11,7 @@ class StatsDisplay(Display):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.pad = self.newpad(self.rows, self.cols)
|
self.pad = self.newpad(self.rows, self.cols)
|
||||||
|
self.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK)
|
||||||
|
|
||||||
def update_player(self, p: Player) -> None:
|
def update_player(self, p: Player) -> None:
|
||||||
self.player = p
|
self.player = p
|
||||||
|
@ -33,9 +36,17 @@ class StatsDisplay(Display):
|
||||||
string3 = string3 + " "
|
string3 = string3 + " "
|
||||||
self.pad.addstr(2, 0, string3)
|
self.pad.addstr(2, 0, string3)
|
||||||
|
|
||||||
|
inventory_str = "Inventaire : " + "".join(
|
||||||
|
self.pack[item.name.upper()] for item in self.player.inventory)
|
||||||
|
self.pad.addstr(3, 0, inventory_str)
|
||||||
|
|
||||||
|
if self.player.dead:
|
||||||
|
self.pad.addstr(4, 0, "VOUS ÊTES MORT",
|
||||||
|
curses.A_BOLD | curses.A_BLINK | curses.A_STANDOUT
|
||||||
|
| self.color_pair(3))
|
||||||
|
|
||||||
def display(self) -> None:
|
def display(self) -> None:
|
||||||
self.pad.clear()
|
self.pad.clear()
|
||||||
self.update_pad()
|
self.update_pad()
|
||||||
self.pad.refresh(0, 0, self.y, self.x,
|
self.pad.refresh(0, 0, self.y, self.x,
|
||||||
2 + self.y,
|
4 + self.y, self.width + self.x)
|
||||||
self.width + self.x)
|
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
|
import curses
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class TexturePack:
|
class TexturePack:
|
||||||
_packs = dict()
|
_packs = dict()
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
|
tile_width: int
|
||||||
|
tile_fg_color: int
|
||||||
|
tile_bg_color: int
|
||||||
|
entity_fg_color: int
|
||||||
|
entity_bg_color: int
|
||||||
EMPTY: str
|
EMPTY: str
|
||||||
WALL: str
|
WALL: str
|
||||||
FLOOR: str
|
FLOOR: str
|
||||||
|
@ -15,23 +24,52 @@ class TexturePack:
|
||||||
self.__dict__.update(**kwargs)
|
self.__dict__.update(**kwargs)
|
||||||
TexturePack._packs[name] = self
|
TexturePack._packs[name] = self
|
||||||
|
|
||||||
|
def __getitem__(self, item: str) -> Any:
|
||||||
|
return self.__dict__[item]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_pack(cls, name: str) -> "TexturePack":
|
def get_pack(cls, name: str) -> "TexturePack":
|
||||||
return cls._packs[name.lower()]
|
return cls._packs[name.lower()]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_next_pack_name(cls, name: str) -> str:
|
||||||
|
return "squirrel" if name == "ascii" else "ascii"
|
||||||
|
|
||||||
|
|
||||||
TexturePack.ASCII_PACK = TexturePack(
|
TexturePack.ASCII_PACK = TexturePack(
|
||||||
name="ascii",
|
name="ascii",
|
||||||
|
tile_width=1,
|
||||||
|
tile_fg_color=curses.COLOR_WHITE,
|
||||||
|
tile_bg_color=curses.COLOR_BLACK,
|
||||||
|
entity_fg_color=curses.COLOR_WHITE,
|
||||||
|
entity_bg_color=curses.COLOR_BLACK,
|
||||||
EMPTY=' ',
|
EMPTY=' ',
|
||||||
WALL='#',
|
WALL='#',
|
||||||
FLOOR='.',
|
FLOOR='.',
|
||||||
PLAYER='@',
|
PLAYER='@',
|
||||||
|
HEDGEHOG='*',
|
||||||
|
HEART='❤',
|
||||||
|
BOMB='o',
|
||||||
|
RABBIT='Y',
|
||||||
|
BEAVER='_',
|
||||||
|
TEDDY_BEAR='8',
|
||||||
)
|
)
|
||||||
|
|
||||||
TexturePack.SQUIRREL_PACK = TexturePack(
|
TexturePack.SQUIRREL_PACK = TexturePack(
|
||||||
name="squirrel",
|
name="squirrel",
|
||||||
EMPTY=' ',
|
tile_width=2,
|
||||||
WALL='█',
|
tile_fg_color=curses.COLOR_WHITE,
|
||||||
FLOOR='.',
|
tile_bg_color=curses.COLOR_BLACK,
|
||||||
PLAYER='🐿️',
|
entity_fg_color=curses.COLOR_WHITE,
|
||||||
|
entity_bg_color=curses.COLOR_WHITE,
|
||||||
|
EMPTY=' ',
|
||||||
|
WALL='🧱',
|
||||||
|
FLOOR='██',
|
||||||
|
PLAYER='🐿 ️',
|
||||||
|
HEDGEHOG='🦔',
|
||||||
|
HEART='💜',
|
||||||
|
BOMB='💣',
|
||||||
|
RABBIT='🐇',
|
||||||
|
BEAVER='🦫',
|
||||||
|
TEDDY_BEAR='🧸',
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,22 +1,46 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from .player import Player
|
||||||
from ..interfaces import Entity, FightingEntity, Map
|
from ..interfaces import Entity, FightingEntity, Map
|
||||||
|
|
||||||
|
|
||||||
class Item(Entity):
|
class Item(Entity):
|
||||||
held: bool
|
held: bool
|
||||||
|
held_by: Optional["Player"]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.held = False
|
self.held = False
|
||||||
|
|
||||||
def drop(self, y: int, x: int) -> None:
|
def drop(self, y: int, x: int) -> None:
|
||||||
self.held = False
|
if self.held:
|
||||||
|
self.held_by.inventory.remove(self)
|
||||||
|
self.held = False
|
||||||
|
self.held_by = None
|
||||||
|
self.map.add_entity(self)
|
||||||
self.move(y, x)
|
self.move(y, x)
|
||||||
|
|
||||||
def hold(self) -> None:
|
def hold(self, player: "Player") -> None:
|
||||||
self.held = True
|
self.held = True
|
||||||
|
self.held_by = player
|
||||||
|
self.map.remove_entity(self)
|
||||||
|
player.inventory.append(self)
|
||||||
|
|
||||||
|
|
||||||
|
class Heart(Item):
|
||||||
|
name: str = "heart"
|
||||||
|
healing: int = 5
|
||||||
|
|
||||||
|
def hold(self, player: "Player") -> None:
|
||||||
|
"""
|
||||||
|
When holding a heart, heal the player and don't put item in inventory.
|
||||||
|
"""
|
||||||
|
player.health = min(player.maxhealth, player.health + self.healing)
|
||||||
|
self.map.remove_entity(self)
|
||||||
|
|
||||||
|
|
||||||
class Bomb(Item):
|
class Bomb(Item):
|
||||||
|
name: str = "bomb"
|
||||||
damage: int = 5
|
damage: int = 5
|
||||||
exploding: bool
|
exploding: bool
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,58 @@
|
||||||
|
from random import choice
|
||||||
|
|
||||||
|
from .player import Player
|
||||||
from ..interfaces import FightingEntity, Map
|
from ..interfaces import FightingEntity, Map
|
||||||
|
|
||||||
|
|
||||||
class Monster(FightingEntity):
|
class Monster(FightingEntity):
|
||||||
def act(self, m: Map) -> None:
|
def act(self, m: Map) -> None:
|
||||||
pass
|
"""
|
||||||
|
By default, a monster will move randomly where it is possible
|
||||||
|
And if a player is close to the monster, the monster run on the player.
|
||||||
|
"""
|
||||||
|
target = None
|
||||||
|
for entity in m.entities:
|
||||||
|
if self.distance_squared(entity) <= 25 and \
|
||||||
|
isinstance(entity, Player):
|
||||||
|
target = entity
|
||||||
|
break
|
||||||
|
|
||||||
|
# A Dijkstra algorithm has ran that targets the player.
|
||||||
|
# With that way, monsters can simply follow the path.
|
||||||
|
# If they can't move and they are already close to the player,
|
||||||
|
# They hit.
|
||||||
|
if target and (self.y, self.x) in target.paths:
|
||||||
|
# Move to target player
|
||||||
|
next_y, next_x = target.paths[(self.y, self.x)]
|
||||||
|
moved = self.check_move(next_y, next_x, True)
|
||||||
|
if not moved and self.distance_squared(target) <= 1:
|
||||||
|
self.hit(target)
|
||||||
|
else:
|
||||||
|
for _ in range(100):
|
||||||
|
if choice([self.move_up, self.move_down,
|
||||||
|
self.move_left, self.move_right])():
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
class Squirrel(Monster):
|
class Beaver(Monster):
|
||||||
|
name = "beaver"
|
||||||
|
maxhealth = 30
|
||||||
|
strength = 2
|
||||||
|
|
||||||
|
|
||||||
|
class Hedgehog(Monster):
|
||||||
|
name = "hedgehog"
|
||||||
maxhealth = 10
|
maxhealth = 10
|
||||||
strength = 3
|
strength = 3
|
||||||
|
|
||||||
|
|
||||||
|
class Rabbit(Monster):
|
||||||
|
name = "rabbit"
|
||||||
|
maxhealth = 15
|
||||||
|
strength = 1
|
||||||
|
|
||||||
|
|
||||||
|
class TeddyBear(Monster):
|
||||||
|
name = "teddy_bear"
|
||||||
|
maxhealth = 50
|
||||||
|
strength = 0
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
|
from random import randint
|
||||||
|
from typing import Dict, Tuple
|
||||||
|
|
||||||
from ..interfaces import FightingEntity
|
from ..interfaces import FightingEntity
|
||||||
|
|
||||||
|
|
||||||
class Player(FightingEntity):
|
class Player(FightingEntity):
|
||||||
|
name = "player"
|
||||||
maxhealth: int = 20
|
maxhealth: int = 20
|
||||||
strength: int = 5
|
strength: int = 5
|
||||||
intelligence: int = 1
|
intelligence: int = 1
|
||||||
|
@ -11,25 +15,88 @@ class Player(FightingEntity):
|
||||||
level: int = 1
|
level: int = 1
|
||||||
current_xp: int = 0
|
current_xp: int = 0
|
||||||
max_xp: int = 10
|
max_xp: int = 10
|
||||||
|
inventory: list
|
||||||
|
paths: Dict[Tuple[int, int], Tuple[int, int]]
|
||||||
|
|
||||||
def move_up(self) -> bool:
|
def __init__(self):
|
||||||
return self.check_move(self.y - 1, self.x, True)
|
super().__init__()
|
||||||
|
self.inventory = list()
|
||||||
|
|
||||||
def move_down(self) -> bool:
|
def move(self, y: int, x: int) -> None:
|
||||||
return self.check_move(self.y + 1, self.x, True)
|
"""
|
||||||
|
When the player moves, move the camera of the map.
|
||||||
def move_left(self) -> bool:
|
"""
|
||||||
return self.check_move(self.y, self.x - 1, True)
|
super().move(y, x)
|
||||||
|
self.map.currenty = y
|
||||||
def move_right(self) -> bool:
|
self.map.currentx = x
|
||||||
return self.check_move(self.y, self.x + 1, True)
|
self.recalculate_paths()
|
||||||
|
|
||||||
def level_up(self) -> None:
|
def level_up(self) -> None:
|
||||||
|
"""
|
||||||
|
Add levels to the player as much as it is possible.
|
||||||
|
"""
|
||||||
while self.current_xp > self.max_xp:
|
while self.current_xp > self.max_xp:
|
||||||
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.health = self.maxhealth
|
||||||
|
# TODO Remove it, that's only fun
|
||||||
|
self.map.spawn_random_entities(randint(3 * self.level,
|
||||||
|
10 * self.level))
|
||||||
|
|
||||||
def add_xp(self, xp: int) -> None:
|
def add_xp(self, xp: int) -> None:
|
||||||
|
"""
|
||||||
|
Add some experience to the player.
|
||||||
|
If the required amount is reached, level up.
|
||||||
|
"""
|
||||||
self.current_xp += xp
|
self.current_xp += xp
|
||||||
self.level_up()
|
self.level_up()
|
||||||
|
|
||||||
|
# noinspection PyTypeChecker,PyUnresolvedReferences
|
||||||
|
def check_move(self, y: int, x: int, move_if_possible: bool = False) \
|
||||||
|
-> bool:
|
||||||
|
"""
|
||||||
|
If the player tries to move but a fighting entity is there,
|
||||||
|
the player fights this entity.
|
||||||
|
It rewards some XP if it is dead.
|
||||||
|
"""
|
||||||
|
# Don't move if we are dead
|
||||||
|
if self.dead:
|
||||||
|
return False
|
||||||
|
for entity in self.map.entities:
|
||||||
|
if entity.y == y and entity.x == x:
|
||||||
|
if entity.is_fighting_entity():
|
||||||
|
self.hit(entity)
|
||||||
|
if entity.dead:
|
||||||
|
self.add_xp(randint(3, 7))
|
||||||
|
return True
|
||||||
|
elif entity.is_item():
|
||||||
|
entity.hold(self)
|
||||||
|
return super().check_move(y, x, move_if_possible)
|
||||||
|
|
||||||
|
def recalculate_paths(self, max_distance: int = 8) -> None:
|
||||||
|
"""
|
||||||
|
Use Dijkstra algorithm to calculate best paths
|
||||||
|
for monsters to go to the player.
|
||||||
|
"""
|
||||||
|
queue = [(self.y, self.x)]
|
||||||
|
visited = []
|
||||||
|
distances = {(self.y, self.x): 0}
|
||||||
|
predecessors = {}
|
||||||
|
while queue:
|
||||||
|
y, x = queue.pop(0)
|
||||||
|
visited.append((y, x))
|
||||||
|
if distances[(y, x)] >= max_distance:
|
||||||
|
continue
|
||||||
|
for diff_y, diff_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
|
||||||
|
new_y, new_x = y + diff_y, x + diff_x
|
||||||
|
if not 0 <= new_y < self.map.height or \
|
||||||
|
not 0 <= new_x < self.map.width or \
|
||||||
|
not self.map.tiles[y][x].can_walk() or \
|
||||||
|
(new_y, new_x) in visited or \
|
||||||
|
(new_y, new_x) in queue:
|
||||||
|
continue
|
||||||
|
predecessors[(new_y, new_x)] = (y, x)
|
||||||
|
distances[(new_y, new_x)] = distances[(y, x)] + 1
|
||||||
|
queue.append((new_y, new_x))
|
||||||
|
self.paths = predecessors
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
from enum import Enum, auto
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from dungeonbattle.settings import Settings
|
||||||
|
|
||||||
|
|
||||||
|
class DisplayActions(Enum):
|
||||||
|
REFRESH = auto()
|
||||||
|
UPDATE = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class GameMode(Enum):
|
||||||
|
MAINMENU = auto()
|
||||||
|
PLAY = auto()
|
||||||
|
SETTINGS = auto()
|
||||||
|
INVENTORY = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class KeyValues(Enum):
|
||||||
|
UP = auto()
|
||||||
|
DOWN = auto()
|
||||||
|
LEFT = auto()
|
||||||
|
RIGHT = auto()
|
||||||
|
ENTER = auto()
|
||||||
|
SPACE = auto()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]:
|
||||||
|
"""
|
||||||
|
Translate the raw string key into an enum value that we can use.
|
||||||
|
"""
|
||||||
|
if key in (settings.KEY_DOWN_SECONDARY,
|
||||||
|
settings.KEY_DOWN_PRIMARY):
|
||||||
|
return KeyValues.DOWN
|
||||||
|
elif key in (settings.KEY_LEFT_PRIMARY,
|
||||||
|
settings.KEY_LEFT_SECONDARY):
|
||||||
|
return KeyValues.LEFT
|
||||||
|
elif key in (settings.KEY_RIGHT_PRIMARY,
|
||||||
|
settings.KEY_RIGHT_SECONDARY):
|
||||||
|
return KeyValues.RIGHT
|
||||||
|
elif key in (settings.KEY_UP_PRIMARY,
|
||||||
|
settings.KEY_UP_SECONDARY):
|
||||||
|
return KeyValues.UP
|
||||||
|
elif key == settings.KEY_ENTER:
|
||||||
|
return KeyValues.ENTER
|
||||||
|
elif key == ' ':
|
||||||
|
return KeyValues.SPACE
|
||||||
|
return None
|
|
@ -1,34 +1,18 @@
|
||||||
import sys
|
from random import randint
|
||||||
from typing import Any
|
from typing import Any, Optional
|
||||||
|
|
||||||
from .entities.player import Player
|
from .entities.player import Player
|
||||||
|
from .enums import GameMode, KeyValues, DisplayActions
|
||||||
from .interfaces import Map
|
from .interfaces import Map
|
||||||
from .settings import Settings
|
from .settings import Settings
|
||||||
from enum import Enum, auto
|
|
||||||
from . import menus
|
from . import menus
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
|
|
||||||
class GameMode(Enum):
|
|
||||||
MAINMENU = auto()
|
|
||||||
PLAY = auto()
|
|
||||||
SETTINGS = auto()
|
|
||||||
INVENTORY = auto()
|
|
||||||
|
|
||||||
|
|
||||||
class KeyValues(Enum):
|
|
||||||
UP = auto()
|
|
||||||
DOWN = auto()
|
|
||||||
LEFT = auto()
|
|
||||||
RIGHT = auto()
|
|
||||||
ENTER = auto()
|
|
||||||
SPACE = auto()
|
|
||||||
|
|
||||||
|
|
||||||
class Game:
|
class Game:
|
||||||
map: Map
|
map: Map
|
||||||
player: Player
|
player: Player
|
||||||
display_refresh: Callable[[], None]
|
display_actions: Callable[[DisplayActions], None]
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -36,21 +20,22 @@ class Game:
|
||||||
"""
|
"""
|
||||||
self.state = GameMode.MAINMENU
|
self.state = GameMode.MAINMENU
|
||||||
self.main_menu = menus.MainMenu()
|
self.main_menu = menus.MainMenu()
|
||||||
|
self.settings_menu = menus.SettingsMenu()
|
||||||
self.settings = Settings()
|
self.settings = Settings()
|
||||||
self.settings.load_settings()
|
self.settings.load_settings()
|
||||||
self.settings.write_settings()
|
self.settings.write_settings()
|
||||||
|
self.settings_menu.update_values(self.settings)
|
||||||
|
|
||||||
def new_game(self) -> None:
|
def new_game(self) -> None:
|
||||||
"""
|
"""
|
||||||
Create a new game on the screen.
|
Create a new game on the screen.
|
||||||
"""
|
"""
|
||||||
# TODO generate a new map procedurally
|
# TODO generate a new map procedurally
|
||||||
self.map = Map.load("resources/example_map.txt")
|
self.map = Map.load("resources/example_map_2.txt")
|
||||||
self.map.currenty = 1
|
|
||||||
self.map.currentx = 6
|
|
||||||
self.player = Player()
|
self.player = Player()
|
||||||
self.player.move(1, 6)
|
|
||||||
self.map.add_entity(self.player)
|
self.map.add_entity(self.player)
|
||||||
|
self.player.move(self.map.start_y, self.map.start_x)
|
||||||
|
self.map.spawn_random_entities(randint(3, 10))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_game(filename: str) -> None:
|
def load_game(filename: str) -> None:
|
||||||
|
@ -63,35 +48,16 @@ class Game:
|
||||||
We wait for a player action, then we do what that should be done
|
We wait for a player action, then we do what that should be done
|
||||||
when the given key got pressed.
|
when the given key got pressed.
|
||||||
"""
|
"""
|
||||||
while True:
|
while True: # pragma no cover
|
||||||
screen.clear()
|
screen.clear()
|
||||||
screen.refresh()
|
screen.refresh()
|
||||||
self.display_refresh()
|
self.display_actions(DisplayActions.REFRESH)
|
||||||
key = screen.getkey()
|
key = screen.getkey()
|
||||||
self.handle_key_pressed(self.translate_key(key))
|
self.handle_key_pressed(
|
||||||
|
KeyValues.translate_key(key, self.settings), key)
|
||||||
|
|
||||||
def translate_key(self, key: str) -> KeyValues:
|
def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str = '')\
|
||||||
"""
|
-> None:
|
||||||
Translate the raw string key into an enum value that we can use.
|
|
||||||
"""
|
|
||||||
if key in (self.settings.KEY_DOWN_SECONDARY,
|
|
||||||
self.settings.KEY_DOWN_PRIMARY):
|
|
||||||
return KeyValues.DOWN
|
|
||||||
elif key in (self.settings.KEY_LEFT_PRIMARY,
|
|
||||||
self.settings.KEY_LEFT_SECONDARY):
|
|
||||||
return KeyValues.LEFT
|
|
||||||
elif key in (self.settings.KEY_RIGHT_PRIMARY,
|
|
||||||
self.settings.KEY_RIGHT_SECONDARY):
|
|
||||||
return KeyValues.RIGHT
|
|
||||||
elif key in (self.settings.KEY_UP_PRIMARY,
|
|
||||||
self.settings.KEY_UP_SECONDARY):
|
|
||||||
return KeyValues.UP
|
|
||||||
elif key == self.settings.KEY_ENTER:
|
|
||||||
return KeyValues.ENTER
|
|
||||||
elif key == ' ':
|
|
||||||
return KeyValues.SPACE
|
|
||||||
|
|
||||||
def handle_key_pressed(self, key: KeyValues) -> None:
|
|
||||||
"""
|
"""
|
||||||
Indicates what should be done when the given key is pressed,
|
Indicates what should be done when the given key is pressed,
|
||||||
according to the current game state.
|
according to the current game state.
|
||||||
|
@ -99,46 +65,26 @@ class Game:
|
||||||
if self.state == GameMode.PLAY:
|
if self.state == GameMode.PLAY:
|
||||||
self.handle_key_pressed_play(key)
|
self.handle_key_pressed_play(key)
|
||||||
elif self.state == GameMode.MAINMENU:
|
elif self.state == GameMode.MAINMENU:
|
||||||
self.handle_key_pressed_main_menu(key)
|
self.main_menu.handle_key_pressed(key, self)
|
||||||
elif self.state == GameMode.SETTINGS:
|
elif self.state == GameMode.SETTINGS:
|
||||||
self.handle_key_pressed_settings(key)
|
self.settings_menu.handle_key_pressed(key, raw_key, self)
|
||||||
self.display_refresh()
|
self.display_actions(DisplayActions.REFRESH)
|
||||||
|
|
||||||
def handle_key_pressed_play(self, key: KeyValues) -> None:
|
def handle_key_pressed_play(self, key: KeyValues) -> None:
|
||||||
"""
|
"""
|
||||||
In play mode, arrows or zqsd should move the main character.
|
In play mode, arrows or zqsd should move the main character.
|
||||||
"""
|
"""
|
||||||
if key == KeyValues.UP:
|
if key == KeyValues.UP:
|
||||||
self.player.move_up()
|
if self.player.move_up():
|
||||||
|
self.map.tick()
|
||||||
elif key == KeyValues.DOWN:
|
elif key == KeyValues.DOWN:
|
||||||
self.player.move_down()
|
if self.player.move_down():
|
||||||
|
self.map.tick()
|
||||||
elif key == KeyValues.LEFT:
|
elif key == KeyValues.LEFT:
|
||||||
self.player.move_left()
|
if self.player.move_left():
|
||||||
|
self.map.tick()
|
||||||
elif key == KeyValues.RIGHT:
|
elif key == KeyValues.RIGHT:
|
||||||
self.player.move_right()
|
if self.player.move_right():
|
||||||
|
self.map.tick()
|
||||||
elif key == KeyValues.SPACE:
|
elif key == KeyValues.SPACE:
|
||||||
self.state = GameMode.MAINMENU
|
self.state = GameMode.MAINMENU
|
||||||
|
|
||||||
def handle_key_pressed_main_menu(self, key: KeyValues) -> None:
|
|
||||||
"""
|
|
||||||
In the main menu, we can navigate through options.
|
|
||||||
"""
|
|
||||||
if key == KeyValues.DOWN:
|
|
||||||
self.main_menu.go_down()
|
|
||||||
if key == KeyValues.UP:
|
|
||||||
self.main_menu.go_up()
|
|
||||||
if key == KeyValues.ENTER:
|
|
||||||
option = self.main_menu.validate()
|
|
||||||
if option == menus.MainMenuValues.START:
|
|
||||||
self.state = GameMode.PLAY
|
|
||||||
elif option == menus.MainMenuValues.SETTINGS:
|
|
||||||
self.state = GameMode.SETTINGS
|
|
||||||
elif option == menus.MainMenuValues.EXIT:
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
def handle_key_pressed_settings(self, key: KeyValues) -> None:
|
|
||||||
"""
|
|
||||||
For now, in the settings mode, we can only go backwards.
|
|
||||||
"""
|
|
||||||
if key == KeyValues.SPACE:
|
|
||||||
self.state = GameMode.MAINMENU
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
|
from math import sqrt
|
||||||
|
from random import choice, randint
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from dungeonbattle.display.texturepack import TexturePack
|
from dungeonbattle.display.texturepack import TexturePack
|
||||||
|
|
||||||
|
@ -11,15 +14,21 @@ class Map:
|
||||||
"""
|
"""
|
||||||
width: int
|
width: int
|
||||||
height: int
|
height: int
|
||||||
tiles: list
|
start_y: int
|
||||||
|
start_x: int
|
||||||
|
tiles: List[List["Tile"]]
|
||||||
|
entities: List["Entity"]
|
||||||
# coordinates of the point that should be
|
# coordinates of the point that should be
|
||||||
# on the topleft corner of the screen
|
# on the topleft corner of the screen
|
||||||
currentx: int
|
currentx: int
|
||||||
currenty: int
|
currenty: int
|
||||||
|
|
||||||
def __init__(self, width: int, height: int, tiles: list):
|
def __init__(self, width: int, height: int, tiles: list,
|
||||||
|
start_y: int, start_x: int):
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
|
self.start_y = start_y
|
||||||
|
self.start_x = start_x
|
||||||
self.tiles = tiles
|
self.tiles = tiles
|
||||||
self.entities = []
|
self.entities = []
|
||||||
|
|
||||||
|
@ -30,8 +39,22 @@ class Map:
|
||||||
self.entities.append(entity)
|
self.entities.append(entity)
|
||||||
entity.map = self
|
entity.map = self
|
||||||
|
|
||||||
|
def remove_entity(self, entity: "Entity") -> None:
|
||||||
|
"""
|
||||||
|
Unregister an entity from the map.
|
||||||
|
"""
|
||||||
|
self.entities.remove(entity)
|
||||||
|
|
||||||
|
def is_free(self, y: int, x: int) -> bool:
|
||||||
|
"""
|
||||||
|
Indicates that the case at the coordinates (y, x) is empty.
|
||||||
|
"""
|
||||||
|
return 0 <= y < self.height and 0 <= x < self.width and \
|
||||||
|
self.tiles[y][x].can_walk() and \
|
||||||
|
not any(entity.x == x and entity.y == y for entity in self.entities)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load(filename: str):
|
def load(filename: str) -> "Map":
|
||||||
"""
|
"""
|
||||||
Read a file that contains the content of a map, and build a Map object.
|
Read a file that contains the content of a map, and build a Map object.
|
||||||
"""
|
"""
|
||||||
|
@ -40,18 +63,20 @@ class Map:
|
||||||
return Map.load_from_string(file)
|
return Map.load_from_string(file)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_from_string(content: str):
|
def load_from_string(content: str) -> "Map":
|
||||||
"""
|
"""
|
||||||
Load a map represented by its characters and build a Map object.
|
Load a map represented by its characters and build a Map object.
|
||||||
"""
|
"""
|
||||||
lines = content.split("\n")
|
lines = content.split("\n")
|
||||||
lines = [line for line in lines if line]
|
first_line = lines[0]
|
||||||
|
start_y, start_x = map(int, first_line.split(" "))
|
||||||
|
lines = [line for line in lines[1:] if line]
|
||||||
height = len(lines)
|
height = len(lines)
|
||||||
width = len(lines[0])
|
width = len(lines[0])
|
||||||
tiles = [[Tile.from_ascii_char(c)
|
tiles = [[Tile.from_ascii_char(c)
|
||||||
for x, c in enumerate(line)] for y, line in enumerate(lines)]
|
for x, c in enumerate(line)] for y, line in enumerate(lines)]
|
||||||
|
|
||||||
return Map(width, height, tiles)
|
return Map(width, height, tiles, start_y, start_x)
|
||||||
|
|
||||||
def draw_string(self, pack: TexturePack) -> str:
|
def draw_string(self, pack: TexturePack) -> str:
|
||||||
"""
|
"""
|
||||||
|
@ -61,6 +86,28 @@ class Map:
|
||||||
return "\n".join("".join(tile.char(pack) for tile in line)
|
return "\n".join("".join(tile.char(pack) for tile in line)
|
||||||
for line in self.tiles)
|
for line in self.tiles)
|
||||||
|
|
||||||
|
def spawn_random_entities(self, count: int) -> None:
|
||||||
|
"""
|
||||||
|
Put randomly {count} hedgehogs on the map, where it is available.
|
||||||
|
"""
|
||||||
|
for _ in range(count):
|
||||||
|
y, x = 0, 0
|
||||||
|
while True:
|
||||||
|
y, x = randint(0, self.height - 1), randint(0, self.width - 1)
|
||||||
|
tile = self.tiles[y][x]
|
||||||
|
if tile.can_walk():
|
||||||
|
break
|
||||||
|
entity = choice(Entity.get_all_entity_classes())()
|
||||||
|
entity.move(y, x)
|
||||||
|
self.add_entity(entity)
|
||||||
|
|
||||||
|
def tick(self) -> None:
|
||||||
|
"""
|
||||||
|
Trigger all entity events.
|
||||||
|
"""
|
||||||
|
for entity in self.entities:
|
||||||
|
entity.act(self)
|
||||||
|
|
||||||
|
|
||||||
class Tile(Enum):
|
class Tile(Enum):
|
||||||
EMPTY = auto()
|
EMPTY = auto()
|
||||||
|
@ -84,7 +131,7 @@ class Tile(Enum):
|
||||||
"""
|
"""
|
||||||
Check if an entity (player or not) can move in this tile.
|
Check if an entity (player or not) can move in this tile.
|
||||||
"""
|
"""
|
||||||
return not self.is_wall()
|
return not self.is_wall() and self != Tile.EMPTY
|
||||||
|
|
||||||
|
|
||||||
class Entity:
|
class Entity:
|
||||||
|
@ -99,14 +146,31 @@ class Entity:
|
||||||
|
|
||||||
def check_move(self, y: int, x: int, move_if_possible: bool = False)\
|
def check_move(self, y: int, x: int, move_if_possible: bool = False)\
|
||||||
-> bool:
|
-> bool:
|
||||||
tile = self.map.tiles[y][x]
|
free = self.map.is_free(y, x)
|
||||||
if tile.can_walk() and move_if_possible:
|
if free and move_if_possible:
|
||||||
self.move(y, x)
|
self.move(y, x)
|
||||||
return tile.can_walk()
|
return free
|
||||||
|
|
||||||
def move(self, y: int, x: int) -> None:
|
def move(self, y: int, x: int) -> bool:
|
||||||
self.y = y
|
self.y = y
|
||||||
self.x = x
|
self.x = x
|
||||||
|
return True
|
||||||
|
|
||||||
|
def move_up(self, force: bool = False) -> bool:
|
||||||
|
return self.move(self.y - 1, self.x) if force else \
|
||||||
|
self.check_move(self.y - 1, self.x, True)
|
||||||
|
|
||||||
|
def move_down(self, force: bool = False) -> bool:
|
||||||
|
return self.move(self.y + 1, self.x) if force else \
|
||||||
|
self.check_move(self.y + 1, self.x, True)
|
||||||
|
|
||||||
|
def move_left(self, force: bool = False) -> bool:
|
||||||
|
return self.move(self.y, self.x - 1) if force else \
|
||||||
|
self.check_move(self.y, self.x - 1, True)
|
||||||
|
|
||||||
|
def move_right(self, force: bool = False) -> bool:
|
||||||
|
return self.move(self.y, self.x + 1) if force else \
|
||||||
|
self.check_move(self.y, self.x + 1, True)
|
||||||
|
|
||||||
def act(self, m: Map) -> None:
|
def act(self, m: Map) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -115,6 +179,33 @@ class Entity:
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def distance_squared(self, other: "Entity") -> int:
|
||||||
|
"""
|
||||||
|
Get the square of the distance to another entity.
|
||||||
|
Useful to check distances since square root takes time.
|
||||||
|
"""
|
||||||
|
return (self.y - other.y) ** 2 + (self.x - other.x) ** 2
|
||||||
|
|
||||||
|
def distance(self, other: "Entity") -> float:
|
||||||
|
"""
|
||||||
|
Get the cartesian distance to another entity.
|
||||||
|
"""
|
||||||
|
return sqrt(self.distance_squared(other))
|
||||||
|
|
||||||
|
def is_fighting_entity(self) -> bool:
|
||||||
|
return isinstance(self, FightingEntity)
|
||||||
|
|
||||||
|
def is_item(self) -> bool:
|
||||||
|
from dungeonbattle.entities.items import Item
|
||||||
|
return isinstance(self, Item)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_all_entity_classes():
|
||||||
|
from dungeonbattle.entities.items import Heart, Bomb
|
||||||
|
from dungeonbattle.entities.monsters import Beaver, Hedgehog, \
|
||||||
|
Rabbit, TeddyBear
|
||||||
|
return [Beaver, Bomb, Heart, Hedgehog, Rabbit, TeddyBear]
|
||||||
|
|
||||||
|
|
||||||
class FightingEntity(Entity):
|
class FightingEntity(Entity):
|
||||||
maxhealth: int
|
maxhealth: int
|
||||||
|
@ -142,3 +233,4 @@ class FightingEntity(Entity):
|
||||||
|
|
||||||
def die(self) -> None:
|
def die(self) -> None:
|
||||||
self.dead = True
|
self.dead = True
|
||||||
|
self.map.remove_entity(self)
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
|
import sys
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from .display.texturepack import TexturePack
|
||||||
|
from .enums import GameMode, KeyValues, DisplayActions
|
||||||
|
from .settings import Settings
|
||||||
|
|
||||||
|
|
||||||
class Menu:
|
class Menu:
|
||||||
|
@ -30,6 +35,84 @@ class MainMenuValues(Enum):
|
||||||
class MainMenu(Menu):
|
class MainMenu(Menu):
|
||||||
values = [e for e in MainMenuValues]
|
values = [e for e in MainMenuValues]
|
||||||
|
|
||||||
|
def handle_key_pressed(self, key: KeyValues, game: Any) -> None:
|
||||||
|
"""
|
||||||
|
In the main menu, we can navigate through options.
|
||||||
|
"""
|
||||||
|
if key == KeyValues.DOWN:
|
||||||
|
self.go_down()
|
||||||
|
if key == KeyValues.UP:
|
||||||
|
self.go_up()
|
||||||
|
if key == KeyValues.ENTER:
|
||||||
|
option = self.validate()
|
||||||
|
if option == MainMenuValues.START:
|
||||||
|
game.state = GameMode.PLAY
|
||||||
|
elif option == MainMenuValues.SETTINGS:
|
||||||
|
game.state = GameMode.SETTINGS
|
||||||
|
elif option == MainMenuValues.EXIT:
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsMenu(Menu):
|
||||||
|
waiting_for_key: bool = False
|
||||||
|
|
||||||
|
def update_values(self, settings: Settings) -> None:
|
||||||
|
self.values = []
|
||||||
|
for i, key in enumerate(settings.settings_keys):
|
||||||
|
s = settings.get_comment(key)
|
||||||
|
s += " : "
|
||||||
|
if self.waiting_for_key and i == self.position:
|
||||||
|
s += "?"
|
||||||
|
else:
|
||||||
|
s += getattr(settings, key).replace("\n", "\\n")
|
||||||
|
s += 8 * " " # Write over old text
|
||||||
|
self.values.append(s)
|
||||||
|
self.values.append("")
|
||||||
|
self.values.append("Changer le pack de textures n'aura effet")
|
||||||
|
self.values.append("qu'après avoir relancé le jeu.")
|
||||||
|
self.values.append("")
|
||||||
|
self.values.append("Retour (espace)")
|
||||||
|
|
||||||
|
def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str,
|
||||||
|
game: Any) -> None:
|
||||||
|
"""
|
||||||
|
Update settings
|
||||||
|
"""
|
||||||
|
if not self.waiting_for_key:
|
||||||
|
# Navigate normally through the menu.
|
||||||
|
if key == KeyValues.SPACE or \
|
||||||
|
key == KeyValues.ENTER and \
|
||||||
|
self.position == len(self.values) - 1:
|
||||||
|
# Go back
|
||||||
|
game.display_actions(DisplayActions.UPDATE)
|
||||||
|
game.state = GameMode.MAINMENU
|
||||||
|
if key == KeyValues.DOWN:
|
||||||
|
self.go_down()
|
||||||
|
if key == KeyValues.UP:
|
||||||
|
self.go_up()
|
||||||
|
if key == KeyValues.ENTER and self.position < len(self.values) - 3:
|
||||||
|
# Change a setting
|
||||||
|
option = list(game.settings.settings_keys)[self.position]
|
||||||
|
if option == "TEXTURE_PACK":
|
||||||
|
game.settings.TEXTURE_PACK = \
|
||||||
|
TexturePack.get_next_pack_name(
|
||||||
|
game.settings.TEXTURE_PACK)
|
||||||
|
game.settings.write_settings()
|
||||||
|
self.update_values(game.settings)
|
||||||
|
else:
|
||||||
|
self.waiting_for_key = True
|
||||||
|
self.update_values(game.settings)
|
||||||
|
else:
|
||||||
|
option = list(game.settings.settings_keys)[self.position]
|
||||||
|
# Don't use an already mapped key
|
||||||
|
if any(getattr(game.settings, opt) == raw_key
|
||||||
|
for opt in game.settings.settings_keys if opt != option):
|
||||||
|
return
|
||||||
|
setattr(game.settings, option, raw_key)
|
||||||
|
game.settings.write_settings()
|
||||||
|
self.waiting_for_key = False
|
||||||
|
self.update_values(game.settings)
|
||||||
|
|
||||||
|
|
||||||
class ArbitraryMenu(Menu):
|
class ArbitraryMenu(Menu):
|
||||||
def __init__(self, values: list):
|
def __init__(self, values: list):
|
||||||
|
|
|
@ -13,6 +13,8 @@ class TermManager: # pragma: no cover
|
||||||
curses.cbreak()
|
curses.cbreak()
|
||||||
# make cursor invisible
|
# make cursor invisible
|
||||||
curses.curs_set(False)
|
curses.curs_set(False)
|
||||||
|
# Enable colors
|
||||||
|
curses.start_color()
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
return self
|
return self
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from dungeonbattle.entities.items import Bomb, Item
|
from dungeonbattle.entities.items import Bomb, Heart, Item
|
||||||
from dungeonbattle.entities.monsters import Squirrel
|
from dungeonbattle.entities.monsters import Hedgehog
|
||||||
from dungeonbattle.entities.player import Player
|
from dungeonbattle.entities.player import Player
|
||||||
from dungeonbattle.interfaces import Entity, Map
|
from dungeonbattle.interfaces import Entity, Map
|
||||||
|
|
||||||
|
@ -12,6 +12,9 @@ class TestEntities(unittest.TestCase):
|
||||||
Load example map that can be used in tests.
|
Load example map that can be used in tests.
|
||||||
"""
|
"""
|
||||||
self.map = Map.load("resources/example_map.txt")
|
self.map = Map.load("resources/example_map.txt")
|
||||||
|
self.player = Player()
|
||||||
|
self.map.add_entity(self.player)
|
||||||
|
self.player.move(self.map.start_y, self.map.start_x)
|
||||||
|
|
||||||
def test_basic_entities(self) -> None:
|
def test_basic_entities(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -23,12 +26,17 @@ class TestEntities(unittest.TestCase):
|
||||||
self.assertEqual(entity.x, 64)
|
self.assertEqual(entity.x, 64)
|
||||||
self.assertIsNone(entity.act(self.map))
|
self.assertIsNone(entity.act(self.map))
|
||||||
|
|
||||||
|
other_entity = Entity()
|
||||||
|
other_entity.move(45, 68)
|
||||||
|
self.assertEqual(entity.distance_squared(other_entity), 25)
|
||||||
|
self.assertEqual(entity.distance(other_entity), 5)
|
||||||
|
|
||||||
def test_fighting_entities(self) -> None:
|
def test_fighting_entities(self) -> None:
|
||||||
"""
|
"""
|
||||||
Test some random stuff with fighting entities.
|
Test some random stuff with fighting entities.
|
||||||
"""
|
"""
|
||||||
entity = Squirrel()
|
entity = Hedgehog()
|
||||||
self.assertIsNone(entity.act(self.map))
|
self.map.add_entity(entity)
|
||||||
self.assertEqual(entity.maxhealth, 10)
|
self.assertEqual(entity.maxhealth, 10)
|
||||||
self.assertEqual(entity.maxhealth, entity.health)
|
self.assertEqual(entity.maxhealth, entity.health)
|
||||||
self.assertEqual(entity.strength, 3)
|
self.assertEqual(entity.strength, 3)
|
||||||
|
@ -41,35 +49,85 @@ class TestEntities(unittest.TestCase):
|
||||||
self.assertIsNone(entity.hit(entity))
|
self.assertIsNone(entity.hit(entity))
|
||||||
self.assertTrue(entity.dead)
|
self.assertTrue(entity.dead)
|
||||||
|
|
||||||
|
entity = Hedgehog()
|
||||||
|
self.map.add_entity(entity)
|
||||||
|
entity.move(15, 44)
|
||||||
|
# Move randomly
|
||||||
|
self.map.tick()
|
||||||
|
self.assertFalse(entity.y == 15 and entity.x == 44)
|
||||||
|
|
||||||
|
# Move to the player
|
||||||
|
entity.move(3, 6)
|
||||||
|
self.map.tick()
|
||||||
|
self.assertTrue(entity.y == 2 and entity.x == 6)
|
||||||
|
|
||||||
|
# Hedgehog should fight
|
||||||
|
old_health = self.player.health
|
||||||
|
self.map.tick()
|
||||||
|
self.assertTrue(entity.y == 2 and entity.x == 6)
|
||||||
|
self.assertEqual(old_health - entity.strength, self.player.health)
|
||||||
|
|
||||||
|
# Fight the hedgehog
|
||||||
|
old_health = entity.health
|
||||||
|
self.player.move_down()
|
||||||
|
self.assertEqual(entity.health, old_health - self.player.strength)
|
||||||
|
self.assertFalse(entity.dead)
|
||||||
|
old_health = entity.health
|
||||||
|
self.player.move_down()
|
||||||
|
self.assertEqual(entity.health, old_health - self.player.strength)
|
||||||
|
self.assertTrue(entity.dead)
|
||||||
|
self.assertGreaterEqual(self.player.current_xp, 3)
|
||||||
|
|
||||||
def test_items(self) -> None:
|
def test_items(self) -> None:
|
||||||
"""
|
"""
|
||||||
Test some random stuff with items.
|
Test some random stuff with items.
|
||||||
"""
|
"""
|
||||||
item = Item()
|
item = Item()
|
||||||
|
self.map.add_entity(item)
|
||||||
self.assertFalse(item.held)
|
self.assertFalse(item.held)
|
||||||
item.hold()
|
item.hold(self.player)
|
||||||
self.assertTrue(item.held)
|
self.assertTrue(item.held)
|
||||||
item.drop(42, 42)
|
item.drop(2, 6)
|
||||||
self.assertEqual(item.y, 42)
|
self.assertEqual(item.y, 2)
|
||||||
self.assertEqual(item.x, 42)
|
self.assertEqual(item.x, 6)
|
||||||
|
|
||||||
|
# Pick up item
|
||||||
|
self.player.move_down()
|
||||||
|
self.assertTrue(item.held)
|
||||||
|
self.assertEqual(item.held_by, self.player)
|
||||||
|
self.assertIn(item, self.player.inventory)
|
||||||
|
self.assertNotIn(item, self.map.entities)
|
||||||
|
|
||||||
def test_bombs(self) -> None:
|
def test_bombs(self) -> None:
|
||||||
"""
|
"""
|
||||||
Test some random stuff with bombs.
|
Test some random stuff with bombs.
|
||||||
"""
|
"""
|
||||||
item = Bomb()
|
item = Bomb()
|
||||||
squirrel = Squirrel()
|
hedgehog = Hedgehog()
|
||||||
self.map.add_entity(item)
|
self.map.add_entity(item)
|
||||||
self.map.add_entity(squirrel)
|
self.map.add_entity(hedgehog)
|
||||||
squirrel.health = 2
|
hedgehog.health = 2
|
||||||
squirrel.move(41, 42)
|
hedgehog.move(41, 42)
|
||||||
item.act(self.map)
|
item.act(self.map)
|
||||||
self.assertFalse(squirrel.dead)
|
self.assertFalse(hedgehog.dead)
|
||||||
item.drop(42, 42)
|
item.drop(42, 42)
|
||||||
self.assertEqual(item.y, 42)
|
self.assertEqual(item.y, 42)
|
||||||
self.assertEqual(item.x, 42)
|
self.assertEqual(item.x, 42)
|
||||||
item.act(self.map)
|
item.act(self.map)
|
||||||
self.assertTrue(squirrel.dead)
|
self.assertTrue(hedgehog.dead)
|
||||||
|
|
||||||
|
def test_hearts(self) -> None:
|
||||||
|
"""
|
||||||
|
Test some random stuff with hearts.
|
||||||
|
"""
|
||||||
|
item = Heart()
|
||||||
|
self.map.add_entity(item)
|
||||||
|
item.move(2, 6)
|
||||||
|
self.player.health -= 2 * item.healing
|
||||||
|
self.player.move_down()
|
||||||
|
self.assertNotIn(item, self.map.entities)
|
||||||
|
self.assertEqual(self.player.health,
|
||||||
|
self.player.maxhealth - item.healing)
|
||||||
|
|
||||||
def test_players(self) -> None:
|
def test_players(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -4,8 +4,10 @@ import unittest
|
||||||
from dungeonbattle.bootstrap import Bootstrap
|
from dungeonbattle.bootstrap import Bootstrap
|
||||||
from dungeonbattle.display.display import Display
|
from dungeonbattle.display.display import Display
|
||||||
from dungeonbattle.display.display_manager import DisplayManager
|
from dungeonbattle.display.display_manager import DisplayManager
|
||||||
|
from dungeonbattle.entities.player import Player
|
||||||
from dungeonbattle.game import Game, KeyValues, GameMode
|
from dungeonbattle.game import Game, KeyValues, GameMode
|
||||||
from dungeonbattle.menus import MainMenuValues
|
from dungeonbattle.menus import MainMenuValues
|
||||||
|
from dungeonbattle.settings import Settings
|
||||||
|
|
||||||
|
|
||||||
class TestGame(unittest.TestCase):
|
class TestGame(unittest.TestCase):
|
||||||
|
@ -16,7 +18,7 @@ class TestGame(unittest.TestCase):
|
||||||
self.game = Game()
|
self.game = Game()
|
||||||
self.game.new_game()
|
self.game.new_game()
|
||||||
display = DisplayManager(None, self.game)
|
display = DisplayManager(None, self.game)
|
||||||
self.game.display_refresh = display.refresh
|
self.game.display_actions = display.handle_display_action
|
||||||
|
|
||||||
def test_load_game(self) -> None:
|
def test_load_game(self) -> None:
|
||||||
self.assertRaises(NotImplementedError, Game.load_game, "game.save")
|
self.assertRaises(NotImplementedError, Game.load_game, "game.save")
|
||||||
|
@ -35,25 +37,39 @@ class TestGame(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
Test key bindings.
|
Test key bindings.
|
||||||
"""
|
"""
|
||||||
self.assertEqual(self.game.translate_key(
|
self.game.settings = Settings()
|
||||||
self.game.settings.KEY_UP_PRIMARY), KeyValues.UP)
|
|
||||||
self.assertEqual(self.game.translate_key(
|
self.assertEqual(KeyValues.translate_key(
|
||||||
self.game.settings.KEY_UP_SECONDARY), KeyValues.UP)
|
self.game.settings.KEY_UP_PRIMARY, self.game.settings),
|
||||||
self.assertEqual(self.game.translate_key(
|
KeyValues.UP)
|
||||||
self.game.settings.KEY_DOWN_PRIMARY), KeyValues.DOWN)
|
self.assertEqual(KeyValues.translate_key(
|
||||||
self.assertEqual(self.game.translate_key(
|
self.game.settings.KEY_UP_SECONDARY, self.game.settings),
|
||||||
self.game.settings.KEY_DOWN_SECONDARY), KeyValues.DOWN)
|
KeyValues.UP)
|
||||||
self.assertEqual(self.game.translate_key(
|
self.assertEqual(KeyValues.translate_key(
|
||||||
self.game.settings.KEY_LEFT_PRIMARY), KeyValues.LEFT)
|
self.game.settings.KEY_DOWN_PRIMARY, self.game.settings),
|
||||||
self.assertEqual(self.game.translate_key(
|
KeyValues.DOWN)
|
||||||
self.game.settings.KEY_LEFT_SECONDARY), KeyValues.LEFT)
|
self.assertEqual(KeyValues.translate_key(
|
||||||
self.assertEqual(self.game.translate_key(
|
self.game.settings.KEY_DOWN_SECONDARY, self.game.settings),
|
||||||
self.game.settings.KEY_RIGHT_PRIMARY), KeyValues.RIGHT)
|
KeyValues.DOWN)
|
||||||
self.assertEqual(self.game.translate_key(
|
self.assertEqual(KeyValues.translate_key(
|
||||||
self.game.settings.KEY_RIGHT_SECONDARY), KeyValues.RIGHT)
|
self.game.settings.KEY_LEFT_PRIMARY, self.game.settings),
|
||||||
self.assertEqual(self.game.translate_key(
|
KeyValues.LEFT)
|
||||||
self.game.settings.KEY_ENTER), KeyValues.ENTER)
|
self.assertEqual(KeyValues.translate_key(
|
||||||
self.assertEqual(self.game.translate_key(' '), KeyValues.SPACE)
|
self.game.settings.KEY_LEFT_SECONDARY, self.game.settings),
|
||||||
|
KeyValues.LEFT)
|
||||||
|
self.assertEqual(KeyValues.translate_key(
|
||||||
|
self.game.settings.KEY_RIGHT_PRIMARY, self.game.settings),
|
||||||
|
KeyValues.RIGHT)
|
||||||
|
self.assertEqual(KeyValues.translate_key(
|
||||||
|
self.game.settings.KEY_RIGHT_SECONDARY, self.game.settings),
|
||||||
|
KeyValues.RIGHT)
|
||||||
|
self.assertEqual(KeyValues.translate_key(
|
||||||
|
self.game.settings.KEY_ENTER, self.game.settings),
|
||||||
|
KeyValues.ENTER)
|
||||||
|
self.assertEqual(KeyValues.translate_key(' ', self.game.settings),
|
||||||
|
KeyValues.SPACE)
|
||||||
|
self.assertEqual(KeyValues.translate_key('plop', self.game.settings),
|
||||||
|
None)
|
||||||
|
|
||||||
def test_key_press(self) -> None:
|
def test_key_press(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -90,6 +106,11 @@ class TestGame(unittest.TestCase):
|
||||||
self.game.handle_key_pressed(KeyValues.ENTER)
|
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||||
self.assertEqual(self.game.state, GameMode.PLAY)
|
self.assertEqual(self.game.state, GameMode.PLAY)
|
||||||
|
|
||||||
|
# Kill entities
|
||||||
|
for entity in self.game.map.entities.copy():
|
||||||
|
if not isinstance(entity, Player):
|
||||||
|
self.game.map.remove_entity(entity)
|
||||||
|
|
||||||
y, x = self.game.player.y, self.game.player.x
|
y, x = self.game.player.y, self.game.player.x
|
||||||
self.game.handle_key_pressed(KeyValues.DOWN)
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
new_y, new_x = self.game.player.y, self.game.player.x
|
new_y, new_x = self.game.player.y, self.game.player.x
|
||||||
|
@ -116,3 +137,80 @@ class TestGame(unittest.TestCase):
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
def test_settings_menu(self) -> None:
|
||||||
|
"""
|
||||||
|
Ensure that the settings menu is working properly.
|
||||||
|
"""
|
||||||
|
self.game.settings = Settings()
|
||||||
|
|
||||||
|
# Open settings menu
|
||||||
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||||
|
self.assertEqual(self.game.state, GameMode.SETTINGS)
|
||||||
|
|
||||||
|
# Define the "move up" key to 'w'
|
||||||
|
self.assertFalse(self.game.settings_menu.waiting_for_key)
|
||||||
|
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||||
|
self.assertTrue(self.game.settings_menu.waiting_for_key)
|
||||||
|
self.game.handle_key_pressed(None, 'w')
|
||||||
|
self.assertFalse(self.game.settings_menu.waiting_for_key)
|
||||||
|
self.assertEqual(self.game.settings.KEY_UP_PRIMARY, 'w')
|
||||||
|
|
||||||
|
# Navigate to "move left"
|
||||||
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
self.game.handle_key_pressed(KeyValues.UP)
|
||||||
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
|
||||||
|
# Define the "move up" key to 'a'
|
||||||
|
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||||
|
self.assertTrue(self.game.settings_menu.waiting_for_key)
|
||||||
|
# Can't used a mapped key
|
||||||
|
self.game.handle_key_pressed(None, 's')
|
||||||
|
self.assertTrue(self.game.settings_menu.waiting_for_key)
|
||||||
|
self.game.handle_key_pressed(None, 'a')
|
||||||
|
self.assertFalse(self.game.settings_menu.waiting_for_key)
|
||||||
|
self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a')
|
||||||
|
|
||||||
|
# Navigate to "texture pack"
|
||||||
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
|
||||||
|
# Change texture pack
|
||||||
|
self.assertEqual(self.game.settings.TEXTURE_PACK, "ascii")
|
||||||
|
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||||
|
self.assertEqual(self.game.settings.TEXTURE_PACK, "squirrel")
|
||||||
|
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||||
|
self.assertEqual(self.game.settings.TEXTURE_PACK, "ascii")
|
||||||
|
|
||||||
|
# Navigate to "back" button
|
||||||
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
|
||||||
|
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||||
|
self.assertEqual(self.game.state, GameMode.MAINMENU)
|
||||||
|
|
||||||
|
def test_dead_screen(self) -> None:
|
||||||
|
"""
|
||||||
|
Kill player and render dead screen.
|
||||||
|
"""
|
||||||
|
self.game.state = GameMode.PLAY
|
||||||
|
# Kill player
|
||||||
|
self.game.player.take_damage(self.game.player,
|
||||||
|
self.game.player.health + 2)
|
||||||
|
y, x = self.game.player.y, self.game.player.x
|
||||||
|
for key in [KeyValues.UP, KeyValues.DOWN,
|
||||||
|
KeyValues.LEFT, KeyValues.RIGHT]:
|
||||||
|
self.game.handle_key_pressed(key)
|
||||||
|
new_y, new_x = self.game.player.y, self.game.player.x
|
||||||
|
self.assertEqual(new_y, y)
|
||||||
|
self.assertEqual(new_x, x)
|
||||||
|
|
|
@ -9,7 +9,7 @@ class TestInterfaces(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
Create a map and check that it is well parsed.
|
Create a map and check that it is well parsed.
|
||||||
"""
|
"""
|
||||||
m = Map.load_from_string(".#\n#.\n")
|
m = Map.load_from_string("0 0\n.#\n#.\n")
|
||||||
self.assertEqual(m.width, 2)
|
self.assertEqual(m.width, 2)
|
||||||
self.assertEqual(m.height, 2)
|
self.assertEqual(m.height, 2)
|
||||||
self.assertEqual(m.draw_string(TexturePack.ASCII_PACK), ".#\n#.")
|
self.assertEqual(m.draw_string(TexturePack.ASCII_PACK), ".#\n#.")
|
||||||
|
@ -31,5 +31,5 @@ class TestInterfaces(unittest.TestCase):
|
||||||
self.assertFalse(Tile.EMPTY.is_wall())
|
self.assertFalse(Tile.EMPTY.is_wall())
|
||||||
self.assertTrue(Tile.FLOOR.can_walk())
|
self.assertTrue(Tile.FLOOR.can_walk())
|
||||||
self.assertFalse(Tile.WALL.can_walk())
|
self.assertFalse(Tile.WALL.can_walk())
|
||||||
self.assertTrue(Tile.EMPTY.can_walk())
|
self.assertFalse(Tile.EMPTY.can_walk())
|
||||||
self.assertRaises(ValueError, Tile.from_ascii_char, 'unknown')
|
self.assertRaises(ValueError, Tile.from_ascii_char, 'unknown')
|
||||||
|
|
|
@ -3,7 +3,7 @@ class FakePad:
|
||||||
In order to run tests, we simulate a fake curses pad that accepts functions
|
In order to run tests, we simulate a fake curses pad that accepts functions
|
||||||
but does nothing with them.
|
but does nothing with them.
|
||||||
"""
|
"""
|
||||||
def addstr(self, y: int, x: int, message: str) -> None:
|
def addstr(self, y: int, x: int, message: str, color: int = 0) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def refresh(self, pminrow: int, pmincol: int, sminrow: int,
|
def refresh(self, pminrow: int, pmincol: int, sminrow: int,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
1 6
|
||||||
####### #############
|
####### #############
|
||||||
#.....# #...........#
|
#.....# #...........#
|
||||||
#.....# #####...........#
|
#.....# #####...........#
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
1 17
|
||||||
|
########### #########
|
||||||
|
#.........# #.......#
|
||||||
|
#.........# ############.......#
|
||||||
|
#.........###############..........#.......##############
|
||||||
|
#.........#........................#....................#
|
||||||
|
#.........#.............#..........#.......#............#
|
||||||
|
########.########.............#..................#............#
|
||||||
|
#.........# #.............####.#######.......#............#
|
||||||
|
#.........# #.............##.........######################
|
||||||
|
#.........# #####.##########.........# ###########
|
||||||
|
#.........# #......# #.........# #.........#
|
||||||
|
########.##########......# #.........# #.........#
|
||||||
|
#...........##......# #.........# #.........#
|
||||||
|
#...........##......# #.........# #.........#
|
||||||
|
#...........##......# #.........# ################.######
|
||||||
|
#...........##......# #.........# #.................############
|
||||||
|
#...........##......# ########.########.......#.........#..........#
|
||||||
|
#...........##......# #...............#.......#.........#..........#
|
||||||
|
#...........######### #...............#.......#.........#..........#
|
||||||
|
#...........# #...............#.......#....................#
|
||||||
|
#####.####### #.......................#.........#..........#
|
||||||
|
#.........# #...............###################..........#
|
||||||
|
#.........############ #...............# #..........#
|
||||||
|
#.........#..........# #...............# ############
|
||||||
|
#....................#####.###########.#############
|
||||||
|
########.#########...................# #.............#
|
||||||
|
#........# #..........#........# #.............#########
|
||||||
|
#........# ######.##########........# #.............#.......#
|
||||||
|
#........# #..........# #........# #.....................#
|
||||||
|
#........# #..........# #........# #.............#.......#
|
||||||
|
#........# #..........# #........# #.............#.......#
|
||||||
|
#........# #..........# #........# #.............#.......#
|
||||||
|
#........# #..........#########.##### #.............#.......#
|
||||||
|
#........# #..........#.........# ##########.############.#######
|
||||||
|
#........# #..........#.........# #..............# #..........#
|
||||||
|
########## #..........#.........# #..............# #..........#
|
||||||
|
############.........# #..............# #..........#
|
||||||
|
#.........# #..............# #..........#
|
||||||
|
########### #..............# #..........#
|
||||||
|
################ ############
|
Loading…
Reference in New Issue