Spaces:
Running
Running
| 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<string, GameRoom> = new Map(); | |
| private playerRoomMap: Map<string, string> = 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(); | |
| } | |
| } | |