feat(P5B.T2): GamePayload + games wrappers + winner-detect helpers + tests

Adds conversation_games table type + game_make_move RPC to db-types, GamePayload variant and parseMessagePayload branch to shared/attachments, games.ts with createGame/getGame/makeGameMove wrappers plus pure tttWinningLine/c4WinningCells/c4DropRow/isBoardFull helpers, 11 tests covering all winner helpers, and re-export from chat/index.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
byGalax
2026-05-16 21:47:36 +02:00
parent e1423dba32
commit 256a613134
5 changed files with 304 additions and 1 deletions
+79
View File
@@ -0,0 +1,79 @@
import { describe, expect, it } from 'vitest';
import {
c4DropRow,
c4WinningCells,
emptyC4Board,
emptyTttBoard,
isBoardFull,
tttWinningLine,
type Cell,
} from './games';
describe('tttWinningLine', () => {
it('detects a row win', () => {
const b: Cell[] = [0, 0, 0, null, null, null, null, null, null];
expect(tttWinningLine(b)).toEqual([0, 1, 2]);
});
it('detects a diagonal win', () => {
const b: Cell[] = [1, null, null, null, 1, null, null, null, 1];
expect(tttWinningLine(b)).toEqual([0, 4, 8]);
});
it('returns null when no winner', () => {
expect(tttWinningLine(emptyTttBoard())).toBeNull();
const mixed: Cell[] = [0, 1, 0, 1, 0, 1, 1, 0, 1];
expect(tttWinningLine(mixed)).toBeNull();
});
});
describe('c4WinningCells', () => {
it('detects a horizontal win', () => {
const b = emptyC4Board();
b[35] = 1; b[36] = 1; b[37] = 1; b[38] = 1;
expect(c4WinningCells(b)).toEqual([35, 36, 37, 38]);
});
it('detects a vertical win', () => {
const b = emptyC4Board();
b[14] = 0; b[21] = 0; b[28] = 0; b[35] = 0;
expect(c4WinningCells(b)).toEqual([14, 21, 28, 35]);
});
it('detects a diagonal ↘ win', () => {
const b = emptyC4Board();
b[14] = 1; b[22] = 1; b[30] = 1; b[38] = 1;
expect(c4WinningCells(b)).toEqual([14, 22, 30, 38]);
});
it('returns null when no winner', () => {
expect(c4WinningCells(emptyC4Board())).toBeNull();
});
});
describe('c4DropRow', () => {
it('returns the bottom row on an empty column', () => {
expect(c4DropRow(emptyC4Board(), 0)).toBe(5);
});
it('stacks on top of an existing piece', () => {
const b = emptyC4Board();
b[35] = 0;
expect(c4DropRow(b, 0)).toBe(4);
});
it('returns -1 when the column is full', () => {
const b = emptyC4Board();
for (let r = 0; r < 6; r++) b[r * 7 + 3] = 0;
expect(c4DropRow(b, 3)).toBe(-1);
});
});
describe('isBoardFull', () => {
it('true for a fully filled board, false otherwise', () => {
expect(isBoardFull(emptyTttBoard())).toBe(false);
const filled: Cell[] = [0, 1, 0, 1, 0, 1, 1, 0, 1];
expect(isBoardFull(filled)).toBe(true);
});
});