Reworked graphics to make it more modular

This commit is contained in:
Nicolas Margulies 2020-11-10 18:08:06 +01:00
parent e522047c74
commit 17530f386c
9 changed files with 155 additions and 132 deletions

View File

@ -0,0 +1,14 @@
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:
game = Game()
game.new_game()
display = DisplayManager(term_manager.screen, game)
game.display_refresh = display.refresh
game.run(term_manager.screen)

View File

@ -1,57 +1,36 @@
import curses
from typing import Any, Union
from typing import Any
from dungeonbattle.tests.screen import FakePad
from dungeonbattle.display.mapdisplay import MapDisplay
from dungeonbattle.display.statsdisplay import StatsDisplay
from dungeonbattle.display.menudisplay import MenuDisplay
from dungeonbattle.interfaces import Map
from dungeonbattle.entities.player import Player
class Display:
player: Player
def __init__(self, screen: Any, m: Map, texture: Any):
def __init__(self, screen: Any, pack: Any):
self.screen = screen
self.texture = texture
self.map = m
self.mapdisplay = MapDisplay(self.map, self.texture, self.rows * 4//5, self.cols)
self.statsdisplay = StatsDisplay(self.rows//5, self.cols, self.rows * 4//5, 0)
def refresh(self, m : Map, p : Player) -> None:
self.map = m
self.player = p
self.mapdisplay.refresh(self.map, self.player)
self.statsdisplay.refresh(self.player)
# self.menudisplay.refresh(self.position)
self.pack = pack
def newpad(self, height: int, width: int) -> Union[FakePad, Any]:
return curses.newpad(height, width) if self.screen else FakePad()
def ensure_resized(self, *pads) -> bool:
"""
If the window got resized, ensure that the pads are also resized.
"""
y, x = self.screen.getmaxyx() if self.screen else (0, 0)
for pad in pads:
pad.resize(y, x)
if self.screen and curses.is_term_resized(self.rows, self.cols):
curses.resizeterm(y, x)
return True
return False
def resize(self, y, x, height, width):
self.x = x
self.y = y
self.width = width
self.height = height
def refresh(self, *args):
if len(args) == 4:
self.resize(*args)
self.display()
def display(self):
pass
@property
def rows(self) -> int:
return curses.LINES if self.screen else 42
@property
def height(self) -> int:
return self.rows
@property
def cols(self) -> int:
return curses.COLS if self.screen else 42
@property
def width(self) -> int:
return self.cols

View File

@ -0,0 +1,48 @@
import curses
from dungeonbattle.display.mapdisplay import MapDisplay
from dungeonbattle.display.statsdisplay import StatsDisplay
from dungeonbattle.display.texturepack import TexturePack
from typing import Any
from dungeonbattle.game import Game
class DisplayManager:
def __init__(self, screen: Any, g: Game):
self.game = g
self.screen = screen
self.mapdisplay = MapDisplay(screen, self.game.settings.TEXTURE_PACK)
self.statsdisplay = StatsDisplay(screen, self.game.settings.TEXTURE_PACK)
self.displays = [self.statsdisplay, self.mapdisplay]
self.update_game_components()
def update_game_components(self):
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:
self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, self.cols)
self.statsdisplay.refresh(self.rows*4//5, 0, self.rows//5, self.cols)
# self.menudisplay.refresh(self.position)
def ensure_resized(self, *pads) -> bool:
"""
If the window got resized, ensure that the pads are also resized.
"""
y, x = self.screen.getmaxyx() if self.screen else (0, 0)
for pad in pads:
pad.resize(y, x)
if self.screen and curses.is_term_resized(self.rows, self.cols):
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

View File

@ -5,15 +5,15 @@ import curses
from dungeonbattle.display.texturepack import TexturePack
from dungeonbattle.entities.player import Player
from dungeonbattle.interfaces import Map
from .display import Display
class MapDisplay:
class MapDisplay(Display):
player: Player
def __init__(self, m: Map, pack: TexturePack, height : int, width : int):
self.height = height
self.width = width
self.pack = pack
def __init__(self, *args):
super().__init__(*args)
def update_map(self, m: Map):
self.map = m
self.pad = curses.newpad(m.height, m.width + 1)
@ -22,7 +22,8 @@ class MapDisplay:
for e in self.map.entities:
self.pad.addstr(e.y, e.x, self.pack.PLAYER)
def display(self, y, x) -> None:
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)
@ -36,9 +37,3 @@ class MapDisplay:
self.pad.clear()
self.update_pad()
self.pad.refresh(pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol)
def refresh(self, m : Map, p : Player) -> None:
self.map = m
self.player = p
y, x = self.map.currenty, self.map.currentx
self.display(y,x)

View File

@ -1,69 +1,70 @@
from dungeonbattle.menus import Menu, MainMenu
from typing import Any
from .display import Display
import curses
class MenuDisplay:
class MenuDisplay(Display):
position: int
def __init__(self, menu : Menu, height : int, width : int, topleftx: int, toplefty: int) :
def __init__(self, *args) :
super().__init__(*args)
self.menubox = self.newpad(self.height,self.width)
def update_menu(self, menu: Menu):
self.menu = menu
self.values = menu.values
self.width = width
self.height = height
self.trueheight = len(menu.values)
self.truewidth = max(map(len,self.values))
self.topleftx = topleftx
self.toplefty = toplefty
#Menu values are printed in pad
self.pad = curses.newpad(self.trueheight,self.truewidth+1)
self.pad = self.newpad(self.trueheight,self.truewidth+1)
for i in range(self.trueheight-1) :
self.pad.addstr(i,0," " + self.values[i])
def update_pad(self) -> None:
for i in range(self.trueheight) :
self.pad.addstr(i,0," ")
self.pad.addstr(self.menu.position,0,">") #set a marker on the selected line
def display(self) -> None:
if self.height-2>=self.menu.position-1 :
cornery = 0
elif self.height-2 >= self.trueheight-self.menu.position :
cornery = self.trueheight-self.height+2
#Menu box
self.menubox = curses.newpad(self.height,self.width)
self.menubox.addstr(0,0,""+""*(self.width-2)+"")
for i in range(1,self.height-2) :
self.menubox.addstr(i,0,""+" "*(self.width-2)+"")
self.menubox.addstr(self.height-2, 0, ""+""*(self.width-2)+"")
def update_pad(self) -> None:
for i in range(self.trueheight) :
self.pad.addstr(i,0," ")
self.pad.addstr(self.position,0,">") #set a marker on the selected line
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+1,
self.height-2 + self.y,
self.width-2 + self.x)
def refresh(self, position : int) -> None:
self.position = position
if self.height-2>=position-1 :
cornery = 0
elif self.height-2 >= self.trueheight-position :
cornery = self.trueheight-self.height+2
self.menubox.refresh(0, 0, self.toplefty, self.topleftx,
self.height + self.toplefty,
self.width + self.topleftx)
self.update_pad(position)
self.pad.refresh(cornery, 0, self.toplefty+1, self.topleftx+1,
self.height-2 + self.toplefty,
self.width-2 + self.topleftx)
class MainMenuDisplay:
def __init__(self, menu : MainMenu) :
class MainMenuDisplay(Display):
def __init__(self, menu : MainMenu, *args) :
super().__init__(*args)
self.menu = menu
self.pad = curses.newpad(curses.LINES, curses.COLS)
self.pad = self.newpad(self.rows, self.cols)
with open("ascii_art.txt", "r") as file:
title = file.read().split("\n")
self.title = file.read().split("\n")
width = len(title[0])
height = len(title)
self.menudisplay = MenuDisplay(self.screen)
self.menudisplay.update_menu(self.menu)
for i in range(len(title)) :
self.pad.addstr(4+i,curses.COLS//2-width//2-1,title[i])
self.pad.refresh(0,0,0,0,curses.LINES,curses.COLS)
self.menudisplay = MenuDisplay(self.menu, 15, 15, height+8, curses.COLS//2-15//2-1)
def refresh(self, position) -> None:
self.menudisplay.refresh(position)
def dislpay(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.width,self.height)
menuy, menux = len(self.title)+8, self.width//2-15//2-1
self.menudisplay.refresh(menuy, menux, min(15, self.rows-menuy), min(15,self.cols-menux))

View File

@ -1,45 +1,43 @@
from typing import Any
from .display import Display
import curses
from dungeonbattle.entities.player import Player
class StatsDisplay():
class StatsDisplay(Display):
player: Player
def __init__(self, height: int, width: int,
toplefty: int, topleftx: int):
self.width = width
self.height = height
self.topleftx = topleftx
self.toplefty = toplefty
def __init__(self, *args):
super().__init__(*args)
self.pad = self.newpad(self.rows, self.cols)
self.pad = curses.newpad(height, width)
def update_player(self, p: Player):
self.player = p
def update_pad(self) -> None:
string = ""
for i in range(self.width - 1):
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 i in range(self.width - len(string2) - 1):
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 i in range(self.width - len(string3) - 1):
for _ in range(self.width - len(string3) - 1):
string3 = string3 + " "
self.pad.addstr(2, 0, string3)
def refresh(self, p : Player) -> None:
self.player = p
def display(self) -> None:
self.pad.clear()
self.update_pad()
self.pad.refresh(0, 0, self.toplefty, self.topleftx,
2 + self.toplefty,
self.width + self.topleftx)
self.pad.refresh(0, 0, self.y, self.x,
2 + self.y,
self.width + self.x)

View File

@ -17,7 +17,7 @@ class TexturePack:
@classmethod
def get_pack(cls, name: str) -> "TexturePack":
return cls._packs[name]
return cls._packs[name.lower()]
TexturePack.ASCII_PACK = TexturePack(

View File

@ -2,14 +2,12 @@ import sys
from typing import Any
from .display.display import Display
from .display.mapdisplay import MapDisplay
from .display.menudisplay import MenuDisplay
from .display.texturepack import TexturePack
from .entities.player import Player
from .interfaces import Map
from .settings import Settings
from enum import Enum, auto
from . import menus
from typing import Callable
class GameMode(Enum):
@ -31,21 +29,19 @@ class KeyValues(Enum):
class Game:
map: Map
player: Player
menu_display: MenuDisplay
map_display: MapDisplay
display: Display
display_refresh: Callable[[], None]
def __init__(self) -> None:
"""
Init the game.
"""
self.state = GameMode.MAINMENU
self.state = GameMode.PLAY
self.main_menu = menus.MainMenu()
self.settings = Settings()
self.settings.load_settings()
self.settings.write_settings()
def new_game(self, screen: Any) -> None:
def new_game(self) -> None:
"""
Create a new game on the screen.
"""
@ -56,8 +52,6 @@ class Game:
self.player = Player()
self.player.move(1, 6)
self.map.add_entity(self.player)
self.display = Display(screen, self.map, TexturePack.get_pack(self.settings.TEXTURE_PACK))
# self.menu_display = MenuDisplay(screen, self.main_menu, 0, 0)
@staticmethod
def load_game(filename: str) -> None:
@ -73,9 +67,10 @@ class Game:
while True:
screen.clear()
screen.refresh()
self.display.refresh(self.map, self.player)
self.display_refresh()
key = screen.getkey()
self.handle_key_pressed(self.translate_key(key))
self.display_refresh()
def translate_key(self, key: str) -> KeyValues:
"""
@ -109,7 +104,7 @@ class Game:
self.handle_key_pressed_main_menu(key)
elif self.state == GameMode.SETTINGS:
self.handle_key_pressed_settings(key)
self.display.refresh(self.map, self.player)
self.display_refresh()
def handle_key_pressed_play(self, key: KeyValues) -> None:
"""
@ -125,7 +120,6 @@ class Game:
self.player.move_right()
elif key == KeyValues.SPACE:
self.state = GameMode.MAINMENU
self.display = self.menu_display
def handle_key_pressed_main_menu(self, key: KeyValues) -> None:
"""
@ -139,7 +133,6 @@ class Game:
option = self.main_menu.validate()
if option == menus.MainMenuValues.START:
self.state = GameMode.PLAY
self.display = self.map_display
elif option == menus.MainMenuValues.SETTINGS:
self.state = GameMode.SETTINGS
elif option == menus.MainMenuValues.EXIT:
@ -151,4 +144,3 @@ class Game:
"""
if key == KeyValues.SPACE:
self.state = GameMode.MAINMENU
self.display = self.menu_display

View File

@ -1,9 +1,5 @@
#!/usr/bin/env python
from dungeonbattle.game import Game
from dungeonbattle.term_manager import TermManager
from dungeonbattle.bootstrap import Bootstrap
if __name__ == "__main__":
with TermManager() as term_manager:
game = Game()
game.new_game(term_manager.screen)
game.run(term_manager.screen)
Bootstrap.run_game()