game flow revamp
This commit is contained in:
@ -25,7 +25,7 @@ export class DominoesGame {
|
||||
winner: PlayerInterface | null = null;
|
||||
rng: PRNG;
|
||||
handSize: number = 7;
|
||||
notificationManager: PlayerNotificationManager = new PlayerNotificationManager(this);
|
||||
notificationManager: PlayerNotificationManager = new PlayerNotificationManager();
|
||||
lastMove: PlayerMove | null = null;
|
||||
|
||||
constructor(public players: PlayerInterface[], seed: PRNG) {
|
||||
@ -44,6 +44,14 @@ export class DominoesGame {
|
||||
this.board.boneyard = this.generateTiles();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.board.reset();
|
||||
this.initializeGame();
|
||||
for (let player of this.players) {
|
||||
player.hand = [];
|
||||
}
|
||||
}
|
||||
|
||||
generateTiles(): Tile[] {
|
||||
const tiles: Tile[] = [];
|
||||
for (let i = 6; i >= 0; i--) {
|
||||
@ -142,18 +150,25 @@ export class DominoesGame {
|
||||
this.nextPlayer();
|
||||
}
|
||||
|
||||
resetPlayersScore() {
|
||||
for (let player of this.players) {
|
||||
player.score = 0;
|
||||
}
|
||||
}
|
||||
|
||||
async start(): Promise<GameSummary> {
|
||||
this.resetPlayersScore();
|
||||
this.gameInProgress = false;
|
||||
this.tileSelectionPhase = true;
|
||||
await this.notificationManager.notifyGameState();
|
||||
await this.notificationManager.notifyPlayersState();
|
||||
await this.notificationManager.notifyGameState(this);
|
||||
await this.notificationManager.notifyPlayersState(this.players);
|
||||
this.logger.debug('clients received boneyard :>> ' + this.board.boneyard);
|
||||
await wait(1000);
|
||||
|
||||
if (this.autoDeal) {
|
||||
this.dealTiles();
|
||||
await this.notificationManager.notifyGameState();
|
||||
await this.notificationManager.notifyPlayersState();
|
||||
await this.notificationManager.notifyGameState(this);
|
||||
await this.notificationManager.notifyPlayersState(this.players);
|
||||
} else {
|
||||
await this.tilesSelection();
|
||||
}
|
||||
@ -164,8 +179,8 @@ export class DominoesGame {
|
||||
printLine(`${this.players[this.currentPlayerIndex].name} is the starting player:`);
|
||||
while (!this.gameOver) {
|
||||
await this.playTurn();
|
||||
await this.notificationManager.notifyGameState();
|
||||
await this.notificationManager.notifyPlayersState();
|
||||
await this.notificationManager.notifyGameState(this);
|
||||
await this.notificationManager.notifyPlayersState(this.players);
|
||||
this.gameBlocked = this.isBlocked();
|
||||
this.gameOver = this.isGameOver();
|
||||
}
|
||||
@ -196,8 +211,8 @@ export class DominoesGame {
|
||||
while (this.board.boneyard.length > 0) {
|
||||
for (let player of this.players) {
|
||||
const choosen = await player.chooseTile(this.board);
|
||||
await this.notificationManager.notifyGameState();
|
||||
await this.notificationManager.notifyPlayersState();
|
||||
await this.notificationManager.notifyGameState(this);
|
||||
await this.notificationManager.notifyPlayersState(this.players);
|
||||
if (this.board.boneyard.length === 0) {
|
||||
break;
|
||||
}
|
||||
@ -217,16 +232,8 @@ export class DominoesGame {
|
||||
gameTied: this.gameTied,
|
||||
gameId: this.id,
|
||||
boneyard: this.board.boneyard.map(tile => ({ id: tile.id})),
|
||||
players: this.players.map(player => ({
|
||||
id: player.id,
|
||||
name: player.name,
|
||||
score: player.score,
|
||||
hand: player.hand.map(tile => tile.id),
|
||||
})),
|
||||
currentPlayer: {
|
||||
id: currentPlayer.id,
|
||||
name: currentPlayer.name
|
||||
},
|
||||
players: this.players.map(player => player.getState()),
|
||||
currentPlayer: currentPlayer.getState(),
|
||||
board: this.board.tiles.map(tile => ({
|
||||
id: tile.id,
|
||||
pips: tile.pips
|
||||
|
@ -1,149 +0,0 @@
|
||||
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 { GameSessionState } from "./dto/GameSessionState";
|
||||
import { PlayerNotificationManager } from './PlayerNotificationManager';
|
||||
import seedrandom, { PRNG } from "seedrandom";
|
||||
|
||||
export class GameSession {
|
||||
private game: DominoesGame | null = null;
|
||||
private minHumanPlayers: number = 1;
|
||||
private waitingForPlayers: boolean = true;
|
||||
private waitingSeconds: number = 0;
|
||||
private logger: LoggingService = new LoggingService();
|
||||
private mode: string = 'classic';
|
||||
private pointsToWin: number = 100;
|
||||
private playerNotificationManager: PlayerNotificationManager;
|
||||
id: string;
|
||||
players: PlayerInterface[] = [];
|
||||
sessionInProgress: boolean = false;
|
||||
maxPlayers: number = 4;
|
||||
seed!: string
|
||||
rng!: PRNG
|
||||
|
||||
constructor(public creator: PlayerInterface, public name?: string) {
|
||||
this.playerNotificationManager = new PlayerNotificationManager(this);
|
||||
this.id = uuid();
|
||||
this.name = name || `Game ${this.id}`;
|
||||
this.addPlayer(creator);
|
||||
this.logger.info(`GameSession created by: ${creator.name}`);
|
||||
this.creator = creator;
|
||||
this.playerNotificationManager.notifySessionState();
|
||||
}
|
||||
|
||||
get numPlayers() {
|
||||
return this.players.length;
|
||||
}
|
||||
|
||||
private async startGame(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.game = new DominoesGame(this.players, this.rng);
|
||||
this.sessionInProgress = true;
|
||||
this.logger.info('Game started');
|
||||
this.playerNotificationManager.notifySessionState();
|
||||
await this.game.start();
|
||||
return this.endGame();
|
||||
}
|
||||
|
||||
private endGame(): any {
|
||||
if (this.game !== null) {
|
||||
this.sessionInProgress = false;
|
||||
const { gameBlocked, gameTied, winner } = this.game;
|
||||
|
||||
gameBlocked ? console.log('Game blocked!') : gameTied ? console.log('Game tied!') : console.log('Game over!');
|
||||
console.log('Winner: ' + winner?.name + ' with ' + winner?.pipsCount() + ' points');
|
||||
this.getScore(this.game);
|
||||
this.sessionInProgress = false;
|
||||
this.logger.info('Game ended');
|
||||
this.game = null;
|
||||
this.playerNotificationManager.notifySessionState();
|
||||
|
||||
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()}`;
|
||||
});
|
||||
console.log(`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}`;
|
||||
});
|
||||
console.log(`Scores: ${scores.join(', ')}`);
|
||||
}
|
||||
|
||||
createPlayerAI(i: number) {
|
||||
const AInames = ["Alice (AI)", "Bob (AI)", "Charlie (AI)", "David (AI)"];
|
||||
return new PlayerAI(AInames[i], this.rng);
|
||||
}
|
||||
|
||||
async start(seed?: string) {
|
||||
this.seed = seed || getRandomSeed();
|
||||
console.log('seed :>> ', this.seed);
|
||||
if (this.sessionInProgress) {
|
||||
throw new Error("Game already in progress");
|
||||
}
|
||||
this.waitingForPlayers = true;
|
||||
this.logger.info('Waiting for players to join');
|
||||
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');
|
||||
this.startGame(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!`);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `GameSession:(${this.id} ${this.name})`;
|
||||
}
|
||||
|
||||
getState(): GameSessionState {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name!,
|
||||
creator: this.creator.id,
|
||||
players: this.players.map(player =>( {
|
||||
id: player.id,
|
||||
name: player.name,
|
||||
})),
|
||||
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'
|
||||
};
|
||||
}
|
||||
}
|
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
|
||||
};
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ export class NetworkClientNotifier {
|
||||
this.io = io;
|
||||
}
|
||||
|
||||
async notifyPlayer(player: NetworkPlayer, event: string, data: any = {}, timeoutSecs: number = 300): Promise<any> {
|
||||
async notifyPlayer(player: NetworkPlayer, event: string, data: any = {}, timeoutSecs: number = 900): Promise<any> {
|
||||
try {
|
||||
const response = await this.io.to(player.socketId)
|
||||
.timeout(timeoutSecs * 1000)
|
||||
@ -29,6 +29,10 @@ export class NetworkClientNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
async sendEvent(player: NetworkPlayer, event: string, data?: any) {
|
||||
this.io.to(player.socketId).emit(event, data);
|
||||
}
|
||||
|
||||
async broadcast(event: string, data: any) {
|
||||
const responses = await this.io.emit(event, data);
|
||||
this.logger.debug('responses :>> ', responses);
|
||||
|
@ -1,39 +1,36 @@
|
||||
import { DominoesGame } from "./DominoesGame";
|
||||
import { GameSession } from "./GameSession";
|
||||
import { MatchSession } from "./MatchSession";
|
||||
import { GameState } from "./dto/GameState";
|
||||
import { PlayerInterface } from "./entities/player/PlayerInterface";
|
||||
|
||||
export class PlayerNotificationManager {
|
||||
game!: DominoesGame;
|
||||
session!: GameSession;
|
||||
|
||||
constructor(game: DominoesGame | GameSession) {
|
||||
if (game instanceof GameSession) {
|
||||
this.session = game;
|
||||
} else {
|
||||
this.game = game;
|
||||
}
|
||||
}
|
||||
|
||||
async notifyGameState() {
|
||||
if(!this.game) throw new Error('Game not initialized');
|
||||
const gameState: GameState = this.game.getGameState();
|
||||
const { players } = this.game;
|
||||
async notifyGameState(game: DominoesGame) {
|
||||
const gameState: GameState = game.getGameState();
|
||||
const { players } = game;
|
||||
let promises: Promise<void>[] = players.map(player => player.notifyGameState(gameState));
|
||||
return await Promise.all(promises);
|
||||
}
|
||||
|
||||
async notifyPlayersState() {
|
||||
if(!this.game) throw new Error('Game not initialized');
|
||||
const { players } = this.game;
|
||||
async notifyPlayersState(players: PlayerInterface[]) {
|
||||
let promises: Promise<void>[] = players.map(player => player.notifyPlayerState(player.getState()));
|
||||
return await Promise.all(promises);
|
||||
}
|
||||
|
||||
|
||||
async notifySessionState() {
|
||||
if(!this.session) throw new Error('Session not initialized');
|
||||
const { players } = this.session;
|
||||
let promises: Promise<void>[] = players.map(player => player.notifySessionState(this.session.getState()));
|
||||
async notifyMatchState(session: MatchSession) {
|
||||
const { players } = session;
|
||||
let promises: Promise<void>[] = players.map(player => player.notifyMatchState(session.getState()));
|
||||
return await Promise.all(promises);
|
||||
}
|
||||
|
||||
async waitForPlayersAction(actionId: string, data: any = {}, players: PlayerInterface[]) {
|
||||
let promises: Promise<boolean>[] = players.map(player => player.waitForAction(actionId, data));
|
||||
return await Promise.all(promises);
|
||||
}
|
||||
|
||||
async sendEventToPlayers(event: string, players: PlayerInterface[]) {
|
||||
let promises: Promise<void>[] = players.map(player => player.sendEvent(event));
|
||||
return await Promise.all(promises);
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { PlayerDto } from "./PlayerDto";
|
||||
|
||||
export interface GameSessionState {
|
||||
export interface MatchSessionState {
|
||||
id: string;
|
||||
name: string;
|
||||
creator: string;
|
||||
@ -14,4 +14,8 @@ export interface GameSessionState {
|
||||
maxPlayers: number;
|
||||
numPlayers: number;
|
||||
waitingSeconds: number;
|
||||
scoreboard: Map<string, number>;
|
||||
matchWinner: PlayerDto | null;
|
||||
matchInProgress: boolean;
|
||||
playersReady: number
|
||||
}
|
@ -2,5 +2,7 @@ export interface PlayerDto {
|
||||
id: string;
|
||||
name: string;
|
||||
score?: number;
|
||||
hand?: string[];
|
||||
hand?: any[];
|
||||
teamedWith?: PlayerDto | null;
|
||||
ready: boolean;
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
export interface PlayerState {
|
||||
id: string;
|
||||
name: string;
|
||||
score: number;
|
||||
hand: any[];
|
||||
teamedWith: string | undefined;
|
||||
}
|
@ -38,6 +38,11 @@ export class Board {
|
||||
return this.rightEnd?.flippedPips[1];
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.tiles = [];
|
||||
this.boneyard = [];
|
||||
}
|
||||
|
||||
getFreeEnds() {
|
||||
if(this.count === 0) {
|
||||
return [];
|
||||
|
@ -7,8 +7,8 @@ import { EventEmitter } from "stream";
|
||||
import { PlayerInteractionInterface } from "../../PlayerInteractionInterface";
|
||||
import { uuid } from "../../../common/utilities";
|
||||
import { GameState } from "../../dto/GameState";
|
||||
import { PlayerState } from "../../dto/PlayerState";
|
||||
import { GameSessionState } from "../../dto/GameSessionState";
|
||||
import { MatchSessionState } from "../../dto/MatchSessionState";
|
||||
import { PlayerDto } from "../../dto/PlayerDto";
|
||||
|
||||
export abstract class AbstractPlayer extends EventEmitter implements PlayerInterface {
|
||||
hand: Tile[] = [];
|
||||
@ -17,6 +17,7 @@ export abstract class AbstractPlayer extends EventEmitter implements PlayerInter
|
||||
teamedWith: PlayerInterface | null = null;
|
||||
playerInteraction: PlayerInteractionInterface = undefined as any;
|
||||
id: string = uuid();
|
||||
ready: boolean = false;
|
||||
|
||||
constructor(public name: string) {
|
||||
super();
|
||||
@ -29,10 +30,17 @@ export abstract class AbstractPlayer extends EventEmitter implements PlayerInter
|
||||
async notifyGameState(state: GameState): Promise<void> {
|
||||
}
|
||||
|
||||
async notifyPlayerState(state: PlayerState): Promise<void> {
|
||||
async notifyPlayerState(state: PlayerDto): Promise<void> {
|
||||
}
|
||||
|
||||
async notifySessionState(state: GameSessionState): Promise<void> {
|
||||
async notifyMatchState(state: MatchSessionState): Promise<void> {
|
||||
}
|
||||
|
||||
async waitForAction(actionId: string): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
async sendEvent(event: string): Promise<void> {
|
||||
}
|
||||
|
||||
pipsCount(): number {
|
||||
@ -54,17 +62,24 @@ export abstract class AbstractPlayer extends EventEmitter implements PlayerInter
|
||||
return highestPair;
|
||||
}
|
||||
|
||||
getState(): PlayerState {
|
||||
getState(showPips: boolean = false): PlayerDto {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
score: this.score,
|
||||
hand: this.hand.map(tile => ({
|
||||
id: tile.id,
|
||||
pips: tile.pips,
|
||||
flipped: tile.revealed,
|
||||
})),
|
||||
teamedWith: this.teamedWith?.id,
|
||||
hand: this.hand.map(tile => {
|
||||
const d = {
|
||||
id: tile.id,
|
||||
pips: tile.pips,
|
||||
flipped: tile.revealed,
|
||||
};
|
||||
if (showPips) {
|
||||
d.pips = tile.pips;
|
||||
}
|
||||
return d;
|
||||
}),
|
||||
teamedWith: this.teamedWith?.getState() ?? null,
|
||||
ready: this.ready,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,8 @@ import { NetworkClientNotifier } from "../../NetworkClientNotifier";
|
||||
import { Tile } from "../Tile";
|
||||
import { Board } from "../Board";
|
||||
import { GameState } from "../../dto/GameState";
|
||||
import { PlayerState } from "../../dto/PlayerState";
|
||||
import { GameSessionState } from "../../dto/GameSessionState";
|
||||
import { PlayerDto } from "../../dto/PlayerDto";
|
||||
import { MatchSessionState } from "../../dto/MatchSessionState";
|
||||
import { SocketDisconnectedError } from "../../../common/exceptions/SocketDisconnectedError";
|
||||
|
||||
export class NetworkPlayer extends PlayerHuman {
|
||||
@ -27,7 +27,7 @@ export class NetworkPlayer extends PlayerHuman {
|
||||
}
|
||||
}
|
||||
|
||||
async notifyPlayerState(state: PlayerState): Promise<void> {
|
||||
async notifyPlayerState(state: PlayerDto): Promise<void> {
|
||||
const response = await this.clientNotifier.notifyPlayer(this, 'playerState', state);
|
||||
console.log('player state notified :>> ', response);
|
||||
if (response === undefined || response.status !== 'ok' ) {
|
||||
@ -35,14 +35,25 @@ export class NetworkPlayer extends PlayerHuman {
|
||||
}
|
||||
}
|
||||
|
||||
async notifySessionState(state: GameSessionState): Promise<void> {
|
||||
const response = await this.clientNotifier.notifyPlayer(this, 'sessionState', state);
|
||||
async notifyMatchState(state: MatchSessionState): Promise<void> {
|
||||
const response = await this.clientNotifier.notifyPlayer(this, 'matchState', state);
|
||||
console.log('session state notified :>> ', response);
|
||||
if (response === undefined || response.status !== 'ok' ) {
|
||||
throw new SocketDisconnectedError();
|
||||
}
|
||||
}
|
||||
|
||||
async waitForAction(actionId: string): Promise<boolean> {
|
||||
const response = await this.clientNotifier.notifyPlayer(this, actionId);
|
||||
if (response === undefined || response.status !== 'ok' ) {
|
||||
throw new SocketDisconnectedError();
|
||||
}
|
||||
const { actionResult } = response;
|
||||
return actionResult;
|
||||
}
|
||||
|
||||
async sendEvent(event: string): Promise<void> {
|
||||
this.clientNotifier.sendEvent(this, event);
|
||||
}
|
||||
|
||||
async chooseTile(board: Board): Promise<Tile> {
|
||||
return await this.playerInteraction.chooseTile(board);
|
||||
|
@ -2,9 +2,9 @@ import { PlayerInteractionInterface } from "../../PlayerInteractionInterface";
|
||||
import { Board } from "../Board";
|
||||
import { GameState } from "../../dto/GameState";
|
||||
import { PlayerMove } from "../PlayerMove";
|
||||
import { PlayerState } from "../../dto/PlayerState";
|
||||
import { Tile } from "../Tile";
|
||||
import { GameSessionState } from "../../dto/GameSessionState";
|
||||
import { MatchSessionState } from "../../dto/MatchSessionState";
|
||||
import { PlayerDto } from "../../dto/PlayerDto";
|
||||
|
||||
export interface PlayerInterface {
|
||||
id: string;
|
||||
@ -13,12 +13,15 @@ export interface PlayerInterface {
|
||||
hand: Tile[];
|
||||
teamedWith: PlayerInterface | null;
|
||||
playerInteraction: PlayerInteractionInterface;
|
||||
ready: boolean;
|
||||
|
||||
makeMove(gameState: Board): Promise<PlayerMove | null>;
|
||||
chooseTile(board: Board): Promise<Tile>;
|
||||
pipsCount(): number;
|
||||
notifyGameState(state: GameState): Promise<void>;
|
||||
notifyPlayerState(state: PlayerState): Promise<void>;
|
||||
notifySessionState(state: GameSessionState): Promise<void>;
|
||||
getState(): PlayerState;
|
||||
notifyPlayerState(state: PlayerDto): Promise<void>;
|
||||
notifyMatchState(state: MatchSessionState): Promise<void>;
|
||||
waitForAction(actionId: string, data: any): Promise<boolean>;
|
||||
sendEvent(event: string): Promise<void>;
|
||||
getState(): PlayerDto;
|
||||
}
|
Reference in New Issue
Block a user