177 lines
4.4 KiB
TypeScript
177 lines
4.4 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 currentPlayer: PlayerColor | null;
|
|
public status: GameStatus;
|
|
public winner: 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.currentPlayer = null;
|
|
this.status = 'waiting';
|
|
this.winner = null;
|
|
this.players = {};
|
|
}
|
|
|
|
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.currentPlayer = '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.currentPlayer !== 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.winner = playerColor;
|
|
this.status = 'finished';
|
|
this.currentPlayer = null;
|
|
return { success: true };
|
|
}
|
|
|
|
// Check for draw condition
|
|
if (this.moveCount === this.boardSize * this.boardSize) {
|
|
this.winner = 'draw';
|
|
this.status = 'finished';
|
|
this.currentPlayer = null;
|
|
return { success: true };
|
|
}
|
|
|
|
// Switch turns
|
|
this.currentPlayer = 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;
|
|
}
|
|
}
|