gomoku/src/game/game-instance.ts

185 lines
4.7 KiB
TypeScript

import { v4 as uuidv4 } from 'uuid';
type PlayerColor = 'black' | 'white';
type GameStatus = 'waiting' | 'playing' | 'finished';
type BoardCell = null | 'black' | 'white';
export class GameInstance {
public readonly id: string;
public readonly board: BoardCell[][];
public currentPlayerColor: PlayerColor | null;
public status: GameStatus;
public winnerColor: null | PlayerColor | 'draw';
public players: { black?: string; white?: string };
private readonly boardSize = 15;
private moveCount = 0;
constructor(id?: string) {
this.id = id || uuidv4();
this.board = Array.from({ length: this.boardSize }, () =>
Array(this.boardSize).fill(null),
);
this.currentPlayerColor = null;
this.status = 'waiting';
this.winnerColor = null;
this.players = {};
}
public getCurrentPlayerId(): string | undefined {
if (this.currentPlayerColor === 'black') {
return this.players.black;
} else {
return this.players.white;
}
}
public getPlayerCount(): number {
return Object.values(this.players).filter(Boolean).length;
}
public addPlayer(playerId: string): boolean {
// If game is full, prevent new players from joining.
if (this.getPlayerCount() >= 2) {
return false;
}
// If player is already in the game, return true.
if (Object.values(this.players).includes(playerId)) {
return true;
}
// Assign black if available, otherwise white
if (!this.players.black) {
this.players.black = playerId;
} else if (!this.players.white) {
this.players.white = playerId;
} else {
return false; // Should not happen if getPlayerCount() check is correct
}
// If both players have joined, start the game.
if (this.players.black && this.players.white) {
this.currentPlayerColor = 'black';
this.status = 'playing';
}
return true;
}
public makeMove(
playerId: string,
row: number,
col: number,
): { success: boolean; error?: string } {
// Find player's color
let playerColor: PlayerColor | null = null;
for (const [color, id] of Object.entries(this.players)) {
if (id === playerId) {
playerColor = color as PlayerColor;
break;
}
}
if (!playerColor) {
return { success: false, error: 'Player not in this game' };
}
// 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++;
// 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 };
}
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;
}
}