Stage 7 (wip): UI shell, libs, mock transport, screens (lobby->game), e2e smoke
- plain Svelte 5 + TS + Vite (no SvelteKit); CSS-token design system (Telegram-ready), hash router, IndexedDB session - pure libs: domain model, premium/value maps ported from solver, board replay, placement state machine, i18n en/ru - in-memory mock transport + seed data; pnpm start runs lobby->active game->board with no backend - board: pointer-drag + tap placement, MakeMove (popup / 1s-hold commit), two-state zoom, blank chooser, exchange, hint, word-check, chat - Playwright smoke (mock) green; svelte-check clean; mock bundle ~37 KB gzip
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
// Seed data for the mock transport. Enough to exercise the playable slice locally
|
||||
// (pnpm start) with no backend: a profile, one active mid-game whose board already
|
||||
// has tiles, and two finished games. Coordinates are 0-indexed (centre 7,7). Words do
|
||||
// not need to be strictly legal here — this is a visual/interaction fixture; real
|
||||
// legality and scoring come from the backend.
|
||||
|
||||
import type { ChatMessage, GameView, MoveRecord, Profile, Seat, Session } from '../model';
|
||||
|
||||
export const ME = 'me';
|
||||
|
||||
export const SESSION: Session = {
|
||||
token: 'mock-token',
|
||||
userId: ME,
|
||||
isGuest: true,
|
||||
displayName: 'You',
|
||||
};
|
||||
|
||||
export const PROFILE: Profile = {
|
||||
userId: ME,
|
||||
displayName: 'You',
|
||||
preferredLanguage: 'en',
|
||||
timeZone: 'UTC',
|
||||
hintBalance: 3,
|
||||
blockChat: false,
|
||||
blockFriendRequests: false,
|
||||
isGuest: true,
|
||||
};
|
||||
|
||||
function seat(s: number, accountId: string, displayName: string, score: number, isWinner = false): Seat {
|
||||
return { seat: s, accountId, displayName, score, hintsUsed: 0, isWinner };
|
||||
}
|
||||
|
||||
function play(
|
||||
player: number,
|
||||
dir: 'H' | 'V',
|
||||
tiles: Array<[number, number, string]>,
|
||||
words: string[],
|
||||
score: number,
|
||||
total: number,
|
||||
): MoveRecord {
|
||||
const ts = tiles.map(([row, col, letter]) => ({ row, col, letter, blank: false }));
|
||||
return {
|
||||
player,
|
||||
action: 'play',
|
||||
dir,
|
||||
mainRow: ts[0]?.row ?? 7,
|
||||
mainCol: ts[0]?.col ?? 7,
|
||||
tiles: ts,
|
||||
words,
|
||||
count: words.length,
|
||||
score,
|
||||
total,
|
||||
};
|
||||
}
|
||||
|
||||
export interface MockGame {
|
||||
view: GameView;
|
||||
moves: MoveRecord[];
|
||||
rack: string[];
|
||||
bagLen: number;
|
||||
hintsRemaining: number;
|
||||
chat: ChatMessage[];
|
||||
}
|
||||
|
||||
// --- active game G1: english, You (seat 0) vs Ann (seat 1), your turn ---
|
||||
|
||||
const G1_MOVES: MoveRecord[] = [
|
||||
play(0, 'H', [
|
||||
[7, 5, 'H'],
|
||||
[7, 6, 'E'],
|
||||
[7, 7, 'L'],
|
||||
[7, 8, 'L'],
|
||||
[7, 9, 'O'],
|
||||
], ['HELLO'], 16, 16),
|
||||
play(1, 'V', [
|
||||
[6, 9, 'W'],
|
||||
[8, 9, 'R'],
|
||||
[9, 9, 'L'],
|
||||
[10, 9, 'D'],
|
||||
], ['WORLD'], 9, 9),
|
||||
play(0, 'H', [
|
||||
[8, 10, 'A'],
|
||||
[8, 11, 'T'],
|
||||
], ['RAT'], 3, 19),
|
||||
play(1, 'V', [
|
||||
[9, 10, 'N'],
|
||||
[10, 10, 'D'],
|
||||
], ['AND'], 4, 13),
|
||||
];
|
||||
|
||||
function activeGame(): MockGame {
|
||||
return {
|
||||
view: {
|
||||
id: 'g1',
|
||||
variant: 'english',
|
||||
dictVersion: 'v1',
|
||||
status: 'active',
|
||||
players: 2,
|
||||
toMove: 0,
|
||||
turnTimeoutSecs: 86400,
|
||||
moveCount: G1_MOVES.length,
|
||||
endReason: '',
|
||||
seats: [seat(0, ME, 'You', 19), seat(1, 'ann', 'Ann', 13)],
|
||||
},
|
||||
moves: G1_MOVES,
|
||||
rack: ['R', 'E', 'T', 'I', 'N', 'A', '?'],
|
||||
bagLen: 58,
|
||||
hintsRemaining: 1,
|
||||
chat: [
|
||||
{
|
||||
id: 'c1',
|
||||
gameId: 'g1',
|
||||
senderId: 'ann',
|
||||
kind: 'message',
|
||||
body: 'good luck!',
|
||||
createdAtUnix: Math.floor(Date.now() / 1000) - 3600,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// --- finished games ---
|
||||
|
||||
function finishedG2(): MockGame {
|
||||
return {
|
||||
view: {
|
||||
id: 'g2',
|
||||
variant: 'english',
|
||||
dictVersion: 'v1',
|
||||
status: 'finished',
|
||||
players: 2,
|
||||
toMove: 0,
|
||||
turnTimeoutSecs: 86400,
|
||||
moveCount: 2,
|
||||
endReason: 'normal',
|
||||
seats: [seat(0, ME, 'You', 320, true), seat(1, 'kaya', 'Kaya', 281)],
|
||||
},
|
||||
moves: [
|
||||
play(0, 'H', [
|
||||
[7, 6, 'Q'],
|
||||
[7, 7, 'U'],
|
||||
[7, 8, 'I'],
|
||||
[7, 9, 'Z'],
|
||||
], ['QUIZ'], 48, 48),
|
||||
play(1, 'V', [
|
||||
[6, 9, 'J'],
|
||||
[8, 9, 'A'],
|
||||
[9, 9, 'M'],
|
||||
], ['JAZM'], 30, 30),
|
||||
],
|
||||
rack: [],
|
||||
bagLen: 0,
|
||||
hintsRemaining: 0,
|
||||
chat: [],
|
||||
};
|
||||
}
|
||||
|
||||
function finishedG3(): MockGame {
|
||||
return {
|
||||
view: {
|
||||
id: 'g3',
|
||||
variant: 'russian',
|
||||
dictVersion: 'v1',
|
||||
status: 'finished',
|
||||
players: 2,
|
||||
toMove: 0,
|
||||
turnTimeoutSecs: 86400,
|
||||
moveCount: 1,
|
||||
endReason: 'resignation',
|
||||
seats: [seat(0, ME, 'You', 150), seat(1, 'rick', 'Rick', 212, true)],
|
||||
},
|
||||
moves: [
|
||||
play(0, 'H', [
|
||||
[7, 6, 'С'],
|
||||
[7, 7, 'Л'],
|
||||
[7, 8, 'О'],
|
||||
[7, 9, 'В'],
|
||||
[7, 10, 'О'],
|
||||
], ['СЛОВО'], 12, 12),
|
||||
],
|
||||
rack: [],
|
||||
bagLen: 0,
|
||||
hintsRemaining: 0,
|
||||
chat: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function seedGames(): Map<string, MockGame> {
|
||||
const m = new Map<string, MockGame>();
|
||||
for (const g of [activeGame(), finishedG2(), finishedG3()]) m.set(g.view.id, g);
|
||||
return m;
|
||||
}
|
||||
Reference in New Issue
Block a user