Files
scrabble-game/ui/src/lib/splash.test.ts
T
Ilia Denisov 9642cafc1f
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 57s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m18s
fix(ui): hold each splash word for the pause before the readiness check
The dismiss check fired at the instant a word finished laying, so a word
was never held: ЭРУДИТ fell straight into the lobby (too fast) and
ЗАГРУЗКА got no readable pause. The pause was a *leading* gap before the
next word, not a hold after the current one.

Move the hold to after each word and run the check after it: every word
(ЭРУДИТ included) now stays up for PAUSE_MS before the splash either
dismisses or lays the next word. prefixMs = WORD_MS + PAUSE_MS,
cycleMs = 2*(WORD_MS + PAUSE_MS).
2026-06-21 10:42:31 +02:00

95 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest';
import {
ERUDIT_KEYS,
ERUDIT_VALUES,
PAUSE_MS,
SPLASH_TILES,
WORD_MS,
splashSchedule,
type SplashStep,
} from './splash';
const at = (word: 'erudit' | 'zagruzka' | 'ozhidanie') =>
SPLASH_TILES.filter((t) => t.word === word);
const text = (word: 'erudit' | 'zagruzka' | 'ozhidanie') => at(word).map((t) => t.letter).join('');
const reveals = (steps: SplashStep[]) => steps.filter((s) => s.kind === 'reveal');
describe('splash layout', () => {
it('uses the Эрудит point values for every splash letter', () => {
// Spot-check the distinctive weights against scrabble-solver rules.Erudit.
expect(ERUDIT_VALUES['Э']).toBe(10);
expect(ERUDIT_VALUES['Ж']).toBe(5);
expect(ERUDIT_VALUES['З']).toBe(5);
expect(ERUDIT_VALUES['У']).toBe(3);
expect(ERUDIT_VALUES['Г']).toBe(3);
expect(ERUDIT_VALUES['Р']).toBe(2);
expect(ERUDIT_VALUES['И']).toBe(1);
});
it('places ЭРУДИТ horizontally on row 3, left to right, with its values', () => {
const e = at('erudit');
expect(text('erudit')).toBe('ЭРУДИТ');
expect(e.map((t) => t.col)).toEqual([0, 1, 2, 3, 4, 5]);
expect(e.every((t) => t.row === 3)).toBe(true);
expect(e.map((t) => t.value)).toEqual([10, 2, 3, 2, 1, 2]);
});
it('drops the shared crossing tiles (Р, Д) from the vertical words', () => {
// ЗАГРУЗКА in column 1 minus the shared Р at row 3; ОЖИДАНИЕ in column 3 minus Д.
expect(at('zagruzka').map((t) => t.row)).toEqual([0, 1, 2, 4, 5, 6, 7]);
expect(text('zagruzka')).toBe('ЗАГУЗКА'); // ЗАГ(Р)УЗКА without the shared Р
expect(at('zagruzka').every((t) => t.col === 1)).toBe(true);
expect(at('ozhidanie').map((t) => t.row)).toEqual([0, 1, 2, 4, 5, 6, 7]);
expect(text('ozhidanie')).toBe('ОЖИАНИЕ'); // ОЖИ(_)АНИЕ without Д
expect(at('ozhidanie').every((t) => t.col === 3)).toBe(true);
});
it('keeps only the ЭРУДИТ tiles as the persistent set', () => {
expect(ERUDIT_KEYS.size).toBe(6);
expect(ERUDIT_KEYS.has('3-1')).toBe(true); // Р crossing
expect(ERUDIT_KEYS.has('3-3')).toBe(true); // Д crossing
expect(SPLASH_TILES).toHaveLength(20); // 6 + 7 + 7 unique cells
});
});
describe('splash schedule', () => {
const s = splashSchedule();
const HOLD = WORD_MS + PAUSE_MS;
it('lays ЭРУДИТ first, then holds it for PAUSE_MS before the check', () => {
const r = reveals(s.prefix);
expect(r).toHaveLength(6);
expect(r.map((x) => x.atMs)).toEqual([...r.map((x) => x.atMs)].sort((a, b) => a - b));
expect(r[0].atMs).toBe(0);
expect(r[r.length - 1].atMs).toBeLessThan(WORD_MS);
// The check waits out the whole word plus the readability hold — it never fires the instant
// the last tile lands.
expect(s.prefix.at(-1)).toEqual({ atMs: HOLD, kind: 'check' });
expect(HOLD - WORD_MS).toBe(PAUSE_MS);
expect(s.prefixMs).toBe(HOLD);
});
it('reveals both vertical words per cycle and never re-lays a shared tile', () => {
const r = reveals(s.cycle);
expect(r).toHaveLength(14); // 7 + 7
expect(r.some((x) => x.key === '3-1' || x.key === '3-3')).toBe(false);
// ЗАГРУЗКА starts at the cycle start (no leading gap); the hold lives after the word.
expect(r[0].atMs).toBe(0);
});
it('checks one hold after each word and clears at the end of the cycle', () => {
const checks = s.cycle.filter((x) => x.kind === 'check').map((x) => x.atMs);
expect(checks).toEqual([HOLD, 2 * HOLD]);
const last = s.cycle.at(-1)!;
expect(last.kind).toBe('clear');
expect(last.atMs).toBe(s.cycleMs);
expect(s.cycleMs).toBe(2 * HOLD);
});
it('honours overridden timings', () => {
const fast = splashSchedule({ wordMs: 700, pauseMs: 100 });
expect(fast.prefixMs).toBe(700 + 100);
expect(fast.cycleMs).toBe(2 * (700 + 100));
});
});