export type PlayerColor = 'black' | 'white'; export type GameStatus = 'waiting' | 'playing' | 'finished'; export type BoardCell = null | 'black' | 'white'; export class GomokuGame { public readonly board: BoardCell[][]; public currentPlayerColor: null | PlayerColor; public status: GameStatus; public winnerColor: null | PlayerColor | 'draw'; public history: { row: number; col: number }[]; private readonly boardSize = 15; private moveCount = 0; constructor() { this.board = Array.from({ length: this.boardSize }, () => Array(this.boardSize).fill(null), ); this.currentPlayerColor = 'black'; this.status = 'waiting'; this.winnerColor = null; this.history = []; } public makeMove( playerColor: PlayerColor, row: number, col: number, ): { success: boolean; error?: string } { // Validate it's the player's turn if (this.currentPlayerColor !== playerColor) { return { success: false, error: 'Not your turn' }; } // Validate move is within bounds if (row < 0 || row >= this.boardSize || col < 0 || col >= this.boardSize) { return { success: false, error: 'Move out of bounds' }; } // Validate cell is empty if (this.board[row][col] !== null) { return { success: false, error: 'Cell already occupied' }; } // Make the move this.board[row][col] = playerColor; this.moveCount++; // If this was the first move, declare the game to have begun if (this.status === 'waiting') { this.status = 'playing'; } // Add the move to the game's history this.history.push({ row, col }); // Check for win condition if (this.checkWin(row, col, playerColor)) { this.winnerColor = playerColor; this.status = 'finished'; this.currentPlayerColor = null; return { success: true }; } // Check for draw condition if (this.moveCount === this.boardSize * this.boardSize) { this.winnerColor = 'draw'; this.status = 'finished'; this.currentPlayerColor = null; return { success: true }; } // Switch turns this.currentPlayerColor = playerColor === 'black' ? 'white' : 'black'; return { success: true }; } public resign(resigningPlayerColor: PlayerColor) { this.winnerColor = resigningPlayerColor === 'white' ? 'black' : 'white'; this.status = 'finished'; this.currentPlayerColor = null; } public undoMove() { if (this.history.length === 0) { return; } const lastMove = this.history.pop(); if (lastMove) { this.board[lastMove.row][lastMove.col] = null; this.moveCount--; this.currentPlayerColor = this.currentPlayerColor === 'black' ? 'white' : 'black'; if (this.status === 'finished') { this.status = 'playing'; this.winnerColor = null; } } } public declareDraw() { this.status = 'finished'; this.winnerColor = 'draw'; this.currentPlayerColor = null; } private checkWin(row: number, col: number, color: PlayerColor): boolean { const directions = [ [1, 0], // vertical [0, 1], // horizontal [1, 1], // diagonal down-right [1, -1], // diagonal down-left ]; for (const [dx, dy] of directions) { let count = 1; // Check in positive direction for (let i = 1; i < 5; i++) { const newRow = row + dx * i; const newCol = col + dy * i; if ( newRow < 0 || newRow >= this.boardSize || newCol < 0 || newCol >= this.boardSize ) { break; } if (this.board[newRow][newCol] === color) { count++; } else { break; } } // Check in negative direction for (let i = 1; i < 5; i++) { const newRow = row - dx * i; const newCol = col - dy * i; if ( newRow < 0 || newRow >= this.boardSize || newCol < 0 || newCol >= this.boardSize ) { break; } if (this.board[newRow][newCol] === color) { count++; } else { break; } } if (count >= 5) { return true; } } return false; } }