Parse MTTS from telnetlib3 writer during connection and store capabilities on Player.caps field. Add convenience property Player.color_depth that delegates to caps.color_depth for easy access by rendering code. Changes: - Add caps field to Player with default 16-color ANSI capabilities - Parse MTTS in server shell after Player creation using parse_mtts() - Add Player.color_depth property for quick capability checks - Add tests verifying Player caps integration and color_depth property
158 lines
4.4 KiB
Python
158 lines
4.4 KiB
Python
"""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)
|