Source code for bot_api.graphics.color

from __future__ import annotations

import re
from dataclasses import dataclass
from typing import Optional

from robocode_tank_royale.schema import Color as ColorSchema

# Regex patterns matching Java's ColorUtil
_NUMERIC_RGB = re.compile(r"^#[0-9a-fA-F]{3,8}$")
_HEX_DIGITS = re.compile(r"^(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$")


[docs] @dataclass(frozen=True) class Color: """Represents an RGBA (red, green, blue, alpha) color used in the Tank Royale game. This graphics Color implementation contains: - Internal 32-bit RGBA value - Factory methods from_rgb(), from_rgba(r,g,b,a), and from_rgba_value(rgba) - Read-only channel properties: red, green, blue, alpha (mapped to R,G,B,A) - to_rgba() for round-trip, to_hex_color() for hex string - Equality/hash based on RGBA value - Many predefined common colors Note: For compatibility with existing Python code, to_color_schema() is preserved and uses lowercase hex values as before. """ # Single internal 32-bit RGBA value, channels stored as: R (bits 31-24), G (23-16), B (15-8), A (7-0) _value: int # --- Factory methods ---
[docs] @classmethod def from_rgba_value(cls, rgba: int) -> Color: """Creates a color from a 32-bit RGBA value. Args: rgba: A 32-bit value specifying the RGBA components. Returns: A new Color initialized with the specified RGBA value. """ return cls(rgba & 0xFFFFFFFF)
[docs] @classmethod def from_rgba(cls, r: int, g: int, b: int, a: int) -> Color: """Creates a color from the specified red, green, blue, and alpha values. Args: r: The red component value (0-255). g: The green component value (0-255). b: The blue component value (0-255). a: The alpha component value (0-255). Returns: A new Color initialized with the specified RGBA values. """ return cls(((r & 0xFF) << 24) | ((g & 0xFF) << 16) | ((b & 0xFF) << 8) | (a & 0xFF))
[docs] @classmethod def from_rgb(cls, r: int, g: int, b: int) -> Color: """Creates a color from the specified red, green, and blue values with alpha 255 (fully opaque). Args: r: The red component value (0-255). g: The green component value (0-255). b: The blue component value (0-255). Returns: A new Color initialized with the specified RGB values and an alpha value of 255. """ return cls.from_rgba(r, g, b, 255)
# Convenience to mirror Java's fromRgba(baseColor, a)
[docs] @classmethod def from_color_with_alpha(cls, base_color: Color, a: int) -> Color: """Creates a color from the specified base color with a new alpha value. Args: base_color: The Color from which to derive the RGB values. a: The alpha component value (0-255). Returns: A new Color with the RGB values from the base color and the specified alpha value. """ return cls.from_rgba(base_color.red, base_color.green, base_color.blue, a)
[docs] @classmethod def from_hex_color(cls, hex_color: Optional[str]) -> Optional[Color]: """Creates a color from a hex color string (#RGB, #RRGGBB, or #RRGGBBAA). This method works the same as from_hex() except that it requires a hash sign before the hex value. An example of a numeric RGB value is "#09C" or "#0099CC", which both represent the same color. Args: hex_color: A string containing a hex color like "#09C", "#0099CC", or "#0099CCFF". Returns: A new Color; None if the input is None. Raises: ValueError: If the string is not in valid numeric RGB format. """ if hex_color is None: return None hex_color = hex_color.strip() if _NUMERIC_RGB.match(hex_color): return cls.from_hex(hex_color[1:]) raise ValueError( 'You must supply the string in numeric RGB format #[0-9a-fA-F], e.g. "#09C" or "#0099CC"' )
[docs] @classmethod def from_hex(cls, hex_triplet: str) -> Color: """Creates a color from a hex triplet (RGB, RRGGBB, or RRGGBBAA without hash). A hex triplet is either three, six, or eight hexadecimal digits that represent an RGB or RGBA color. An example of a hex triplet is "09C" or "0099CC", which both represent the same color. Args: hex_triplet: A string containing hex digits like "09C", "0099CC", or "0099CCFF". Returns: A new Color. Raises: ValueError: If the string is not valid hex digits (3, 6, or 8 characters). """ hex_triplet = hex_triplet.strip() if not _HEX_DIGITS.match(hex_triplet): raise ValueError( 'You must supply 3, 6, or 8 hex digits [0-9a-fA-F], e.g. "09C", "0099CC", or "0099CCFF"' ) length = len(hex_triplet) if length == 3: # Short form: RGB -> expand to RRGGBB r = int(hex_triplet[0], 16) g = int(hex_triplet[1], 16) b = int(hex_triplet[2], 16) r = (r << 4) | r g = (g << 4) | g b = (b << 4) | b return cls.from_rgb(r, g, b) elif length == 6: # Standard form: RRGGBB r = int(hex_triplet[0:2], 16) g = int(hex_triplet[2:4], 16) b = int(hex_triplet[4:6], 16) return cls.from_rgb(r, g, b) else: # Extended form: RRGGBBAA r = int(hex_triplet[0:2], 16) g = int(hex_triplet[2:4], 16) b = int(hex_triplet[4:6], 16) a = int(hex_triplet[6:8], 16) return cls.from_rgba(r, g, b, a)
# --- Channel properties --- @property def red(self) -> int: """Gets the red component value of this color. Returns: The red component value between 0 and 255. """ return (self._value >> 24) & 0xFF @property def green(self) -> int: """Gets the green component value of this color. Returns: The green component value between 0 and 255. """ return (self._value >> 16) & 0xFF @property def blue(self) -> int: """Gets the blue component value of this color. Returns: The blue component value between 0 and 255. """ return (self._value >> 8) & 0xFF @property def alpha(self) -> int: """Gets the alpha component value of this color. Returns: The alpha component value between 0 and 255. """ return self._value & 0xFF # Aliases for short names, if needed @property def r(self) -> int: return self.red @property def g(self) -> int: return self.green @property def b(self) -> int: return self.blue @property def a(self) -> int: return self.alpha # --- Conversions ---
[docs] def to_rgba(self) -> int: """Converts this Color to a 32-bit RGBA value. Returns: A 32-bit integer containing the RGBA representation of this color. """ return self._value & 0xFFFFFFFF
[docs] def to_hex_color(self) -> str: """Converts the color to its hexadecimal representation. Returns: A string representing the color in hexadecimal format: - If alpha is 255 (fully opaque), returns "#RRGGBB". - If alpha is not 255, returns "#RRGGBBAA". """ # Uppercase hex like Java version if self.alpha == 255: return f"#{self.red:02X}{self.green:02X}{self.blue:02X}" return f"#{self.red:02X}{self.green:02X}{self.blue:02X}{self.alpha:02X}"
# Kept for Python API compatibility (lowercase hex like before)
[docs] def to_color_schema(self) -> ColorSchema: """Converts this color to the schema Color representation used by the API. Note: The returned hex string uses lowercase letters for compatibility with the Python schema representation. Returns: A Color schema object with the hex value set. """ if self.alpha == 255: return ColorSchema(value=f"#{self.red:02x}{self.green:02x}{self.blue:02x}") else: return ColorSchema(value=f"#{self.red:02x}{self.green:02x}{self.blue:02x}{self.alpha:02x}")
# --- Equality / hashing / string --- def __eq__(self, other: object) -> bool: if self is other: return True if not isinstance(other, Color): return False return self._value == other._value def __hash__(self) -> int: return self._value def __str__(self) -> str: if self.alpha == 255: return f"Color(r={self.red}, g={self.green}, b={self.blue})" return f"Color(r={self.red}, g={self.green}, b={self.blue}, a={self.alpha})"
# --- Predefined colors (mirroring Java) --- Color.TRANSPARENT = Color.from_rgba(255, 255, 255, 0) Color.ALICE_BLUE = Color.from_rgb(240, 248, 255) Color.ANTIQUE_WHITE = Color.from_rgb(250, 235, 215) Color.AQUA = Color.from_rgb(0, 255, 255) Color.AQUAMARINE = Color.from_rgb(127, 255, 212) Color.AZURE = Color.from_rgb(240, 255, 255) Color.BEIGE = Color.from_rgb(245, 245, 220) Color.BISQUE = Color.from_rgb(255, 228, 196) Color.BLACK = Color.from_rgb(0, 0, 0) Color.BLANCHED_ALMOND = Color.from_rgb(255, 235, 205) Color.BLUE = Color.from_rgb(0, 0, 255) Color.BLUE_VIOLET = Color.from_rgb(138, 43, 226) Color.BROWN = Color.from_rgb(165, 42, 42) Color.BURLY_WOOD = Color.from_rgb(222, 184, 135) Color.CADET_BLUE = Color.from_rgb(95, 158, 160) Color.CHARTREUSE = Color.from_rgb(127, 255, 0) Color.CHOCOLATE = Color.from_rgb(210, 105, 30) Color.CORAL = Color.from_rgb(255, 127, 80) Color.CORNFLOWER_BLUE = Color.from_rgb(100, 149, 237) Color.CORNSILK = Color.from_rgb(255, 248, 220) Color.CRIMSON = Color.from_rgb(220, 20, 60) Color.CYAN = Color.from_rgb(0, 255, 255) Color.DARK_BLUE = Color.from_rgb(0, 0, 139) Color.DARK_CYAN = Color.from_rgb(0, 139, 139) Color.DARK_GOLDENROD = Color.from_rgb(184, 134, 11) Color.DARK_GRAY = Color.from_rgb(169, 169, 169) Color.DARK_GREEN = Color.from_rgb(0, 100, 0) Color.DARK_KHAKI = Color.from_rgb(189, 183, 107) Color.DARK_MAGENTA = Color.from_rgb(139, 0, 139) Color.DARK_OLIVE_GREEN = Color.from_rgb(85, 107, 47) Color.DARK_ORANGE = Color.from_rgb(255, 140, 0) Color.DARK_ORCHID = Color.from_rgb(153, 50, 204) Color.DARK_RED = Color.from_rgb(139, 0, 0) Color.DARK_SALMON = Color.from_rgb(233, 150, 122) Color.DARK_SEA_GREEN = Color.from_rgb(143, 188, 139) Color.DARK_SLATE_BLUE = Color.from_rgb(72, 61, 139) Color.DARK_SLATE_GRAY = Color.from_rgb(47, 79, 79) Color.DARK_TURQUOISE = Color.from_rgb(0, 206, 209) Color.DARK_VIOLET = Color.from_rgb(148, 0, 211) Color.DEEP_PINK = Color.from_rgb(255, 20, 147) Color.DEEP_SKY_BLUE = Color.from_rgb(0, 191, 255) Color.DIM_GRAY = Color.from_rgb(105, 105, 105) Color.DODGER_BLUE = Color.from_rgb(30, 144, 255) Color.FIREBRICK = Color.from_rgb(178, 34, 34) Color.FLORAL_WHITE = Color.from_rgb(255, 250, 240) Color.FOREST_GREEN = Color.from_rgb(34, 139, 34) Color.FUCHSIA = Color.from_rgb(255, 0, 255) Color.GAINSBORO = Color.from_rgb(220, 220, 220) Color.GHOST_WHITE = Color.from_rgb(248, 248, 255) Color.GOLD = Color.from_rgb(255, 215, 0) Color.GOLDENROD = Color.from_rgb(218, 165, 32) Color.GRAY = Color.from_rgb(128, 128, 128) Color.GREEN = Color.from_rgb(0, 128, 0) Color.GREEN_YELLOW = Color.from_rgb(173, 255, 47) Color.HONEYDEW = Color.from_rgb(240, 255, 240) Color.HOT_PINK = Color.from_rgb(255, 105, 180) Color.INDIAN_RED = Color.from_rgb(205, 92, 92) Color.INDIGO = Color.from_rgb(75, 0, 130) Color.IVORY = Color.from_rgb(255, 255, 240) Color.KHAKI = Color.from_rgb(240, 230, 140) Color.LAVENDER = Color.from_rgb(230, 230, 250) Color.LAVENDER_BLUSH = Color.from_rgb(255, 240, 245) Color.LAWN_GREEN = Color.from_rgb(124, 252, 0) Color.LEMON_CHIFFON = Color.from_rgb(255, 250, 205) Color.LIGHT_BLUE = Color.from_rgb(173, 216, 230) Color.LIGHT_CORAL = Color.from_rgb(240, 128, 128) Color.LIGHT_CYAN = Color.from_rgb(224, 255, 255) Color.LIGHT_GOLDENROD_YELLOW = Color.from_rgb(250, 250, 210) Color.LIGHT_GRAY = Color.from_rgb(211, 211, 211) Color.LIGHT_GREEN = Color.from_rgb(144, 238, 144) Color.LIGHT_PINK = Color.from_rgb(255, 182, 193) Color.LIGHT_SALMON = Color.from_rgb(255, 160, 122) Color.LIGHT_SEA_GREEN = Color.from_rgb(32, 178, 170) Color.LIGHT_SKY_BLUE = Color.from_rgb(135, 206, 250) Color.LIGHT_SLATE_GRAY = Color.from_rgb(119, 136, 153) Color.LIGHT_STEEL_BLUE = Color.from_rgb(176, 196, 222) Color.LIGHT_YELLOW = Color.from_rgb(255, 255, 224) Color.LIME = Color.from_rgb(0, 255, 0) Color.LIME_GREEN = Color.from_rgb(50, 205, 50) Color.LINEN = Color.from_rgb(250, 240, 230) Color.MAGENTA = Color.from_rgb(255, 0, 255) Color.MAROON = Color.from_rgb(128, 0, 0) Color.MEDIUM_AQUAMARINE = Color.from_rgb(102, 205, 170) Color.MEDIUM_BLUE = Color.from_rgb(0, 0, 205) Color.MEDIUM_ORCHID = Color.from_rgb(186, 85, 211) Color.MEDIUM_PURPLE = Color.from_rgb(147, 112, 219) Color.MEDIUM_SEA_GREEN = Color.from_rgb(60, 179, 113) Color.MEDIUM_SLATE_BLUE = Color.from_rgb(123, 104, 238) Color.MEDIUM_SPRING_GREEN = Color.from_rgb(0, 250, 154) Color.MEDIUM_TURQUOISE = Color.from_rgb(72, 209, 204) Color.MEDIUM_VIOLET_RED = Color.from_rgb(199, 21, 133) Color.MIDNIGHT_BLUE = Color.from_rgb(25, 25, 112) Color.MINT_CREAM = Color.from_rgb(245, 255, 250) Color.MISTY_ROSE = Color.from_rgb(255, 228, 225) Color.MOCCASIN = Color.from_rgb(255, 228, 181) Color.NAVAJO_WHITE = Color.from_rgb(255, 222, 173) Color.NAVY = Color.from_rgb(0, 0, 128) Color.OLD_LACE = Color.from_rgb(253, 245, 230) Color.OLIVE = Color.from_rgb(128, 128, 0) Color.OLIVE_DRAB = Color.from_rgb(107, 142, 35) Color.ORANGE = Color.from_rgb(255, 165, 0) Color.ORANGE_RED = Color.from_rgb(255, 69, 0) Color.ORCHID = Color.from_rgb(218, 112, 214) Color.PALE_GOLDENROD = Color.from_rgb(238, 232, 170) Color.PALE_GREEN = Color.from_rgb(152, 251, 152) Color.PALE_TURQUOISE = Color.from_rgb(175, 238, 238) Color.PALE_VIOLET_RED = Color.from_rgb(219, 112, 147) Color.PAPAYA_WHIP = Color.from_rgb(255, 239, 213) Color.PEACH_PUFF = Color.from_rgb(255, 218, 185) Color.PERU = Color.from_rgb(205, 133, 63) Color.PINK = Color.from_rgb(255, 192, 203) Color.PLUM = Color.from_rgb(221, 160, 221) Color.POWDER_BLUE = Color.from_rgb(176, 224, 230) Color.PURPLE = Color.from_rgb(128, 0, 128) Color.RED = Color.from_rgb(255, 0, 0) Color.ROSY_BROWN = Color.from_rgb(188, 143, 143) Color.ROYAL_BLUE = Color.from_rgb(65, 105, 225) Color.SADDLE_BROWN = Color.from_rgb(139, 69, 19) Color.SALMON = Color.from_rgb(250, 128, 114) Color.SANDY_BROWN = Color.from_rgb(244, 164, 96) Color.SEA_GREEN = Color.from_rgb(46, 139, 87) Color.SEA_SHELL = Color.from_rgb(255, 245, 238) Color.SIENNA = Color.from_rgb(160, 82, 45) Color.SILVER = Color.from_rgb(192, 192, 192) Color.SKY_BLUE = Color.from_rgb(135, 206, 235) Color.SLATE_BLUE = Color.from_rgb(106, 90, 205) Color.SLATE_GRAY = Color.from_rgb(112, 128, 144) Color.SNOW = Color.from_rgb(255, 250, 250) Color.SPRING_GREEN = Color.from_rgb(0, 255, 127) Color.STEEL_BLUE = Color.from_rgb(70, 130, 180) Color.TAN = Color.from_rgb(210, 180, 140) Color.TEAL = Color.from_rgb(0, 128, 128) Color.THISTLE = Color.from_rgb(216, 191, 216) Color.TOMATO = Color.from_rgb(255, 99, 71) Color.TURQUOISE = Color.from_rgb(64, 224, 208) Color.VIOLET = Color.from_rgb(238, 130, 238) Color.WHEAT = Color.from_rgb(245, 222, 179) Color.WHITE = Color.from_rgb(255, 255, 255) Color.WHITE_SMOKE = Color.from_rgb(245, 245, 245) Color.YELLOW = Color.from_rgb(255, 255, 0) Color.YELLOW_GREEN = Color.from_rgb(154, 205, 50)