Files
scrabble-game/ui/src/lib/mock/data.ts
T
Ilia Denisov 3a823ca7ef feat(profile): carry linked identities in the profile (email, telegram, vk)
Add email / telegram_linked / vk_linked to the Profile (fbs table + regenerated
Go/TS bindings, gateway ProfileResp + encodeProfile, backend DTO, UI model +
decode). They are filled outside the pure projection — Server.profileResponse now
reads the account's identities (like the banner seam) — and will drive the profile's
Add / Unlink / change-email controls.
2026-07-03 09:17:17 +02:00

295 lines
7.5 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.
// 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 {
AccountRef,
ChatMessage,
GameView,
Invitation,
MoveRecord,
Profile,
Seat,
Session,
Stats,
} 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',
awayStart: '00:00',
awayEnd: '07:00',
hintBalance: 3,
blockChat: false,
blockFriendRequests: false,
isGuest: false,
notificationsInAppOnly: true,
// Every variant enabled, so the mock-driven UI offers the full New Game picker.
variantPreferences: ['erudit_ru', 'scrabble_ru', 'scrabble_en'],
email: 'you@example.com',
telegramLinked: false,
vkLinked: false,
};
// Seed social/account data for the mock (pnpm start + Playwright). The mock profile
// is a durable account so the social surfaces (friends, stats, history) are reachable.
// Ann is the active game's opponent but deliberately not a friend, so the in-game
// "add to friends" flow is demonstrable; Kaya (a finished-game opponent) is the friend.
export const MOCK_FRIENDS: AccountRef[] = [{ accountId: 'kaya', displayName: 'Kaya' }];
export const MOCK_INCOMING: AccountRef[] = [{ accountId: 'rick', displayName: 'Rick' }];
export const MOCK_STATS: Stats = {
wins: 7,
losses: 4,
draws: 1,
maxGamePoints: 421,
maxWordPoints: 134,
moves: 248, // plays across all games
hintsUsed: 12, // -> hint share 12/248 = 4.8%
// Letters are lower-cased as the backend emits them; the tile renderer upper-cases for
// display. The 'd' in "wonderful" is a blank (value 0) to exercise wildcard rendering.
// Erudit is absent on purpose, so the screen demonstrates skipping a not-yet-played variant.
bestMoves: [
{
variant: 'scrabble_en',
score: 134,
word: [
{ letter: 'w', value: 4, blank: false },
{ letter: 'o', value: 1, blank: false },
{ letter: 'n', value: 1, blank: false },
{ letter: 'd', value: 0, blank: true },
{ letter: 'e', value: 1, blank: false },
{ letter: 'r', value: 1, blank: false },
{ letter: 'f', value: 4, blank: false },
{ letter: 'u', value: 1, blank: false },
{ letter: 'l', value: 1, blank: false },
],
},
{
variant: 'scrabble_ru',
score: 88,
word: [
{ letter: 'с', value: 1, blank: false },
{ letter: 'ъ', value: 10, blank: false },
{ letter: 'ё', value: 4, blank: false },
{ letter: 'м', value: 2, blank: false },
{ letter: 'к', value: 2, blank: false },
{ letter: 'а', value: 1, blank: false },
],
},
],
};
export function mockInvitations(): Invitation[] {
return [
{
id: 'inv1',
inviter: { accountId: 'kaya', displayName: 'Kaya' },
invitees: [{ accountId: ME, displayName: 'You', seat: 1, response: 'pending' }],
variant: 'scrabble_ru',
turnTimeoutSecs: 86400,
hintsAllowed: true,
hintsPerPlayer: 1,
multipleWordsPerTurn: false,
dropoutTiles: 'remove',
status: 'pending',
gameId: '',
expiresAtUnix: Math.floor(Date.now() / 1000) + 7 * 86400,
},
];
}
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: scrabble_en, 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: 'scrabble_en',
dictVersion: 'v1',
vsAi: false,
unreadChat: false,
unreadMessages: false,
status: 'active',
players: 2,
toMove: 0,
turnTimeoutSecs: 86400,
multipleWordsPerTurn: true,
moveCount: G1_MOVES.length,
endReason: '',
lastActivityUnix: Math.floor(Date.now() / 1000) - 7200,
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: 'scrabble_en',
dictVersion: 'v1',
vsAi: false,
unreadChat: false,
unreadMessages: false,
status: 'finished',
players: 2,
toMove: 0,
turnTimeoutSecs: 86400,
multipleWordsPerTurn: true,
moveCount: 2,
endReason: 'normal',
lastActivityUnix: Math.floor(Date.now() / 1000) - 86400,
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: 'scrabble_ru',
dictVersion: 'v1',
vsAi: false,
unreadChat: false,
unreadMessages: false,
status: 'finished',
players: 2,
toMove: 0,
turnTimeoutSecs: 86400,
multipleWordsPerTurn: false,
moveCount: 1,
endReason: 'resignation',
lastActivityUnix: Math.floor(Date.now() / 1000) - 172800,
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;
}