mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 07:50:45 +00:00
Feature/colorblind mode (#4150)
**Add approved & assigned issue number here:** Resolves #2549 ## Description: Adds colorblind mode. Similar to dark mode, it exists as a toggle in settings. When enabled, it swaps the game's theme (which is refactored to extend from a theme base class) to use more colorblind-friendly colors and brightness variations. Borders are darkened, and terrarin is separated by lightness. Friendly/Foe colors and switched to blue/orange instead of red/green. The theme refactor supports adding new themes without having to reimplement the color distribution system. New themes can extend the BaseTheme and supply the data, such as palettes, team-color variations, and terrain. New setting: <img width="880" height="273" alt="Screenshot 2026-06-04 at 11 30 27 AM" src="https://github.com/user-attachments/assets/d5d573d5-cc64-4ac1-95c2-00627faf17cc" /> New color palette: <img width="1119" height="757" alt="Screenshot 2026-06-04 at 11 30 59 AM" src="https://github.com/user-attachments/assets/2bb15bc9-992b-41ae-ab0e-b01fe0c3c6bb" /> ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory ## Please put your Discord username so you can be contacted if a bug or regression is found: jetaviz
This commit is contained in:
+46
-58
@@ -3,6 +3,7 @@ import {
|
||||
ColorAllocator,
|
||||
selectDistinctColorIndex,
|
||||
} from "../src/client/theme/ColorAllocator";
|
||||
import { ColorblindTheme } from "../src/client/theme/ColorblindTheme";
|
||||
import {
|
||||
blue,
|
||||
botColor,
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
teal,
|
||||
yellow,
|
||||
} from "../src/client/theme/Colors";
|
||||
import { PastelTheme } from "../src/client/theme/PastelTheme";
|
||||
import { ColoredTeams } from "../src/core/game/Game";
|
||||
|
||||
const mockColors: Colord[] = [
|
||||
@@ -80,71 +82,58 @@ describe("ColorAllocator", () => {
|
||||
expect(c1.isEqual(c1Again)).toBe(true);
|
||||
expect(c2.isEqual(c2Again)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test("assignTeamColor returns the base color from the team", () => {
|
||||
expect(allocator.assignTeamColor(ColoredTeams.Blue)).toEqual(blue);
|
||||
expect(allocator.assignTeamColor(ColoredTeams.Red)).toEqual(red);
|
||||
expect(allocator.assignTeamColor(ColoredTeams.Teal)).toEqual(teal);
|
||||
expect(allocator.assignTeamColor(ColoredTeams.Purple)).toEqual(purple);
|
||||
expect(allocator.assignTeamColor(ColoredTeams.Yellow)).toEqual(yellow);
|
||||
expect(allocator.assignTeamColor(ColoredTeams.Orange)).toEqual(orange);
|
||||
expect(allocator.assignTeamColor(ColoredTeams.Green)).toEqual(green);
|
||||
expect(allocator.assignTeamColor(ColoredTeams.Bot)).toEqual(botColor);
|
||||
expect(allocator.assignTeamColor(ColoredTeams.Humans)).toEqual(blue);
|
||||
expect(allocator.assignTeamColor(ColoredTeams.Nations)).toEqual(red);
|
||||
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("assignTeamPlayerColor always returns the same color for the same playerID", () => {
|
||||
const playerId = "player123";
|
||||
|
||||
const blueColor1 = allocator.assignTeamPlayerColor(
|
||||
ColoredTeams.Blue,
|
||||
playerId,
|
||||
);
|
||||
const blueColor2 = allocator.assignTeamPlayerColor(
|
||||
ColoredTeams.Blue,
|
||||
playerId,
|
||||
);
|
||||
|
||||
expect(blueColor1.isEqual(blueColor2)).toBe(true);
|
||||
|
||||
const redColor1 = allocator.assignTeamPlayerColor(
|
||||
ColoredTeams.Red,
|
||||
playerId,
|
||||
);
|
||||
const redColor2 = allocator.assignTeamPlayerColor(
|
||||
ColoredTeams.Red,
|
||||
playerId,
|
||||
);
|
||||
|
||||
expect(redColor1.isEqual(redColor2)).toBe(true);
|
||||
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("assignTeamPlayerColor returns a different color when the playerID is different", () => {
|
||||
const playerIdOne = "player1";
|
||||
const playerIdTwo = "player2";
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
const blueColorPlayerOne = allocator.assignTeamPlayerColor(
|
||||
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,
|
||||
playerIdOne,
|
||||
);
|
||||
const blueColorPlayerTwo = allocator.assignTeamPlayerColor(
|
||||
ColoredTeams.Blue,
|
||||
playerIdTwo,
|
||||
);
|
||||
|
||||
expect(blueColorPlayerOne.isEqual(blueColorPlayerTwo)).toBe(false);
|
||||
|
||||
const redColorPlayerOne = allocator.assignTeamPlayerColor(
|
||||
ColoredTeams.Red,
|
||||
playerIdOne,
|
||||
ColoredTeams.Teal,
|
||||
ColoredTeams.Purple,
|
||||
ColoredTeams.Yellow,
|
||||
ColoredTeams.Orange,
|
||||
ColoredTeams.Green,
|
||||
];
|
||||
const anyDifferent = teams.some(
|
||||
(team) => !pastel.teamColor(team).isEqual(colorblind.teamColor(team)),
|
||||
);
|
||||
const redColorPlayerTwo = allocator.assignTeamPlayerColor(
|
||||
ColoredTeams.Red,
|
||||
playerIdTwo,
|
||||
);
|
||||
|
||||
expect(redColorPlayerOne.isEqual(redColorPlayerTwo)).toBe(false);
|
||||
expect(anyDifferent).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -158,8 +147,7 @@ describe("selectDistinctColor", () => {
|
||||
];
|
||||
|
||||
const result = selectDistinctColorIndex(availableColors, assignedColors);
|
||||
expect(result).not.toBeNull();
|
||||
const rgb = availableColors[result!].toRgb();
|
||||
const rgb = availableColors[result].toRgb();
|
||||
expect([
|
||||
{ r: 0, g: 255, b: 0, a: 1 },
|
||||
{ r: 0, g: 0, b: 255, a: 1 },
|
||||
|
||||
Reference in New Issue
Block a user