"""UI renderer for the Village Simulation.""" import pygame from typing import TYPE_CHECKING, Optional if TYPE_CHECKING: from frontend.client import SimulationState class Colors: # UI colors PANEL_BG = (35, 40, 50) PANEL_BORDER = (70, 80, 95) TEXT_PRIMARY = (230, 230, 235) TEXT_SECONDARY = (160, 165, 175) TEXT_HIGHLIGHT = (100, 180, 255) TEXT_WARNING = (255, 180, 80) TEXT_DANGER = (255, 100, 100) # Day/Night indicator DAY_COLOR = (255, 220, 100) NIGHT_COLOR = (100, 120, 180) class UIRenderer: """Renders UI elements (HUD, panels, text info).""" def __init__(self, screen: pygame.Surface, font: pygame.font.Font): self.screen = screen self.font = font self.small_font = pygame.font.Font(None, 20) self.title_font = pygame.font.Font(None, 28) # Panel dimensions self.top_panel_height = 50 self.right_panel_width = 200 def _draw_panel(self, rect: pygame.Rect, title: Optional[str] = None) -> None: """Draw a panel background.""" pygame.draw.rect(self.screen, Colors.PANEL_BG, rect) pygame.draw.rect(self.screen, Colors.PANEL_BORDER, rect, 1) if title: title_text = self.small_font.render(title, True, Colors.TEXT_SECONDARY) self.screen.blit(title_text, (rect.x + 8, rect.y + 4)) def draw_top_bar(self, state: "SimulationState") -> None: """Draw the top information bar.""" rect = pygame.Rect(0, 0, self.screen.get_width(), self.top_panel_height) pygame.draw.rect(self.screen, Colors.PANEL_BG, rect) pygame.draw.line( self.screen, Colors.PANEL_BORDER, (0, self.top_panel_height), (self.screen.get_width(), self.top_panel_height), ) # Day/Night and Turn info is_night = state.time_of_day == "night" time_color = Colors.NIGHT_COLOR if is_night else Colors.DAY_COLOR time_text = "NIGHT" if is_night else "DAY" # Draw time indicator circle pygame.draw.circle(self.screen, time_color, (25, 25), 12) pygame.draw.circle(self.screen, Colors.PANEL_BORDER, (25, 25), 12, 1) # Time/day text info_text = f"{time_text} | Day {state.day}, Step {state.step_in_day} | Turn {state.turn}" text = self.font.render(info_text, True, Colors.TEXT_PRIMARY) self.screen.blit(text, (50, 15)) # Mode indicator mode_color = Colors.TEXT_HIGHLIGHT if state.mode == "auto" else Colors.TEXT_SECONDARY mode_text = f"Mode: {state.mode.upper()}" text = self.small_font.render(mode_text, True, mode_color) self.screen.blit(text, (self.screen.get_width() - 120, 8)) # Running indicator if state.is_running: status_text = "RUNNING" status_color = (100, 200, 100) else: status_text = "STOPPED" status_color = Colors.TEXT_DANGER text = self.small_font.render(status_text, True, status_color) self.screen.blit(text, (self.screen.get_width() - 120, 28)) def draw_right_panel(self, state: "SimulationState") -> None: """Draw the right information panel.""" panel_x = self.screen.get_width() - self.right_panel_width rect = pygame.Rect( panel_x, self.top_panel_height, self.right_panel_width, self.screen.get_height() - self.top_panel_height, ) pygame.draw.rect(self.screen, Colors.PANEL_BG, rect) pygame.draw.line( self.screen, Colors.PANEL_BORDER, (panel_x, self.top_panel_height), (panel_x, self.screen.get_height()), ) y = self.top_panel_height + 10 # Statistics section y = self._draw_statistics_section(state, panel_x + 10, y) # Market section y = self._draw_market_section(state, panel_x + 10, y + 20) # Controls help section self._draw_controls_help(panel_x + 10, self.screen.get_height() - 100) def _draw_statistics_section(self, state: "SimulationState", x: int, y: int) -> int: """Draw the statistics section.""" # Title title = self.title_font.render("Statistics", True, Colors.TEXT_PRIMARY) self.screen.blit(title, (x, y)) y += 30 stats = state.statistics living = len(state.get_living_agents()) # Population pop_color = Colors.TEXT_PRIMARY if living > 2 else Colors.TEXT_DANGER text = self.small_font.render(f"Population: {living}", True, pop_color) self.screen.blit(text, (x, y)) y += 18 # Deaths deaths = stats.get("total_agents_died", 0) if deaths > 0: text = self.small_font.render(f"Deaths: {deaths}", True, Colors.TEXT_WARNING) self.screen.blit(text, (x, y)) y += 18 # Total money total_money = stats.get("total_money_in_circulation", 0) text = self.small_font.render(f"Total Coins: {total_money}", True, Colors.TEXT_SECONDARY) self.screen.blit(text, (x, y)) y += 18 # Professions professions = stats.get("professions", {}) if professions: y += 5 text = self.small_font.render("Professions:", True, Colors.TEXT_SECONDARY) self.screen.blit(text, (x, y)) y += 16 for prof, count in professions.items(): text = self.small_font.render(f" {prof}: {count}", True, Colors.TEXT_SECONDARY) self.screen.blit(text, (x, y)) y += 14 return y def _draw_market_section(self, state: "SimulationState", x: int, y: int) -> int: """Draw the market section.""" # Title title = self.title_font.render("Market", True, Colors.TEXT_PRIMARY) self.screen.blit(title, (x, y)) y += 30 # Order count order_count = len(state.market_orders) text = self.small_font.render(f"Active Orders: {order_count}", True, Colors.TEXT_SECONDARY) self.screen.blit(text, (x, y)) y += 20 # Price summary for each resource with available stock prices = state.market_prices for resource, data in prices.items(): if data.get("total_available", 0) > 0: price = data.get("lowest_price", "?") qty = data.get("total_available", 0) text = self.small_font.render( f"{resource}: {qty}x @ {price}c", True, Colors.TEXT_SECONDARY, ) self.screen.blit(text, (x, y)) y += 16 return y def _draw_controls_help(self, x: int, y: int) -> None: """Draw controls help at bottom of panel.""" pygame.draw.line( self.screen, Colors.PANEL_BORDER, (x - 5, y - 10), (self.screen.get_width() - 5, y - 10), ) title = self.small_font.render("Controls", True, Colors.TEXT_PRIMARY) self.screen.blit(title, (x, y)) y += 20 controls = [ "SPACE - Next Turn", "R - Reset Simulation", "M - Toggle Mode", "S - Settings", "ESC - Quit", ] for control in controls: text = self.small_font.render(control, True, Colors.TEXT_SECONDARY) self.screen.blit(text, (x, y)) y += 16 def draw_connection_status(self, connected: bool) -> None: """Draw connection status overlay when disconnected.""" if connected: return # Semi-transparent overlay overlay = pygame.Surface(self.screen.get_size(), pygame.SRCALPHA) overlay.fill((0, 0, 0, 180)) self.screen.blit(overlay, (0, 0)) # Connection message text = self.title_font.render("Connecting to server...", True, Colors.TEXT_WARNING) text_rect = text.get_rect(center=(self.screen.get_width() // 2, self.screen.get_height() // 2)) self.screen.blit(text, text_rect) hint = self.small_font.render("Make sure the backend is running on localhost:8000", True, Colors.TEXT_SECONDARY) hint_rect = hint.get_rect(center=(self.screen.get_width() // 2, self.screen.get_height() // 2 + 30)) self.screen.blit(hint, hint_rect) def draw(self, state: "SimulationState") -> None: """Draw all UI elements.""" self.draw_top_bar(state) self.draw_right_panel(state)