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)); }); });