import { StartGameCommand } from './commands/StartGameCommand'; import { StartGameCommandHandler } from './commands/StartGameCommandHandler'; import { JoinGameCommand } from './commands/JoinGameCommand'; import { JoinGameCommandHandler } from './commands/JoinGameCommandHandler'; import { StartGamePlayCommand } from './commands/StartGamePlayCommand'; import { StartGamePlayCommandHandler, GameStartResult } from './commands/StartGamePlayCommandHandler'; import { GameAggregate, LoginType } from '../../Domain/Game/GameAggregate'; import { logOther, logError } from '../Services/Logger'; export class GameService { private startGameHandler: StartGameCommandHandler; private joinGameHandler: JoinGameCommandHandler; private startGamePlayHandler: StartGamePlayCommandHandler; constructor() { this.startGameHandler = new StartGameCommandHandler(); this.joinGameHandler = new JoinGameCommandHandler(); this.startGamePlayHandler = new StartGamePlayCommandHandler(); } /** * Starts a new game with the provided deck IDs * @param deckids Array of deck IDs (should contain 3 types: LUCK, JOKER, QUESTION) * @param maxplayers Maximum number of players allowed in the game * @param logintype How players can join the game (PUBLIC, PRIVATE, ORGANIZATION) * @param userid Optional ID of the user creating the game * @returns Promise The created game */ async startGame( deckids: string[], maxplayers: number, logintype: LoginType, userid?: string, orgid?: string | null ): Promise { const startTime = performance.now(); try { logOther('GameService.startGame called', { deckCount: deckids.length, maxplayers, logintype, userid, orgid }); // Validate input parameters this.validateStartGameInput(deckids, maxplayers, logintype); // Create and execute the command const command: StartGameCommand = { deckids, maxplayers, logintype, userid, orgid }; const game = await this.startGameHandler.handle(command); const endTime = performance.now(); logOther('Game started successfully', { gameId: game.id, gameCode: game.gamecode, deckCount: game.gamedecks.length, totalCards: game.gamedecks.reduce((sum, deck) => sum + deck.cards.length, 0), executionTime: Math.round(endTime - startTime) }); return game; } catch (error) { const endTime = performance.now(); logError('GameService.startGame failed', error instanceof Error ? error : new Error(String(error))); logOther('Game start failed', { executionTime: Math.round(endTime - startTime), error: error instanceof Error ? error.message : String(error) }); throw error; } } /** * Join an existing game using game code * @param gameCode 6-character game code * @param playerId ID of the player joining (optional for public games) * @param playerName Display name for the player * @param orgId Organization ID (for organization games) * @param loginType Type of join being attempted * @returns Promise The updated game with new player */ async joinGame( gameCode: string, playerId?: string, playerName?: string, orgId?: string | null, loginType?: LoginType ): Promise { const startTime = performance.now(); try { logOther('GameService.joinGame called', { gameCode, playerId: playerId || 'anonymous', playerName, orgId, loginType }); // Validate input parameters this.validateJoinGameInput(gameCode, playerId, loginType); // Create and execute the command const command: JoinGameCommand = { gameCode, playerId, playerName, orgId, loginType: loginType || LoginType.PUBLIC }; const game = await this.joinGameHandler.handle(command); const endTime = performance.now(); logOther('Player joined game successfully', { gameId: game.id, gameCode: game.gamecode, playerId, playerCount: game.players.length, maxPlayers: game.maxplayers, executionTime: Math.round(endTime - startTime) }); return game; } catch (error) { const endTime = performance.now(); logError('GameService.joinGame failed', error instanceof Error ? error : new Error(String(error))); logOther('Game join failed', { gameCode, playerId, executionTime: Math.round(endTime - startTime), error: error instanceof Error ? error.message : String(error) }); throw error; } } /** * Start an existing game (move from WAITING to ACTIVE) * Initializes all player positions to 0 and assigns random turn order * @param gameId Game ID to start * @param userId User ID of the game master (optional for public games) * @returns Promise The updated game */ async startGamePlay( gameId: string, userId?: string ): Promise { const startTime = performance.now(); try { logOther('GameService.startGamePlay called', { gameId, userId: userId || 'system' }); // Validate input parameters this.validateStartGamePlayInput(gameId); // Create and execute the command const command: StartGamePlayCommand = { gameId, userId }; const result = await this.startGamePlayHandler.handle(command); const endTime = performance.now(); logOther('Game play started successfully', { gameId: result.game.id, gameCode: result.game.gamecode, playerCount: result.game.players.length, gameState: result.game.state, executionTime: Math.round(endTime - startTime) }); return result; } catch (error) { const endTime = performance.now(); logError('GameService.startGamePlay failed', error instanceof Error ? error : new Error(String(error))); logOther('Game play start failed', { gameId, userId, executionTime: Math.round(endTime - startTime), error: error instanceof Error ? error.message : String(error) }); throw error; } } private validateStartGamePlayInput(gameId: string): void { // Validate game ID if (!gameId || typeof gameId !== 'string') { throw new Error('Game ID is required and must be a string'); } logOther('Start game play input validation passed', { gameId }); } private validateJoinGameInput(gameCode: string, playerId?: string, loginType?: LoginType): void { // Validate game code if (!gameCode || typeof gameCode !== 'string') { throw new Error('Game code is required and must be a string'); } if (gameCode.length !== 6) { throw new Error('Game code must be exactly 6 characters long'); } // Validate login type specific requirements if (loginType === LoginType.PRIVATE || loginType === LoginType.ORGANIZATION) { if (!playerId || typeof playerId !== 'string') { throw new Error(`Player ID is required for ${LoginType[loginType]} games`); } } logOther('Join game input validation passed', { gameCode, playerId: playerId || 'anonymous', loginType }); } private validateStartGameInput(deckids: string[], maxplayers: number, logintype: LoginType): void { // Validate deck IDs if (!deckids || deckids.length === 0) { throw new Error('At least one deck ID must be provided'); } if (deckids.length < 3) { throw new Error('At least 3 decks are required to start a game (one for each type: LUCK, JOKER, QUESTION)'); } // Validate max players if (!maxplayers || maxplayers < 2) { throw new Error('Maximum players must be at least 2'); } if (maxplayers > 8) { throw new Error('Maximum players cannot exceed 8'); } // Validate login type if (logintype < 0 || logintype > 2) { throw new Error('Invalid login type. Must be PUBLIC (0), PRIVATE (1), or ORGANIZATION (2)'); } // Check for duplicate deck IDs const uniqueIds = new Set(deckids); if (uniqueIds.size !== deckids.length) { throw new Error('Duplicate deck IDs are not allowed'); } logOther('Start game input validation passed', { deckCount: deckids.length, maxplayers, logintype }); } /** * Game flow explanation (to be implemented later): * * 1. START GAME (implemented above): * - Input: deckids, maxplayers, logintype, gamecode * - Process: Fetch decks, validate types, shuffle cards, create game * - Output: Game with shuffled deck objects * * 2. JOIN GAME (to be implemented): * - Input: gamecode, playerid * - Process: Find game, validate capacity, add player * - Output: Updated game with new player * * 3. GAME ROUNDS (to be implemented): * - Input: gameid, current player * - Process: Manage turn order, track game state * - Output: Current player information * * 4. PICK CARD (to be implemented): * - Input: gameid, playerid, deck type * - Process: Draw card from specific deck, apply consequence * - Output: Card details and consequence effects * * 5. END GAME (to be implemented): * - Input: gameid, winner * - Process: Set game as finished, record winner * - Output: Final game state */ }