from retro.graph import Vertex, Edge, Graph from retro.errors import TerminalTooSmall class View: BORDER_X = 2 BORDER_Y = 3 STATE_HEIGHT = 5 DEBUG_WIDTH = 60 def __init__(self, terminal, color='white_on_black'): self.terminal = terminal self.color = color def render(self, game): self.render_layout(game) ox, oy = self.get_board_origin_coords(game) self.render_state(game) if game.debug: self.render_debug_log(game) for agent in sorted(game.agents, key=lambda a: getattr(a, 'z', 0)): if getattr(agent, 'display', True): ax, ay = agent.position if hasattr(agent, 'color'): color = self.get_color(agent.color) print(self.terminal.move_xy(ox + ax, oy + ay) + color(agent.character)) else: print(self.terminal.move_xy(ox + ax, oy + ay) + agent.character) def render_layout(self, game): bw, bh = game.board_size self.check_terminal_size(game) self.clear_screen() layout_graph = self.get_layout_graph(game) layout_graph.render(self.terminal) def clear_screen(self): print(self.terminal.home + self.get_color(self.color) + self.terminal.clear) def get_color(self, color_string): if not hasattr(self.terminal, color_string): msg = ( f"{color_string} is not a supported color." "See https://blessed.readthedocs.io/en/latest/colors.html" ) raise ValueError(msg) return getattr(self.terminal, color_string) def render_state(self, game): bw, bh = game.board_size ox, oy = self.get_state_origin_coords(game) for i, key in enumerate(sorted(game.state.keys())): msg = f"{key}: {game.state[key]}"[:bw] print(self.terminal.move_xy(ox, oy + i) + msg) def render_debug_log(self, game): bw, bh = game.board_size debug_height = bh + self.STATE_HEIGHT ox, oy = self.get_debug_origin_coords(game) for i, (turn_number, message) in enumerate(game.log_messages[-debug_height:]): msg = f"{turn_number}. {message}"[:self.DEBUG_WIDTH] print(self.terminal.move_xy(ox, oy + i) + msg) def get_layout_graph(self, game): bw, bh = game.board_size sh = self.STATE_HEIGHT ox, oy = self.get_board_origin_coords(game) vertices = [ Vertex(ox - 1, oy - 1), Vertex(ox + bw, oy - 1), Vertex(ox + bw, oy + bh), Vertex(ox + bw, oy + bh + sh), Vertex(ox - 1, oy + bh + sh), Vertex(ox - 1, oy + bh) ] edges = [ Edge(vertices[0], vertices[1]), Edge(vertices[1], vertices[2]), Edge(vertices[2], vertices[3]), Edge(vertices[3], vertices[4]), Edge(vertices[4], vertices[5]), Edge(vertices[5], vertices[0]), Edge(vertices[5], vertices[2]), ] graph = Graph(vertices, edges) if game.debug: dw = self.DEBUG_WIDTH graph.vertices.append(Vertex(ox + bw + dw, oy - 1)) graph.vertices.append(Vertex(ox + bw + dw, oy + bh + sh)) graph.edges.append(Edge(graph.vertices[1], graph.vertices[6])) graph.edges.append(Edge(graph.vertices[6], graph.vertices[7])) graph.edges.append(Edge(graph.vertices[3], graph.vertices[7])) return graph def check_terminal_size(self, game): bw, bh = game.board_size width_needed = bw + self.BORDER_X height_needed = bh + self.BORDER_Y + self.STATE_HEIGHT if self.terminal.width < width_needed: raise TerminalTooSmall(width=self.terminal.width, width_needed=width_needed) elif self.terminal.height < height_needed: raise TerminalTooSmall(height=self.terminal.height, height_needed=height_needed) def board_origin(self, game): x, y = self.get_board_origin_coords(game) return self.terminal.move_xy(x, y) def get_board_origin_coords(self, game): bw, bh = game.board_size margin_top = (self.terminal.height - bh - self.BORDER_Y) // 2 if game.debug: margin_left = (self.terminal.width - bw - self.DEBUG_WIDTH - self.BORDER_X) // 2 else: margin_left = (self.terminal.width - bw - self.BORDER_X) // 2 return margin_left, margin_top def get_state_origin_coords(self, game): bw, bh = game.board_size ox, oy = self.get_board_origin_coords(game) return ox, oy + bh + 1 def get_debug_origin_coords(self, game): bw, bh = game.board_size ox, oy = self.get_board_origin_coords(game) return ox + bw + 1, oy