use bevy::prelude::*;
use std::sync::atomic::{AtomicBool, Ordering};
pub const IDLE_ROW: Color = Color::srgb(0.66, 0.54, 0.17);
pub const SELECTED_ROW: Color = Color::srgb(0.95, 0.9, 0.6);
pub const CARD_BG: Color = Color::srgb(0.07, 0.08, 0.11);
pub const GOLD: Color = Color::srgb(0.96, 0.83, 0.35);
pub const PARCHMENT: Color = Color::srgb(0.92, 0.89, 0.78);
pub const CARD_FILL: Color = Color::srgba(0.04, 0.07, 0.12, 0.95);
pub const CARD_EDGE: Color = Color::srgba(0.96, 0.83, 0.35, 0.30);
pub const PILL_FILL: Color = Color::srgba(0.05, 0.07, 0.11, 0.92);
pub const FOAM_LINE: Color = Color::srgba(0.62, 0.82, 0.92, 0.16);
pub const HEADING_ICON: Color = Color::srgba(0.96, 0.83, 0.35, 0.85);
pub const INK_TIDE: Color = Color::srgb(0.55, 0.8, 0.95);
pub const INK_LURE: Color = Color::srgb(0.75, 0.6, 0.95);
pub const INK_SURGE: Color = Color::srgb(0.5, 0.9, 0.7);
pub const INK_RAID: Color = Color::srgb(0.96, 0.45, 0.38);
pub const HEADER_FILL: Color = Color::srgb(0.08, 0.09, 0.12);
pub const TOAST_FILL: Color = Color::srgb(0.1, 0.12, 0.16);
pub const CARD_SHADOW: Color = Color::srgba(0.0, 0.05, 0.12, 0.45);
pub const PICKED_WASH: Color = Color::srgba(0.96, 0.83, 0.35, 0.16);
pub const HAIRLINE: Color = Color::srgba(1.0, 1.0, 1.0, 0.14);
pub const UNLIT_RING: Color = Color::srgba(1.0, 1.0, 1.0, 0.22);
pub const BAR_TRACK: Color = Color::srgba(1.0, 1.0, 1.0, 0.08);
pub const PIP_ON: Color = Color::srgba(1.0, 1.0, 1.0, 0.95);
pub const PIP_OFF: Color = Color::srgba(1.0, 1.0, 1.0, 0.25);
pub const CHIP_NAME: Color = Color::srgba(1.0, 1.0, 1.0, 0.92);
pub const HUD_INK: Color = Color::WHITE;
pub const TITLE_INK: Color = Color::srgb(0.99, 0.85, 0.36);
pub const TITLE_SHADOW: Color = Color::srgba(0.05, 0.10, 0.20, 0.55);
pub const SIGN_FILL: Color = Color::srgba(0.05, 0.09, 0.14, 0.96);
pub const CLOCK_CALM: Color = Color::srgb(0.95, 0.93, 0.84);
pub const CLOCK_RED: Color = Color::srgb(0.96, 0.25, 0.18);
pub const CLOCK_RED_BRIGHT: Color = Color::srgb(1.0, 0.55, 0.25);
pub const INK_ONE_POST: Color = Color::srgb(0.52, 0.82, 0.55);
pub const INK_THREE_POSTS: Color = Color::srgb(0.96, 0.62, 0.30);
pub const TILE_CLEARED_INK: Color = Color::srgb(1.0, 0.96, 0.86);
pub const TILE_OPEN_FILL: Color = Color::srgba(0.95, 0.93, 0.84, 0.12);
pub const TILE_LOCKED_FILL: Color = Color::srgba(0.06, 0.08, 0.11, 0.45);
pub const TILE_LOCKED_INK: Color = Color::srgba(0.95, 0.93, 0.84, 0.22);
pub const MEDAL_GOLD: Color = Color::srgb(0.95, 0.78, 0.25);
pub const MEDAL_SILVER: Color = Color::srgb(0.76, 0.79, 0.83);
pub const MEDAL_BRONZE: Color = Color::srgb(0.75, 0.52, 0.33);
pub const MEDAL_DRIFTWOOD: Color = Color::srgb(0.42, 0.44, 0.5);
pub const MEDAL_DIGIT: Color = Color::srgb(0.12, 0.11, 0.09);
pub const GULL_INK: Color = Color::srgba(0.85, 0.88, 0.92, 0.9);
static COLORBLIND: AtomicBool = AtomicBool::new(false);
pub fn set_colorblind(on: bool) {
COLORBLIND.store(on, Ordering::Relaxed);
}
pub fn player_color(player: u8) -> Color {
if COLORBLIND.load(Ordering::Relaxed) {
safe_color(player)
} else {
classic_color(player)
}
}
pub(crate) fn classic_color(player: u8) -> Color {
match player {
0 => Color::srgb(0.84, 0.27, 0.27), 1 => Color::srgb(0.25, 0.47, 0.79), 2 => Color::srgb(0.30, 0.69, 0.31), 3 => Color::srgb(0.89, 0.73, 0.23), 4 => Color::srgb(0.70, 0.40, 0.82), _ => Color::srgb(0.95, 0.55, 0.20), }
}
fn safe_color(player: u8) -> Color {
match player {
0 => Color::srgb(0.80, 0.40, 0.00), 1 => Color::srgb(0.35, 0.70, 0.90), 2 => Color::srgb(0.95, 0.90, 0.25), 3 => Color::srgb(0.80, 0.60, 0.70), 4 => Color::srgb(0.00, 0.45, 0.70), _ => Color::srgb(0.35, 0.35, 0.38), }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn player_colors_are_distinct() {
for palette in [classic_color, safe_color] {
let colors: Vec<_> = (0..crate::sim::MAX_PLAYERS as u8).map(palette).collect();
for i in 0..colors.len() {
for j in (i + 1)..colors.len() {
assert_ne!(colors[i], colors[j], "seats {i} and {j}");
}
}
}
}
fn dichromat_view(color: Color) -> (f32, f32) {
let c = color.to_srgba();
let brightness = 0.35 * c.red + 0.55 * c.green + 0.10 * c.blue;
let blue_yellow = c.blue - (c.red + c.green) / 2.0;
(brightness, blue_yellow)
}
#[test]
fn the_safe_palette_survives_color_blind_vision() {
let separation = |palette: fn(u8) -> Color| {
let mut worst = f32::INFINITY;
for a in 0..crate::sim::MAX_PLAYERS as u8 {
for b in (a + 1)..crate::sim::MAX_PLAYERS as u8 {
let (ba, ya) = dichromat_view(palette(a));
let (bb, yb) = dichromat_view(palette(b));
worst = worst.min((ba - bb).abs().max((ya - yb).abs()));
}
}
worst
};
let safe = separation(safe_color);
assert!(safe > 0.2, "closest safe pair is only {safe} apart");
assert!(
separation(classic_color) < safe,
"the accessible palette must beat the default it replaces"
);
}
}