gomoku/src/game/game-instance.ts

166 lines
4.2 KiB
TypeScript
Executable File

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;
}
}