From c1751751afdbfb317f308f878dadf30ab1b7adba Mon Sep 17 00:00:00 2001 From: "claude (duplicate_chess)" Date: Tue, 19 May 2026 00:43:23 -0400 Subject: [PATCH] feat(engine): board/player constant maps and shared types --- src/engine/boards.test.ts | 29 +++++++++++++++++++++++++++++ src/engine/boards.ts | 32 ++++++++++++++++++++++++++++++++ src/engine/types.ts | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 src/engine/boards.test.ts create mode 100644 src/engine/boards.ts create mode 100644 src/engine/types.ts diff --git a/src/engine/boards.test.ts b/src/engine/boards.test.ts new file mode 100644 index 0000000..c2b6149 --- /dev/null +++ b/src/engine/boards.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from 'vitest'; +import { BOARD_IDS, PLAYERS, PLAYER_BOARDS, PLAYER_COLOR, BOARD_PLAYERS, BOARD_ROTATION } from './boards'; + +describe('boards constants', () => { + it('lists four boards and four players in turn order', () => { + expect(BOARD_IDS).toEqual(['NW', 'NE', 'SW', 'SE']); + expect(PLAYERS).toEqual(['N', 'S', 'E', 'W']); + }); + + it('each player controls exactly two boards', () => { + for (const p of PLAYERS) expect(PLAYER_BOARDS[p]).toHaveLength(2); + expect(PLAYER_BOARDS.N).toEqual(['NW', 'NE']); + expect(PLAYER_BOARDS.W).toEqual(['NW', 'SW']); + }); + + it('board players are consistent with player boards', () => { + for (const b of BOARD_IDS) { + const { w, b: black } = BOARD_PLAYERS[b]; + expect(PLAYER_BOARDS[w]).toContain(b); + expect(PLAYER_BOARDS[black]).toContain(b); + expect(PLAYER_COLOR[w]).toBe('w'); + expect(PLAYER_COLOR[black]).toBe('b'); + } + }); + + it('has a rotation for every board', () => { + expect(BOARD_ROTATION).toEqual({ NW: 225, NE: 135, SW: 315, SE: 45 }); + }); +}); diff --git a/src/engine/boards.ts b/src/engine/boards.ts new file mode 100644 index 0000000..06e544c --- /dev/null +++ b/src/engine/boards.ts @@ -0,0 +1,32 @@ +import type { BoardId, Player, Color } from './types'; + +export const BOARD_IDS: BoardId[] = ['NW', 'NE', 'SW', 'SE']; + +/** Turn order. */ +export const PLAYERS: Player[] = ['N', 'S', 'E', 'W']; + +/** The two boards each player controls (order is stable: [boardA, boardB]). */ +export const PLAYER_BOARDS: Record = { + N: ['NW', 'NE'], + S: ['SW', 'SE'], + E: ['NE', 'SE'], + W: ['NW', 'SW'], +}; + +/** The colour each player plays on both their boards. */ +export const PLAYER_COLOR: Record = { + N: 'w', S: 'w', E: 'b', W: 'b', +}; + +/** The white and black player of each board. */ +export const BOARD_PLAYERS: Record = { + NW: { w: 'N', b: 'W' }, + NE: { w: 'N', b: 'E' }, + SW: { w: 'S', b: 'W' }, + SE: { w: 'S', b: 'E' }, +}; + +/** Compass rotation in degrees for rendering each board (see spec ยง5.1). */ +export const BOARD_ROTATION: Record = { + NW: 225, NE: 135, SW: 315, SE: 45, +}; diff --git a/src/engine/types.ts b/src/engine/types.ts new file mode 100644 index 0000000..c4c35f2 --- /dev/null +++ b/src/engine/types.ts @@ -0,0 +1,35 @@ +export type BoardId = 'NW' | 'NE' | 'SW' | 'SE'; +export type Player = 'N' | 'S' | 'E' | 'W'; +export type Color = 'w' | 'b'; +export type Square = string; +export type PromotionPiece = 'q' | 'r' | 'b' | 'n'; + +export interface SyncMove { + from: Square; + to: Square; + promotion?: PromotionPiece; +} + +export interface HistoryEntry extends SyncMove { + player: Player; +} + +export interface GhostMarker { + board: BoardId; + square: Square; +} + +export type PlayerResult = 'win' | 'draw' | 'loss'; +export type GameResult = Record; +export type GameState = 'playing' | 'checkmate' | 'stalemate' | 'draw'; +export type DrawReason = 'stalemate' | 'threefold' | 'fifty-move' | 'manual'; + +export interface GameStatus { + state: GameState; + /** Present when state !== 'playing'. */ + result?: GameResult; + /** Present for a draw/stalemate. */ + reason?: DrawReason; + /** Boards on which the player to move is currently in check. */ + checks: BoardId[]; +}