reworked
This commit is contained in:
@ -2,24 +2,30 @@ 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 { getRandomSeed, uuid, wait, whileNot } from "../common/utilities";
|
||||
import { MatchSessionState } from "./dto/MatchSessionState";
|
||||
import { PlayerNotificationService } from '../server/services/PlayerNotificationService';
|
||||
import seedrandom, { PRNG } from "seedrandom";
|
||||
import { NetworkPlayer } from "./entities/player/NetworkPlayer";
|
||||
import { PlayerHuman } from "./entities/player/PlayerHuman";
|
||||
import { GameSummary } from "./dto/GameSummary";
|
||||
import { PlayerMove } from "./entities/PlayerMove";
|
||||
|
||||
|
||||
export class MatchSession {
|
||||
private currentGame: DominoesGame | null = null;
|
||||
private minHumanPlayers: number = 1;
|
||||
private gameNumber: number = 0;
|
||||
private waitingForPlayers: boolean = true;
|
||||
private waitingSeconds: number = 0;
|
||||
private logger: LoggingService = new LoggingService();
|
||||
private playerNotificationManager = new PlayerNotificationService();
|
||||
private notificationService = new PlayerNotificationService();
|
||||
private winnerIndex: number | null = null;
|
||||
private clientsReady: string[] = [];
|
||||
|
||||
id: string;
|
||||
matchInProgress: boolean = false;
|
||||
gameInProgress: boolean = false;
|
||||
matchWinner?: PlayerInterface = undefined;
|
||||
maxPlayers: number = 4;
|
||||
mode: string = 'classic';
|
||||
@ -35,7 +41,7 @@ export class MatchSession {
|
||||
this.seed = seed || getRandomSeed();
|
||||
this.id = uuid();
|
||||
this.name = name || `Game ${this.id}`;
|
||||
this.addPlayer(creator);
|
||||
this.addPlayerToSession(creator);
|
||||
this.creator = creator;
|
||||
|
||||
this.logger.info(`Match session created by: ${creator.name}`);
|
||||
@ -43,7 +49,8 @@ export class MatchSession {
|
||||
this.logger.info(`Match session name: ${this.name}`);
|
||||
this.logger.info(`Points to win: ${this.pointsToWin}`);
|
||||
this.sessionInProgress = true;
|
||||
this.matchInProgress = false;
|
||||
this.waitingForPlayers = true;
|
||||
this.logger.info('Waiting for players to be ready');
|
||||
}
|
||||
|
||||
get numPlayers() {
|
||||
@ -58,56 +65,137 @@ export class MatchSession {
|
||||
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));
|
||||
setClientReady(userId: string) {
|
||||
this.logger.trace(`${userId} - ${this.clientsReady}`);
|
||||
if (!this.clientsReady.includes(userId)) {
|
||||
this.logger.trace(`Client ${userId} is ready`)
|
||||
this.clientsReady.push(userId);
|
||||
}
|
||||
this.state = 'ready'
|
||||
this.resetScoreboard()
|
||||
let gameNumber: number = 0;
|
||||
this.matchInProgress = true
|
||||
this.playerNotificationManager.notifyMatchState(this);
|
||||
let winnerIndex: number | null = null;
|
||||
while (this.matchInProgress) {
|
||||
this.currentGame = new DominoesGame(this.players, this.rng);
|
||||
if (winnerIndex !== null) {
|
||||
this.currentGame.setForcedInitialPlayerIndex(winnerIndex);
|
||||
}
|
||||
gameNumber += 1;
|
||||
this.state = 'started'
|
||||
this.logger.info(`Game #${gameNumber} started`);
|
||||
// this.game.reset()
|
||||
const gameSummary = await this.currentGame.start();
|
||||
winnerIndex = this.players.findIndex(player => player.id === gameSummary.winner?.id);
|
||||
this.setScores();
|
||||
this.checkMatchWinner();
|
||||
this.resetPlayers();
|
||||
this.state = 'waiting'
|
||||
await this.playerNotificationManager.notifyMatchState(this);
|
||||
this.playerNotificationManager.sendEventToPlayers('game-finished', this.players);
|
||||
if (this.matchInProgress) {
|
||||
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);
|
||||
async checkAllClientsReadyBeforeStart() {
|
||||
try {
|
||||
if (this.currentGame) {
|
||||
const conditionFn = () => {
|
||||
this.logger.trace(`Clients ready: ${this.clientsReady.length}/${this.numHumanPlayers}`);
|
||||
return this.clientsReady.length === this.numHumanPlayers
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
await whileNot(conditionFn, 10);
|
||||
this.logger.info(`Game #${this.gameNumber} started`);
|
||||
this.currentGame.start();
|
||||
this.gameInProgress = true;
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error, 'Error starting game');
|
||||
}
|
||||
}
|
||||
|
||||
getPlayer(userId: string) {
|
||||
return this.players.find(player => player.id === userId);
|
||||
}
|
||||
|
||||
playerMove(move: any) {
|
||||
if (this.currentGame) {
|
||||
if ((move === null) || (move === undefined) || move.type === 'pass') {
|
||||
this.currentGame.finishTurn(null);
|
||||
return;
|
||||
}
|
||||
const player = this.getPlayer(move.playerId);
|
||||
if (!player) {
|
||||
throw new Error("Player not found");
|
||||
}
|
||||
const tile = player.hand.find(tile => tile.id === move.tile.id);
|
||||
if (!tile) {
|
||||
throw new Error("Tile not found");
|
||||
}
|
||||
const newMove = new PlayerMove(tile, move.type, move. playerId)
|
||||
this.currentGame.finishTurn(newMove);
|
||||
}
|
||||
}
|
||||
|
||||
// This is the entry point for the game, method called by session host
|
||||
async start() {
|
||||
if (this.matchInProgress) {
|
||||
throw new Error("Game already in progress");
|
||||
}
|
||||
this.waitingForPlayers = false;
|
||||
await this.startMatch(this.seed);
|
||||
}
|
||||
|
||||
addPlayerToSession(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!`);
|
||||
}
|
||||
|
||||
private continueMatch(gameSummary: GameSummary) {
|
||||
this.winnerIndex = this.players.findIndex(player => player.id === gameSummary?.winner?.id);
|
||||
if (this.winnerIndex !== null) {
|
||||
this.currentGame?.setForcedInitialPlayerIndex(this.winnerIndex);
|
||||
}
|
||||
this.setScores(gameSummary || undefined);
|
||||
this.checkMatchWinner();
|
||||
this.resetPlayers();
|
||||
if (!this.matchInProgress) {
|
||||
this.state = 'end'
|
||||
this.notificationService.sendEventToPlayers('server:match-finished', this.players, {
|
||||
lastGame: gameSummary,
|
||||
sessionState: this.getState(),
|
||||
});
|
||||
} else {
|
||||
this.state = 'waiting'
|
||||
// await this.playerNotificationManager.notifyMatchState(this);
|
||||
this.notificationService.sendEventToPlayers('server:game-finished', this.players, {
|
||||
lastGame: gameSummary,
|
||||
sessionState: this.getState()
|
||||
});
|
||||
this.waitingForPlayers = true;
|
||||
}
|
||||
}
|
||||
|
||||
private startGame() {
|
||||
this.gameNumber += 1;
|
||||
this.logger.info(`Game #${this.gameNumber} started`);
|
||||
this.currentGame = new DominoesGame(this.players, this.rng);
|
||||
this.currentGame.on('game-over', (gameSummary: GameSummary) => {
|
||||
this.gameInProgress = false;
|
||||
this.continueMatch(gameSummary);
|
||||
});
|
||||
this.logger.info(`Waiting for ${this.numHumanPlayers} clients to be ready`);
|
||||
this.checkAllClientsReadyBeforeStart();
|
||||
}
|
||||
|
||||
private async startMatch(seed: string) {
|
||||
try {
|
||||
this.state = 'in-game'
|
||||
this.rng = seedrandom(seed);
|
||||
this.resetScoreboard()
|
||||
this.gameNumber = 0;
|
||||
this.matchInProgress = true
|
||||
// this.playerNotificationManager.notifyMatchState(this);
|
||||
this.winnerIndex = null;
|
||||
this.startGame();
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
this.matchInProgress = false;
|
||||
this.state = 'error'
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
// });
|
||||
// }
|
||||
|
||||
resetPlayers() {
|
||||
this.players.forEach(player => {
|
||||
@ -134,17 +222,19 @@ export class MatchSession {
|
||||
});
|
||||
}
|
||||
|
||||
setScores() {
|
||||
const totalPips = this.currentGame?.players.reduce((acc, player) => acc + player.pipsCount(), 0) || 0;
|
||||
if (this.currentGame && this.currentGame.winner !== null) {
|
||||
const winner = this.currentGame.winner;
|
||||
const currentPips = this.scoreboard.get(winner.name) || 0;
|
||||
this.logger.debug (`${winner.name} has ${currentPips} points`);
|
||||
this.scoreboard.set(winner.name, currentPips + totalPips);
|
||||
if (winner.teamedWith !== null) {
|
||||
this.scoreboard.set(winner.teamedWith.name, currentPips + totalPips);
|
||||
}
|
||||
setScores(gameSummary?: GameSummary) {
|
||||
if (!gameSummary) {
|
||||
return;
|
||||
}
|
||||
const { score } = gameSummary;
|
||||
score.forEach(playerScore => {
|
||||
const currentScore = this.scoreboard.get(playerScore.name) ?? 0;
|
||||
this.scoreboard.set(playerScore.name, currentScore + playerScore.score);
|
||||
});
|
||||
}
|
||||
|
||||
afterTileAnimation(data: any) {
|
||||
this.currentGame?.setCanAskNextPlayerMove(true);
|
||||
}
|
||||
|
||||
private endGame(): any {
|
||||
@ -155,13 +245,6 @@ export class MatchSession {
|
||||
this.getScore(this.currentGame);
|
||||
this.logger.info('Game ended');
|
||||
this.currentGame = null;
|
||||
this.playerNotificationManager.notifyMatchState(this);
|
||||
|
||||
return {
|
||||
gameBlocked,
|
||||
gameTied,
|
||||
winner
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,46 +269,23 @@ export class MatchSession {
|
||||
|
||||
createPlayerAI(i: number) {
|
||||
const AInames = ["Alice (AI)", "Bob (AI)", "Charlie (AI)", "David (AI)"];
|
||||
const player = new PlayerAI(AInames[i], this.rng);
|
||||
const player = new PlayerAI(AInames[i], this.rng, this.id);
|
||||
player.ready = true;
|
||||
return player;
|
||||
}
|
||||
|
||||
async start() {
|
||||
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(userId: string) {
|
||||
this.logger.debug(userId)
|
||||
const player = this.players.find(player => player.id === userId);
|
||||
if (!player) {
|
||||
throw new Error("Player not found");
|
||||
}
|
||||
player.ready = true;
|
||||
this.logger.info(`${player.name} is ready!`);
|
||||
this.playerNotificationManager.notifyMatchState(this);
|
||||
}
|
||||
|
||||
this.notificationService.notifyMatchState(this);
|
||||
if (this.matchInProgress && this.numPlayersReady === this.numHumanPlayers) {
|
||||
this.startGame();
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `GameSession:(${this.id} ${this.name})`;
|
||||
|
Reference in New Issue
Block a user