"""ANSI color codes for terminal rendering.""" # ANSI color codes RESET = "\033[0m" BOLD = "\033[1m" # foreground colors BLACK = "\033[30m" RED = "\033[31m" GREEN = "\033[32m" YELLOW = "\033[33m" BLUE = "\033[34m" MAGENTA = "\033[35m" CYAN = "\033[36m" WHITE = "\033[37m" # bright foreground colors BRIGHT_BLACK = "\033[90m" BRIGHT_RED = "\033[91m" BRIGHT_GREEN = "\033[92m" BRIGHT_YELLOW = "\033[93m" BRIGHT_BLUE = "\033[94m" BRIGHT_MAGENTA = "\033[95m" BRIGHT_CYAN = "\033[96m" BRIGHT_WHITE = "\033[97m" def fg_256(n: int) -> str: """Generate 256-color foreground escape code. Args: n: Color index (0-255). Values outside range are clamped. Returns: ANSI escape sequence for 256-color foreground. """ clamped = max(0, min(255, n)) return f"\033[38;5;{clamped}m" def bg_256(n: int) -> str: """Generate 256-color background escape code. Args: n: Color index (0-255). Values outside range are clamped. Returns: ANSI escape sequence for 256-color background. """ clamped = max(0, min(255, n)) return f"\033[48;5;{clamped}m" def fg_rgb(r: int, g: int, b: int) -> str: """Generate truecolor foreground escape code. Args: r: Red component (0-255). Values outside range are clamped. g: Green component (0-255). Values outside range are clamped. b: Blue component (0-255). Values outside range are clamped. Returns: ANSI escape sequence for truecolor foreground. """ r_clamped = max(0, min(255, r)) g_clamped = max(0, min(255, g)) b_clamped = max(0, min(255, b)) return f"\033[38;2;{r_clamped};{g_clamped};{b_clamped}m" def bg_rgb(r: int, g: int, b: int) -> str: """Generate truecolor background escape code. Args: r: Red component (0-255). Values outside range are clamped. g: Green component (0-255). Values outside range are clamped. b: Blue component (0-255). Values outside range are clamped. Returns: ANSI escape sequence for truecolor background. """ r_clamped = max(0, min(255, r)) g_clamped = max(0, min(255, g)) b_clamped = max(0, min(255, b)) return f"\033[48;2;{r_clamped};{g_clamped};{b_clamped}m" # terrain color mapping (16-color baseline) TERRAIN_COLORS = { ".": GREEN, # grass "^": BRIGHT_BLACK, # mountain "~": BLUE, # water "T": GREEN, # forest (darker would be "\033[32m") ":": YELLOW, # sand "@": BOLD + BRIGHT_WHITE, # player "*": BOLD + BRIGHT_RED, # other entity } # 256-color terrain palette TERRAIN_COLORS_256 = { ".": fg_256(34), # grass - richer green "^": fg_256(242), # mountain - gray "~": fg_256(27), # water - deeper blue "T": fg_256(22), # forest - darker green ":": fg_256(221), # sand - warm yellow "@": BOLD + fg_256(231), # player - bright white "*": BOLD + fg_256(196), # other entity - bright red } # truecolor terrain palette TERRAIN_COLORS_RGB = { ".": fg_rgb(76, 175, 80), # grass - material green "^": fg_rgb(96, 125, 139), # mountain - blue-gray "~": fg_rgb(33, 150, 243), # water - material blue "T": fg_rgb(27, 94, 32), # forest - dark green ":": fg_rgb(255, 235, 59), # sand - bright yellow "@": BOLD + fg_rgb(255, 255, 255), # player - pure white "*": BOLD + fg_rgb(244, 67, 54), # other entity - material red } def colorize_terrain(char: str, color_depth: str = "16") -> str: """Return ANSI-colored version of terrain character. Args: char: Terrain character to colorize color_depth: Color mode - "16", "256", or "truecolor" Returns: ANSI-colored character with reset code, or unchanged char if no color mapping. """ if color_depth == "truecolor": color = TERRAIN_COLORS_RGB.get(char, "") elif color_depth == "256": color = TERRAIN_COLORS_256.get(char, "") else: color = TERRAIN_COLORS.get(char, "") if color: return f"{color}{char}{RESET}" return char def colorize_map(grid: list[list[str]], color_depth: str = "16") -> str: """Colorize a 2D grid of terrain and return as string. Args: grid: 2D list of terrain characters color_depth: Color mode - "16", "256", or "truecolor" Returns: Newline-separated string of colorized terrain rows. """ lines = [] for row in grid: colored_row = "".join(colorize_terrain(char, color_depth) for char in row) lines.append(colored_row) return "\n".join(lines)