Refactor: Remove audio processing and game state management modules
This commit is contained in:
179
src/server-deno/domain/models/GameState.ts
Normal file
179
src/server-deno/domain/models/GameState.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import type { GamePhase, GameState, GameStatus, GuessResult, ID, TimelinePosition } from '../types.ts';
|
||||
|
||||
/**
|
||||
* Game state domain model
|
||||
* Encapsulates all game state and provides methods to manipulate it
|
||||
*/
|
||||
export class GameStateModel implements GameState {
|
||||
status: GameStatus;
|
||||
phase: GamePhase;
|
||||
turnOrder: ID[];
|
||||
currentGuesser: ID | null;
|
||||
currentTrack: any;
|
||||
timeline: Record<ID, TimelinePosition[]>;
|
||||
tokens: Record<ID, number>;
|
||||
ready: Record<ID, boolean>;
|
||||
spectators: Record<ID, boolean>;
|
||||
lastResult: GuessResult | null;
|
||||
trackStartAt: number | null;
|
||||
paused: boolean;
|
||||
pausedPosSec: number;
|
||||
goal: number;
|
||||
playlist: string | null;
|
||||
titleArtistAwardedThisRound: Record<ID, boolean>;
|
||||
|
||||
constructor(goal = 10) {
|
||||
this.status = 'lobby' as GameStatus;
|
||||
this.phase = 'guess' as GamePhase;
|
||||
this.turnOrder = [];
|
||||
this.currentGuesser = null;
|
||||
this.currentTrack = null;
|
||||
this.timeline = {};
|
||||
this.tokens = {};
|
||||
this.ready = {};
|
||||
this.spectators = {};
|
||||
this.lastResult = null;
|
||||
this.trackStartAt = null;
|
||||
this.paused = false;
|
||||
this.pausedPosSec = 0;
|
||||
this.goal = goal;
|
||||
this.playlist = null;
|
||||
this.titleArtistAwardedThisRound = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize player in game state
|
||||
*/
|
||||
addPlayer(playerId: ID, isHost = false): void {
|
||||
this.ready[playerId] = isHost; // Host is ready by default
|
||||
this.timeline[playerId] = [];
|
||||
this.tokens[playerId] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove player from game state
|
||||
*/
|
||||
removePlayer(playerId: ID): void {
|
||||
delete this.ready[playerId];
|
||||
delete this.timeline[playerId];
|
||||
delete this.tokens[playerId];
|
||||
delete this.spectators[playerId];
|
||||
this.turnOrder = this.turnOrder.filter((id) => id !== playerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set player ready status
|
||||
*/
|
||||
setReady(playerId: ID, ready: boolean): void {
|
||||
this.ready[playerId] = ready;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if all players are ready
|
||||
*/
|
||||
areAllReady(): boolean {
|
||||
const playerIds = Object.keys(this.ready).filter((id) => !this.spectators[id]);
|
||||
return playerIds.length > 0 && playerIds.every((id) => this.ready[id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the game
|
||||
*/
|
||||
startGame(playerIds: ID[]): void {
|
||||
this.status = 'playing' as GameStatus;
|
||||
this.turnOrder = this.shuffleArray([...playerIds.filter((id) => !this.spectators[id])]);
|
||||
this.currentGuesser = this.turnOrder[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* End the game
|
||||
*/
|
||||
endGame(): void {
|
||||
this.status = 'ended' as GameStatus;
|
||||
this.currentTrack = null;
|
||||
this.currentGuesser = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to next player's turn
|
||||
*/
|
||||
nextTurn(): ID | null {
|
||||
if (!this.currentGuesser || this.turnOrder.length === 0) {
|
||||
return this.turnOrder[0] || null;
|
||||
}
|
||||
const currentIndex = this.turnOrder.indexOf(this.currentGuesser);
|
||||
const nextIndex = (currentIndex + 1) % this.turnOrder.length;
|
||||
this.currentGuesser = this.turnOrder[nextIndex];
|
||||
return this.currentGuesser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Award tokens to a player
|
||||
*/
|
||||
awardTokens(playerId: ID, amount: number): void {
|
||||
this.tokens[playerId] = (this.tokens[playerId] || 0) + amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add card to player's timeline
|
||||
*/
|
||||
addToTimeline(playerId: ID, year: number, position: number): void {
|
||||
if (!this.timeline[playerId]) {
|
||||
this.timeline[playerId] = [];
|
||||
}
|
||||
this.timeline[playerId].push({ year, position });
|
||||
this.timeline[playerId].sort((a, b) => a.position - b.position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if player has won
|
||||
*/
|
||||
hasPlayerWon(playerId: ID): boolean {
|
||||
return (this.timeline[playerId]?.length || 0) >= this.goal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get winner (if any)
|
||||
*/
|
||||
getWinner(): ID | null {
|
||||
for (const playerId of Object.keys(this.timeline)) {
|
||||
if (this.hasPlayerWon(playerId)) {
|
||||
return playerId;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set spectator status
|
||||
*/
|
||||
setSpectator(playerId: ID, spectator: boolean): void {
|
||||
this.spectators[playerId] = spectator;
|
||||
if (spectator) {
|
||||
// Remove from turn order if spectating
|
||||
this.turnOrder = this.turnOrder.filter((id) => id !== playerId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset round state (after track is complete)
|
||||
* Note: currentTrack and trackStartAt are set separately by the caller
|
||||
*/
|
||||
resetRound(): void {
|
||||
this.phase = 'guess' as GamePhase;
|
||||
this.lastResult = null;
|
||||
this.titleArtistAwardedThisRound = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility: Shuffle array
|
||||
*/
|
||||
private shuffleArray<T>(array: T[]): T[] {
|
||||
const shuffled = [...array];
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
}
|
||||
return shuffled;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user