Add more color variations
This commit is contained in:
parent
3655d4d456
commit
d6fa339755
2 changed files with 152 additions and 14 deletions
24
constants.ts
24
constants.ts
|
|
@ -101,17 +101,13 @@ export const getPlayerRank = (playerId: string, players: Player[]): number => {
|
|||
return sorted.findIndex(p => p.id === playerId) + 1;
|
||||
};
|
||||
|
||||
export const PLAYER_COLORS = [
|
||||
'#2563eb',
|
||||
'#e21b3c',
|
||||
'#26890c',
|
||||
'#ffa602',
|
||||
'#d89e00',
|
||||
'#0aa3a3',
|
||||
'#b8008a',
|
||||
'#6a4c93',
|
||||
'#ff6b6b',
|
||||
'#4ecdc4',
|
||||
'#45b7d1',
|
||||
'#8b5cf6',
|
||||
];
|
||||
const GOLDEN_RATIO = 0.618033988749895;
|
||||
|
||||
export const generatePlayerColor = (index: number): string => {
|
||||
const hue = ((index * GOLDEN_RATIO) % 1) * 360;
|
||||
const saturation = 70 + (index % 3) * 10;
|
||||
const lightness = 45 + (index % 2) * 10;
|
||||
return `hsl(${Math.round(hue)}, ${saturation}%, ${lightness}%)`;
|
||||
};
|
||||
|
||||
export const PLAYER_COLORS = Array.from({ length: 50 }, (_, i) => generatePlayerColor(i));
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import {
|
|||
calculateBasePoints,
|
||||
calculatePointsWithBreakdown,
|
||||
getPlayerRank,
|
||||
generatePlayerColor,
|
||||
PLAYER_COLORS,
|
||||
POINTS_PER_QUESTION,
|
||||
QUESTION_TIME_MS,
|
||||
} from '../constants';
|
||||
|
|
@ -563,3 +565,143 @@ describe('getPlayerRank', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('generatePlayerColor', () => {
|
||||
describe('output format', () => {
|
||||
it('returns valid HSL format', () => {
|
||||
const color = generatePlayerColor(0);
|
||||
expect(color).toMatch(/^hsl\(\d+, \d+%, \d+%\)$/);
|
||||
});
|
||||
|
||||
it('returns valid HSL values within correct ranges', () => {
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const color = generatePlayerColor(i);
|
||||
const match = color.match(/^hsl\((\d+), (\d+)%, (\d+)%\)$/);
|
||||
|
||||
expect(match).not.toBeNull();
|
||||
const [, hue, saturation, lightness] = match!.map(Number);
|
||||
|
||||
expect(hue).toBeGreaterThanOrEqual(0);
|
||||
expect(hue).toBeLessThan(360);
|
||||
expect(saturation).toBeGreaterThanOrEqual(70);
|
||||
expect(saturation).toBeLessThanOrEqual(90);
|
||||
expect(lightness).toBeGreaterThanOrEqual(45);
|
||||
expect(lightness).toBeLessThanOrEqual(55);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('determinism', () => {
|
||||
it('returns same color for same index', () => {
|
||||
const color1 = generatePlayerColor(5);
|
||||
const color2 = generatePlayerColor(5);
|
||||
expect(color1).toBe(color2);
|
||||
});
|
||||
|
||||
it('returns consistent colors across multiple calls', () => {
|
||||
const colors = Array.from({ length: 10 }, (_, i) => generatePlayerColor(i));
|
||||
const colorsAgain = Array.from({ length: 10 }, (_, i) => generatePlayerColor(i));
|
||||
expect(colors).toEqual(colorsAgain);
|
||||
});
|
||||
});
|
||||
|
||||
describe('color distribution (golden ratio)', () => {
|
||||
it('generates distinct hues for consecutive indices (golden ratio creates ~137° separation)', () => {
|
||||
const hues: number[] = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const color = generatePlayerColor(i);
|
||||
const match = color.match(/^hsl\((\d+),/);
|
||||
hues.push(Number(match![1]));
|
||||
}
|
||||
|
||||
for (let i = 1; i < hues.length; i++) {
|
||||
const diff = Math.abs(hues[i] - hues[i - 1]);
|
||||
const wrappedDiff = Math.min(diff, 360 - diff);
|
||||
expect(wrappedDiff).toBeGreaterThan(30);
|
||||
}
|
||||
});
|
||||
|
||||
it('generates well-distributed colors for large player counts', () => {
|
||||
const hues = new Set<number>();
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const color = generatePlayerColor(i);
|
||||
const match = color.match(/^hsl\((\d+),/);
|
||||
hues.add(Number(match![1]));
|
||||
}
|
||||
|
||||
expect(hues.size).toBeGreaterThan(30);
|
||||
});
|
||||
|
||||
it('produces 3 saturation levels (70, 80, 90)', () => {
|
||||
const saturations = new Set<number>();
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const color = generatePlayerColor(i);
|
||||
const match = color.match(/, (\d+)%,/);
|
||||
saturations.add(Number(match![1]));
|
||||
}
|
||||
|
||||
expect(saturations.size).toBe(3);
|
||||
});
|
||||
|
||||
it('produces 2 lightness levels (45, 55)', () => {
|
||||
const lightnesses = new Set<number>();
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const color = generatePlayerColor(i);
|
||||
const match = color.match(/, (\d+)%\)$/);
|
||||
lightnesses.add(Number(match![1]));
|
||||
}
|
||||
|
||||
expect(lightnesses.size).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('handles index 0', () => {
|
||||
const color = generatePlayerColor(0);
|
||||
expect(color).toMatch(/^hsl\(\d+, \d+%, \d+%\)$/);
|
||||
});
|
||||
|
||||
it('handles large indices', () => {
|
||||
const color = generatePlayerColor(1000);
|
||||
expect(color).toMatch(/^hsl\(\d+, \d+%, \d+%\)$/);
|
||||
|
||||
const match = color.match(/^hsl\((\d+), (\d+)%, (\d+)%\)$/);
|
||||
const [, hue, sat, light] = match!.map(Number);
|
||||
expect(hue).toBeLessThan(360);
|
||||
expect(sat).toBeLessThanOrEqual(90);
|
||||
expect(light).toBeLessThanOrEqual(55);
|
||||
});
|
||||
|
||||
it('handles negative indices gracefully', () => {
|
||||
const color = generatePlayerColor(-1);
|
||||
expect(color).toMatch(/^hsl\(-?\d+, \d+%, \d+%\)$/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PLAYER_COLORS', () => {
|
||||
it('contains exactly 50 colors', () => {
|
||||
expect(PLAYER_COLORS).toHaveLength(50);
|
||||
});
|
||||
|
||||
it('all colors are valid HSL strings', () => {
|
||||
for (const color of PLAYER_COLORS) {
|
||||
expect(color).toMatch(/^hsl\(\d+, \d+%, \d+%\)$/);
|
||||
}
|
||||
});
|
||||
|
||||
it('first color matches generatePlayerColor(0)', () => {
|
||||
expect(PLAYER_COLORS[0]).toBe(generatePlayerColor(0));
|
||||
});
|
||||
|
||||
it('colors are generated in order', () => {
|
||||
for (let i = 0; i < PLAYER_COLORS.length; i++) {
|
||||
expect(PLAYER_COLORS[i]).toBe(generatePlayerColor(i));
|
||||
}
|
||||
});
|
||||
|
||||
it('has 20 unique colors for first 20 indices (typical game size)', () => {
|
||||
const uniqueColors = new Set(PLAYER_COLORS.slice(0, 20));
|
||||
expect(uniqueColors.size).toBe(20);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue