game flow revamp
This commit is contained in:
250
src/game/MatchSession.ts
Normal file
250
src/game/MatchSession.ts
Normal file
@ -0,0 +1,250 @@
|
||||
import { DominoesGame } from "./DominoesGame";
|
||||
import { PlayerAI } from "./entities/player/PlayerAI";
|
||||
import { PlayerInterface } from "./entities/player/PlayerInterface";
|
||||
import { LoggingService } from "../common/LoggingService";
|
||||
import { getRandomSeed, uuid, wait } from "../common/utilities";
|
||||
import { MatchSessionState } from "./dto/MatchSessionState";
|
||||
import { PlayerNotificationManager } from './PlayerNotificationManager';
|
||||
import seedrandom, { PRNG } from "seedrandom";
|
||||
import { NetworkPlayer } from "./entities/player/NetworkPlayer";
|
||||
import { PlayerHuman } from "./entities/player/PlayerHuman";
|
||||
|
||||
|
||||
export class MatchSession {
|
||||
private currentGame: DominoesGame | null = null;
|
||||
private minHumanPlayers: number = 1;
|
||||
private waitingForPlayers: boolean = true;
|
||||
private waitingSeconds: number = 0;
|
||||
private logger: LoggingService = new LoggingService();
|
||||
private playerNotificationManager = new PlayerNotificationManager();
|
||||
|
||||
id: string;
|
||||
matchInProgress: boolean = false;
|
||||
matchWinner: PlayerInterface | null = null;
|
||||
maxPlayers: number = 4;
|
||||
mode: string = 'classic';
|
||||
players: PlayerInterface[] = [];
|
||||
pointsToWin: number = 50;
|
||||
rng!: PRNG
|
||||
scoreboard: Map<string, number> = new Map();
|
||||
seed!: string
|
||||
sessionInProgress: boolean = false;
|
||||
state: string = 'created'
|
||||
|
||||
constructor(public creator: PlayerInterface, public name?: string) {
|
||||
this.id = uuid();
|
||||
this.name = name || `Game ${this.id}`;
|
||||
this.addPlayer(creator);
|
||||
|
||||
this.creator = creator;
|
||||
this.logger.info(`Match session created by: ${creator.name}`);
|
||||
this.logger.info(`Match session ID: ${this.id}`);
|
||||
this.logger.info(`Match session name: ${this.name}`);
|
||||
this.logger.info(`Points to win: ${this.pointsToWin}`);
|
||||
this.sessionInProgress = true;
|
||||
this.matchInProgress = false;
|
||||
this.playerNotificationManager.notifyMatchState(this);
|
||||
this.playerNotificationManager.notifyPlayersState(this.players);
|
||||
}
|
||||
|
||||
get numPlayers() {
|
||||
return this.players.length;
|
||||
}
|
||||
|
||||
get numPlayersReady() {
|
||||
return this.players.filter(player => player.ready).length;
|
||||
}
|
||||
|
||||
get numHumanPlayers() {
|
||||
return this.players.filter(player => player instanceof PlayerHuman).length;
|
||||
}
|
||||
|
||||
private async startMatch(seed: string) {
|
||||
this.rng = seedrandom(seed);
|
||||
const missingPlayers = this.maxPlayers - this.numPlayers;
|
||||
for (let i = 0; i < missingPlayers; i++) {
|
||||
this.addPlayer(this.createPlayerAI(i));
|
||||
}
|
||||
this.state = 'ready'
|
||||
this.resetScoreboard()
|
||||
let gameNumber: number = 0;
|
||||
this.matchInProgress = true
|
||||
this.playerNotificationManager.notifyMatchState(this);
|
||||
while (this.matchInProgress) {
|
||||
|
||||
this.currentGame = new DominoesGame(this.players, this.rng);
|
||||
gameNumber += 1;
|
||||
this.state = 'started'
|
||||
this.logger.info(`Game #${gameNumber} started`);
|
||||
// this.game.reset()
|
||||
await this.currentGame.start();
|
||||
this.setScores();
|
||||
this.checkMatchWinner();
|
||||
this.resetReadiness();
|
||||
this.state = 'waiting'
|
||||
await this.playerNotificationManager.notifyMatchState(this);
|
||||
this.playerNotificationManager.sendEventToPlayers('game-finished', this.players);
|
||||
await this.checkHumanPlayersReady();
|
||||
}
|
||||
this.state = 'end'
|
||||
// await this.game.start();
|
||||
return this.endGame();
|
||||
}
|
||||
|
||||
async checkHumanPlayersReady() {
|
||||
this.logger.info('Waiting for human players to be ready');
|
||||
return new Promise((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
this.logger.debug(`Human players ready: ${this.numPlayersReady}/${this.numHumanPlayers}`)
|
||||
if (this.numPlayersReady === this.numHumanPlayers) {
|
||||
clearInterval(interval);
|
||||
resolve(true);
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
resetReadiness() {
|
||||
this.players.forEach(player => {
|
||||
player.ready = false
|
||||
});
|
||||
}
|
||||
|
||||
checkMatchWinner() {
|
||||
const scores = Array.from(this.scoreboard.values());
|
||||
const maxScore = Math.max(...scores);
|
||||
if (maxScore >= this.pointsToWin) {
|
||||
this.matchWinner = this.players.find(player => this.scoreboard.get(player.id) === maxScore)!;
|
||||
this.logger.info(`Match winner: ${this.matchWinner.name} with ${maxScore} points`);
|
||||
this.matchInProgress = false;
|
||||
}
|
||||
}
|
||||
resetScoreboard() {
|
||||
this.scoreboard = new Map();
|
||||
this.players.forEach(player => {
|
||||
this.scoreboard.set(player.id, 0);
|
||||
});
|
||||
}
|
||||
|
||||
setScores() {
|
||||
const totalPips = this.currentGame?.players.reduce((acc, player) => acc + player.pipsCount(), 0);
|
||||
if (this.currentGame && this.currentGame.winner !== null) {
|
||||
const winner = this.currentGame.winner;
|
||||
this.scoreboard.set(winner.id, this.scoreboard.get(winner.id)! + totalPips!);
|
||||
if (winner.teamedWith !== null) {
|
||||
this.scoreboard.set(winner.teamedWith.id, this.scoreboard.get(winner.teamedWith.id)! + totalPips!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private endGame(): any {
|
||||
if (this.currentGame !== null) {
|
||||
const { gameBlocked, gameTied, winner } = this.currentGame;
|
||||
gameBlocked ? this.logger.info('Game blocked!') : gameTied ? this.logger.info('Game tied!') : this.logger.info('Game over!');
|
||||
this.logger.info('Winner: ' + winner?.name + ' with ' + winner?.pipsCount() + ' points');
|
||||
this.getScore(this.currentGame);
|
||||
this.logger.info('Game ended');
|
||||
this.currentGame = null;
|
||||
this.playerNotificationManager.notifyMatchState(this);
|
||||
|
||||
return {
|
||||
gameBlocked,
|
||||
gameTied,
|
||||
winner
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private getScore(game: DominoesGame) {
|
||||
const pips = game.players
|
||||
.sort((a,b) => (b.pipsCount() - a.pipsCount()))
|
||||
.map(player => {
|
||||
return `${player.name}: ${player.pipsCount()}`;
|
||||
});
|
||||
this.logger.info(`Pips count: ${pips.join(', ')}`);
|
||||
const totalPoints = game.players.reduce((acc, player) => acc + player.pipsCount(), 0);
|
||||
if (game.winner !== null) {
|
||||
game.winner.score += totalPoints;
|
||||
}
|
||||
const scores = game.players
|
||||
.sort((a,b) => (b.score - a.score))
|
||||
.map(player => {
|
||||
return `${player.name}: ${player.score}`;
|
||||
});
|
||||
this.logger.info(`Scores: ${scores.join(', ')}`);
|
||||
}
|
||||
|
||||
createPlayerAI(i: number) {
|
||||
const AInames = ["Alice (AI)", "Bob (AI)", "Charlie (AI)", "David (AI)"];
|
||||
const player = new PlayerAI(AInames[i], this.rng);
|
||||
player.ready = true;
|
||||
return player;
|
||||
}
|
||||
|
||||
async start(seed?: string) {
|
||||
this.seed = seed || getRandomSeed();
|
||||
console.log('seed :>> ', this.seed);
|
||||
if (this.matchInProgress) {
|
||||
throw new Error("Game already in progress");
|
||||
}
|
||||
this.waitingForPlayers = true;
|
||||
this.logger.info('Waiting for players to be ready');
|
||||
while (this.numPlayers < this.maxPlayers) {
|
||||
this.waitingSeconds += 1;
|
||||
this.logger.info(`Waiting for players to join: ${this.waitingSeconds}`);
|
||||
await wait(1000);
|
||||
}
|
||||
this.waitingForPlayers = false;
|
||||
this.logger.info('All players joined');
|
||||
await this.startMatch(this.seed);
|
||||
}
|
||||
|
||||
addPlayer(player: PlayerInterface) {
|
||||
if (this.numPlayers >= this.maxPlayers) {
|
||||
throw new Error("GameSession is full");
|
||||
}
|
||||
this.players.push(player);
|
||||
this.logger.info(`${player.name} joined the game!`);
|
||||
}
|
||||
|
||||
setPlayerReady(user: string) {
|
||||
const player = this.players.find(player => player.name === user);
|
||||
if (!player) {
|
||||
throw new Error("Player not found");
|
||||
}
|
||||
player.ready = true;
|
||||
this.logger.info(`${player.name} is ready!`);
|
||||
this.playerNotificationManager.notifyMatchState(this);
|
||||
}
|
||||
|
||||
|
||||
toString() {
|
||||
return `GameSession:(${this.id} ${this.name})`;
|
||||
}
|
||||
|
||||
getState(): MatchSessionState {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name!,
|
||||
creator: this.creator.id,
|
||||
players: this.players.map(player =>( {
|
||||
id: player.id,
|
||||
name: player.name,
|
||||
ready: player.ready,
|
||||
})),
|
||||
playersReady: this.numPlayersReady,
|
||||
sessionInProgress: this.sessionInProgress,
|
||||
maxPlayers: this.maxPlayers,
|
||||
numPlayers: this.numPlayers,
|
||||
waitingForPlayers: this.waitingForPlayers,
|
||||
waitingSeconds: this.waitingSeconds,
|
||||
seed: this.seed,
|
||||
mode: this.mode,
|
||||
pointsToWin: this.pointsToWin,
|
||||
status: this.sessionInProgress ? 'in progress' : 'waiting',
|
||||
scoreboard: this.scoreboard,
|
||||
matchWinner: this.matchWinner?.getState() || null,
|
||||
matchInProgress: this.matchInProgress
|
||||
};
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user