import type { AppSupabaseClient } from '../supabase/client'; export type GameType = 'ttt' | 'c4'; export type Cell = 0 | 1 | null; export const TTT_CELLS = 9; export const C4_ROWS = 6; export const C4_COLS = 7; export const C4_CELLS = C4_ROWS * C4_COLS; export const TTT_LINES: ReadonlyArray = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6], ]; export interface GameRecord { id: string; conversationId: string; gameType: GameType; state: { kind: GameType; board: Cell[] }; players: [string, string]; currentTurnUserId: string | null; winnerUserId: string | null; createdAt: string; finishedAt: string | null; } export function emptyTttBoard(): Cell[] { return new Array(TTT_CELLS).fill(null); } export function emptyC4Board(): Cell[] { return new Array(C4_CELLS).fill(null); } export function tttWinningLine(board: Cell[]): readonly [number, number, number] | null { for (const line of TTT_LINES) { const a = board[line[0]]; const b = board[line[1]]; const c = board[line[2]]; if (a !== null && a === b && b === c) return line; } return null; } export function c4WinningCells(board: Cell[]): readonly number[] | null { const directions: Array<[number, number]> = [ [0, 1], [1, 0], [1, 1], [1, -1], ]; for (let r = 0; r < C4_ROWS; r++) { for (let c = 0; c < C4_COLS; c++) { const base = board[r * C4_COLS + c]; if (base === null) continue; for (const [dr, dc] of directions) { const rEnd = r + dr * 3; const cEnd = c + dc * 3; if (rEnd < 0 || rEnd >= C4_ROWS || cEnd < 0 || cEnd >= C4_COLS) continue; let ok = true; const cells: number[] = [r * C4_COLS + c]; for (let i = 1; i < 4; i++) { const idx = (r + dr * i) * C4_COLS + (c + dc * i); if (board[idx] !== base) { ok = false; break; } cells.push(idx); } if (ok) return cells; } } } return null; } export function isBoardFull(board: Cell[]): boolean { return board.every((c) => c !== null); } export function c4DropRow(board: Cell[], column: number): number { for (let r = C4_ROWS - 1; r >= 0; r--) { if (board[r * C4_COLS + column] === null) return r; } return -1; } export async function createGame( client: AppSupabaseClient, params: { conversationId: string; gameType: GameType; opponentUserId: string }, ): Promise { const { data: session } = await client.auth.getUser(); if (!session.user) throw new Error('not authenticated'); const board = params.gameType === 'ttt' ? emptyTttBoard() : emptyC4Board(); const state = { kind: params.gameType, board }; const players: [string, string] = [session.user.id, params.opponentUserId]; const { data, error } = await client .from('conversation_games') .insert({ conversation_id: params.conversationId, game_type: params.gameType, state, players, current_turn_user_id: session.user.id, }) .select( 'id, conversation_id, game_type, state, players, current_turn_user_id, winner_user_id, created_at, finished_at', ) .single(); if (error) throw error; return mapRow(data); } export async function getGame( client: AppSupabaseClient, gameId: string, ): Promise { const { data, error } = await client .from('conversation_games') .select( 'id, conversation_id, game_type, state, players, current_turn_user_id, winner_user_id, created_at, finished_at', ) .eq('id', gameId) .maybeSingle(); if (error) throw error; return data ? mapRow(data) : null; } export async function makeGameMove( client: AppSupabaseClient, params: { gameId: string; move: object }, ): Promise { const { error } = await client.rpc('game_make_move', { p_game_id: params.gameId, p_move: params.move, }); if (error) throw new Error(error.message); } function mapRow(row: { id: string; conversation_id: string; game_type: string; state: unknown; players: unknown; current_turn_user_id: string | null; winner_user_id: string | null; created_at: string; finished_at: string | null; }): GameRecord { const rawState = (row.state ?? {}) as { kind?: string; board?: unknown }; const kind: GameType = rawState.kind === 'c4' ? 'c4' : 'ttt'; const rawBoard = Array.isArray(rawState.board) ? rawState.board : []; const board: Cell[] = rawBoard.map((c) => typeof c === 'number' && (c === 0 || c === 1) ? (c as Cell) : null, ); const players = Array.isArray(row.players) ? row.players : []; return { id: row.id, conversationId: row.conversation_id, gameType: row.game_type === 'c4' ? 'c4' : 'ttt', state: { kind, board }, players: [String(players[0] ?? ''), String(players[1] ?? '')], currentTurnUserId: row.current_turn_user_id, winnerUserId: row.winner_user_id, createdAt: row.created_at, finishedAt: row.finished_at, }; }