Higher abstraction level on addmsg, fixes #43

This commit is contained in:
Yohann D'ANELLO 2020-12-12 13:46:45 +01:00
parent 48318a91fe
commit 04ae56e451
5 changed files with 97 additions and 33 deletions

View File

@ -2,7 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import curses import curses
from typing import Any, Optional, Union from typing import Any, Optional, Tuple, Union
from squirrelbattle.display.texturepack import TexturePack from squirrelbattle.display.texturepack import TexturePack
from squirrelbattle.game import Game from squirrelbattle.game import Game
@ -16,6 +16,9 @@ class Display:
height: int height: int
pad: Any pad: Any
_color_pairs = {(curses.COLOR_WHITE, curses.COLOR_BLACK): 0}
_colors_rgb = {}
def __init__(self, screen: Any, pack: Optional[TexturePack] = None): def __init__(self, screen: Any, pack: Optional[TexturePack] = None):
self.screen = screen self.screen = screen
self.pack = pack or TexturePack.get_pack("ascii") self.pack = pack or TexturePack.get_pack("ascii")
@ -31,15 +34,84 @@ class Display:
lines = [line[:width] for line in lines] lines = [line[:width] for line in lines]
return "\n".join(lines) return "\n".join(lines)
def addstr(self, pad: Any, y: int, x: int, msg: str, *options) -> None: def translate_color(self, color: Union[int, Tuple[int, int, int]]) -> int:
"""
Translate a tuple (R, G, B) into a curses color index.
If we have already a color index, then nothing is processed.
If this is a tuple, we construct a new color index if non-existing
and we return this index.
The values of R, G and B must be between 0 and 1000, and not
between 0 and 255.
"""
if isinstance(color, tuple):
# The color is a tuple (R, G, B), that is potentially unknown.
# We translate it into a curses color number.
if color not in self._colors_rgb:
# The color does not exist, we create it.
color_nb = len(self._colors_rgb) + 8
self.init_color(color_nb, color[0], color[1], color[2])
self._colors_rgb[color] = color_nb
color = self._colors_rgb[color]
return color
def addstr(self, pad: Any, y: int, x: int, msg: str,
fg_color: Union[int, Tuple[int, int, int]] = curses.COLOR_WHITE,
bg_color: Union[int, Tuple[int, int, int]] = curses.COLOR_BLACK,
*, altcharset: bool = False, blink: bool = False,
bold: bool = False, dim: bool = False, invis: bool = False,
italic: bool = False, normal: bool = False,
protect: bool = False, reverse: bool = False,
standout: bool = False, underline: bool = False,
horizontal: bool = False, left: bool = False,
low: bool = False, right: bool = False, top: bool = False,
vertical: bool = False, chartext: bool = False) -> None:
""" """
Display a message onto the pad. Display a message onto the pad.
If the message is too large, it is truncated vertically and horizontally If the message is too large, it is truncated vertically and horizontally
The text can be bold, italic, blinking, ... if the good parameters are
given. These parameters are translated into curses attributes.
The foreground and background colors can be given as curses constants
(curses.COLOR_*), or by giving a tuple (R, G, B) that corresponds to
the color. R, G, B must be between 0 and 1000, and not 0 and 255.
""" """
height, width = pad.getmaxyx() height, width = pad.getmaxyx()
# Truncate message if it is too large
msg = self.truncate(msg, height - y, width - x - 1) msg = self.truncate(msg, height - y, width - x - 1)
if msg.replace("\n", "") and x >= 0 and y >= 0: if msg.replace("\n", "") and x >= 0 and y >= 0:
return pad.addstr(y, x, msg, *options) fg_color = self.translate_color(fg_color)
bg_color = self.translate_color(bg_color)
# Get the pair number for the tuple (fg, bg)
# If it does not exist, create it and give a new unique id.
if (fg_color, bg_color) in self._color_pairs:
pair_nb = self._color_pairs[(fg_color, bg_color)]
else:
pair_nb = len(self._color_pairs)
self.init_pair(pair_nb, fg_color, bg_color)
self._color_pairs[(fg_color, bg_color)] = pair_nb
# Compute curses attributes from the parameters
attr = self.color_pair(pair_nb)
attr |= curses.A_ALTCHARSET if altcharset else 0
attr |= curses.A_BLINK if blink else 0
attr |= curses.A_BOLD if bold else 0
attr |= curses.A_DIM if dim else 0
attr |= curses.A_INVIS if invis else 0
attr |= curses.A_ITALIC if italic else 0
attr |= curses.A_NORMAL if normal else 0
attr |= curses.A_PROTECT if protect else 0
attr |= curses.A_REVERSE if reverse else 0
attr |= curses.A_STANDOUT if standout else 0
attr |= curses.A_UNDERLINE if underline else 0
attr |= curses.A_HORIZONTAL if horizontal else 0
attr |= curses.A_LEFT if left else 0
attr |= curses.A_LOW if low else 0
attr |= curses.A_RIGHT if right else 0
attr |= curses.A_TOP if top else 0
attr |= curses.A_VERTICAL if vertical else 0
attr |= curses.A_CHARTEXT if chartext else 0
return pad.addstr(y, x, msg, attr)
def init_pair(self, number: int, foreground: int, background: int) -> None: def init_pair(self, number: int, foreground: int, background: int) -> None:
return curses.init_pair(number, foreground, background) \ return curses.init_pair(number, foreground, background) \
@ -156,17 +228,13 @@ class Box(Display):
self.pad = self.newpad(self.rows, self.cols) self.pad = self.newpad(self.rows, self.cols)
self.fg_border_color = fg_border_color or curses.COLOR_WHITE self.fg_border_color = fg_border_color or curses.COLOR_WHITE
pair_number = 4 + self.fg_border_color
self.init_pair(pair_number, self.fg_border_color, curses.COLOR_BLACK)
self.pair = self.color_pair(pair_number)
def display(self) -> None: def display(self) -> None:
self.addstr(self.pad, 0, 0, "" + "" * (self.width - 2) + "", self.addstr(self.pad, 0, 0, "" + "" * (self.width - 2) + "",
self.pair) self.fg_border_color)
for i in range(1, self.height - 1): for i in range(1, self.height - 1):
self.addstr(self.pad, i, 0, "", self.pair) self.addstr(self.pad, i, 0, "", self.fg_border_color)
self.addstr(self.pad, i, self.width - 1, "", self.pair) self.addstr(self.pad, i, self.width - 1, "", self.fg_border_color)
self.addstr(self.pad, self.height - 1, 0, self.addstr(self.pad, self.height - 1, 0,
"" + "" * (self.width - 2) + "", self.pair) "" + "" * (self.width - 2) + "", self.fg_border_color)
self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.refresh_pad(self.pad, 0, 0, self.y, self.x,
self.y + self.height - 1, self.x + self.width - 1) self.y + self.height - 1, self.x + self.width - 1)

View File

@ -15,15 +15,14 @@ class MapDisplay(Display):
self.pad = self.newpad(m.height, self.pack.tile_width * 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.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.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack),
self.color_pair(1)) self.pack.tile_fg_color, self.pack.tile_bg_color)
for e in self.map.entities: for e in self.map.entities:
self.addstr(self.pad, e.y, self.pack.tile_width * e.x, self.addstr(self.pad, e.y, self.pack.tile_width * e.x,
self.pack[e.name.upper()], self.color_pair(2)) self.pack[e.name.upper()],
self.pack.entity_fg_color, self.pack.entity_bg_color)
# Display Path map for deubg purposes # Display Path map for debug purposes
# from squirrelbattle.entities.player import Player # from squirrelbattle.entities.player import Player
# players = [ p for p in self.map.entities if isinstance(p,Player) ] # players = [ p for p in self.map.entities if isinstance(p,Player) ]
# player = players[0] if len(players) > 0 else None # player = players[0] if len(players) > 0 else None
@ -42,7 +41,8 @@ class MapDisplay(Display):
# else: # else:
# character = '←' # character = '←'
# self.addstr(self.pad, y, self.pack.tile_width * x, # self.addstr(self.pad, y, self.pack.tile_width * x,
# character, self.color_pair(1)) # character, self.pack.tile_fg_color,
# self.pack.tile_bg_color)
def display(self) -> None: def display(self) -> None:
y, x = self.map.currenty, self.pack.tile_width * self.map.currentx y, x = self.map.currenty, self.pack.tile_width * self.map.currentx

View File

@ -2,6 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import curses import curses
from random import randint
from typing import List from typing import List
from squirrelbattle.menus import Menu, MainMenu from squirrelbattle.menus import Menu, MainMenu
@ -43,7 +44,7 @@ class MenuDisplay(Display):
self.menubox.refresh(self.y, self.x, self.height, self.width) self.menubox.refresh(self.y, self.x, self.height, self.width)
self.pad.erase() self.pad.erase()
self.update_pad() self.update_pad()
self.refresh_pad(self.pad, cornery, 0, self.y + 1, self.x + 2, self.refresh_pad(self.pad, cornery, 0, self.y + 1, self.x + 1,
self.height - 2 + self.y, self.height - 2 + self.y,
self.width - 2 + self.x) self.width - 2 + self.x)
@ -102,8 +103,7 @@ class MainMenuDisplay(Display):
self.pad = self.newpad(max(self.rows, len(self.title) + 30), self.pad = self.newpad(max(self.rows, len(self.title) + 30),
max(len(self.title[0]) + 5, self.cols)) max(len(self.title[0]) + 5, self.cols))
self.init_color(42, 1000, 1000, 1000) self.fg_color = curses.COLOR_WHITE
self.init_pair(42, 42, curses.COLOR_BLACK)
self.menudisplay = MenuDisplay(self.screen, self.pack) self.menudisplay = MenuDisplay(self.screen, self.pack)
self.menudisplay.update_menu(self.menu) self.menudisplay.update_menu(self.menu)
@ -112,7 +112,7 @@ class MainMenuDisplay(Display):
for i in range(len(self.title)): for i in range(len(self.title)):
self.addstr(self.pad, 4 + i, max(self.width // 2 self.addstr(self.pad, 4 + i, max(self.width // 2
- len(self.title[0]) // 2 - 1, 0), self.title[i], - len(self.title[0]) // 2 - 1, 0), self.title[i],
self.color_pair(42)) self.fg_color)
self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.refresh_pad(self.pad, 0, 0, self.y, self.x,
self.height + self.y - 1, self.height + self.y - 1,
self.width + self.x - 1) self.width + self.x - 1)
@ -131,9 +131,7 @@ class MainMenuDisplay(Display):
self.menudisplay.handle_click(y - menuy, x - menux, game) self.menudisplay.handle_click(y - menuy, x - menux, game)
if y <= len(self.title): if y <= len(self.title):
from random import randint self.fg_color = randint(0, 1000), randint(0, 1000), randint(0, 1000)
self.init_color(42, randint(0, 1000), randint(0, 1000),
randint(0, 1000))
class PlayerInventoryDisplay(MenuDisplay): class PlayerInventoryDisplay(MenuDisplay):
@ -141,7 +139,7 @@ class PlayerInventoryDisplay(MenuDisplay):
def update_pad(self) -> None: def update_pad(self) -> None:
self.addstr(self.pad, 0, (self.width - len(self.message)) // 2, self.addstr(self.pad, 0, (self.width - len(self.message)) // 2,
self.message, curses.A_BOLD | curses.A_ITALIC) self.message, bold=True, italic=True)
for i, item in enumerate(self.menu.values): for i, item in enumerate(self.menu.values):
rep = self.pack[item.name.upper()] rep = self.pack[item.name.upper()]
selection = f"[{rep}]" if i == self.menu.position else f" {rep} " selection = f"[{rep}]" if i == self.menu.position else f" {rep} "
@ -169,7 +167,7 @@ class StoreInventoryDisplay(MenuDisplay):
def update_pad(self) -> None: def update_pad(self) -> None:
self.addstr(self.pad, 0, (self.width - len(self.message)) // 2, self.addstr(self.pad, 0, (self.width - len(self.message)) // 2,
self.message, curses.A_BOLD | curses.A_ITALIC) self.message, bold=True, italic=True)
for i, item in enumerate(self.menu.values): for i, item in enumerate(self.menu.values):
rep = self.pack[item.name.upper()] rep = self.pack[item.name.upper()]
selection = f"[{rep}]" if i == self.menu.position else f" {rep} " selection = f"[{rep}]" if i == self.menu.position else f" {rep} "

View File

@ -25,7 +25,7 @@ class MessageDisplay(Display):
self.height + 2, self.width + 4) self.height + 2, self.width + 4)
self.box.display() self.box.display()
self.pad.erase() self.pad.erase()
self.addstr(self.pad, 0, 0, self.message, curses.A_BOLD) self.addstr(self.pad, 0, 0, self.message, bold=True)
self.refresh_pad(self.pad, 0, 0, self.y, self.x, self.refresh_pad(self.pad, 0, 0, self.y, self.x,
self.height + self.y - 1, self.height + self.y - 1,
self.width + self.x - 1) self.width + self.x - 1)

View File

@ -14,7 +14,6 @@ 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
@ -50,9 +49,8 @@ class StatsDisplay(Display):
f"x{self.player.hazel}") f"x{self.player.hazel}")
if self.player.dead: if self.player.dead:
self.addstr(self.pad, 11, 0, _("YOU ARE DEAD"), self.addstr(self.pad, 11, 0, _("YOU ARE DEAD"), curses.COLOR_RED,
curses.A_BOLD | curses.A_BLINK | curses.A_STANDOUT bold=True, blink=True, standout=True)
| self.color_pair(3))
def display(self) -> None: def display(self) -> None:
self.pad.erase() self.pad.erase()