166 lines
4.2 KiB
TypeScript
Executable File
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;
|
|
}
|
|
}
|