303 lines
11 KiB
TypeScript
303 lines
11 KiB
TypeScript
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<GameAggregate> The created game
|
|
*/
|
|
async startGame(
|
|
deckids: string[],
|
|
maxplayers: number,
|
|
logintype: LoginType,
|
|
userid?: string,
|
|
orgid?: string | null
|
|
): Promise<GameAggregate> {
|
|
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<GameAggregate> The updated game with new player
|
|
*/
|
|
async joinGame(
|
|
gameCode: string,
|
|
playerId?: string,
|
|
playerName?: string,
|
|
orgId?: string | null,
|
|
loginType?: LoginType
|
|
): Promise<GameAggregate> {
|
|
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<GameAggregate> The updated game
|
|
*/
|
|
async startGamePlay(
|
|
gameId: string,
|
|
userId?: string
|
|
): Promise<GameStartResult> {
|
|
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
|
|
*/
|
|
} |