From 04ae56e451e949fc231b09d71db316fe93256162 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 12 Dec 2020 13:46:45 +0100 Subject: [PATCH] Higher abstraction level on addmsg, fixes #43 --- squirrelbattle/display/display.py | 90 +++++++++++++++++++++--- squirrelbattle/display/mapdisplay.py | 12 ++-- squirrelbattle/display/menudisplay.py | 20 +++--- squirrelbattle/display/messagedisplay.py | 2 +- squirrelbattle/display/statsdisplay.py | 6 +- 5 files changed, 97 insertions(+), 33 deletions(-) diff --git a/squirrelbattle/display/display.py b/squirrelbattle/display/display.py index 0ca5673..f6dd6db 100644 --- a/squirrelbattle/display/display.py +++ b/squirrelbattle/display/display.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import curses -from typing import Any, Optional, Union +from typing import Any, Optional, Tuple, Union from squirrelbattle.display.texturepack import TexturePack from squirrelbattle.game import Game @@ -16,6 +16,9 @@ class Display: height: int pad: Any + _color_pairs = {(curses.COLOR_WHITE, curses.COLOR_BLACK): 0} + _colors_rgb = {} + def __init__(self, screen: Any, pack: Optional[TexturePack] = None): self.screen = screen self.pack = pack or TexturePack.get_pack("ascii") @@ -31,15 +34,84 @@ class Display: lines = [line[:width] for line in 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. 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() + # Truncate message if it is too large msg = self.truncate(msg, height - y, width - x - 1) 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: return curses.init_pair(number, foreground, background) \ @@ -156,17 +228,13 @@ class Box(Display): self.pad = self.newpad(self.rows, self.cols) 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: self.addstr(self.pad, 0, 0, "┏" + "━" * (self.width - 2) + "┓", - self.pair) + self.fg_border_color) for i in range(1, self.height - 1): - self.addstr(self.pad, i, 0, "┃", self.pair) - self.addstr(self.pad, i, self.width - 1, "┃", self.pair) + self.addstr(self.pad, i, 0, "┃", self.fg_border_color) + self.addstr(self.pad, i, self.width - 1, "┃", self.fg_border_color) 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.y + self.height - 1, self.x + self.width - 1) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index d403f7f..54d9432 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -15,15 +15,14 @@ class MapDisplay(Display): self.pad = self.newpad(m.height, self.pack.tile_width * m.width + 1) 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.color_pair(1)) + self.pack.tile_fg_color, self.pack.tile_bg_color) for e in self.map.entities: 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 # players = [ p for p in self.map.entities if isinstance(p,Player) ] # player = players[0] if len(players) > 0 else None @@ -42,7 +41,8 @@ class MapDisplay(Display): # else: # character = '←' # 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: y, x = self.map.currenty, self.pack.tile_width * self.map.currentx diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index 7b49a64..d067547 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import curses +from random import randint from typing import List from squirrelbattle.menus import Menu, MainMenu @@ -30,9 +31,9 @@ class MenuDisplay(Display): def update_pad(self) -> None: for i in range(self.trueheight): - self.addstr(self.pad, i, 0, " " + self.values[i]) + self.addstr(self.pad, i, 0, " " + self.values[i]) # set a marker on the selected line - self.addstr(self.pad, self.menu.position, 0, ">") + self.addstr(self.pad, self.menu.position, 0, " >") def display(self) -> None: cornery = 0 if self.height - 2 >= self.menu.position - 1 \ @@ -43,7 +44,7 @@ class MenuDisplay(Display): self.menubox.refresh(self.y, self.x, self.height, self.width) self.pad.erase() 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.width - 2 + self.x) @@ -102,8 +103,7 @@ class MainMenuDisplay(Display): self.pad = self.newpad(max(self.rows, len(self.title) + 30), max(len(self.title[0]) + 5, self.cols)) - self.init_color(42, 1000, 1000, 1000) - self.init_pair(42, 42, curses.COLOR_BLACK) + self.fg_color = curses.COLOR_WHITE self.menudisplay = MenuDisplay(self.screen, self.pack) self.menudisplay.update_menu(self.menu) @@ -112,7 +112,7 @@ class MainMenuDisplay(Display): for i in range(len(self.title)): self.addstr(self.pad, 4 + i, max(self.width // 2 - 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.height + self.y - 1, self.width + self.x - 1) @@ -131,9 +131,7 @@ class MainMenuDisplay(Display): self.menudisplay.handle_click(y - menuy, x - menux, game) if y <= len(self.title): - from random import randint - self.init_color(42, randint(0, 1000), randint(0, 1000), - randint(0, 1000)) + self.fg_color = randint(0, 1000), randint(0, 1000), randint(0, 1000) class PlayerInventoryDisplay(MenuDisplay): @@ -141,7 +139,7 @@ class PlayerInventoryDisplay(MenuDisplay): def update_pad(self) -> None: 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): rep = self.pack[item.name.upper()] selection = f"[{rep}]" if i == self.menu.position else f" {rep} " @@ -169,7 +167,7 @@ class StoreInventoryDisplay(MenuDisplay): def update_pad(self) -> None: 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): rep = self.pack[item.name.upper()] selection = f"[{rep}]" if i == self.menu.position else f" {rep} " diff --git a/squirrelbattle/display/messagedisplay.py b/squirrelbattle/display/messagedisplay.py index bcc2539..32f7139 100644 --- a/squirrelbattle/display/messagedisplay.py +++ b/squirrelbattle/display/messagedisplay.py @@ -25,7 +25,7 @@ class MessageDisplay(Display): self.height + 2, self.width + 4) self.box.display() 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.height + self.y - 1, self.width + self.x - 1) diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index ec0c90a..9937c3e 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -14,7 +14,6 @@ class StatsDisplay(Display): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) 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: self.player = p @@ -50,9 +49,8 @@ class StatsDisplay(Display): f"x{self.player.hazel}") if self.player.dead: - self.addstr(self.pad, 11, 0, _("YOU ARE DEAD"), - curses.A_BOLD | curses.A_BLINK | curses.A_STANDOUT - | self.color_pair(3)) + self.addstr(self.pad, 11, 0, _("YOU ARE DEAD"), curses.COLOR_RED, + bold=True, blink=True, standout=True) def display(self) -> None: self.pad.erase()