import { v4 as uuidv4 } from "uuid"; import type { BoardShape, Position } from "../../../inc/trigo"; import { TrigoGame, StepType, StoneType } from "../../../inc/trigo/game"; export interface Player { id: string; nickname: string; color: "black" | "white"; connected: boolean; } export interface GameState { gameStatus: "waiting" | "playing" | "finished"; winner: "black" | "white" | null; } export interface GameRoom { id: string; players: { [playerId: string]: Player }; game: TrigoGame; // The actual game instance gameState: GameState; // Game status metadata createdAt: Date; startedAt: Date | null; } export class GameManager { private rooms: Map = new Map(); private playerRoomMap: Map = new Map(); private defaultBoardShape: BoardShape = { x: 5, y: 5, z: 5 }; // Default 5x5x5 board constructor() { console.log("GameManager initialized"); } createRoom(playerId: string, nickname: string, boardShape?: BoardShape): GameRoom | null { const roomId = this.generateRoomId(); const shape = boardShape || this.defaultBoardShape; const room: GameRoom = { id: roomId, players: { [playerId]: { id: playerId, nickname, color: "black", connected: true } }, game: new TrigoGame(shape, { onStepAdvance: (_step, history) => { console.log(`Step ${history.length}: Player made move`); }, onCapture: (captured) => { console.log(`Captured ${captured.length} stones`); }, onWin: (winner) => { console.log(`Game won by ${winner}`); } }), gameState: { gameStatus: "waiting", winner: null }, createdAt: new Date(), startedAt: null }; this.rooms.set(roomId, room); this.playerRoomMap.set(playerId, roomId); console.log(`Room ${roomId} created by ${playerId}`); return room; } joinRoom(roomId: string, playerId: string, nickname: string): GameRoom | null { const room = this.rooms.get(roomId); if (!room) { return null; } const playerCount = Object.keys(room.players).length; if (playerCount >= 2) { return null; // Room is full } // Assign white color to the second player room.players[playerId] = { id: playerId, nickname, color: "white", connected: true }; this.playerRoomMap.set(playerId, roomId); // Start the game when second player joins if (playerCount === 1) { room.gameState.gameStatus = "playing"; room.startedAt = new Date(); } return room; } leaveRoom(roomId: string, playerId: string): void { const room = this.rooms.get(roomId); if (!room) return; if (room.players[playerId]) { room.players[playerId].connected = false; } this.playerRoomMap.delete(playerId); // Check if room should be deleted const connectedPlayers = Object.values(room.players).filter((p) => p.connected); if (connectedPlayers.length === 0) { this.rooms.delete(roomId); console.log(`Room ${roomId} deleted - no players remaining`); } } makeMove(roomId: string, playerId: string, move: { x: number; y: number; z: number }): boolean { const room = this.rooms.get(roomId); if (!room) return false; const player = room.players[playerId]; if (!player) return false; // Check game status if (room.gameState.gameStatus !== "playing") { return false; } // Convert player color to Stone type const expectedPlayer = player.color === "black" ? StoneType.BLACK : StoneType.WHITE; const currentPlayer = room.game.getCurrentPlayer(); // Check if it's the player's turn if (currentPlayer !== expectedPlayer) { return false; } // Attempt to make the move using TrigoGame const position: Position = { x: move.x, y: move.y, z: move.z }; const success = room.game.drop(position); return success; } passTurn(roomId: string, playerId: string): boolean { const room = this.rooms.get(roomId); if (!room) return false; const player = room.players[playerId]; if (!player) return false; // Check game status if (room.gameState.gameStatus !== "playing") { return false; } // Convert player color to Stone type const expectedPlayer = player.color === "black" ? StoneType.BLACK : StoneType.WHITE; const currentPlayer = room.game.getCurrentPlayer(); // Check if it's the player's turn if (currentPlayer !== expectedPlayer) { return false; } return room.game.pass(); } resign(roomId: string, playerId: string): boolean { const room = this.rooms.get(roomId); if (!room) return false; const player = room.players[playerId]; if (!player) return false; // Surrender the game room.game.surrender(); // Update room state room.gameState.gameStatus = "finished"; room.gameState.winner = player.color === "black" ? "white" : "black"; return true; } /** * Undo the last move (悔棋) */ undoMove(roomId: string, playerId: string): boolean { const room = this.rooms.get(roomId); if (!room) return false; const player = room.players[playerId]; if (!player) return false; // Check game status if (room.gameState.gameStatus !== "playing") { return false; } return room.game.undo(); } /** * Get game board state for a room */ getGameBoard(roomId: string): number[][][] | null { const room = this.rooms.get(roomId); if (!room) return null; return room.game.getBoard(); } /** * Get game statistics for a room */ getGameStats(roomId: string) { const room = this.rooms.get(roomId); if (!room) return null; return room.game.getStats(); } /** * Get current player for a room */ getCurrentPlayer(roomId: string): "black" | "white" | null { const room = this.rooms.get(roomId); if (!room) return null; const currentStone = room.game.getCurrentPlayer(); return currentStone === StoneType.BLACK ? "black" : "white"; } /** * Calculate and get territory for a room */ getTerritory(roomId: string) { const room = this.rooms.get(roomId); if (!room) return null; return room.game.getTerritory(); } /** * End the game and determine winner based on territory */ endGameByTerritory(roomId: string): boolean { const room = this.rooms.get(roomId); if (!room) return false; if (room.gameState.gameStatus !== "playing") { return false; } // Calculate final territory const territory = room.game.getTerritory(); // Determine winner if (territory.black > territory.white) { room.gameState.winner = "black"; } else if (territory.white > territory.black) { room.gameState.winner = "white"; } else { // Draw - could set winner to null or handle differently room.gameState.winner = null; } room.gameState.gameStatus = "finished"; console.log( `Game ${roomId} ended. Black: ${territory.black}, White: ${territory.white}, Winner: ${room.gameState.winner}` ); return true; } /** * Check if both players passed consecutively (game should end) * Returns true if game was ended */ checkConsecutivePasses(roomId: string): boolean { const room = this.rooms.get(roomId); if (!room) return false; const history = room.game.getHistory(); if (history.length < 2) return false; // Get last two moves const lastMove = history[history.length - 1]; const secondLastMove = history[history.length - 2]; // Check if both were passes if (lastMove.type === StepType.PASS && secondLastMove.type === StepType.PASS) { // Two consecutive passes - end the game this.endGameByTerritory(roomId); return true; } return false; } getRoom(roomId: string): GameRoom | undefined { return this.rooms.get(roomId); } getPlayerRoom(playerId: string): GameRoom | undefined { const roomId = this.playerRoomMap.get(playerId); if (!roomId) return undefined; return this.rooms.get(roomId); } getActiveRooms(): GameRoom[] { return Array.from(this.rooms.values()).filter( (room) => room.gameState.gameStatus !== "finished" ); } private generateRoomId(): string { return uuidv4().substring(0, 8).toUpperCase(); } }