changes
This commit is contained in:
@ -6,7 +6,7 @@ import { Tile } from "./entities/Tile";
|
||||
import { LoggingService } from "../common/LoggingService";
|
||||
import { printBoard, printLine, uuid, wait } from '../common/utilities';
|
||||
import { GameSummary } from './dto/GameSummary';
|
||||
import { PlayerNotificationManager } from './PlayerNotificationManager';
|
||||
import { PlayerNotificationService } from '../server/services/PlayerNotificationService';
|
||||
import { GameState } from './dto/GameState';
|
||||
|
||||
export class DominoesGame {
|
||||
@ -25,13 +25,12 @@ export class DominoesGame {
|
||||
winner: PlayerInterface | null = null;
|
||||
rng: PRNG;
|
||||
handSize: number = 7;
|
||||
notificationManager: PlayerNotificationManager = new PlayerNotificationManager();
|
||||
notificationManager: PlayerNotificationService = new PlayerNotificationService();
|
||||
lastMove: PlayerMove | null = null;
|
||||
|
||||
constructor(public players: PlayerInterface[], seed: PRNG) {
|
||||
this.id = uuid();
|
||||
this.logger.info(`Game ID: ${this.id}`);
|
||||
this.logger.info(`Seed: ${this.seed}`);
|
||||
this.rng = seed
|
||||
this.board = new Board(seed);
|
||||
this.initializeGame();
|
||||
|
@ -4,7 +4,7 @@ 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 { PlayerNotificationService } from '../server/services/PlayerNotificationService';
|
||||
import seedrandom, { PRNG } from "seedrandom";
|
||||
import { NetworkPlayer } from "./entities/player/NetworkPlayer";
|
||||
import { PlayerHuman } from "./entities/player/PlayerHuman";
|
||||
@ -16,11 +16,11 @@ export class MatchSession {
|
||||
private waitingForPlayers: boolean = true;
|
||||
private waitingSeconds: number = 0;
|
||||
private logger: LoggingService = new LoggingService();
|
||||
private playerNotificationManager = new PlayerNotificationManager();
|
||||
private playerNotificationManager = new PlayerNotificationService();
|
||||
|
||||
id: string;
|
||||
matchInProgress: boolean = false;
|
||||
matchWinner: PlayerInterface | null = null;
|
||||
matchWinner?: PlayerInterface = undefined;
|
||||
maxPlayers: number = 4;
|
||||
mode: string = 'classic';
|
||||
players: PlayerInterface[] = [];
|
||||
@ -31,20 +31,19 @@ export class MatchSession {
|
||||
sessionInProgress: boolean = false;
|
||||
state: string = 'created'
|
||||
|
||||
constructor(public creator: PlayerInterface, public name?: string) {
|
||||
constructor(public creator: PlayerInterface, public name?: string, seed?: string) {
|
||||
this.seed = seed || getRandomSeed();
|
||||
this.id = uuid();
|
||||
this.name = name || `Game ${this.id}`;
|
||||
this.addPlayer(creator);
|
||||
|
||||
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() {
|
||||
@ -77,14 +76,17 @@ export class MatchSession {
|
||||
this.state = 'started'
|
||||
this.logger.info(`Game #${gameNumber} started`);
|
||||
// this.game.reset()
|
||||
await this.currentGame.start();
|
||||
const gameSummary = await this.currentGame.start();
|
||||
this.logger.debug('gameSummary :>> ', gameSummary);
|
||||
this.setScores();
|
||||
this.checkMatchWinner();
|
||||
this.resetReadiness();
|
||||
this.resetPlayers();
|
||||
this.state = 'waiting'
|
||||
await this.playerNotificationManager.notifyMatchState(this);
|
||||
this.playerNotificationManager.sendEventToPlayers('game-finished', this.players);
|
||||
await this.checkHumanPlayersReady();
|
||||
if (this.matchInProgress) {
|
||||
await this.checkHumanPlayersReady();
|
||||
}
|
||||
}
|
||||
this.state = 'end'
|
||||
// await this.game.start();
|
||||
@ -103,10 +105,10 @@ export class MatchSession {
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
resetReadiness() {
|
||||
|
||||
resetPlayers() {
|
||||
this.players.forEach(player => {
|
||||
player.ready = false
|
||||
player.reset()
|
||||
});
|
||||
}
|
||||
|
||||
@ -114,7 +116,10 @@ export class MatchSession {
|
||||
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.matchWinner = this.players.find(player => this.scoreboard.get(player.name) === maxScore);
|
||||
if (!this.matchWinner) {
|
||||
throw new Error('Match winner not found');
|
||||
}
|
||||
this.logger.info(`Match winner: ${this.matchWinner.name} with ${maxScore} points`);
|
||||
this.matchInProgress = false;
|
||||
}
|
||||
@ -122,17 +127,19 @@ export class MatchSession {
|
||||
resetScoreboard() {
|
||||
this.scoreboard = new Map();
|
||||
this.players.forEach(player => {
|
||||
this.scoreboard.set(player.id, 0);
|
||||
this.scoreboard.set(player.name, 0);
|
||||
});
|
||||
}
|
||||
|
||||
setScores() {
|
||||
const totalPips = this.currentGame?.players.reduce((acc, player) => acc + player.pipsCount(), 0);
|
||||
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;
|
||||
this.scoreboard.set(winner.id, this.scoreboard.get(winner.id)! + totalPips!);
|
||||
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.id, this.scoreboard.get(winner.teamedWith.id)! + totalPips!);
|
||||
this.scoreboard.set(winner.teamedWith.name, currentPips + totalPips);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -181,9 +188,7 @@ export class MatchSession {
|
||||
return player;
|
||||
}
|
||||
|
||||
async start(seed?: string) {
|
||||
this.seed = seed || getRandomSeed();
|
||||
console.log('seed :>> ', this.seed);
|
||||
async start() {
|
||||
if (this.matchInProgress) {
|
||||
throw new Error("Game already in progress");
|
||||
}
|
||||
@ -207,8 +212,9 @@ export class MatchSession {
|
||||
this.logger.info(`${player.name} joined the game!`);
|
||||
}
|
||||
|
||||
setPlayerReady(user: string) {
|
||||
const player = this.players.find(player => player.name === user);
|
||||
setPlayerReady(userId: string) {
|
||||
this.logger.debug(userId)
|
||||
const player = this.players.find(player => player.id === userId);
|
||||
if (!player) {
|
||||
throw new Error("Player not found");
|
||||
}
|
||||
@ -242,7 +248,7 @@ export class MatchSession {
|
||||
mode: this.mode,
|
||||
pointsToWin: this.pointsToWin,
|
||||
status: this.sessionInProgress ? 'in progress' : 'waiting',
|
||||
scoreboard: this.scoreboard,
|
||||
scoreboard: [...this.scoreboard.entries()],
|
||||
matchWinner: this.matchWinner?.getState() || null,
|
||||
matchInProgress: this.matchInProgress
|
||||
};
|
||||
|
@ -30,7 +30,15 @@ export class NetworkClientNotifier {
|
||||
}
|
||||
|
||||
async sendEvent(player: NetworkPlayer, event: string, data?: any) {
|
||||
this.io.to(player.socketId).emit(event, data);
|
||||
const eventData = { event, data };
|
||||
this.io.to(player.socketId).emit('game-event', eventData);
|
||||
}
|
||||
|
||||
async sendEventWithAck(player: NetworkPlayer, event: string, data: any, timeoutSecs: number = 900) {
|
||||
const eventData = { event, data };
|
||||
const response = await this.io.to(player.socketId)
|
||||
.timeout(timeoutSecs * 1000).emitWithAck('game-event-ack', eventData);
|
||||
return response[0];
|
||||
}
|
||||
|
||||
async broadcast(event: string, data: any) {
|
||||
|
@ -6,7 +6,7 @@ import { Tile } from './entities/Tile';
|
||||
import { NetworkClientNotifier } from './NetworkClientNotifier';
|
||||
import { NetworkPlayer } from './entities/player/NetworkPlayer';
|
||||
import { PlayerMoveSide, PlayerMoveSideType } from './constants';
|
||||
import { SocketDisconnectedError } from '../common/exceptions/SocketDisconnectedError';
|
||||
import { SocketDisconnectedError } from '../common/errors/SocketDisconnectedError';
|
||||
|
||||
export class PlayerInteractionNetwork implements PlayerInteractionInterface {
|
||||
player: PlayerInterface;
|
||||
@ -19,7 +19,7 @@ export class PlayerInteractionNetwork implements PlayerInteractionInterface {
|
||||
async makeMove(board: Board): Promise<PlayerMove | null> {
|
||||
let response = undefined;
|
||||
try {
|
||||
response = await this.clientNotifier.notifyPlayer(this.player as NetworkPlayer, 'makeMove', {
|
||||
response = await this.clientNotifier.sendEventWithAck(this.player as NetworkPlayer, 'ask-client-for-move', {
|
||||
freeHands: board.getFreeEnds(),
|
||||
});
|
||||
} catch (error) {
|
||||
|
@ -1,36 +0,0 @@
|
||||
import { DominoesGame } from "./DominoesGame";
|
||||
import { MatchSession } from "./MatchSession";
|
||||
import { GameState } from "./dto/GameState";
|
||||
import { PlayerInterface } from "./entities/player/PlayerInterface";
|
||||
|
||||
export class PlayerNotificationManager {
|
||||
|
||||
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(players: PlayerInterface[]) {
|
||||
let promises: Promise<void>[] = players.map(player => player.notifyPlayerState(player.getState()));
|
||||
return await Promise.all(promises);
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ export interface MatchSessionState {
|
||||
maxPlayers: number;
|
||||
numPlayers: number;
|
||||
waitingSeconds: number;
|
||||
scoreboard: Map<string, number>;
|
||||
scoreboard: [string, number][];
|
||||
matchWinner: PlayerDto | null;
|
||||
matchInProgress: boolean;
|
||||
playersReady: number
|
||||
|
@ -6,8 +6,6 @@ import { LoggingService } from "../../../common/LoggingService";
|
||||
import { EventEmitter } from "stream";
|
||||
import { PlayerInteractionInterface } from "../../PlayerInteractionInterface";
|
||||
import { uuid } from "../../../common/utilities";
|
||||
import { GameState } from "../../dto/GameState";
|
||||
import { MatchSessionState } from "../../dto/MatchSessionState";
|
||||
import { PlayerDto } from "../../dto/PlayerDto";
|
||||
|
||||
export abstract class AbstractPlayer extends EventEmitter implements PlayerInterface {
|
||||
@ -27,20 +25,16 @@ export abstract class AbstractPlayer extends EventEmitter implements PlayerInter
|
||||
abstract chooseTile(board: Board): Promise<Tile>;
|
||||
|
||||
|
||||
async notifyGameState(state: GameState): Promise<void> {
|
||||
}
|
||||
|
||||
async notifyPlayerState(state: PlayerDto): Promise<void> {
|
||||
async sendEventWithAck(event: string, data: any): Promise<void> {
|
||||
}
|
||||
|
||||
async notifyMatchState(state: MatchSessionState): Promise<void> {
|
||||
async sendEvent(event: string, data: any = {}): Promise<void> {
|
||||
}
|
||||
|
||||
async waitForAction(actionId: string): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
async sendEvent(event: string): Promise<void> {
|
||||
reset(): void {
|
||||
this.hand = [];
|
||||
this.score = 0;
|
||||
this.ready = false;
|
||||
}
|
||||
|
||||
pipsCount(): number {
|
||||
|
@ -4,55 +4,22 @@ import { PlayerHuman } from "./PlayerHuman";
|
||||
import { NetworkClientNotifier } from "../../NetworkClientNotifier";
|
||||
import { Tile } from "../Tile";
|
||||
import { Board } from "../Board";
|
||||
import { GameState } from "../../dto/GameState";
|
||||
import { PlayerDto } from "../../dto/PlayerDto";
|
||||
import { MatchSessionState } from "../../dto/MatchSessionState";
|
||||
import { SocketDisconnectedError } from "../../../common/exceptions/SocketDisconnectedError";
|
||||
|
||||
export class NetworkPlayer extends PlayerHuman {
|
||||
socketId: string;
|
||||
socketId!: string;
|
||||
playerInteraction: PlayerInteractionInterface = new PlayerInteractionNetwork(this);
|
||||
clientNotifier: NetworkClientNotifier = new NetworkClientNotifier();
|
||||
|
||||
constructor(name: string, socketId: string) {
|
||||
super(name);
|
||||
constructor(id: string, name: string, socketId: string ) {
|
||||
super(id, name);
|
||||
this.socketId = socketId;
|
||||
}
|
||||
|
||||
async notifyGameState(state: GameState): Promise<void> {
|
||||
const response = await this.clientNotifier.notifyPlayer(this, 'gameState', state);
|
||||
console.log('game state notified :>> ', response);
|
||||
if (response === undefined || response.status !== 'ok' ) {
|
||||
throw new SocketDisconnectedError();
|
||||
}
|
||||
}
|
||||
|
||||
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' ) {
|
||||
throw new SocketDisconnectedError();
|
||||
}
|
||||
async sendEvent(event: string, data:any = {}): Promise<void> {
|
||||
this.clientNotifier.sendEvent(this, event, data);
|
||||
}
|
||||
|
||||
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 sendEventWithAck(event: string, data: any): Promise<any> {
|
||||
return await this.clientNotifier.sendEventWithAck(this, event, data);
|
||||
}
|
||||
|
||||
async chooseTile(board: Board): Promise<Tile> {
|
||||
|
@ -8,8 +8,9 @@ import { PlayerInteractionInterface } from '../../PlayerInteractionInterface';
|
||||
|
||||
export class PlayerHuman extends AbstractPlayer {
|
||||
playerInteraction: PlayerInteractionInterface = new PlayerInteractionConsole(this);
|
||||
constructor(name: string) {
|
||||
constructor(id: string, name: string) {
|
||||
super(name);
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
async makeMove(board: Board): Promise<PlayerMove | null> {
|
||||
|
@ -18,10 +18,10 @@ export interface PlayerInterface {
|
||||
makeMove(gameState: Board): Promise<PlayerMove | null>;
|
||||
chooseTile(board: Board): Promise<Tile>;
|
||||
pipsCount(): number;
|
||||
notifyGameState(state: GameState): Promise<void>;
|
||||
notifyPlayerState(state: PlayerDto): Promise<void>;
|
||||
notifyMatchState(state: MatchSessionState): Promise<void>;
|
||||
waitForAction(actionId: string, data: any): Promise<boolean>;
|
||||
sendEvent(event: string): Promise<void>;
|
||||
reset(): void;
|
||||
|
||||
sendEvent(event: string, data: any): Promise<void>;
|
||||
sendEventWithAck(event: string, data: any): Promise<any>;
|
||||
|
||||
getState(): PlayerDto;
|
||||
}
|
Reference in New Issue
Block a user