"""Map renderer for the Village Simulation.""" import pygame from typing import TYPE_CHECKING if TYPE_CHECKING: from frontend.client import SimulationState # Color palette class Colors: # Background colors DAY_BG = (180, 200, 160) # Soft green for day NIGHT_BG = (40, 45, 60) # Dark blue for night GRID_LINE = (120, 140, 110) # Subtle grid lines GRID_LINE_NIGHT = (60, 65, 80) # Terrain features (for visual variety) GRASS_LIGHT = (160, 190, 140) GRASS_DARK = (140, 170, 120) WATER_SPOT = (100, 140, 180) class MapRenderer: """Renders the map/terrain background.""" def __init__( self, screen: pygame.Surface, map_rect: pygame.Rect, world_width: int = 20, world_height: int = 20, ): self.screen = screen self.map_rect = map_rect self.world_width = world_width self.world_height = world_height self._cell_width = map_rect.width / world_width self._cell_height = map_rect.height / world_height # Pre-generate some terrain variation self._terrain_cache = self._generate_terrain() def _generate_terrain(self) -> list[list[int]]: """Generate simple terrain variation (0 = light, 1 = dark, 2 = water).""" import random terrain = [] for y in range(self.world_height): row = [] for x in range(self.world_width): # Simple pattern: mostly grass with occasional water spots if random.random() < 0.05: row.append(2) # Water spot elif (x + y) % 3 == 0: row.append(1) # Dark grass else: row.append(0) # Light grass terrain.append(row) return terrain def update_dimensions(self, world_width: int, world_height: int) -> None: """Update world dimensions and recalculate cell sizes.""" if world_width != self.world_width or world_height != self.world_height: self.world_width = world_width self.world_height = world_height self._cell_width = self.map_rect.width / world_width self._cell_height = self.map_rect.height / world_height self._terrain_cache = self._generate_terrain() def grid_to_screen(self, grid_x: int, grid_y: int) -> tuple[int, int]: """Convert grid coordinates to screen coordinates (center of cell).""" screen_x = self.map_rect.left + (grid_x + 0.5) * self._cell_width screen_y = self.map_rect.top + (grid_y + 0.5) * self._cell_height return int(screen_x), int(screen_y) def get_cell_size(self) -> tuple[int, int]: """Get the size of a single cell.""" return int(self._cell_width), int(self._cell_height) def draw(self, state: "SimulationState") -> None: """Draw the map background.""" is_night = state.time_of_day == "night" # Fill background bg_color = Colors.NIGHT_BG if is_night else Colors.DAY_BG pygame.draw.rect(self.screen, bg_color, self.map_rect) # Draw terrain cells for y in range(self.world_height): for x in range(self.world_width): cell_rect = pygame.Rect( self.map_rect.left + x * self._cell_width, self.map_rect.top + y * self._cell_height, self._cell_width + 1, # +1 to avoid gaps self._cell_height + 1, ) terrain_type = self._terrain_cache[y][x] if is_night: # Darker colors at night if terrain_type == 2: color = (60, 80, 110) elif terrain_type == 1: color = (35, 40, 55) else: color = (45, 50, 65) else: if terrain_type == 2: color = Colors.WATER_SPOT elif terrain_type == 1: color = Colors.GRASS_DARK else: color = Colors.GRASS_LIGHT pygame.draw.rect(self.screen, color, cell_rect) # Draw grid lines grid_color = Colors.GRID_LINE_NIGHT if is_night else Colors.GRID_LINE # Vertical lines for x in range(self.world_width + 1): start_x = self.map_rect.left + x * self._cell_width pygame.draw.line( self.screen, grid_color, (start_x, self.map_rect.top), (start_x, self.map_rect.bottom), 1, ) # Horizontal lines for y in range(self.world_height + 1): start_y = self.map_rect.top + y * self._cell_height pygame.draw.line( self.screen, grid_color, (self.map_rect.left, start_y), (self.map_rect.right, start_y), 1, ) # Draw border border_color = (80, 90, 70) if not is_night else (80, 85, 100) pygame.draw.rect(self.screen, border_color, self.map_rect, 2)