import { colord, Colord } from "colord"; import { ColorAllocator, selectDistinctColorIndex, } from "../src/client/theme/ColorAllocator"; import { ColorblindTheme } from "../src/client/theme/ColorblindTheme"; import { blue, botColor, green, orange, purple, red, teal, yellow, } from "../src/client/theme/Colors"; import { PastelTheme } from "../src/client/theme/PastelTheme"; import { ColoredTeams } from "../src/core/game/Game"; const mockColors: Colord[] = [ colord({ r: 255, g: 0, b: 0 }), colord({ r: 0, g: 255, b: 0 }), colord({ r: 0, g: 0, b: 255 }), ]; const fallbackMockColors: Colord[] = [ colord({ r: 0, g: 0, b: 0 }), colord({ r: 255, g: 255, b: 255 }), ]; const fallbackColors = [...fallbackMockColors, ...mockColors]; describe("ColorAllocator", () => { let allocator: ColorAllocator; beforeEach(() => { allocator = new ColorAllocator(mockColors, fallbackMockColors); }); test("returns a unique color for each new ID", () => { const c1 = allocator.assignColor("a"); const c2 = allocator.assignColor("b"); const c3 = allocator.assignColor("c"); expect(c1.isEqual(c2)).toBe(false); expect(c1.isEqual(c3)).toBe(false); expect(c2.isEqual(c3)).toBe(false); }); test("returns the same color for the same ID", () => { const c1 = allocator.assignColor("a"); const c2 = allocator.assignColor("a"); expect(c1.isEqual(c2)).toBe(true); }); test("falls back when colors are exhausted", () => { allocator.assignColor("1"); allocator.assignColor("2"); allocator.assignColor("3"); const fallback = allocator.assignColor("4"); const fallback2 = allocator.assignColor("5"); const match = fallbackColors.some((color) => color.isEqual(fallback)); expect(match).toBe(true); const match2 = fallback.isEqual(fallback2); expect(match2).toBe(false); }); test("assignBotColor returns deterministic color from botColors", () => { const allocator = new ColorAllocator(mockColors, mockColors); const id1 = "bot123"; const id2 = "bot456"; const c1 = allocator.assignColor(id1); const c2 = allocator.assignColor(id2); const c1Again = allocator.assignColor(id1); const c2Again = allocator.assignColor(id2); expect(c1.isEqual(c1Again)).toBe(true); expect(c2.isEqual(c2Again)).toBe(true); }); }); describe("PastelTheme team colors", () => { test("teamColor returns the base color from the team", () => { const theme = new PastelTheme(); expect(theme.teamColor(ColoredTeams.Blue)).toEqual(blue); expect(theme.teamColor(ColoredTeams.Red)).toEqual(red); expect(theme.teamColor(ColoredTeams.Teal)).toEqual(teal); expect(theme.teamColor(ColoredTeams.Purple)).toEqual(purple); expect(theme.teamColor(ColoredTeams.Yellow)).toEqual(yellow); expect(theme.teamColor(ColoredTeams.Orange)).toEqual(orange); expect(theme.teamColor(ColoredTeams.Green)).toEqual(green); expect(theme.teamColor(ColoredTeams.Bot)).toEqual(botColor); expect(theme.teamColor(ColoredTeams.Humans)).toEqual(blue); expect(theme.teamColor(ColoredTeams.Nations)).toEqual(red); }); test("teamColorForPlayer is stable for the same playerID", () => { const theme = new PastelTheme(); const a = theme.teamColorForPlayer(ColoredTeams.Blue, "player123"); const b = theme.teamColorForPlayer(ColoredTeams.Blue, "player123"); expect(a.isEqual(b)).toBe(true); }); test("teamColorForPlayer differs for different playerIDs", () => { const theme = new PastelTheme(); const a = theme.teamColorForPlayer(ColoredTeams.Blue, "player1"); const b = theme.teamColorForPlayer(ColoredTeams.Blue, "player2"); expect(a.isEqual(b)).toBe(false); }); }); describe("ColorblindTheme", () => { test("applies a palette distinct from PastelTheme", () => { const pastel = new PastelTheme(); const colorblind = new ColorblindTheme(); // At least one team's base color should differ — the colorblind theme // swaps the team palettes for CVD-safe (Okabe-Ito) colors. const teams = [ ColoredTeams.Blue, ColoredTeams.Red, ColoredTeams.Teal, ColoredTeams.Purple, ColoredTeams.Yellow, ColoredTeams.Orange, ColoredTeams.Green, ]; const anyDifferent = teams.some( (team) => !pastel.teamColor(team).isEqual(colorblind.teamColor(team)), ); expect(anyDifferent).toBe(true); }); }); describe("selectDistinctColor", () => { test("returns the most distant color", () => { const assignedColors = [colord({ r: 255, g: 0, b: 0 })]; // bright red const availableColors = [ colord({ r: 254, g: 1, b: 1 }), // too close colord({ r: 0, g: 255, b: 0 }), // distinct green colord({ r: 0, g: 0, b: 255 }), // distinct blue ]; const result = selectDistinctColorIndex(availableColors, assignedColors); const rgb = availableColors[result].toRgb(); expect([ { r: 0, g: 255, b: 0, a: 1 }, { r: 0, g: 0, b: 255, a: 1 }, ]).toContainEqual(rgb); }); });