From 6780b0495c7a14a6ca658209304677154ad4ed48 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 11 Feb 2023 01:21:52 -0800 Subject: [PATCH] Move all the interface stuff to interface.Interface Draw three windows with frames: - map window - info window (hit point bar; turn count) - message window Clean up the UI code in the Engine. --- erynrl/engine.py | 22 +++------- erynrl/interface/__init__.py | 78 ++++++++++++++++++++++++++++++++++++ erynrl/interface/window.py | 25 ++++++++++++ erynrl/map/__init__.py | 4 +- erynrl/messages.py | 6 ++- 5 files changed, 114 insertions(+), 21 deletions(-) create mode 100644 erynrl/interface/window.py diff --git a/erynrl/engine.py b/erynrl/engine.py index 26d3d0c..6779aef 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -4,7 +4,7 @@ import random from dataclasses import dataclass -from typing import TYPE_CHECKING, MutableSet, NoReturn, Optional +from typing import TYPE_CHECKING, List, MutableSet, NoReturn, Optional import tcod @@ -15,8 +15,7 @@ from .actions.result import ActionResult from .ai import HostileEnemy from .events import GameOverEventHandler, MainGameEventHandler from .geometry import Point, Rect, Size -from .interface import color -from .interface.percentage_bar import PercentageBar +from .interface import Interface from .map import Map from .map.generator import RoomsAndCorridorsGenerator from .map.generator.room import BSPRoomGenerator @@ -104,26 +103,15 @@ class Engine: self.update_field_of_view() # Interface elements - self.hit_points_bar = PercentageBar(position=Point(4, 45), width=20, colors=list(color.HealthBar.bar_colors())) - + self.interface = Interface(Size(80, 50), self.map, self.message_log) self.message_log.add_message('Greetings adventurer!', fg=(127, 127, 255), stack=False) def print_to_console(self, console): '''Print the whole game to the given console.''' self.map.highlight_points(self.__mouse_path_points or []) - self.map.print_to_console(console) - - console.print(x=1, y=45, string='HP:') - hp, max_hp = self.hero.fighter.hit_points, self.hero.fighter.maximum_hit_points - self.hit_points_bar.percent_filled = hp / max_hp - self.hit_points_bar.render_to_console(console) - console.print(x=6, y=45, string=f'{hp}/{max_hp}', fg=color.WHITE) - - console.print(x=1, y=46, string=f'Turn: {self.current_turn}') - - messages_rect = Rect(Point(x=27, y=45), Size(width=40, height=5)) - self.message_log.render_to_console(console, messages_rect) + self.interface.update(self.hero, self.current_turn) + self.interface.draw(console) entities_at_mouse_position = [] for ent in sorted(self.entities, key=lambda e: e.render_order.value): diff --git a/erynrl/interface/__init__.py b/erynrl/interface/__init__.py index 12ba5c3..7df7ba4 100644 --- a/erynrl/interface/__init__.py +++ b/erynrl/interface/__init__.py @@ -1 +1,79 @@ # Eryn Wells + +from typing import Optional + +from tcod.console import Console + +from .color import HealthBar +from .percentage_bar import PercentageBar +from .window import Window +from ..geometry import Point, Rect, Size +from ..map import Map +from ..messages import MessageLog +from ..object import Hero + + +class Interface: + def __init__(self, size: Size, map: Map, message_log: MessageLog): + self.map_window = MapWindow(Rect(Point(0, 0), Size(size.width, size.height - 5)), map) + self.info_window = InfoWindow(Rect(Point(0, size.height - 5), Size(28, 5))) + self.message_window = MessageLogWindow(Rect(Point(28, size.height - 5), Size(size.width - 28, 5)), message_log) + + def update(self, hero: Hero, turn_count: int): + self.info_window.turn_count = turn_count + self.info_window.update_hero(hero) + + def draw(self, console: Console): + self.map_window.draw(console) + self.info_window.draw(console) + self.message_window.draw(console) + + +class MapWindow(Window): + def __init__(self, bounds: Rect, map: Map): + super().__init__(bounds) + self.map = map + + def draw(self, console): + super().draw(console) + + # TODO: Get a 2D slice of tiles from the map given a rect based on the window's drawable area + drawable_area = self.drawable_area + self.map.print_to_console(console, drawable_area) + + +class InfoWindow(Window): + def __init__(self, bounds: Rect): + super().__init__(bounds, framed=True) + + self.turn_count: int = 0 + + drawable_area = self.drawable_area + self.hit_points_bar = PercentageBar( + position=Point(drawable_area.min_x + 6, drawable_area.min_y), + width=20, + colors=list(HealthBar.bar_colors())) + + def update_hero(self, hero: Hero): + hp, max_hp = hero.fighter.hit_points, hero.fighter.maximum_hit_points + self.hit_points_bar.percent_filled = hp / max_hp + + def draw(self, console): + super().draw(console) + + drawable_area = self.drawable_area + console.print(x=drawable_area.min_x + 2, y=drawable_area.min_y, string='HP:') + self.hit_points_bar.render_to_console(console) + + if self.turn_count: + console.print(x=drawable_area.min_x, y=drawable_area.min_y + 1, string=f'Turn: {self.turn_count}') + + +class MessageLogWindow(Window): + def __init__(self, bounds: Rect, message_log: MessageLog): + super().__init__(bounds, framed=True) + self.message_log = message_log + + def draw(self, console): + super().draw(console) + self.message_log.render_to_console(console, self.drawable_area) diff --git a/erynrl/interface/window.py b/erynrl/interface/window.py new file mode 100644 index 0000000..0926c3b --- /dev/null +++ b/erynrl/interface/window.py @@ -0,0 +1,25 @@ +# Eryn Wells + +from tcod.console import Console + +from ..geometry import Rect + + +class Window: + def __init__(self, bounds: Rect, *, framed: bool = True): + self.bounds = bounds + self.is_framed = framed + + @property + def drawable_area(self) -> Rect: + if self.is_framed: + return self.bounds.inset_rect(1, 1, 1, 1) + return self.bounds + + def draw(self, console: Console): + if self.is_framed: + console.draw_frame( + self.bounds.origin.x, + self.bounds.origin.y, + self.bounds.size.width, + self.bounds.size.height) diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index d19797f..4f19d84 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -12,7 +12,7 @@ import numpy as np import numpy.typing as npt import tcod -from ..geometry import Point, Size +from ..geometry import Point, Rect, Size from .generator import MapGenerator from .tile import Empty, Shroud @@ -57,7 +57,7 @@ class Map: for pt in points if points: self.highlighted[pt.x, pt.y] = True - def print_to_console(self, console: tcod.Console) -> None: + def print_to_console(self, console: tcod.Console, bounds: Rect) -> None: '''Render the map to the console.''' size = self.size diff --git a/erynrl/messages.py b/erynrl/messages.py index 3606462..56179af 100644 --- a/erynrl/messages.py +++ b/erynrl/messages.py @@ -13,6 +13,7 @@ import tcod from .geometry import Rect + class Message: '''A message in the message log @@ -44,6 +45,7 @@ class Message: def __repr__(self) -> str: return f'{self.__class__.__name__}({repr(self.text)}, fg={self.foreground})' + class MessageLog: '''A buffer of messages sent to the player by the game''' @@ -76,12 +78,12 @@ class MessageLog: @staticmethod def render_messages(console: tcod.console.Console, rect: Rect, messages: Reversible[Message]): '''Render a list of messages to the console in the given rect''' - y_offset = min(rect.size.height, len(messages)) + y_offset = min(rect.size.height, len(messages)) - 1 for message in reversed(messages): wrapped_text = textwrap.wrap(message.full_text, rect.size.width) for line in wrapped_text: - console.print(x=rect.min_x, y=rect.min_y + y_offset - 1, string=line, fg=message.foreground) + console.print(x=rect.min_x, y=rect.min_y + y_offset, string=line, fg=message.foreground) y_offset -= 1 if y_offset < 0: