Merge branch 'display' into 'master'
Meilleur affichage See merge request ynerant/dungeon-battle!8
This commit was merged in pull request #89.
	This commit is contained in:
		
							
								
								
									
										15
									
								
								dungeonbattle/bootstrap.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								dungeonbattle/bootstrap.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					from dungeonbattle.game import Game
 | 
				
			||||||
 | 
					from dungeonbattle.display.display_manager import DisplayManager
 | 
				
			||||||
 | 
					from dungeonbattle.term_manager import TermManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Bootstrap:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def run_game():
 | 
				
			||||||
 | 
					        with TermManager() as term_manager:  # pragma: no cover
 | 
				
			||||||
 | 
					            game = Game()
 | 
				
			||||||
 | 
					            game.new_game()
 | 
				
			||||||
 | 
					            display = DisplayManager(term_manager.screen, game)
 | 
				
			||||||
 | 
					            game.display_refresh = display.refresh
 | 
				
			||||||
 | 
					            game.run(term_manager.screen)
 | 
				
			||||||
							
								
								
									
										0
									
								
								dungeonbattle/display/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								dungeonbattle/display/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										44
									
								
								dungeonbattle/display/display.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								dungeonbattle/display/display.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					import curses
 | 
				
			||||||
 | 
					from typing import Any, Optional, Union
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from dungeonbattle.display.texturepack import TexturePack
 | 
				
			||||||
 | 
					from dungeonbattle.tests.screen import FakePad
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Display:
 | 
				
			||||||
 | 
					    x: int
 | 
				
			||||||
 | 
					    y: int
 | 
				
			||||||
 | 
					    width: int
 | 
				
			||||||
 | 
					    height: int
 | 
				
			||||||
 | 
					    pad: Any
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, screen: Any, pack: Optional[TexturePack] = None):
 | 
				
			||||||
 | 
					        self.screen = screen
 | 
				
			||||||
 | 
					        self.pack = pack or TexturePack.get_pack("ascii")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def newpad(self, height: int, width: int) -> Union[FakePad, Any]:
 | 
				
			||||||
 | 
					        return curses.newpad(height, width) if self.screen else FakePad()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def resize(self, y: int, x: int, height: int, width: int) -> None:
 | 
				
			||||||
 | 
					        self.x = x
 | 
				
			||||||
 | 
					        self.y = y
 | 
				
			||||||
 | 
					        self.width = width
 | 
				
			||||||
 | 
					        self.height = height
 | 
				
			||||||
 | 
					        if self.pad:
 | 
				
			||||||
 | 
					            self.pad.resize(height - 1, width - 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def refresh(self, *args) -> None:
 | 
				
			||||||
 | 
					        if len(args) == 4:
 | 
				
			||||||
 | 
					            self.resize(*args)
 | 
				
			||||||
 | 
					        self.display()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def display(self) -> None:
 | 
				
			||||||
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def rows(self) -> int:
 | 
				
			||||||
 | 
					        return curses.LINES if self.screen else 42
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def cols(self) -> int:
 | 
				
			||||||
 | 
					        return curses.COLS if self.screen else 42
 | 
				
			||||||
							
								
								
									
										56
									
								
								dungeonbattle/display/display_manager.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								dungeonbattle/display/display_manager.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					import curses
 | 
				
			||||||
 | 
					from dungeonbattle.display.mapdisplay import MapDisplay
 | 
				
			||||||
 | 
					from dungeonbattle.display.statsdisplay import StatsDisplay
 | 
				
			||||||
 | 
					from dungeonbattle.display.menudisplay import MainMenuDisplay
 | 
				
			||||||
 | 
					from dungeonbattle.display.texturepack import TexturePack
 | 
				
			||||||
 | 
					from typing import Any
 | 
				
			||||||
 | 
					from dungeonbattle.game import Game, GameMode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DisplayManager:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, screen: Any, g: Game):
 | 
				
			||||||
 | 
					        self.game = g
 | 
				
			||||||
 | 
					        self.screen = screen
 | 
				
			||||||
 | 
					        pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK)
 | 
				
			||||||
 | 
					        self.mapdisplay = MapDisplay(screen, pack)
 | 
				
			||||||
 | 
					        self.statsdisplay = StatsDisplay(screen, pack)
 | 
				
			||||||
 | 
					        self.mainmenudisplay = MainMenuDisplay(self.game.main_menu,
 | 
				
			||||||
 | 
					                                               screen, pack)
 | 
				
			||||||
 | 
					        self.displays = [self.statsdisplay, self.mapdisplay,
 | 
				
			||||||
 | 
					                         self.mainmenudisplay]
 | 
				
			||||||
 | 
					        self.update_game_components()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_game_components(self) -> None:
 | 
				
			||||||
 | 
					        for d in self.displays:
 | 
				
			||||||
 | 
					            d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK)
 | 
				
			||||||
 | 
					        self.mapdisplay.update_map(self.game.map)
 | 
				
			||||||
 | 
					        self.statsdisplay.update_player(self.game.player)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def refresh(self) -> None:
 | 
				
			||||||
 | 
					        if self.game.state == GameMode.PLAY:
 | 
				
			||||||
 | 
					            self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, self.cols)
 | 
				
			||||||
 | 
					            self.statsdisplay.refresh(self.rows * 4 // 5, 0,
 | 
				
			||||||
 | 
					                                      self.rows // 5, self.cols)
 | 
				
			||||||
 | 
					        if self.game.state == GameMode.MAINMENU:
 | 
				
			||||||
 | 
					            self.mainmenudisplay.refresh(0, 0, self.rows, self.cols)
 | 
				
			||||||
 | 
					        self.resize_window()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def resize_window(self) -> bool:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        If the window got resized, ensure that the screen size got updated.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        y, x = self.screen.getmaxyx() if self.screen else (0, 0)
 | 
				
			||||||
 | 
					        if self.screen and curses.is_term_resized(self.rows,
 | 
				
			||||||
 | 
					                                                  self.cols):  # pragma: nocover
 | 
				
			||||||
 | 
					            curses.resizeterm(y, x)
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def rows(self) -> int:
 | 
				
			||||||
 | 
					        return curses.LINES if self.screen else 42
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def cols(self) -> int:
 | 
				
			||||||
 | 
					        return curses.COLS if self.screen else 42
 | 
				
			||||||
							
								
								
									
										36
									
								
								dungeonbattle/display/mapdisplay.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								dungeonbattle/display/mapdisplay.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					from dungeonbattle.entities.player import Player
 | 
				
			||||||
 | 
					from dungeonbattle.interfaces import Map
 | 
				
			||||||
 | 
					from .display import Display
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MapDisplay(Display):
 | 
				
			||||||
 | 
					    player: Player
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, *args):
 | 
				
			||||||
 | 
					        super().__init__(*args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_map(self, m: Map) -> None:
 | 
				
			||||||
 | 
					        self.map = m
 | 
				
			||||||
 | 
					        self.pad = self.newpad(m.height, m.width + 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_pad(self) -> None:
 | 
				
			||||||
 | 
					        self.pad.addstr(0, 0, self.map.draw_string(self.pack))
 | 
				
			||||||
 | 
					        for e in self.map.entities:
 | 
				
			||||||
 | 
					            self.pad.addstr(e.y, e.x, self.pack.PLAYER)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def display(self) -> None:
 | 
				
			||||||
 | 
					        y, x = self.map.currenty, self.map.currentx
 | 
				
			||||||
 | 
					        deltay, deltax = (self.height // 2) + 1, (self.width // 2) + 1
 | 
				
			||||||
 | 
					        pminrow, pmincol = y - deltay, x - deltax
 | 
				
			||||||
 | 
					        sminrow, smincol = max(-pminrow, 0), max(-pmincol, 0)
 | 
				
			||||||
 | 
					        deltay, deltax = self.height - deltay, self.width - deltax
 | 
				
			||||||
 | 
					        smaxrow = self.map.height - (y + deltay) + self.height - 1
 | 
				
			||||||
 | 
					        smaxrow = min(smaxrow, self.height - 1)
 | 
				
			||||||
 | 
					        smaxcol = self.map.width - (x + deltax) + self.width - 1
 | 
				
			||||||
 | 
					        smaxcol = min(smaxcol, self.width - 1)
 | 
				
			||||||
 | 
					        pminrow = max(0, min(self.map.height, pminrow))
 | 
				
			||||||
 | 
					        pmincol = max(0, min(self.map.width, pmincol))
 | 
				
			||||||
 | 
					        self.pad.clear()
 | 
				
			||||||
 | 
					        self.update_pad()
 | 
				
			||||||
 | 
					        self.pad.refresh(pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol)
 | 
				
			||||||
							
								
								
									
										79
									
								
								dungeonbattle/display/menudisplay.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								dungeonbattle/display/menudisplay.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					from dungeonbattle.menus import Menu, MainMenu
 | 
				
			||||||
 | 
					from .display import Display
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MenuDisplay(Display):
 | 
				
			||||||
 | 
					    position: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, *args):
 | 
				
			||||||
 | 
					        super().__init__(*args)
 | 
				
			||||||
 | 
					        self.menubox = self.newpad(self.rows, self.cols)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_menu(self, menu: Menu) -> None:
 | 
				
			||||||
 | 
					        self.menu = menu
 | 
				
			||||||
 | 
					        self.values = [str(a) for a in menu.values]
 | 
				
			||||||
 | 
					        self.trueheight = len(self.values)
 | 
				
			||||||
 | 
					        self.truewidth = max([len(a) for a in self.values])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Menu values are printed in pad
 | 
				
			||||||
 | 
					        self.pad = self.newpad(self.trueheight, self.truewidth + 2)
 | 
				
			||||||
 | 
					        for i in range(self.trueheight):
 | 
				
			||||||
 | 
					            self.pad.addstr(i, 0, "  " + self.values[i])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_pad(self) -> None:
 | 
				
			||||||
 | 
					        for i in range(self.trueheight):
 | 
				
			||||||
 | 
					            self.pad.addstr(i, 0, " ")
 | 
				
			||||||
 | 
					        # set a marker on the selected line
 | 
				
			||||||
 | 
					        self.pad.addstr(self.menu.position, 0, ">")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def display(self) -> None:
 | 
				
			||||||
 | 
					        cornery = 0 if self.height - 2 >= self.menu.position - 1 \
 | 
				
			||||||
 | 
					            else self.trueheight - self.height + 2 \
 | 
				
			||||||
 | 
					            if self.height - 2 >= self.trueheight - self.menu.position else 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Menu box
 | 
				
			||||||
 | 
					        self.menubox.addstr(0, 0, "┏" + "━" * (self.width - 2) + "┓")
 | 
				
			||||||
 | 
					        for i in range(1, self.height - 1):
 | 
				
			||||||
 | 
					            self.menubox.addstr(i, 0, "┃" + " " * (self.width - 2) + "┃")
 | 
				
			||||||
 | 
					        self.menubox.addstr(self.height - 1, 0,
 | 
				
			||||||
 | 
					                            "┗" + "━" * (self.width - 2) + "┛")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.menubox.refresh(0, 0, self.y, self.x,
 | 
				
			||||||
 | 
					                             self.height + self.y,
 | 
				
			||||||
 | 
					                             self.width + self.x)
 | 
				
			||||||
 | 
					        self.update_pad()
 | 
				
			||||||
 | 
					        self.pad.refresh(cornery, 0, self.y + 1, self.x + 2,
 | 
				
			||||||
 | 
					                         self.height - 2 + self.y,
 | 
				
			||||||
 | 
					                         self.width - 2 + self.x)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def preferred_width(self) -> int:
 | 
				
			||||||
 | 
					        return self.truewidth + 6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def preferred_height(self) -> int:
 | 
				
			||||||
 | 
					        return self.trueheight + 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MainMenuDisplay(Display):
 | 
				
			||||||
 | 
					    def __init__(self, menu: MainMenu, *args):
 | 
				
			||||||
 | 
					        super().__init__(*args)
 | 
				
			||||||
 | 
					        self.menu = menu
 | 
				
			||||||
 | 
					        self.pad = self.newpad(self.rows, self.cols)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with open("resources/ascii_art.txt", "r") as file:
 | 
				
			||||||
 | 
					            self.title = file.read().split("\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.menudisplay = MenuDisplay(self.screen, self.pack)
 | 
				
			||||||
 | 
					        self.menudisplay.update_menu(self.menu)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def display(self) -> None:
 | 
				
			||||||
 | 
					        for i in range(len(self.title)):
 | 
				
			||||||
 | 
					            self.pad.addstr(4 + i, self.width // 2
 | 
				
			||||||
 | 
					                            - len(self.title[0]) // 2 - 1, self.title[i])
 | 
				
			||||||
 | 
					        self.pad.refresh(0, 0, self.y, self.x, self.height, self.width)
 | 
				
			||||||
 | 
					        menuwidth = min(self.menudisplay.preferred_width, self.width)
 | 
				
			||||||
 | 
					        menuy, menux = len(self.title) + 8, self.width // 2 - menuwidth // 2 - 1
 | 
				
			||||||
 | 
					        self.menudisplay.refresh(
 | 
				
			||||||
 | 
					            menuy, menux, min(self.menudisplay.preferred_height,
 | 
				
			||||||
 | 
					                              self.height - menuy), menuwidth)
 | 
				
			||||||
							
								
								
									
										41
									
								
								dungeonbattle/display/statsdisplay.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								dungeonbattle/display/statsdisplay.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					from .display import Display
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from dungeonbattle.entities.player import Player
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StatsDisplay(Display):
 | 
				
			||||||
 | 
					    player: Player
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        super().__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					        self.pad = self.newpad(self.rows, self.cols)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_player(self, p: Player) -> None:
 | 
				
			||||||
 | 
					        self.player = p
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def update_pad(self) -> None:
 | 
				
			||||||
 | 
					        string = ""
 | 
				
			||||||
 | 
					        for _ in range(self.width - 1):
 | 
				
			||||||
 | 
					            string = string + "-"
 | 
				
			||||||
 | 
					        self.pad.addstr(0, 0, string)
 | 
				
			||||||
 | 
					        string2 = "Player -- LVL {}  EXP {}/{}  HP {}/{}"\
 | 
				
			||||||
 | 
					            .format(self.player.level, self.player.current_xp,
 | 
				
			||||||
 | 
					                    self.player.max_xp, self.player.health,
 | 
				
			||||||
 | 
					                    self.player.maxhealth)
 | 
				
			||||||
 | 
					        for _ in range(self.width - len(string2) - 1):
 | 
				
			||||||
 | 
					            string2 = string2 + " "
 | 
				
			||||||
 | 
					        self.pad.addstr(1, 0, string2)
 | 
				
			||||||
 | 
					        string3 = "Stats : STR {}  INT {}  CHR {}  DEX {} CON {}"\
 | 
				
			||||||
 | 
					            .format(self.player.strength,
 | 
				
			||||||
 | 
					                    self.player.intelligence, self.player.charisma,
 | 
				
			||||||
 | 
					                    self.player.dexterity, self.player.constitution)
 | 
				
			||||||
 | 
					        for _ in range(self.width - len(string3) - 1):
 | 
				
			||||||
 | 
					            string3 = string3 + " "
 | 
				
			||||||
 | 
					        self.pad.addstr(2, 0, string3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def display(self) -> None:
 | 
				
			||||||
 | 
					        self.pad.clear()
 | 
				
			||||||
 | 
					        self.update_pad()
 | 
				
			||||||
 | 
					        self.pad.refresh(0, 0, self.y, self.x,
 | 
				
			||||||
 | 
					                         2 + self.y,
 | 
				
			||||||
 | 
					                         self.width + self.x)
 | 
				
			||||||
							
								
								
									
										37
									
								
								dungeonbattle/display/texturepack.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								dungeonbattle/display/texturepack.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					class TexturePack:
 | 
				
			||||||
 | 
					    _packs = dict()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name: str
 | 
				
			||||||
 | 
					    EMPTY: str
 | 
				
			||||||
 | 
					    WALL: str
 | 
				
			||||||
 | 
					    FLOOR: str
 | 
				
			||||||
 | 
					    PLAYER: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ASCII_PACK: "TexturePack"
 | 
				
			||||||
 | 
					    SQUIRREL_PACK: "TexturePack"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, name: str, **kwargs):
 | 
				
			||||||
 | 
					        self.name = name
 | 
				
			||||||
 | 
					        self.__dict__.update(**kwargs)
 | 
				
			||||||
 | 
					        TexturePack._packs[name] = self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def get_pack(cls, name: str) -> "TexturePack":
 | 
				
			||||||
 | 
					        return cls._packs[name.lower()]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TexturePack.ASCII_PACK = TexturePack(
 | 
				
			||||||
 | 
					    name="ascii",
 | 
				
			||||||
 | 
					    EMPTY=' ',
 | 
				
			||||||
 | 
					    WALL='#',
 | 
				
			||||||
 | 
					    FLOOR='.',
 | 
				
			||||||
 | 
					    PLAYER='@',
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TexturePack.SQUIRREL_PACK = TexturePack(
 | 
				
			||||||
 | 
					    name="squirrel",
 | 
				
			||||||
 | 
					    EMPTY=' ',
 | 
				
			||||||
 | 
					    WALL='█',
 | 
				
			||||||
 | 
					    FLOOR='.',
 | 
				
			||||||
 | 
					    PLAYER='🐿️',
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -2,8 +2,15 @@ from ..interfaces import FightingEntity
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Player(FightingEntity):
 | 
					class Player(FightingEntity):
 | 
				
			||||||
    maxhealth = 20
 | 
					    maxhealth: int = 20
 | 
				
			||||||
    strength = 5
 | 
					    strength: int = 5
 | 
				
			||||||
 | 
					    intelligence: int = 1
 | 
				
			||||||
 | 
					    charisma: int = 1
 | 
				
			||||||
 | 
					    dexterity: int = 1
 | 
				
			||||||
 | 
					    constitution: int = 1
 | 
				
			||||||
 | 
					    level: int = 1
 | 
				
			||||||
 | 
					    current_xp: int = 0
 | 
				
			||||||
 | 
					    max_xp: int = 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def move_up(self) -> bool:
 | 
					    def move_up(self) -> bool:
 | 
				
			||||||
        return self.check_move(self.y - 1, self.x, True)
 | 
					        return self.check_move(self.y - 1, self.x, True)
 | 
				
			||||||
@@ -16,3 +23,13 @@ class Player(FightingEntity):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def move_right(self) -> bool:
 | 
					    def move_right(self) -> bool:
 | 
				
			||||||
        return self.check_move(self.y, self.x + 1, True)
 | 
					        return self.check_move(self.y, self.x + 1, True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def level_up(self) -> None:
 | 
				
			||||||
 | 
					        while self.current_xp > self.max_xp:
 | 
				
			||||||
 | 
					            self.level += 1
 | 
				
			||||||
 | 
					            self.current_xp -= self.max_xp
 | 
				
			||||||
 | 
					            self.max_xp = self.level * 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add_xp(self, xp: int) -> None:
 | 
				
			||||||
 | 
					        self.current_xp += xp
 | 
				
			||||||
 | 
					        self.level_up()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,10 +3,10 @@ from typing import Any
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from .entities.player import Player
 | 
					from .entities.player import Player
 | 
				
			||||||
from .interfaces import Map
 | 
					from .interfaces import Map
 | 
				
			||||||
from .mapdisplay import MapDisplay
 | 
					 | 
				
			||||||
from .settings import Settings
 | 
					from .settings import Settings
 | 
				
			||||||
from enum import Enum, auto
 | 
					from enum import Enum, auto
 | 
				
			||||||
from . import menus
 | 
					from . import menus
 | 
				
			||||||
 | 
					from typing import Callable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class GameMode(Enum):
 | 
					class GameMode(Enum):
 | 
				
			||||||
@@ -22,23 +22,35 @@ class KeyValues(Enum):
 | 
				
			|||||||
    LEFT = auto()
 | 
					    LEFT = auto()
 | 
				
			||||||
    RIGHT = auto()
 | 
					    RIGHT = auto()
 | 
				
			||||||
    ENTER = auto()
 | 
					    ENTER = auto()
 | 
				
			||||||
 | 
					    SPACE = auto()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Game:
 | 
					class Game:
 | 
				
			||||||
 | 
					    map: Map
 | 
				
			||||||
 | 
					    player: Player
 | 
				
			||||||
 | 
					    display_refresh: Callable[[], None]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self) -> None:
 | 
					    def __init__(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Init the game.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        self.state = GameMode.MAINMENU
 | 
					        self.state = GameMode.MAINMENU
 | 
				
			||||||
        self.main_menu = menus.MainMenu()
 | 
					        self.main_menu = menus.MainMenu()
 | 
				
			||||||
        self.settings = Settings()
 | 
					        self.settings = Settings()
 | 
				
			||||||
        self.settings.load_settings()
 | 
					        self.settings.load_settings()
 | 
				
			||||||
        self.settings.write_settings()
 | 
					        self.settings.write_settings()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def new_game(self, init_pad: bool = True) -> None:
 | 
					    def new_game(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Create a new game on the screen.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        # TODO generate a new map procedurally
 | 
					        # TODO generate a new map procedurally
 | 
				
			||||||
        self.m = Map.load("example_map.txt")
 | 
					        self.map = Map.load("resources/example_map.txt")
 | 
				
			||||||
 | 
					        self.map.currenty = 1
 | 
				
			||||||
 | 
					        self.map.currentx = 6
 | 
				
			||||||
        self.player = Player()
 | 
					        self.player = Player()
 | 
				
			||||||
        self.player.move(1, 6)
 | 
					        self.player.move(1, 6)
 | 
				
			||||||
        self.m.add_entity(self.player)
 | 
					        self.map.add_entity(self.player)
 | 
				
			||||||
        self.d = MapDisplay(self.m, self.player, init_pad)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def load_game(filename: str) -> None:
 | 
					    def load_game(filename: str) -> None:
 | 
				
			||||||
@@ -46,14 +58,22 @@ class Game:
 | 
				
			|||||||
        raise NotImplementedError()
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def run(self, screen: Any) -> None:
 | 
					    def run(self, screen: Any) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Main infinite loop.
 | 
				
			||||||
 | 
					        We wait for a player action, then we do what that should be done
 | 
				
			||||||
 | 
					        when the given key got pressed.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        while True:
 | 
					        while True:
 | 
				
			||||||
            screen.clear()
 | 
					            screen.clear()
 | 
				
			||||||
            screen.refresh()
 | 
					            screen.refresh()
 | 
				
			||||||
            self.d.display(self.player.y, self.player.x)
 | 
					            self.display_refresh()
 | 
				
			||||||
            key = screen.getkey()
 | 
					            key = screen.getkey()
 | 
				
			||||||
            self.handle_key_pressed(self.translate_key(key))
 | 
					            self.handle_key_pressed(self.translate_key(key))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def translate_key(self, key: str) -> KeyValues:
 | 
					    def translate_key(self, key: str) -> KeyValues:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Translate the raw string key into an enum value that we can use.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        if key in (self.settings.KEY_DOWN_SECONDARY,
 | 
					        if key in (self.settings.KEY_DOWN_SECONDARY,
 | 
				
			||||||
                   self.settings.KEY_DOWN_PRIMARY):
 | 
					                   self.settings.KEY_DOWN_PRIMARY):
 | 
				
			||||||
            return KeyValues.DOWN
 | 
					            return KeyValues.DOWN
 | 
				
			||||||
@@ -68,27 +88,57 @@ class Game:
 | 
				
			|||||||
            return KeyValues.UP
 | 
					            return KeyValues.UP
 | 
				
			||||||
        elif key == self.settings.KEY_ENTER:
 | 
					        elif key == self.settings.KEY_ENTER:
 | 
				
			||||||
            return KeyValues.ENTER
 | 
					            return KeyValues.ENTER
 | 
				
			||||||
 | 
					        elif key == ' ':
 | 
				
			||||||
 | 
					            return KeyValues.SPACE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def handle_key_pressed(self, key: KeyValues) -> None:
 | 
					    def handle_key_pressed(self, key: KeyValues) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Indicates what should be done when the given key is pressed,
 | 
				
			||||||
 | 
					        according to the current game state.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        if self.state == GameMode.PLAY:
 | 
					        if self.state == GameMode.PLAY:
 | 
				
			||||||
            if key == KeyValues.UP:
 | 
					            self.handle_key_pressed_play(key)
 | 
				
			||||||
                self.player.move_up()
 | 
					        elif self.state == GameMode.MAINMENU:
 | 
				
			||||||
            if key == KeyValues.DOWN:
 | 
					            self.handle_key_pressed_main_menu(key)
 | 
				
			||||||
                self.player.move_down()
 | 
					        elif self.state == GameMode.SETTINGS:
 | 
				
			||||||
            if key == KeyValues.LEFT:
 | 
					            self.handle_key_pressed_settings(key)
 | 
				
			||||||
                self.player.move_left()
 | 
					        self.display_refresh()
 | 
				
			||||||
            if key == KeyValues.RIGHT:
 | 
					
 | 
				
			||||||
                self.player.move_right()
 | 
					    def handle_key_pressed_play(self, key: KeyValues) -> None:
 | 
				
			||||||
        if self.state == GameMode.MAINMENU:
 | 
					        """
 | 
				
			||||||
            if key == KeyValues.DOWN:
 | 
					        In play mode, arrows or zqsd should move the main character.
 | 
				
			||||||
                self.main_menu.go_down()
 | 
					        """
 | 
				
			||||||
            if key == KeyValues.UP:
 | 
					        if key == KeyValues.UP:
 | 
				
			||||||
                self.main_menu.go_up()
 | 
					            self.player.move_up()
 | 
				
			||||||
            if key == KeyValues.ENTER:
 | 
					        elif key == KeyValues.DOWN:
 | 
				
			||||||
                option = self.main_menu.validate()
 | 
					            self.player.move_down()
 | 
				
			||||||
                if option == menus.MainMenuValues.START:
 | 
					        elif key == KeyValues.LEFT:
 | 
				
			||||||
                    self.state = GameMode.PLAY
 | 
					            self.player.move_left()
 | 
				
			||||||
                elif option == menus.MainMenuValues.SETTINGS:
 | 
					        elif key == KeyValues.RIGHT:
 | 
				
			||||||
                    self.state = GameMode.SETTINGS
 | 
					            self.player.move_right()
 | 
				
			||||||
                elif option == menus.MainMenuValues.EXIT:
 | 
					        elif key == KeyValues.SPACE:
 | 
				
			||||||
                    sys.exit(0)
 | 
					            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,7 @@
 | 
				
			|||||||
#!/usr/bin/env python
 | 
					#!/usr/bin/env python
 | 
				
			||||||
from enum import Enum
 | 
					from enum import Enum, auto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from dungeonbattle.display.texturepack import TexturePack
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Map:
 | 
					class Map:
 | 
				
			||||||
@@ -10,6 +12,10 @@ class Map:
 | 
				
			|||||||
    width: int
 | 
					    width: int
 | 
				
			||||||
    height: int
 | 
					    height: int
 | 
				
			||||||
    tiles: list
 | 
					    tiles: list
 | 
				
			||||||
 | 
					    # coordinates of the point that should be
 | 
				
			||||||
 | 
					    # on the topleft corner of the screen
 | 
				
			||||||
 | 
					    currentx: int
 | 
				
			||||||
 | 
					    currenty: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, width: int, height: int, tiles: list):
 | 
					    def __init__(self, width: int, height: int, tiles: list):
 | 
				
			||||||
        self.width = width
 | 
					        self.width = width
 | 
				
			||||||
@@ -42,24 +48,34 @@ class Map:
 | 
				
			|||||||
        lines = [line for line in lines if line]
 | 
					        lines = [line for line in lines if line]
 | 
				
			||||||
        height = len(lines)
 | 
					        height = len(lines)
 | 
				
			||||||
        width = len(lines[0])
 | 
					        width = len(lines[0])
 | 
				
			||||||
        tiles = [[Tile(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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def draw_string(self) -> str:
 | 
					    def draw_string(self, pack: TexturePack) -> str:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Draw the current map as a string object that can be rendered
 | 
					        Draw the current map as a string object that can be rendered
 | 
				
			||||||
        in the window.
 | 
					        in the window.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        return "\n".join("".join(tile.value 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Tile(Enum):
 | 
					class Tile(Enum):
 | 
				
			||||||
    EMPTY = ' '
 | 
					    EMPTY = auto()
 | 
				
			||||||
    WALL = '█'
 | 
					    WALL = auto()
 | 
				
			||||||
    FLOOR = '.'
 | 
					    FLOOR = auto()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def from_ascii_char(cls, ch: str) -> "Tile":
 | 
				
			||||||
 | 
					        for tile in Tile:
 | 
				
			||||||
 | 
					            if tile.char(TexturePack.ASCII_PACK) == ch:
 | 
				
			||||||
 | 
					                return tile
 | 
				
			||||||
 | 
					        raise ValueError(ch)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def char(self, pack: TexturePack) -> str:
 | 
				
			||||||
 | 
					        return getattr(pack, self.name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def is_wall(self) -> bool:
 | 
					    def is_wall(self) -> bool:
 | 
				
			||||||
        return self == Tile.WALL
 | 
					        return self == Tile.WALL
 | 
				
			||||||
@@ -74,6 +90,7 @@ class Tile(Enum):
 | 
				
			|||||||
class Entity:
 | 
					class Entity:
 | 
				
			||||||
    y: int
 | 
					    y: int
 | 
				
			||||||
    x: int
 | 
					    x: int
 | 
				
			||||||
 | 
					    name: str
 | 
				
			||||||
    map: Map
 | 
					    map: Map
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
@@ -104,6 +121,11 @@ class FightingEntity(Entity):
 | 
				
			|||||||
    health: int
 | 
					    health: int
 | 
				
			||||||
    strength: int
 | 
					    strength: int
 | 
				
			||||||
    dead: bool
 | 
					    dead: bool
 | 
				
			||||||
 | 
					    intelligence: int
 | 
				
			||||||
 | 
					    charisma: int
 | 
				
			||||||
 | 
					    dexterity: int
 | 
				
			||||||
 | 
					    constitution: int
 | 
				
			||||||
 | 
					    level: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
        super().__init__()
 | 
					        super().__init__()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,34 +0,0 @@
 | 
				
			|||||||
#!/usr/bin/env python
 | 
					 | 
				
			||||||
import curses
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from dungeonbattle.entities.player import Player
 | 
					 | 
				
			||||||
from dungeonbattle.interfaces import Map
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class MapDisplay:
 | 
					 | 
				
			||||||
    def __init__(self, m: Map, player: Player, init_pad: bool = True):
 | 
					 | 
				
			||||||
        self.map = m
 | 
					 | 
				
			||||||
        self.player = player
 | 
					 | 
				
			||||||
        if init_pad:
 | 
					 | 
				
			||||||
            self.pad = curses.newpad(m.height, m.width + 1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def update_pad(self) -> None:
 | 
					 | 
				
			||||||
        self.pad.addstr(0, 0, self.map.draw_string())
 | 
					 | 
				
			||||||
        # TODO Not all entities should be a player
 | 
					 | 
				
			||||||
        for e in self.map.entities:
 | 
					 | 
				
			||||||
            self.pad.addstr(e.y, e.x, '🐿')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def display(self, y: int, x: int) -> None:
 | 
					 | 
				
			||||||
        deltay, deltax = (curses.LINES // 2) + 1, (curses.COLS // 2) + 1
 | 
					 | 
				
			||||||
        pminrow, pmincol = y - deltay, x - deltax
 | 
					 | 
				
			||||||
        sminrow, smincol = max(-pminrow, 0), max(-pmincol, 0)
 | 
					 | 
				
			||||||
        deltay, deltax = curses.LINES - deltay, curses.COLS - deltax
 | 
					 | 
				
			||||||
        smaxrow = self.map.height - (y + deltay) + curses.LINES - 1
 | 
					 | 
				
			||||||
        smaxrow = min(smaxrow, curses.LINES - 1)
 | 
					 | 
				
			||||||
        smaxcol = self.map.width - (x + deltax) + curses.COLS - 1
 | 
					 | 
				
			||||||
        smaxcol = min(smaxcol, curses.COLS - 1)
 | 
					 | 
				
			||||||
        pminrow = max(0, min(self.map.height, pminrow))
 | 
					 | 
				
			||||||
        pmincol = max(0, min(self.map.width, pmincol))
 | 
					 | 
				
			||||||
        self.pad.clear()
 | 
					 | 
				
			||||||
        self.update_pad()
 | 
					 | 
				
			||||||
        self.pad.refresh(pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol)
 | 
					 | 
				
			||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
from enum import Enum, auto
 | 
					from enum import Enum
 | 
				
			||||||
from typing import Any
 | 
					from typing import Any
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -19,9 +19,12 @@ class Menu:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MainMenuValues(Enum):
 | 
					class MainMenuValues(Enum):
 | 
				
			||||||
    START = auto()
 | 
					    START = 'Jouer'
 | 
				
			||||||
    SETTINGS = auto()
 | 
					    SETTINGS = 'Paramètres'
 | 
				
			||||||
    EXIT = auto()
 | 
					    EXIT = 'Quitter'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        return self.value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MainMenu(Menu):
 | 
					class MainMenu(Menu):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,7 +29,7 @@ class Settings:
 | 
				
			|||||||
            ['KEY_RIGHT', 'Touche secondaire pour aller vers la droite']
 | 
					            ['KEY_RIGHT', 'Touche secondaire pour aller vers la droite']
 | 
				
			||||||
        self.KEY_ENTER = \
 | 
					        self.KEY_ENTER = \
 | 
				
			||||||
            ['\n', 'Touche pour valider un menu']
 | 
					            ['\n', 'Touche pour valider un menu']
 | 
				
			||||||
        self.TEXTURE_PACK = ['ASCII', 'Pack de textures utilisé']
 | 
					        self.TEXTURE_PACK = ['ascii', 'Pack de textures utilisé']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __getattribute__(self, item: str) -> Any:
 | 
					    def __getattribute__(self, item: str) -> Any:
 | 
				
			||||||
        superattribute = super().__getattribute__(item)
 | 
					        superattribute = super().__getattribute__(item)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ import curses
 | 
				
			|||||||
from types import TracebackType
 | 
					from types import TracebackType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TermManager:
 | 
					class TermManager:  # pragma: no cover
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
        self.screen = curses.initscr()
 | 
					        self.screen = curses.initscr()
 | 
				
			||||||
        # convert escapes sequences to curses abstraction
 | 
					        # convert escapes sequences to curses abstraction
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@ 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("example_map.txt")
 | 
					        self.map = Map.load("resources/example_map.txt")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_basic_entities(self) -> None:
 | 
					    def test_basic_entities(self) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@@ -95,3 +95,8 @@ class TestEntities(unittest.TestCase):
 | 
				
			|||||||
        self.assertFalse(player.move_right())
 | 
					        self.assertFalse(player.move_right())
 | 
				
			||||||
        self.assertTrue(player.move_down())
 | 
					        self.assertTrue(player.move_down())
 | 
				
			||||||
        self.assertTrue(player.move_down())
 | 
					        self.assertTrue(player.move_down())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        player.add_xp(70)
 | 
				
			||||||
 | 
					        self.assertEqual(player.current_xp, 10)
 | 
				
			||||||
 | 
					        self.assertEqual(player.max_xp, 40)
 | 
				
			||||||
 | 
					        self.assertEqual(player.level, 4)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,9 @@
 | 
				
			|||||||
 | 
					import os
 | 
				
			||||||
import unittest
 | 
					import unittest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from dungeonbattle.bootstrap import Bootstrap
 | 
				
			||||||
 | 
					from dungeonbattle.display.display import Display
 | 
				
			||||||
 | 
					from dungeonbattle.display.display_manager import DisplayManager
 | 
				
			||||||
from dungeonbattle.game import Game, KeyValues, GameMode
 | 
					from dungeonbattle.game import Game, KeyValues, GameMode
 | 
				
			||||||
from dungeonbattle.menus import MainMenuValues
 | 
					from dungeonbattle.menus import MainMenuValues
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -10,10 +14,22 @@ class TestGame(unittest.TestCase):
 | 
				
			|||||||
        Setup game.
 | 
					        Setup game.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        self.game = Game()
 | 
					        self.game = Game()
 | 
				
			||||||
        self.game.new_game(False)
 | 
					        self.game.new_game()
 | 
				
			||||||
 | 
					        display = DisplayManager(None, self.game)
 | 
				
			||||||
 | 
					        self.game.display_refresh = display.refresh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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")
 | 
				
			||||||
 | 
					        self.assertRaises(NotImplementedError, Display(None).display)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_bootstrap_fail(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Ensure that the test can't play the game,
 | 
				
			||||||
 | 
					        because there is no associated shell.
 | 
				
			||||||
 | 
					        Yeah, that's only for coverage.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.assertRaises(Exception, Bootstrap.run_game)
 | 
				
			||||||
 | 
					        self.assertEqual(os.getenv("TERM", "unknown"), "unknown")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_key_translation(self) -> None:
 | 
					    def test_key_translation(self) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@@ -37,6 +53,7 @@ class TestGame(unittest.TestCase):
 | 
				
			|||||||
            self.game.settings.KEY_RIGHT_SECONDARY), KeyValues.RIGHT)
 | 
					            self.game.settings.KEY_RIGHT_SECONDARY), KeyValues.RIGHT)
 | 
				
			||||||
        self.assertEqual(self.game.translate_key(
 | 
					        self.assertEqual(self.game.translate_key(
 | 
				
			||||||
            self.game.settings.KEY_ENTER), KeyValues.ENTER)
 | 
					            self.game.settings.KEY_ENTER), KeyValues.ENTER)
 | 
				
			||||||
 | 
					        self.assertEqual(self.game.translate_key(' '), KeyValues.SPACE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_key_press(self) -> None:
 | 
					    def test_key_press(self) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@@ -54,7 +71,8 @@ class TestGame(unittest.TestCase):
 | 
				
			|||||||
        self.game.handle_key_pressed(KeyValues.ENTER)
 | 
					        self.game.handle_key_pressed(KeyValues.ENTER)
 | 
				
			||||||
        self.assertEqual(self.game.state, GameMode.SETTINGS)
 | 
					        self.assertEqual(self.game.state, GameMode.SETTINGS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.game.state = GameMode.MAINMENU
 | 
					        self.game.handle_key_pressed(KeyValues.SPACE)
 | 
				
			||||||
 | 
					        self.assertEqual(self.game.state, GameMode.MAINMENU)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.game.handle_key_pressed(KeyValues.DOWN)
 | 
					        self.game.handle_key_pressed(KeyValues.DOWN)
 | 
				
			||||||
        self.assertEqual(self.game.main_menu.validate(),
 | 
					        self.assertEqual(self.game.main_menu.validate(),
 | 
				
			||||||
@@ -95,3 +113,6 @@ class TestGame(unittest.TestCase):
 | 
				
			|||||||
        new_y, new_x = self.game.player.y, self.game.player.x
 | 
					        new_y, new_x = self.game.player.y, self.game.player.x
 | 
				
			||||||
        self.assertEqual(new_y, y)
 | 
					        self.assertEqual(new_y, y)
 | 
				
			||||||
        self.assertEqual(new_x, x - 1)
 | 
					        self.assertEqual(new_x, x - 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.game.handle_key_pressed(KeyValues.SPACE)
 | 
				
			||||||
 | 
					        self.assertEqual(self.game.state, GameMode.MAINMENU)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import unittest
 | 
					import unittest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from dungeonbattle.display.texturepack import TexturePack
 | 
				
			||||||
from dungeonbattle.interfaces import Map, Tile
 | 
					from dungeonbattle.interfaces import Map, Tile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -8,16 +9,16 @@ 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(".#\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(), ".█\n█.")
 | 
					        self.assertEqual(m.draw_string(TexturePack.ASCII_PACK), ".#\n#.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_load_map(self) -> None:
 | 
					    def test_load_map(self) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Try to load a map from a file.
 | 
					        Try to load a map from a file.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        m = Map.load("example_map.txt")
 | 
					        m = Map.load("resources/example_map.txt")
 | 
				
			||||||
        self.assertEqual(m.width, 52)
 | 
					        self.assertEqual(m.width, 52)
 | 
				
			||||||
        self.assertEqual(m.height, 17)
 | 
					        self.assertEqual(m.height, 17)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -31,3 +32,4 @@ class TestInterfaces(unittest.TestCase):
 | 
				
			|||||||
        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.assertTrue(Tile.EMPTY.can_walk())
 | 
				
			||||||
 | 
					        self.assertRaises(ValueError, Tile.from_ascii_char, 'unknown')
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										17
									
								
								dungeonbattle/tests/screen.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								dungeonbattle/tests/screen.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					class FakePad:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    In order to run tests, we simulate a fake curses pad that accepts functions
 | 
				
			||||||
 | 
					    but does nothing with them.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def addstr(self, y: int, x: int, message: str) -> None:
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def refresh(self, pminrow: int, pmincol: int, sminrow: int,
 | 
				
			||||||
 | 
					                smincol: int, smaxrow: int, smaxcol: int) -> None:
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clear(self) -> None:
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def resize(self, height: int, width: int) -> None:
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
@@ -17,16 +17,16 @@ class TestSettings(unittest.TestCase):
 | 
				
			|||||||
        self.assertEqual(settings.KEY_DOWN_SECONDARY, 'KEY_DOWN')
 | 
					        self.assertEqual(settings.KEY_DOWN_SECONDARY, 'KEY_DOWN')
 | 
				
			||||||
        self.assertEqual(settings.KEY_LEFT_SECONDARY, 'KEY_LEFT')
 | 
					        self.assertEqual(settings.KEY_LEFT_SECONDARY, 'KEY_LEFT')
 | 
				
			||||||
        self.assertEqual(settings.KEY_RIGHT_SECONDARY, 'KEY_RIGHT')
 | 
					        self.assertEqual(settings.KEY_RIGHT_SECONDARY, 'KEY_RIGHT')
 | 
				
			||||||
        self.assertEqual(settings.TEXTURE_PACK, 'ASCII')
 | 
					        self.assertEqual(settings.TEXTURE_PACK, 'ascii')
 | 
				
			||||||
        self.assertEqual(settings.get_comment(settings.TEXTURE_PACK),
 | 
					        self.assertEqual(settings.get_comment(settings.TEXTURE_PACK),
 | 
				
			||||||
                         settings.get_comment('TEXTURE_PACK'))
 | 
					                         settings.get_comment('TEXTURE_PACK'))
 | 
				
			||||||
        self.assertEqual(settings.get_comment(settings.TEXTURE_PACK),
 | 
					        self.assertEqual(settings.get_comment(settings.TEXTURE_PACK),
 | 
				
			||||||
                         'Pack de textures utilisé')
 | 
					                         'Pack de textures utilisé')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        settings.TEXTURE_PACK = 'UNICODE'
 | 
					        settings.TEXTURE_PACK = 'squirrel'
 | 
				
			||||||
        self.assertEqual(settings.TEXTURE_PACK, 'UNICODE')
 | 
					        self.assertEqual(settings.TEXTURE_PACK, 'squirrel')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        settings.write_settings()
 | 
					        settings.write_settings()
 | 
				
			||||||
        settings.load_settings()
 | 
					        settings.load_settings()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(settings.TEXTURE_PACK, 'UNICODE')
 | 
					        self.assertEqual(settings.TEXTURE_PACK, 'squirrel')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +0,0 @@
 | 
				
			|||||||
# This is the base ascii texturepack
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ascii = {
 | 
					 | 
				
			||||||
    "EMPTY": ' ',
 | 
					 | 
				
			||||||
    "WALL": '#',
 | 
					 | 
				
			||||||
    "FLOOR": '.',
 | 
					 | 
				
			||||||
    "PLAYER": '@'
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +0,0 @@
 | 
				
			|||||||
    ███████                    █████████████        
 | 
					 | 
				
			||||||
    █.....█                    █...........█        
 | 
					 | 
				
			||||||
    █.....█                █████...........█        
 | 
					 | 
				
			||||||
    █.....█                █...............█        
 | 
					 | 
				
			||||||
    █.█████                █.███...........█        
 | 
					 | 
				
			||||||
    █.█                    █.█ █...........█        
 | 
					 | 
				
			||||||
    █.█                    █.█ █████████████        
 | 
					 | 
				
			||||||
    █.█                    █.█                      
 | 
					 | 
				
			||||||
    █.████                 █.█                      
 | 
					 | 
				
			||||||
    █....█                 █.█                      
 | 
					 | 
				
			||||||
    ████.███████████████████.█                      
 | 
					 | 
				
			||||||
       █.....................█     █████████████████
 | 
					 | 
				
			||||||
       █.....................█     █...............█
 | 
					 | 
				
			||||||
       █.....................███████...............█
 | 
					 | 
				
			||||||
       █...........................................█
 | 
					 | 
				
			||||||
       █.....................███████...............█
 | 
					 | 
				
			||||||
       ███████████████████████     █████████████████
 | 
					 | 
				
			||||||
							
								
								
									
										8
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								main.py
									
									
									
									
									
								
							@@ -1,9 +1,5 @@
 | 
				
			|||||||
#!/usr/bin/env python
 | 
					#!/usr/bin/env python
 | 
				
			||||||
from dungeonbattle.game import Game
 | 
					from dungeonbattle.bootstrap import Bootstrap
 | 
				
			||||||
from dungeonbattle.term_manager import TermManager
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
    with TermManager() as term_manager:
 | 
					    Bootstrap.run_game()
 | 
				
			||||||
        game = Game()
 | 
					 | 
				
			||||||
        game.new_game()
 | 
					 | 
				
			||||||
        game.run(term_manager.screen)
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								resources/ascii_art.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								resources/ascii_art.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					  ██████   █████   █    ██  ██▓ ██▀███   ██▀███  ▓█████  ██▓        ▄▄▄▄    ▄▄▄     ▄▄▄█████▓▄▄▄█████▓ ██▓    ▓█████ 
 | 
				
			||||||
 | 
					▒██    ▒ ▒██▓  ██▒ ██  ▓██▒▓██▒▓██ ▒ ██▒▓██ ▒ ██▒▓█   ▀ ▓██▒       ▓█████▄ ▒████▄   ▓  ██▒ ▓▒▓  ██▒ ▓▒▓██▒    ▓█   ▀ 
 | 
				
			||||||
 | 
					░ ▓██▄   ▒██▒  ██░▓██  ▒██░▒██▒▓██ ░▄█ ▒▓██ ░▄█ ▒▒███   ▒██░       ▒██▒ ▄██▒██  ▀█▄ ▒ ▓██░ ▒░▒ ▓██░ ▒░▒██░    ▒███   
 | 
				
			||||||
 | 
					  ▒   ██▒░██  █▀ ░▓▓█  ░██░░██░▒██▀▀█▄  ▒██▀▀█▄  ▒▓█  ▄ ▒██░       ▒██░█▀  ░██▄▄▄▄██░ ▓██▓ ░ ░ ▓██▓ ░ ▒██░    ▒▓█  ▄ 
 | 
				
			||||||
 | 
					▒██████▒▒░▒███▒█▄ ▒▒█████▓ ░██░░██▓ ▒██▒░██▓ ▒██▒░▒████▒░██████▒   ░▓█  ▀█▓ ▓█   ▓██▒ ▒██▒ ░   ▒██▒ ░ ░██████▒░▒████▒
 | 
				
			||||||
 | 
					▒ ▒▓▒ ▒ ░░░ ▒▒░ ▒ ░▒▓▒ ▒ ▒ ░▓  ░ ▒▓ ░▒▓░░ ▒▓ ░▒▓░░░ ▒░ ░░ ▒░▓  ░   ░▒▓███▀▒ ▒▒   ▓▒█░ ▒ ░░     ▒ ░░   ░ ▒░▓  ░░░ ▒░ ░
 | 
				
			||||||
 | 
					░ ░▒  ░ ░ ░ ▒░  ░ ░░▒░ ░ ░  ▒ ░  ░▒ ░ ▒░  ░▒ ░ ▒░ ░ ░  ░░ ░ ▒  ░   ▒░▒   ░   ▒   ▒▒ ░   ░        ░    ░ ░ ▒  ░ ░ ░  ░
 | 
				
			||||||
 | 
					░  ░  ░     ░   ░  ░░░ ░ ░  ▒ ░  ░░   ░   ░░   ░    ░     ░ ░       ░    ░   ░   ▒    ░        ░        ░ ░      ░   
 | 
				
			||||||
 | 
					      ░      ░       ░      ░     ░        ░        ░  ░    ░  ░    ░            ░  ░                     ░  ░   ░  ░
 | 
				
			||||||
 | 
					                                                                         ░                                           
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										17
									
								
								resources/example_map.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								resources/example_map.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					    #######                    #############        
 | 
				
			||||||
 | 
					    #.....#                    #...........#        
 | 
				
			||||||
 | 
					    #.....#                #####...........#        
 | 
				
			||||||
 | 
					    #.....#                #...............#        
 | 
				
			||||||
 | 
					    #.#####                #.###...........#        
 | 
				
			||||||
 | 
					    #.#                    #.# #...........#        
 | 
				
			||||||
 | 
					    #.#                    #.# #############        
 | 
				
			||||||
 | 
					    #.#                    #.#                      
 | 
				
			||||||
 | 
					    #.####                 #.#                      
 | 
				
			||||||
 | 
					    #....#                 #.#                      
 | 
				
			||||||
 | 
					    ####.###################.#                      
 | 
				
			||||||
 | 
					       #.....................#     #################
 | 
				
			||||||
 | 
					       #.....................#     #...............#
 | 
				
			||||||
 | 
					       #.....................#######...............#
 | 
				
			||||||
 | 
					       #...........................................#
 | 
				
			||||||
 | 
					       #.....................#######...............#
 | 
				
			||||||
 | 
					       #######################     #################
 | 
				
			||||||
		Reference in New Issue
	
	Block a user