// Pure grouping + ordering of the lobby's game list. The lobby shows three // sections — games awaiting the caller's move, games awaiting the opponent, and finished // games — each ordered by last activity: your-turn oldest-first (the longest-neglected on // top), the other two newest-first. import type { GameView } from './model'; /** isMyTurn reports whether an active game's seat-to-move belongs to the caller. */ export function isMyTurn(game: GameView, myId: string): boolean { const me = game.seats.find((s) => s.accountId === myId); // 'open' (an auto-match game still awaiting an opponent) is in progress like 'active'. return (game.status === 'active' || game.status === 'open') && !!me && game.toMove === me.seat; } /** LobbyPhase is a game's lobby bucket — which of the three card sections it currently sits in. */ export type LobbyPhase = 'mine' | 'theirs' | 'finished'; /** * gamePhase reports a game's lobby bucket for myId: 'finished' for any non-active/open status, * else 'mine' when it is the caller's turn, else 'theirs'. It mirrors groupGames' partition and * drives the per-card blink, which fires when a card transitions into 'mine' or 'finished'. */ export function gamePhase(game: GameView, myId: string): LobbyPhase { if (game.status !== 'active' && game.status !== 'open') return 'finished'; return isMyTurn(game, myId) ? 'mine' : 'theirs'; } /** * shouldBlink reports whether a card's bucket change should fire the attention blink: only a * transition into 'mine' (it became your turn) or 'finished' (the game ended) does — an * opponent's-turn change is in-place. A first observation (prev === undefined) never blinks, so a * cold render and games arriving from another screen stay quiet. */ export function shouldBlink(prev: LobbyPhase | undefined, next: LobbyPhase): boolean { return prev !== undefined && prev !== next && (next === 'mine' || next === 'finished'); } /** LobbyGroups holds the three ordered lobby sections. */ export interface LobbyGroups { yourTurn: GameView[]; theirTurn: GameView[]; finished: GameView[]; } /** * groupGames partitions games for myId into the three lobby sections and orders each: the * your-turn games by ascending last activity (the longest-waiting first), the opponent-turn * and finished games by descending last activity (the most recent first). */ export function groupGames(games: GameView[], myId: string): LobbyGroups { const yourTurn: GameView[] = []; const theirTurn: GameView[] = []; const finished: GameView[] = []; for (const g of games) { if (g.status !== 'active' && g.status !== 'open') finished.push(g); else if (isMyTurn(g, myId)) yourTurn.push(g); else theirTurn.push(g); } yourTurn.sort((a, b) => a.lastActivityUnix - b.lastActivityUnix); theirTurn.sort((a, b) => b.lastActivityUnix - a.lastActivityUnix); finished.sort((a, b) => b.lastActivityUnix - a.lastActivityUnix); return { yourTurn, theirTurn, finished }; }