Merge branch 'entities' into 'master'
Entities See merge request ynerant/dungeon-battle!10
This commit was merged in pull request #91.
	This commit is contained in:
		@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										48
									
								
								dungeonbattle/enums.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								dungeonbattle/enums.py
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
    #######                    #############        
 | 
					    #######                    #############        
 | 
				
			||||||
    #.....#                    #...........#        
 | 
					    #.....#                    #...........#        
 | 
				
			||||||
    #.....#                #####...........#        
 | 
					    #.....#                #####...........#        
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										41
									
								
								resources/example_map_2.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								resources/example_map_2.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					1 17
 | 
				
			||||||
 | 
					            ###########                        #########                        
 | 
				
			||||||
 | 
					            #.........#                        #.......#                        
 | 
				
			||||||
 | 
					            #.........#             ############.......#                        
 | 
				
			||||||
 | 
					            #.........###############..........#.......##############           
 | 
				
			||||||
 | 
					            #.........#........................#....................#           
 | 
				
			||||||
 | 
					            #.........#.............#..........#.......#............#           
 | 
				
			||||||
 | 
					      ########.########.............#..................#............#           
 | 
				
			||||||
 | 
					      #.........#     #.............####.#######.......#............#           
 | 
				
			||||||
 | 
					      #.........#     #.............##.........######################           
 | 
				
			||||||
 | 
					      #.........#     #####.##########.........#              ###########       
 | 
				
			||||||
 | 
					      #.........#       #......#     #.........#              #.........#       
 | 
				
			||||||
 | 
					      ########.##########......#     #.........#              #.........#       
 | 
				
			||||||
 | 
					           #...........##......#     #.........#              #.........#       
 | 
				
			||||||
 | 
					           #...........##......#     #.........#              #.........#       
 | 
				
			||||||
 | 
					           #...........##......#     #.........#  ################.######       
 | 
				
			||||||
 | 
					           #...........##......#     #.........#  #.................############
 | 
				
			||||||
 | 
					           #...........##......#  ########.########.......#.........#..........#
 | 
				
			||||||
 | 
					           #...........##......#  #...............#.......#.........#..........#
 | 
				
			||||||
 | 
					           #...........#########  #...............#.......#.........#..........#
 | 
				
			||||||
 | 
					           #...........#          #...............#.......#....................#
 | 
				
			||||||
 | 
					           #####.#######          #.......................#.........#..........#
 | 
				
			||||||
 | 
					           #.........#            #...............###################..........#
 | 
				
			||||||
 | 
					           #.........############ #...............#                 #..........#
 | 
				
			||||||
 | 
					           #.........#..........# #...............#                 ############
 | 
				
			||||||
 | 
					           #....................#####.###########.#############                 
 | 
				
			||||||
 | 
					    ########.#########...................#      #.............#                 
 | 
				
			||||||
 | 
					    #........#       #..........#........#      #.............#########         
 | 
				
			||||||
 | 
					    #........#  ######.##########........#      #.............#.......#         
 | 
				
			||||||
 | 
					    #........#  #..........#    #........#      #.....................#         
 | 
				
			||||||
 | 
					    #........#  #..........#    #........#      #.............#.......#         
 | 
				
			||||||
 | 
					    #........#  #..........#    #........#      #.............#.......#         
 | 
				
			||||||
 | 
					    #........#  #..........#    #........#      #.............#.......#         
 | 
				
			||||||
 | 
					    #........#  #..........#########.#####      #.............#.......#         
 | 
				
			||||||
 | 
					    #........#  #..........#.........#  ##########.############.#######         
 | 
				
			||||||
 | 
					    #........#  #..........#.........#  #..............#   #..........#         
 | 
				
			||||||
 | 
					    ##########  #..........#.........#  #..............#   #..........#         
 | 
				
			||||||
 | 
					                ############.........#  #..............#   #..........#         
 | 
				
			||||||
 | 
					                           #.........#  #..............#   #..........#         
 | 
				
			||||||
 | 
					                           ###########  #..............#   #..........#         
 | 
				
			||||||
 | 
					                                        ################   ############         
 | 
				
			||||||
		Reference in New Issue
	
	Block a user