changes
This commit is contained in:
parent
a974f576b3
commit
f67c262b0e
@ -1,7 +1,6 @@
|
|||||||
export class ErrorBase extends Error {
|
export class ErrorBase extends Error {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
super(message);
|
super(message);
|
||||||
console.log('this.constructor.name :>> ', this.constructor.name);
|
|
||||||
this.name = this.constructor.name;
|
this.name = this.constructor.name;
|
||||||
this.stack = (new Error()).stack;
|
this.stack = (new Error()).stack;
|
||||||
}
|
}
|
7
src/common/errors/SessionCreationError.ts
Normal file
7
src/common/errors/SessionCreationError.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { ErrorBase } from "./ErrorBase";
|
||||||
|
|
||||||
|
export class SessionCreationError extends ErrorBase {
|
||||||
|
constructor() {
|
||||||
|
super('Session creation error');
|
||||||
|
}
|
||||||
|
}
|
7
src/common/errors/SessionNotFoundError.ts
Normal file
7
src/common/errors/SessionNotFoundError.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { ErrorBase } from "./ErrorBase";
|
||||||
|
|
||||||
|
export class SessionNotFoundError extends ErrorBase {
|
||||||
|
constructor() {
|
||||||
|
super('Session not found');
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,21 @@ export async function wait(ms: number) {
|
|||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const whileNotUndefined = async (fn: Function, maxQueries: number = 20, millis: number = 500): Promise<any> => {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
let result;
|
||||||
|
while (result === undefined) {
|
||||||
|
await wait(millis);
|
||||||
|
result = fn()
|
||||||
|
if (maxQueries-- < 0) {
|
||||||
|
reject()
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function askQuestion(question: string): Promise<string> {
|
export function askQuestion(question: string): Promise<string> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
// console.log(chalk.yellow(question));
|
// console.log(chalk.yellow(question));
|
||||||
|
@ -6,7 +6,7 @@ import { Tile } from "./entities/Tile";
|
|||||||
import { LoggingService } from "../common/LoggingService";
|
import { LoggingService } from "../common/LoggingService";
|
||||||
import { printBoard, printLine, uuid, wait } from '../common/utilities';
|
import { printBoard, printLine, uuid, wait } from '../common/utilities';
|
||||||
import { GameSummary } from './dto/GameSummary';
|
import { GameSummary } from './dto/GameSummary';
|
||||||
import { PlayerNotificationManager } from './PlayerNotificationManager';
|
import { PlayerNotificationService } from '../server/services/PlayerNotificationService';
|
||||||
import { GameState } from './dto/GameState';
|
import { GameState } from './dto/GameState';
|
||||||
|
|
||||||
export class DominoesGame {
|
export class DominoesGame {
|
||||||
@ -25,13 +25,12 @@ export class DominoesGame {
|
|||||||
winner: PlayerInterface | null = null;
|
winner: PlayerInterface | null = null;
|
||||||
rng: PRNG;
|
rng: PRNG;
|
||||||
handSize: number = 7;
|
handSize: number = 7;
|
||||||
notificationManager: PlayerNotificationManager = new PlayerNotificationManager();
|
notificationManager: PlayerNotificationService = new PlayerNotificationService();
|
||||||
lastMove: PlayerMove | null = null;
|
lastMove: PlayerMove | null = null;
|
||||||
|
|
||||||
constructor(public players: PlayerInterface[], seed: PRNG) {
|
constructor(public players: PlayerInterface[], seed: PRNG) {
|
||||||
this.id = uuid();
|
this.id = uuid();
|
||||||
this.logger.info(`Game ID: ${this.id}`);
|
this.logger.info(`Game ID: ${this.id}`);
|
||||||
this.logger.info(`Seed: ${this.seed}`);
|
|
||||||
this.rng = seed
|
this.rng = seed
|
||||||
this.board = new Board(seed);
|
this.board = new Board(seed);
|
||||||
this.initializeGame();
|
this.initializeGame();
|
||||||
|
@ -4,7 +4,7 @@ import { PlayerInterface } from "./entities/player/PlayerInterface";
|
|||||||
import { LoggingService } from "../common/LoggingService";
|
import { LoggingService } from "../common/LoggingService";
|
||||||
import { getRandomSeed, uuid, wait } from "../common/utilities";
|
import { getRandomSeed, uuid, wait } from "../common/utilities";
|
||||||
import { MatchSessionState } from "./dto/MatchSessionState";
|
import { MatchSessionState } from "./dto/MatchSessionState";
|
||||||
import { PlayerNotificationManager } from './PlayerNotificationManager';
|
import { PlayerNotificationService } from '../server/services/PlayerNotificationService';
|
||||||
import seedrandom, { PRNG } from "seedrandom";
|
import seedrandom, { PRNG } from "seedrandom";
|
||||||
import { NetworkPlayer } from "./entities/player/NetworkPlayer";
|
import { NetworkPlayer } from "./entities/player/NetworkPlayer";
|
||||||
import { PlayerHuman } from "./entities/player/PlayerHuman";
|
import { PlayerHuman } from "./entities/player/PlayerHuman";
|
||||||
@ -16,11 +16,11 @@ export class MatchSession {
|
|||||||
private waitingForPlayers: boolean = true;
|
private waitingForPlayers: boolean = true;
|
||||||
private waitingSeconds: number = 0;
|
private waitingSeconds: number = 0;
|
||||||
private logger: LoggingService = new LoggingService();
|
private logger: LoggingService = new LoggingService();
|
||||||
private playerNotificationManager = new PlayerNotificationManager();
|
private playerNotificationManager = new PlayerNotificationService();
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
matchInProgress: boolean = false;
|
matchInProgress: boolean = false;
|
||||||
matchWinner: PlayerInterface | null = null;
|
matchWinner?: PlayerInterface = undefined;
|
||||||
maxPlayers: number = 4;
|
maxPlayers: number = 4;
|
||||||
mode: string = 'classic';
|
mode: string = 'classic';
|
||||||
players: PlayerInterface[] = [];
|
players: PlayerInterface[] = [];
|
||||||
@ -31,20 +31,19 @@ export class MatchSession {
|
|||||||
sessionInProgress: boolean = false;
|
sessionInProgress: boolean = false;
|
||||||
state: string = 'created'
|
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.id = uuid();
|
||||||
this.name = name || `Game ${this.id}`;
|
this.name = name || `Game ${this.id}`;
|
||||||
this.addPlayer(creator);
|
this.addPlayer(creator);
|
||||||
|
|
||||||
this.creator = creator;
|
this.creator = creator;
|
||||||
|
|
||||||
this.logger.info(`Match session created by: ${creator.name}`);
|
this.logger.info(`Match session created by: ${creator.name}`);
|
||||||
this.logger.info(`Match session ID: ${this.id}`);
|
this.logger.info(`Match session ID: ${this.id}`);
|
||||||
this.logger.info(`Match session name: ${this.name}`);
|
this.logger.info(`Match session name: ${this.name}`);
|
||||||
this.logger.info(`Points to win: ${this.pointsToWin}`);
|
this.logger.info(`Points to win: ${this.pointsToWin}`);
|
||||||
this.sessionInProgress = true;
|
this.sessionInProgress = true;
|
||||||
this.matchInProgress = false;
|
this.matchInProgress = false;
|
||||||
this.playerNotificationManager.notifyMatchState(this);
|
|
||||||
this.playerNotificationManager.notifyPlayersState(this.players);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get numPlayers() {
|
get numPlayers() {
|
||||||
@ -77,14 +76,17 @@ export class MatchSession {
|
|||||||
this.state = 'started'
|
this.state = 'started'
|
||||||
this.logger.info(`Game #${gameNumber} started`);
|
this.logger.info(`Game #${gameNumber} started`);
|
||||||
// this.game.reset()
|
// this.game.reset()
|
||||||
await this.currentGame.start();
|
const gameSummary = await this.currentGame.start();
|
||||||
|
this.logger.debug('gameSummary :>> ', gameSummary);
|
||||||
this.setScores();
|
this.setScores();
|
||||||
this.checkMatchWinner();
|
this.checkMatchWinner();
|
||||||
this.resetReadiness();
|
this.resetPlayers();
|
||||||
this.state = 'waiting'
|
this.state = 'waiting'
|
||||||
await this.playerNotificationManager.notifyMatchState(this);
|
await this.playerNotificationManager.notifyMatchState(this);
|
||||||
this.playerNotificationManager.sendEventToPlayers('game-finished', this.players);
|
this.playerNotificationManager.sendEventToPlayers('game-finished', this.players);
|
||||||
await this.checkHumanPlayersReady();
|
if (this.matchInProgress) {
|
||||||
|
await this.checkHumanPlayersReady();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.state = 'end'
|
this.state = 'end'
|
||||||
// await this.game.start();
|
// await this.game.start();
|
||||||
@ -104,9 +106,9 @@ export class MatchSession {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
resetReadiness() {
|
resetPlayers() {
|
||||||
this.players.forEach(player => {
|
this.players.forEach(player => {
|
||||||
player.ready = false
|
player.reset()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +116,10 @@ export class MatchSession {
|
|||||||
const scores = Array.from(this.scoreboard.values());
|
const scores = Array.from(this.scoreboard.values());
|
||||||
const maxScore = Math.max(...scores);
|
const maxScore = Math.max(...scores);
|
||||||
if (maxScore >= this.pointsToWin) {
|
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.logger.info(`Match winner: ${this.matchWinner.name} with ${maxScore} points`);
|
||||||
this.matchInProgress = false;
|
this.matchInProgress = false;
|
||||||
}
|
}
|
||||||
@ -122,17 +127,19 @@ export class MatchSession {
|
|||||||
resetScoreboard() {
|
resetScoreboard() {
|
||||||
this.scoreboard = new Map();
|
this.scoreboard = new Map();
|
||||||
this.players.forEach(player => {
|
this.players.forEach(player => {
|
||||||
this.scoreboard.set(player.id, 0);
|
this.scoreboard.set(player.name, 0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setScores() {
|
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) {
|
if (this.currentGame && this.currentGame.winner !== null) {
|
||||||
const winner = this.currentGame.winner;
|
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) {
|
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;
|
return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(seed?: string) {
|
async start() {
|
||||||
this.seed = seed || getRandomSeed();
|
|
||||||
console.log('seed :>> ', this.seed);
|
|
||||||
if (this.matchInProgress) {
|
if (this.matchInProgress) {
|
||||||
throw new Error("Game already in progress");
|
throw new Error("Game already in progress");
|
||||||
}
|
}
|
||||||
@ -207,8 +212,9 @@ export class MatchSession {
|
|||||||
this.logger.info(`${player.name} joined the game!`);
|
this.logger.info(`${player.name} joined the game!`);
|
||||||
}
|
}
|
||||||
|
|
||||||
setPlayerReady(user: string) {
|
setPlayerReady(userId: string) {
|
||||||
const player = this.players.find(player => player.name === user);
|
this.logger.debug(userId)
|
||||||
|
const player = this.players.find(player => player.id === userId);
|
||||||
if (!player) {
|
if (!player) {
|
||||||
throw new Error("Player not found");
|
throw new Error("Player not found");
|
||||||
}
|
}
|
||||||
@ -242,7 +248,7 @@ export class MatchSession {
|
|||||||
mode: this.mode,
|
mode: this.mode,
|
||||||
pointsToWin: this.pointsToWin,
|
pointsToWin: this.pointsToWin,
|
||||||
status: this.sessionInProgress ? 'in progress' : 'waiting',
|
status: this.sessionInProgress ? 'in progress' : 'waiting',
|
||||||
scoreboard: this.scoreboard,
|
scoreboard: [...this.scoreboard.entries()],
|
||||||
matchWinner: this.matchWinner?.getState() || null,
|
matchWinner: this.matchWinner?.getState() || null,
|
||||||
matchInProgress: this.matchInProgress
|
matchInProgress: this.matchInProgress
|
||||||
};
|
};
|
||||||
|
@ -30,7 +30,15 @@ export class NetworkClientNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async sendEvent(player: NetworkPlayer, event: string, data?: any) {
|
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) {
|
async broadcast(event: string, data: any) {
|
||||||
|
@ -6,7 +6,7 @@ import { Tile } from './entities/Tile';
|
|||||||
import { NetworkClientNotifier } from './NetworkClientNotifier';
|
import { NetworkClientNotifier } from './NetworkClientNotifier';
|
||||||
import { NetworkPlayer } from './entities/player/NetworkPlayer';
|
import { NetworkPlayer } from './entities/player/NetworkPlayer';
|
||||||
import { PlayerMoveSide, PlayerMoveSideType } from './constants';
|
import { PlayerMoveSide, PlayerMoveSideType } from './constants';
|
||||||
import { SocketDisconnectedError } from '../common/exceptions/SocketDisconnectedError';
|
import { SocketDisconnectedError } from '../common/errors/SocketDisconnectedError';
|
||||||
|
|
||||||
export class PlayerInteractionNetwork implements PlayerInteractionInterface {
|
export class PlayerInteractionNetwork implements PlayerInteractionInterface {
|
||||||
player: PlayerInterface;
|
player: PlayerInterface;
|
||||||
@ -19,7 +19,7 @@ export class PlayerInteractionNetwork implements PlayerInteractionInterface {
|
|||||||
async makeMove(board: Board): Promise<PlayerMove | null> {
|
async makeMove(board: Board): Promise<PlayerMove | null> {
|
||||||
let response = undefined;
|
let response = undefined;
|
||||||
try {
|
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(),
|
freeHands: board.getFreeEnds(),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -14,7 +14,7 @@ export interface MatchSessionState {
|
|||||||
maxPlayers: number;
|
maxPlayers: number;
|
||||||
numPlayers: number;
|
numPlayers: number;
|
||||||
waitingSeconds: number;
|
waitingSeconds: number;
|
||||||
scoreboard: Map<string, number>;
|
scoreboard: [string, number][];
|
||||||
matchWinner: PlayerDto | null;
|
matchWinner: PlayerDto | null;
|
||||||
matchInProgress: boolean;
|
matchInProgress: boolean;
|
||||||
playersReady: number
|
playersReady: number
|
||||||
|
@ -6,8 +6,6 @@ import { LoggingService } from "../../../common/LoggingService";
|
|||||||
import { EventEmitter } from "stream";
|
import { EventEmitter } from "stream";
|
||||||
import { PlayerInteractionInterface } from "../../PlayerInteractionInterface";
|
import { PlayerInteractionInterface } from "../../PlayerInteractionInterface";
|
||||||
import { uuid } from "../../../common/utilities";
|
import { uuid } from "../../../common/utilities";
|
||||||
import { GameState } from "../../dto/GameState";
|
|
||||||
import { MatchSessionState } from "../../dto/MatchSessionState";
|
|
||||||
import { PlayerDto } from "../../dto/PlayerDto";
|
import { PlayerDto } from "../../dto/PlayerDto";
|
||||||
|
|
||||||
export abstract class AbstractPlayer extends EventEmitter implements PlayerInterface {
|
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>;
|
abstract chooseTile(board: Board): Promise<Tile>;
|
||||||
|
|
||||||
|
|
||||||
async notifyGameState(state: GameState): Promise<void> {
|
async sendEventWithAck(event: string, data: any): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async notifyPlayerState(state: PlayerDto): Promise<void> {
|
async sendEvent(event: string, data: any = {}): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async notifyMatchState(state: MatchSessionState): Promise<void> {
|
reset(): void {
|
||||||
}
|
this.hand = [];
|
||||||
|
this.score = 0;
|
||||||
async waitForAction(actionId: string): Promise<boolean> {
|
this.ready = false;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendEvent(event: string): Promise<void> {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pipsCount(): number {
|
pipsCount(): number {
|
||||||
|
@ -4,55 +4,22 @@ import { PlayerHuman } from "./PlayerHuman";
|
|||||||
import { NetworkClientNotifier } from "../../NetworkClientNotifier";
|
import { NetworkClientNotifier } from "../../NetworkClientNotifier";
|
||||||
import { Tile } from "../Tile";
|
import { Tile } from "../Tile";
|
||||||
import { Board } from "../Board";
|
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 {
|
export class NetworkPlayer extends PlayerHuman {
|
||||||
socketId: string;
|
socketId!: string;
|
||||||
playerInteraction: PlayerInteractionInterface = new PlayerInteractionNetwork(this);
|
playerInteraction: PlayerInteractionInterface = new PlayerInteractionNetwork(this);
|
||||||
clientNotifier: NetworkClientNotifier = new NetworkClientNotifier();
|
clientNotifier: NetworkClientNotifier = new NetworkClientNotifier();
|
||||||
|
|
||||||
constructor(name: string, socketId: string) {
|
constructor(id: string, name: string, socketId: string ) {
|
||||||
super(name);
|
super(id, name);
|
||||||
this.socketId = socketId;
|
this.socketId = socketId;
|
||||||
}
|
}
|
||||||
|
|
||||||
async notifyGameState(state: GameState): Promise<void> {
|
async sendEvent(event: string, data:any = {}): Promise<void> {
|
||||||
const response = await this.clientNotifier.notifyPlayer(this, 'gameState', state);
|
this.clientNotifier.sendEvent(this, event, data);
|
||||||
console.log('game state notified :>> ', response);
|
|
||||||
if (response === undefined || response.status !== 'ok' ) {
|
|
||||||
throw new SocketDisconnectedError();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
async sendEventWithAck(event: string, data: any): Promise<any> {
|
||||||
async notifyPlayerState(state: PlayerDto): Promise<void> {
|
return await this.clientNotifier.sendEventWithAck(this, event, data);
|
||||||
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 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> {
|
async chooseTile(board: Board): Promise<Tile> {
|
||||||
|
@ -8,8 +8,9 @@ import { PlayerInteractionInterface } from '../../PlayerInteractionInterface';
|
|||||||
|
|
||||||
export class PlayerHuman extends AbstractPlayer {
|
export class PlayerHuman extends AbstractPlayer {
|
||||||
playerInteraction: PlayerInteractionInterface = new PlayerInteractionConsole(this);
|
playerInteraction: PlayerInteractionInterface = new PlayerInteractionConsole(this);
|
||||||
constructor(name: string) {
|
constructor(id: string, name: string) {
|
||||||
super(name);
|
super(name);
|
||||||
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
async makeMove(board: Board): Promise<PlayerMove | null> {
|
async makeMove(board: Board): Promise<PlayerMove | null> {
|
||||||
|
@ -18,10 +18,10 @@ export interface PlayerInterface {
|
|||||||
makeMove(gameState: Board): Promise<PlayerMove | null>;
|
makeMove(gameState: Board): Promise<PlayerMove | null>;
|
||||||
chooseTile(board: Board): Promise<Tile>;
|
chooseTile(board: Board): Promise<Tile>;
|
||||||
pipsCount(): number;
|
pipsCount(): number;
|
||||||
notifyGameState(state: GameState): Promise<void>;
|
reset(): void;
|
||||||
notifyPlayerState(state: PlayerDto): Promise<void>;
|
|
||||||
notifyMatchState(state: MatchSessionState): Promise<void>;
|
sendEvent(event: string, data: any): Promise<void>;
|
||||||
waitForAction(actionId: string, data: any): Promise<boolean>;
|
sendEventWithAck(event: string, data: any): Promise<any>;
|
||||||
sendEvent(event: string): Promise<void>;
|
|
||||||
getState(): PlayerDto;
|
getState(): PlayerDto;
|
||||||
}
|
}
|
39
src/server/controllers/GameController.ts
Normal file
39
src/server/controllers/GameController.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
import { BaseController } from "./BaseController";
|
||||||
|
import { SessionService } from "../services/SessionService";
|
||||||
|
|
||||||
|
export class GameController extends BaseController {
|
||||||
|
private sessionService: SessionService = new SessionService();
|
||||||
|
|
||||||
|
public async createMatch(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const { user, body } = req;
|
||||||
|
const { sessionName, seed } = body;
|
||||||
|
const sessionId = await this.sessionService.createSession(user, sessionName, seed);
|
||||||
|
res.status(201).json({ sessionId });
|
||||||
|
} catch (error) {
|
||||||
|
this.handleError(res, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public joinMatch(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const { user, body } = req;
|
||||||
|
const { sessionId } = body;
|
||||||
|
this.sessionService.joinSession(user, sessionId);
|
||||||
|
res.status(200).json({ status: 'ok' });
|
||||||
|
} catch (error) {
|
||||||
|
this.handleError(res, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public listMatches(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
this.sessionService.listSessions().then((sessions) => {
|
||||||
|
res.status(200).json(sessions);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.handleError(res, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,11 +9,11 @@ export abstract class BaseMongoManager {
|
|||||||
protected abstract collection?: string;
|
protected abstract collection?: string;
|
||||||
logger = new LoggingService().logger;
|
logger = new LoggingService().logger;
|
||||||
|
|
||||||
create(data: Entity) {
|
create(data: Entity): Promise<ObjectId | undefined>{
|
||||||
return mongoExecute(
|
return mongoExecute(
|
||||||
async ({ collection }) => {
|
async ({ collection }) => {
|
||||||
await collection?.insertOne(data as any);
|
const result = await collection?.insertOne(data as any);
|
||||||
return data;
|
return result?.insertedId;
|
||||||
},
|
},
|
||||||
{ colName: this.collection }
|
{ colName: this.collection }
|
||||||
);
|
);
|
||||||
|
@ -1,86 +1,38 @@
|
|||||||
import { MatchSession } from "../../game/MatchSession";
|
import { MatchSession } from "../../game/MatchSession";
|
||||||
import { NetworkPlayer } from "../../game/entities/player/NetworkPlayer";
|
|
||||||
import { SessionService } from "../services/SessionService";
|
|
||||||
|
|
||||||
import { ManagerBase } from "./ManagerBase";
|
import { ManagerBase } from "./ManagerBase";
|
||||||
|
|
||||||
export class SessionManager extends ManagerBase {
|
export class SessionManager extends ManagerBase {
|
||||||
private static sessions: any = {};
|
private static sessions: Map<string, MatchSession> = new Map();
|
||||||
private sessionService: SessionService = new SessionService();
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.logger.info('SessionController created');
|
this.logger.info('SessionController created');
|
||||||
}
|
}
|
||||||
|
|
||||||
createSession(data: any, socketId: string): any {
|
getSessionPlayer(sessionId: string, userId: string): any {
|
||||||
const { user, sessionName } = data;
|
const session: MatchSession | undefined = SessionManager.sessions.get(sessionId);
|
||||||
const player = new NetworkPlayer(user, socketId);
|
if (session !== undefined) {
|
||||||
const session = new MatchSession(player, sessionName);
|
return session.players.find(player => player.id === userId);
|
||||||
SessionManager.sessions[session.id] = session;
|
|
||||||
this.sessionService.createSession(session);
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: 'ok',
|
|
||||||
sessionId: session.id,
|
|
||||||
playerId: player.id
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
joinSession(data: any, socketId: string): any {
|
|
||||||
this.logger.debug('joinSession data :>> ')
|
|
||||||
this.logger.object(data);
|
|
||||||
const { user, sessionId } = data;
|
|
||||||
const session: MatchSession = SessionManager.sessions[sessionId];
|
|
||||||
const player = new NetworkPlayer(user, socketId);
|
|
||||||
session.addPlayer(player);
|
|
||||||
this.sessionService.updateSession(session);
|
|
||||||
return {
|
|
||||||
status: 'ok',
|
|
||||||
sessionId: session.id,
|
|
||||||
playerId: player.id
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
setPlayerReady(data: any): any {
|
|
||||||
const { user, sessionId } = data;
|
|
||||||
const session: MatchSession = SessionManager.sessions[sessionId];
|
|
||||||
session.setPlayerReady(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
startSession(data: any): any {
|
|
||||||
const sessionId: string = data.sessionId;
|
|
||||||
const seed: string | undefined = data.seed;
|
|
||||||
const session = SessionManager.sessions[sessionId];
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
return ({
|
|
||||||
status: 'error',
|
|
||||||
message: 'Session not found'
|
|
||||||
});
|
|
||||||
} else if (session.gameInProgress) {
|
|
||||||
return {
|
|
||||||
status: 'error',
|
|
||||||
message: 'Game already in progress'
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const missingHumans = session.maxPlayers - session.numPlayers;
|
|
||||||
for (let i = 0; i < missingHumans; i++) {
|
|
||||||
session.addPlayer(session.createPlayerAI(i));
|
|
||||||
}
|
|
||||||
session.start(seed);
|
|
||||||
return {
|
|
||||||
status: 'ok'
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateSocketId(sessionId: string, userId: string, socketId: string): any {
|
||||||
|
const player = this.getSessionPlayer(sessionId, userId);
|
||||||
|
if (player !== undefined) {
|
||||||
|
player.socketId = socketId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSession(session: MatchSession) {
|
||||||
|
SessionManager.sessions.set(session.id, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSession(session: MatchSession) {
|
||||||
|
SessionManager.sessions.delete(session.id);
|
||||||
|
}
|
||||||
|
|
||||||
getSession(id: string) {
|
getSession(id: string) {
|
||||||
return SessionManager.sessions[id];
|
return SessionManager.sessions.get(id);
|
||||||
}
|
|
||||||
|
|
||||||
deleteSession(id: string) {
|
|
||||||
delete SessionManager.sessions[id];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,6 +3,7 @@ import { AuthController } from '../controllers/AuthController';
|
|||||||
|
|
||||||
import adminRouter from './adminRouter';
|
import adminRouter from './adminRouter';
|
||||||
import userRouter from './userRouter';
|
import userRouter from './userRouter';
|
||||||
|
import gameRouter from './gameRouter';
|
||||||
|
|
||||||
export default function(): Router {
|
export default function(): Router {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
@ -15,8 +16,10 @@ export default function(): Router {
|
|||||||
router.post('/auth/code', (req: Request, res: Response) => authController.twoFactorCodeAuthentication(req, res));
|
router.post('/auth/code', (req: Request, res: Response) => authController.twoFactorCodeAuthentication(req, res));
|
||||||
router.post('/login', (req: Request, res: Response) => authController.login(req, res));
|
router.post('/login', (req: Request, res: Response) => authController.login(req, res));
|
||||||
|
|
||||||
router .use('/admin', adminRouter());
|
router.use('/admin', adminRouter());
|
||||||
router .use('/user', userRouter());
|
router.use('/user', userRouter());
|
||||||
|
router.use('/game', gameRouter());
|
||||||
|
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
17
src/server/router/gameRouter.ts
Normal file
17
src/server/router/gameRouter.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Request, Response, Router } from 'express';
|
||||||
|
import { AuthController } from '../controllers/AuthController';
|
||||||
|
import { GameController } from '../controllers/GameController';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default function(): Router {
|
||||||
|
const router = Router();
|
||||||
|
const gameController = new GameController();
|
||||||
|
const { authenticate } = AuthController
|
||||||
|
|
||||||
|
router.post('/match', authenticate({ roles: ['user']}), (req: Request, res: Response) => gameController.createMatch(req, res));
|
||||||
|
router.patch('/match/join', authenticate({ roles: ['user']}), (req: Request, res: Response) => gameController.joinMatch(req, res));
|
||||||
|
router.get('/match/list', authenticate({ roles: ['user']}), (req: Request, res: Response) => gameController.listMatches(req, res));
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
@ -1,36 +1,35 @@
|
|||||||
import { DominoesGame } from "./DominoesGame";
|
import { DominoesGame } from "../../game/DominoesGame";
|
||||||
import { MatchSession } from "./MatchSession";
|
import { MatchSession } from "../../game/MatchSession";
|
||||||
import { GameState } from "./dto/GameState";
|
import { GameState } from "../../game/dto/GameState";
|
||||||
import { PlayerInterface } from "./entities/player/PlayerInterface";
|
import { PlayerInterface } from "../../game/entities/player/PlayerInterface";
|
||||||
|
|
||||||
export class PlayerNotificationManager {
|
export class PlayerNotificationService {
|
||||||
|
|
||||||
async notifyGameState(game: DominoesGame) {
|
async notifyGameState(game: DominoesGame) {
|
||||||
const gameState: GameState = game.getGameState();
|
const gameState: GameState = game.getGameState();
|
||||||
const { players } = game;
|
const { players } = game;
|
||||||
let promises: Promise<void>[] = players.map(player => player.notifyGameState(gameState));
|
let promises: Promise<void>[] = players.map(player => player.sendEventWithAck('update-game-state', gameState));
|
||||||
return await Promise.all(promises);
|
return await Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
async notifyPlayersState(players: PlayerInterface[]) {
|
async notifyPlayersState(players: PlayerInterface[]) {
|
||||||
let promises: Promise<void>[] = players.map(player => player.notifyPlayerState(player.getState()));
|
let promises: Promise<void>[] = players.map(player => player.sendEventWithAck('update-player-state', player.getState()));
|
||||||
return await Promise.all(promises);
|
return await Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async notifyMatchState(session: MatchSession) {
|
async notifyMatchState(session: MatchSession) {
|
||||||
const { players } = session;
|
const { players } = session;
|
||||||
let promises: Promise<void>[] = players.map(player => player.notifyMatchState(session.getState()));
|
let promises: Promise<void>[] = players.map(player => player.sendEventWithAck('update-match-session-state', session.getState()));
|
||||||
return await Promise.all(promises);
|
return await Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForPlayersAction(actionId: string, data: any = {}, players: PlayerInterface[]) {
|
async sendEventToPlayers(event: string, players: PlayerInterface[], data: any = {}) {
|
||||||
let promises: Promise<boolean>[] = players.map(player => player.waitForAction(actionId, data));
|
let promises: Promise<void>[] = players.map(player => player.sendEvent(event, data));
|
||||||
return await Promise.all(promises);
|
return await Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendEventToPlayers(event: string, players: PlayerInterface[]) {
|
async sendEvent(event: string, player: PlayerInterface, data: any = {}) {
|
||||||
let promises: Promise<void>[] = players.map(player => player.sendEvent(event));
|
player.sendEvent(event, data)
|
||||||
return await Promise.all(promises);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,19 +1,107 @@
|
|||||||
|
import { SessionCreationError } from "../../common/errors/SessionCreationError";
|
||||||
|
import { SessionNotFoundError } from "../../common/errors/SessionNotFoundError";
|
||||||
|
import { wait, whileNotUndefined } from "../../common/utilities";
|
||||||
|
import { NetworkPlayer } from "../../game/entities/player/NetworkPlayer";
|
||||||
import { MatchSession } from "../../game/MatchSession";
|
import { MatchSession } from "../../game/MatchSession";
|
||||||
|
import { PlayerNotificationService } from "./PlayerNotificationService";
|
||||||
import { matchSessionAdapter } from "../db/DbAdapter";
|
import { matchSessionAdapter } from "../db/DbAdapter";
|
||||||
|
import { DbMatchSession } from "../db/interfaces";
|
||||||
import { MatchSessionMongoManager } from "../db/mongo/MatchSessionMongoManager";
|
import { MatchSessionMongoManager } from "../db/mongo/MatchSessionMongoManager";
|
||||||
|
import { SessionManager } from "../managers/SessionManager";
|
||||||
import { ServiceBase } from "./ServiceBase";
|
import { ServiceBase } from "./ServiceBase";
|
||||||
|
import { SocketIoService } from "./SocketIoService";
|
||||||
|
|
||||||
export class SessionService extends ServiceBase{
|
export class SessionService extends ServiceBase{
|
||||||
private dbManager: MatchSessionMongoManager = new MatchSessionMongoManager();
|
private dbManager: MatchSessionMongoManager = new MatchSessionMongoManager();
|
||||||
|
private sessionManager: SessionManager = new SessionManager();
|
||||||
|
private notifyService = new PlayerNotificationService();
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
public createSession(session: MatchSession): any {
|
public async createSession(user: any, sessionName: string, seed: string ): Promise<string> {
|
||||||
this.dbManager.create(matchSessionAdapter(session));
|
let socketClient;
|
||||||
|
try {
|
||||||
|
socketClient = await whileNotUndefined(() => SocketIoService.getClient(user._id));
|
||||||
|
} catch (error) {
|
||||||
|
throw new SessionCreationError();
|
||||||
|
}
|
||||||
|
const player = new NetworkPlayer(user._id, user.username, socketClient.socketId);
|
||||||
|
const session = new MatchSession(player, sessionName, seed);
|
||||||
|
const dbSessionId = await this.dbManager.create(matchSessionAdapter(session));
|
||||||
|
if (dbSessionId === undefined) {
|
||||||
|
throw new SessionCreationError();
|
||||||
|
}
|
||||||
|
session.id = dbSessionId.toString();
|
||||||
|
socketClient.sessionId = session.id;
|
||||||
|
this.sessionManager.setSession(session);
|
||||||
|
this.notifyService.notifyMatchState(session);
|
||||||
|
this.notifyService.notifyPlayersState(session.players);
|
||||||
|
return session.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateSession(session: MatchSession): any {
|
public async joinSession(user: any, sessionId: string): Promise<void> {
|
||||||
|
const session: MatchSession | undefined = this.sessionManager.getSession(sessionId);
|
||||||
|
if (session === undefined) {
|
||||||
|
throw new SessionNotFoundError();
|
||||||
|
} let socketClient;
|
||||||
|
try {
|
||||||
|
socketClient = await whileNotUndefined(() => SocketIoService.getClient(user._id));
|
||||||
|
} catch (error) {
|
||||||
|
throw new SessionCreationError();
|
||||||
|
}
|
||||||
|
const player = new NetworkPlayer(user._id, user.name, socketClient.socketId);
|
||||||
|
session.addPlayer(player);
|
||||||
|
socketClient.sessionId = session.id;
|
||||||
this.dbManager.replaceOne({id: session.id}, matchSessionAdapter(session));
|
this.dbManager.replaceOne({id: session.id}, matchSessionAdapter(session));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public listSessions(): Promise<DbMatchSession[]> {
|
||||||
|
return this.dbManager.listByFilter({});
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateSocketId(sessionId: string, userId: string, socketId: string): any {
|
||||||
|
this.sessionManager.updateSocketId(sessionId, userId, socketId);
|
||||||
|
}
|
||||||
|
|
||||||
|
setPlayerReady(data: any): any {
|
||||||
|
const { userId, sessionId } = data;
|
||||||
|
const session: MatchSession | undefined = this.sessionManager.getSession(sessionId);
|
||||||
|
if (session !== undefined) {
|
||||||
|
session.setPlayerReady(userId)
|
||||||
|
this.notifyService.notifyMatchState(session);
|
||||||
|
this.notifyService.notifyPlayersState(session.players);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startSession(data: any): any {
|
||||||
|
const sessionId: string = data.sessionId;
|
||||||
|
const seed: string | undefined = data.seed;
|
||||||
|
const session: MatchSession | undefined = this.sessionManager.getSession(sessionId);
|
||||||
|
|
||||||
|
if (session === undefined) {
|
||||||
|
return ({
|
||||||
|
status: 'error',
|
||||||
|
message: 'Session not found'
|
||||||
|
});
|
||||||
|
} else if (session.matchInProgress) {
|
||||||
|
return {
|
||||||
|
status: 'error',
|
||||||
|
message: 'Game already in progress'
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const missingHumans = session.maxPlayers - session.numPlayers;
|
||||||
|
for (let i = 0; i < missingHumans; i++) {
|
||||||
|
session.addPlayer(session.createPlayerAI(i));
|
||||||
|
}
|
||||||
|
session.start();
|
||||||
|
return {
|
||||||
|
status: 'ok'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// public updateSession(session: MatchSession): any {
|
||||||
|
// this.dbManager.replaceOne({id: session.id}, matchSessionAdapter(session));
|
||||||
|
// }
|
||||||
}
|
}
|
@ -2,14 +2,39 @@ import { Server as HttpServer } from "http";
|
|||||||
import { ServiceBase } from "./ServiceBase";
|
import { ServiceBase } from "./ServiceBase";
|
||||||
import { Server } from "socket.io";
|
import { Server } from "socket.io";
|
||||||
import { SessionManager } from "../managers/SessionManager";
|
import { SessionManager } from "../managers/SessionManager";
|
||||||
|
import { SecurityManager } from "../managers/SecurityManager";
|
||||||
|
import { User } from "../db/interfaces";
|
||||||
|
import { Socket } from "socket.io";
|
||||||
|
import { SessionService } from "./SessionService";
|
||||||
|
|
||||||
export class SocketIoService extends ServiceBase{
|
export class SocketIoService extends ServiceBase{
|
||||||
io: Server
|
io: Server
|
||||||
clients: Map<string, any> = new Map();
|
private static clients: Map<string, any> = new Map();
|
||||||
|
private sessionService: SessionService = new SessionService();
|
||||||
|
|
||||||
|
static getClient(id: string) {
|
||||||
|
return this.clients.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
security = new SecurityManager();
|
||||||
constructor(private httpServer: HttpServer) {
|
constructor(private httpServer: HttpServer) {
|
||||||
super()
|
super()
|
||||||
this.io = this.socketIo(httpServer);
|
this.io = this.socketIo(httpServer);
|
||||||
|
this.io.use(async (socket: Socket, next: any) => {
|
||||||
|
if (socket.handshake.auth && socket.handshake.auth.token) {
|
||||||
|
const token = socket.handshake.auth.token;
|
||||||
|
try {
|
||||||
|
const user: User = await this.security.verifyJwt(token);
|
||||||
|
socket.user = user;
|
||||||
|
next();
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error(err);
|
||||||
|
next(new Error('Authentication error'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next(new Error('Authentication error'));
|
||||||
|
}
|
||||||
|
});
|
||||||
this.initListeners();
|
this.initListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,8 +42,12 @@ export class SocketIoService extends ServiceBase{
|
|||||||
return this.io;
|
return this.io;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUserId(socket: Socket) {
|
||||||
|
const { user } = socket;
|
||||||
|
return user?._id?.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private initListeners() {
|
private initListeners() {
|
||||||
const sessionController = new SessionManager();
|
|
||||||
this.io.on('connection', (socket) => {
|
this.io.on('connection', (socket) => {
|
||||||
this.logger.debug(`connect ${socket.id}`);
|
this.logger.debug(`connect ${socket.id}`);
|
||||||
if (socket.recovered) {
|
if (socket.recovered) {
|
||||||
@ -28,59 +57,79 @@ export class SocketIoService extends ServiceBase{
|
|||||||
this.logger.debug("socket.data:", socket.data);
|
this.logger.debug("socket.data:", socket.data);
|
||||||
} else {
|
} else {
|
||||||
this.logger.debug("new connection");
|
this.logger.debug("new connection");
|
||||||
this.clients.set(socket.id, { alive: true });
|
const { id: socketId, user } = socket;
|
||||||
socket.join('room-general')
|
if (user !== undefined && user._id !== undefined) {
|
||||||
socket.data.foo = "bar";
|
const userId = user._id.toString();
|
||||||
|
if (!SocketIoService.clients.has(userId)) {
|
||||||
|
SocketIoService.clients.set(userId, { socketId, alive: true, user: socket.user });
|
||||||
|
socket.join('room-general')
|
||||||
|
} else {
|
||||||
|
const client = SocketIoService.clients.get(userId);
|
||||||
|
this.sessionService.updateSocketId(client.sessionId, userId, socketId);
|
||||||
|
client.socketId = socketId;
|
||||||
|
this.logger.debug(`User '${user.username}' already connected. Updating socketId to ${socketId}`);
|
||||||
|
client.alive = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.logger.error('User not found');
|
||||||
|
socket.disconnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
const id = this.getUserId(socket);
|
||||||
|
if (id) {
|
||||||
|
this.logger.info('user disconnected');
|
||||||
|
SocketIoService.clients.delete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// socket.on('createSession', (data, callback) => {
|
||||||
|
// const response = sessionController.createSession(data, socket.id);
|
||||||
|
// callback(response);
|
||||||
|
// });
|
||||||
|
|
||||||
|
socket.on('startSession', (data, callback) => {
|
||||||
|
const response = this.sessionService.startSession(data);
|
||||||
|
callback(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
// socket.on('joinSession', (data, callback) => {
|
||||||
|
// const response = sessionController.joinSession(data, socket.id);
|
||||||
|
// callback(response);
|
||||||
|
// });
|
||||||
|
|
||||||
|
socket.on('playerReady', (data, callback) => {
|
||||||
|
const response = this.sessionService.setPlayerReady(data);
|
||||||
|
callback(response);
|
||||||
|
});
|
||||||
|
|
||||||
socket.on('pong', () => {
|
socket.on('pong', () => {
|
||||||
if (this.clients.has(socket.id)) {
|
const id = this.getUserId(socket);
|
||||||
this.clients.set(socket.id, { alive: true });
|
if (id && SocketIoService.clients.has(id)) {
|
||||||
|
const client = SocketIoService.clients.get(id);
|
||||||
|
SocketIoService.clients.set(id, {...client, alive: true });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
|
||||||
this.logger.debug('user disconnected');
|
|
||||||
this.clients.delete(socket.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('createSession', (data, callback) => {
|
|
||||||
const response = sessionController.createSession(data, socket.id);
|
|
||||||
callback(response);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('startSession', (data, callback) => {
|
|
||||||
const response = sessionController.startSession(data);
|
|
||||||
callback(response);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('joinSession', (data, callback) => {
|
|
||||||
const response = sessionController.joinSession(data, socket.id);
|
|
||||||
callback(response);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('playerReady', (data, callback) => {
|
|
||||||
const response = sessionController.setPlayerReady(data);
|
|
||||||
callback(response);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.pingClients()
|
this.pingClients()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private pingClients() {
|
private pingClients() {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
for (let [id, client] of this.clients.entries()) {
|
for (let [id, client] of SocketIoService.clients.entries()) {
|
||||||
if (!client.alive) {
|
if (!client.alive) {
|
||||||
this.logger.debug(`Client ${id} did not respond. Disconnecting.`);
|
this.logger.debug(`Client ${id} did not respond. Disconnecting.`);
|
||||||
this.io.to(id).disconnectSockets(true); // Disconnect client
|
this.io.to(id).disconnectSockets(true); // Disconnect client
|
||||||
this.clients.delete(id);
|
SocketIoService.clients.delete(id);
|
||||||
} else {
|
} else {
|
||||||
client.alive = false; // Reset alive status for the next ping
|
client.alive = false; // Reset alive status for the next ping
|
||||||
this.io.to(id).emit('ping'); // Send ping message
|
this.io.to(client.socketId).emit('ping'); // Send ping message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 30000);
|
}, 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private socketIo(httpServer: HttpServer): Server {
|
private socketIo(httpServer: HttpServer): Server {
|
||||||
|
8
src/server/types/socket/index.d.ts
vendored
Normal file
8
src/server/types/socket/index.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Socket } from 'socket.io';
|
||||||
|
import { User } from '../../db/interfaces';
|
||||||
|
|
||||||
|
declare module 'socket.io' {
|
||||||
|
interface Socket {
|
||||||
|
user?: User;
|
||||||
|
}
|
||||||
|
}
|
@ -31,7 +31,7 @@
|
|||||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
"typeRoots": ["./src/server/types"], /* Specify multiple folders that act like './node_modules/@types'. */
|
"typeRoots": ["./src/server/types"], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user