mirror of
https://github.com/AlecM33/Werewolf.git
synced 2025-12-26 15:57:50 +01:00
further incomplete redis draft
This commit is contained in:
8
index.js
8
index.js
@@ -6,6 +6,9 @@ const main = async () => {
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
const ServerBootstrapper = require('./server/modules/ServerBootstrapper');
|
||||
const ActiveGameRunner = require('./server/modules/singletons/ActiveGameRunner');
|
||||
const GameManager = require('./server/modules/singletons/GameManager');
|
||||
const SocketManager = require('./server/modules/singletons/SocketManager');
|
||||
const globals = require('./server/config/globals');
|
||||
|
||||
app.use(express.json({limit: '10kb'}));
|
||||
@@ -25,10 +28,13 @@ const main = async () => {
|
||||
}
|
||||
return id;
|
||||
})());
|
||||
singletons.gameManager.activeGameRunner = ActiveGameRunner.instance;
|
||||
singletons.gameManager.socketManager = SocketManager.instance;
|
||||
singletons.socketManager.activeGameRunner = ActiveGameRunner.instance;
|
||||
singletons.socketManager.gameManager = GameManager.instance;
|
||||
|
||||
await singletons.activeGameRunner.client.connect();
|
||||
console.log('Root Redis client connected');
|
||||
await singletons.activeGameRunner.refreshActiveGames();
|
||||
await singletons.activeGameRunner.createGameSyncSubscriber(singletons.gameManager, singletons.socketManager);
|
||||
await singletons.socketManager.createRedisPublisher();
|
||||
await singletons.gameManager.createRedisPublisher();
|
||||
|
||||
@@ -20,11 +20,11 @@ router.post('/sockets/broadcast', function (req, res) {
|
||||
res.status(201).send('Broadcasted message to all connected sockets: ' + req.body?.message);
|
||||
});
|
||||
|
||||
router.get('/games/state', function (req, res) {
|
||||
router.get('/games/state', async (req, res) => {
|
||||
const gamesArray = [];
|
||||
for (const key of gameManager.activeGameRunner.activeGames.keys()) {
|
||||
gamesArray.push(gameManager.activeGameRunner.activeGames.get(key));
|
||||
}
|
||||
await this.client.hGetAll('activeGames').then(async (r) => {
|
||||
Object.values(r).forEach((v) => gamesArray.push(v));
|
||||
});
|
||||
res.status(200).send(gamesArray);
|
||||
});
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ router.get('/:code/availability', function (req, res) {
|
||||
});
|
||||
});
|
||||
|
||||
router.patch('/:code/players', function (req, res) {
|
||||
router.patch('/:code/players', async function (req, res) {
|
||||
if (
|
||||
req.body === null
|
||||
|| !validateAccessCode(req.body.accessCode)
|
||||
@@ -77,7 +77,7 @@ router.patch('/:code/players', function (req, res) {
|
||||
) {
|
||||
res.status(400).send();
|
||||
} else {
|
||||
const game = gameManager.activeGameRunner.activeGames.get(req.body.accessCode);
|
||||
const game = await gameManager.activeGameRunner.getActiveGame(req.body.accessCode);
|
||||
if (game) {
|
||||
const inUseCookie = gameManager.environment === globals.ENVIRONMENT.PRODUCTION ? req.body.localCookie : req.body.sessionCookie;
|
||||
gameManager.joinGame(game, req.body.playerName, inUseCookie, req.body.joinAsSpectator).then((data) => {
|
||||
@@ -101,7 +101,7 @@ router.patch('/:code/restart', function (req, res) {
|
||||
) {
|
||||
res.status(400).send();
|
||||
} else {
|
||||
const game = gameManager.activeGameRunner.activeGames.get(req.body.accessCode);
|
||||
const game = gameManager.activeGameRunner.getActiveGame(req.body.accessCode);
|
||||
if (game) {
|
||||
gameManager.restartGame(game, gameManager.namespace).then((data) => {
|
||||
res.status(200).send();
|
||||
|
||||
@@ -48,7 +48,27 @@ const globals = {
|
||||
END_GAME: 'endGame',
|
||||
RESTART_GAME: 'restartGame',
|
||||
PLAYER_JOINED: 'playerJoined',
|
||||
SPECTATOR_JOINED: 'spectatorJoined'
|
||||
UPDATE_SPECTATORS: 'updateSpectators',
|
||||
SYNC_GAME_STATE: 'syncGameState',
|
||||
UPDATE_SOCKET: 'updateSocket'
|
||||
},
|
||||
SYNCABLE_EVENTS: function () {
|
||||
return [
|
||||
this.EVENT_IDS.NEW_GAME,
|
||||
this.EVENT_IDS.START_GAME,
|
||||
this.EVENT_IDS.PAUSE_TIMER,
|
||||
this.EVENT_IDS.RESUME_TIMER,
|
||||
this.EVENT_IDS.GET_TIME_REMAINING,
|
||||
this.EVENT_IDS.KILL_PLAYER,
|
||||
this.EVENT_IDS.REVEAL_PLAYER,
|
||||
this.EVENT_IDS.TRANSFER_MODERATOR,
|
||||
this.EVENT_IDS.END_GAME,
|
||||
this.EVENT_IDS.RESTART_GAME,
|
||||
this.EVENT_IDS.PLAYER_JOINED,
|
||||
this.EVENT_IDS.UPDATE_SPECTATORS,
|
||||
this.EVENT_IDS.SYNC_GAME_STATE,
|
||||
this.EVENT_IDS.UPDATE_SOCKET
|
||||
]
|
||||
},
|
||||
MESSAGES: {
|
||||
ENTER_NAME: 'Client must enter name.'
|
||||
|
||||
68
server/modules/Events.js
Normal file
68
server/modules/Events.js
Normal file
@@ -0,0 +1,68 @@
|
||||
const globals = require('../config/globals');
|
||||
const GameStateCurator = require("./GameStateCurator");
|
||||
const EVENT_IDS = globals.EVENT_IDS;
|
||||
|
||||
const Events = [
|
||||
{
|
||||
id: EVENT_IDS.PLAYER_JOINED,
|
||||
stateChange: (game, args, gameManager) => {
|
||||
let toBeAssignedIndex = game.people.findIndex(
|
||||
(person) => person.id === args.id && person.assigned === false
|
||||
);
|
||||
if (toBeAssignedIndex >= 0) {
|
||||
game.people[toBeAssignedIndex] = args;
|
||||
game.isFull = gameManager.isGameFull(game);
|
||||
}
|
||||
},
|
||||
communicate: (game, args, gameManager) => {
|
||||
gameManager.namespace.in(game.accessCode).emit(
|
||||
globals.EVENTS.PLAYER_JOINED,
|
||||
GameStateCurator.mapPerson(args),
|
||||
game.isFull
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: EVENT_IDS.UPDATE_SPECTATORS,
|
||||
stateChange: (game, args, gameManager) => {
|
||||
game.spectators = args;
|
||||
},
|
||||
communicate: (game, args, gameManager) => {
|
||||
gameManager.namespace.in(game.accessCode).emit(
|
||||
globals.EVENTS.UPDATE_SPECTATORS,
|
||||
game.spectators.map((spectator) => { return GameStateCurator.mapPerson(spectator); })
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: EVENT_IDS.FETCH_GAME_STATE,
|
||||
stateChange: (game, args, gameManager) => {
|
||||
const matchingPerson = gameManager.findPersonByField(game, 'cookie', args.personId);
|
||||
if (matchingPerson) {
|
||||
if (matchingPerson.socketId === socketId) {
|
||||
logger.debug('matching person found with an established connection to the room: ' + matchingPerson.name);
|
||||
if (ackFn) {
|
||||
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, matchingPerson));
|
||||
}
|
||||
} else {
|
||||
logger.debug('matching person found with a new connection to the room: ' + matchingPerson.name);
|
||||
this.namespace.sockets.get(socketId).join(accessCode);
|
||||
matchingPerson.socketId = socketId;
|
||||
await this.publisher.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
game.accessCode + ';' + globals.EVENT_IDS.UPDATE_SOCKET + ';' + JSON.stringify({ personId: matchingPerson.id, socketId: socketId }) + ';' + this.instanceId
|
||||
);
|
||||
if (ackFn) {
|
||||
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, matchingPerson));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (ackFn) {
|
||||
rejectClientRequestForGameState(ackFn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = Events;
|
||||
@@ -15,10 +15,10 @@ const ServerBootstrapper = {
|
||||
singletons: (logger, instanceId) => {
|
||||
return {
|
||||
activeGameRunner: new ActiveGameRunner(logger, instanceId),
|
||||
socketManager: new SocketManager(logger, ActiveGameRunner.instance, instanceId),
|
||||
socketManager: new SocketManager(logger, instanceId),
|
||||
gameManager: process.env.NODE_ENV.trim() === 'development'
|
||||
? new GameManager(logger, ENVIRONMENT.LOCAL, ActiveGameRunner.instance, instanceId)
|
||||
: new GameManager(logger, ENVIRONMENT.PRODUCTION, ActiveGameRunner.instance, instanceId)
|
||||
? new GameManager(logger, ENVIRONMENT.LOCAL, instanceId)
|
||||
: new GameManager(logger, ENVIRONMENT.PRODUCTION, instanceId)
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -18,12 +18,9 @@ class ActiveGameRunner {
|
||||
ActiveGameRunner.instance = this;
|
||||
}
|
||||
|
||||
refreshActiveGames = async () => {
|
||||
await this.client.hGetAll('activeGames').then(async (r) => {
|
||||
this.activeGames = new Map(Object.entries(r).map(([k, v]) => {
|
||||
return [k, JSON.parse(v)];
|
||||
}));
|
||||
});
|
||||
getActiveGame = async (accessCode) => {
|
||||
const r = await this.client.hGet('activeGames', accessCode);
|
||||
return JSON.parse(r);
|
||||
}
|
||||
|
||||
createGameSyncSubscriber = async (gameManager, socketManager) => {
|
||||
@@ -36,13 +33,21 @@ class ActiveGameRunner {
|
||||
this.logger.trace('Disregarding self-authored message');
|
||||
return;
|
||||
}
|
||||
const game = this.activeGames.get(messageComponents[0]);
|
||||
const game = await this.getActiveGame(messageComponents[0]);
|
||||
let args;
|
||||
if (messageComponents[2]) {
|
||||
args = JSON.parse(messageComponents[2]);
|
||||
}
|
||||
if (game || messageComponents[1] === globals.EVENT_IDS.NEW_GAME) {
|
||||
await socketManager.handleEventById(messageComponents[1], game, null, gameManager, game?.accessCode || messageComponents[0], args ? args : null, null)
|
||||
if (game) {
|
||||
await socketManager.handleEventById(
|
||||
messageComponents[1],
|
||||
game,
|
||||
null,
|
||||
game?.accessCode || messageComponents[0],
|
||||
args ? args : null,
|
||||
null,
|
||||
true
|
||||
)
|
||||
}
|
||||
});
|
||||
this.logger.info('ACTIVE GAME RUNNER - CREATED GAME SYNC SUBSCRIBER');
|
||||
|
||||
@@ -8,14 +8,15 @@ const redis = require('redis');
|
||||
|
||||
|
||||
class GameManager {
|
||||
constructor (logger, environment, activeGameRunner, instanceId) {
|
||||
constructor (logger, environment, instanceId) {
|
||||
if (GameManager.instance) {
|
||||
throw new Error('The server tried to instantiate more than one GameManager');
|
||||
}
|
||||
logger.info('CREATING SINGLETON GAME MANAGER');
|
||||
this.logger = logger;
|
||||
this.environment = environment;
|
||||
this.activeGameRunner = activeGameRunner;
|
||||
this.activeGameRunner = null;
|
||||
this.socketManager = null;
|
||||
this.namespace = null;
|
||||
this.publisher = null;
|
||||
this.instanceId = instanceId;
|
||||
@@ -47,7 +48,7 @@ class GameManager {
|
||||
gameParams.moderatorName,
|
||||
gameParams.hasDedicatedModerator
|
||||
);
|
||||
await this.pruneStaleGames();
|
||||
//await this.pruneStaleGames();
|
||||
const newAccessCode = await this.generateAccessCode(globals.ACCESS_CODE_CHAR_POOL);
|
||||
if (newAccessCode === null) {
|
||||
return Promise.reject(globals.ERROR_MESSAGE.NO_UNIQUE_ACCESS_CODE);
|
||||
@@ -69,15 +70,7 @@ class GameManager {
|
||||
new Date().toJSON(),
|
||||
req.timerParams
|
||||
);
|
||||
|
||||
this.activeGameRunner.activeGames.set(newAccessCode, newGame);
|
||||
await this.activeGameRunner.client.hSet('activeGames', newAccessCode, JSON.stringify(newGame));
|
||||
|
||||
this.publisher?.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
newGame.accessCode + ';' + globals.EVENT_IDS.NEW_GAME + ';' + JSON.stringify({ newGame: newGame }) + ';' + this.instanceId
|
||||
);
|
||||
|
||||
return Promise.resolve({ accessCode: newAccessCode, cookie: moderator.cookie, environment: this.environment });
|
||||
}).catch((message) => {
|
||||
console.log(message);
|
||||
@@ -93,7 +86,6 @@ class GameManager {
|
||||
game.timerParams.paused = true;
|
||||
this.activeGameRunner.runGame(game, namespace);
|
||||
}
|
||||
await this.refreshGame(game);
|
||||
namespace.in(game.accessCode).emit(globals.EVENT_IDS.START_GAME);
|
||||
}
|
||||
};
|
||||
@@ -111,7 +103,6 @@ class GameManager {
|
||||
};
|
||||
|
||||
resumeTimer = async (game, logger) => {
|
||||
await this.activeGameRunner.refreshActiveGames();
|
||||
const thread = this.activeGameRunner.timerThreads[game.accessCode];
|
||||
if (thread && !thread.killed) {
|
||||
this.logger.debug('Timer thread found for game ' + game.accessCode);
|
||||
@@ -125,7 +116,6 @@ class GameManager {
|
||||
|
||||
getTimeRemaining = async (game, socketId) => {
|
||||
if (socketId) {
|
||||
await this.activeGameRunner.refreshActiveGames();
|
||||
const thread = this.activeGameRunner.timerThreads[game.accessCode];
|
||||
if (thread && (!thread.killed && thread.exitCode === null)) {
|
||||
thread.send({
|
||||
@@ -147,7 +137,6 @@ class GameManager {
|
||||
if (person && !person.revealed) {
|
||||
this.logger.debug('game ' + game.accessCode + ': revealing player ' + person.name);
|
||||
person.revealed = true;
|
||||
await this.refreshGame(game);
|
||||
this.namespace.in(game.accessCode).emit(
|
||||
globals.EVENT_IDS.REVEAL_PLAYER,
|
||||
{
|
||||
@@ -168,12 +157,11 @@ class GameManager {
|
||||
for (const person of game.people) {
|
||||
person.revealed = true;
|
||||
}
|
||||
await this.refreshGame(game);
|
||||
this.namespace.in(game.accessCode).emit(globals.EVENT_IDS.END_GAME, GameStateCurator.mapPeopleForModerator(game.people));
|
||||
};
|
||||
|
||||
checkAvailability = async (code) => {
|
||||
const game = this.activeGameRunner.activeGames.get(code.toUpperCase().trim());
|
||||
const game = await this.activeGameRunner.getActiveGame(code.toUpperCase().trim());
|
||||
if (game) {
|
||||
return Promise.resolve({ accessCode: code, playerCount: getGameSize(game.deck), timerParams: game.timerParams });
|
||||
} else {
|
||||
@@ -185,7 +173,8 @@ class GameManager {
|
||||
const charCount = charPool.length;
|
||||
let codeDigits, accessCode;
|
||||
let attempts = 0;
|
||||
while (!accessCode || (this.activeGameRunner.activeGames.get(accessCode) && attempts < globals.ACCESS_CODE_GENERATION_ATTEMPTS)) {
|
||||
while (!accessCode || ((await this.activeGameRunner.client.hKeys('activeGames')).includes(accessCode)
|
||||
&& attempts < globals.ACCESS_CODE_GENERATION_ATTEMPTS)) {
|
||||
codeDigits = [];
|
||||
let iterations = globals.ACCESS_CODE_LENGTH;
|
||||
while (iterations > 0) {
|
||||
@@ -195,7 +184,7 @@ class GameManager {
|
||||
accessCode = codeDigits.join('');
|
||||
attempts ++;
|
||||
}
|
||||
return this.activeGameRunner.activeGames.get(accessCode)
|
||||
return (await this.activeGameRunner.client.hKeys('activeGames')).includes(accessCode)
|
||||
? null
|
||||
: accessCode;
|
||||
};
|
||||
@@ -210,9 +199,9 @@ class GameManager {
|
||||
logger.debug('game ' + game.accessCode + ': transferring mod powers to ' + person.name);
|
||||
if (game.moderator === person) {
|
||||
person.userType = globals.USER_TYPES.MODERATOR;
|
||||
this.namespace.to(person.socketId).emit(globals.EVENTS.SYNC_GAME_STATE);
|
||||
if (socketId) {
|
||||
this.namespace.sockets.get(socketId).to(game.accessCode).emit(globals.EVENT_IDS.KILL_PLAYER, person.id);
|
||||
const socket = this.namespace.sockets.get(socketId);
|
||||
if (socket) {
|
||||
this.namespace.to(socketId).emit(globals.EVENTS.SYNC_GAME_STATE); // they are guaranteed to be connected to this instance.
|
||||
}
|
||||
} else {
|
||||
const oldModerator = game.moderator;
|
||||
@@ -233,10 +222,9 @@ class GameManager {
|
||||
game.spectators.map((spectator) => GameStateCurator.mapPerson(spectator))
|
||||
);
|
||||
}
|
||||
this.namespace.to(person.socketId).emit(globals.EVENTS.SYNC_GAME_STATE);
|
||||
this.namespace.to(oldModerator.socketId).emit(globals.EVENTS.SYNC_GAME_STATE);
|
||||
await notifyPlayerInvolvedInModTransfer(game, this.namespace, person);
|
||||
await notifyPlayerInvolvedInModTransfer(game, this.namespace, oldModerator);
|
||||
}
|
||||
await this.refreshGame(game);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -247,18 +235,27 @@ class GameManager {
|
||||
person.userType = globals.USER_TYPES.KILLED_PLAYER;
|
||||
}
|
||||
person.out = true;
|
||||
const socket = namespace.sockets.get(socketId);
|
||||
if (socket && game.moderator.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
|
||||
socket.to(game.accessCode).emit(globals.EVENT_IDS.KILL_PLAYER, person.id);
|
||||
} else {
|
||||
namespace.in(game.accessCode).emit(globals.EVENT_IDS.KILL_PLAYER, person.id);
|
||||
}
|
||||
// temporary moderators will transfer their powers automatically to the first person they kill.
|
||||
if (game.moderator.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
|
||||
await this.transferModeratorPowers(socketId, game, person, namespace, logger);
|
||||
} else {
|
||||
await this.refreshGame(game);
|
||||
namespace.in(game.accessCode).emit(globals.EVENT_IDS.KILL_PLAYER, person.id);
|
||||
await this.socketManager.handleAndSyncEvent(
|
||||
globals.EVENT_IDS.TRANSFER_MODERATOR,
|
||||
game,
|
||||
socket,
|
||||
{ personId: person.id },
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
joinGame = async (game, name, cookie, joinAsSpectator) => {
|
||||
const matchingPerson = findPersonByField(game, 'cookie', cookie);
|
||||
const matchingPerson = this.findPersonByField(game, 'cookie', cookie);
|
||||
if (matchingPerson) {
|
||||
return Promise.resolve(matchingPerson.cookie);
|
||||
}
|
||||
@@ -279,15 +276,15 @@ class GameManager {
|
||||
unassignedPerson.name = name;
|
||||
game.isFull = this.isGameFull(game);
|
||||
await this.refreshGame(game);
|
||||
await this.publisher?.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
game.accessCode + ';' + globals.EVENT_IDS.PLAYER_JOINED + ';' + JSON.stringify(unassignedPerson) + ';' + this.instanceId
|
||||
);
|
||||
this.namespace.in(game.accessCode).emit(
|
||||
globals.EVENTS.PLAYER_JOINED,
|
||||
GameStateCurator.mapPerson(unassignedPerson),
|
||||
game.isFull
|
||||
);
|
||||
await this.publisher?.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
game.accessCode + ';' + globals.EVENT_IDS.PLAYER_JOINED + ';' + JSON.stringify(unassignedPerson) + ';' + this.instanceId
|
||||
);
|
||||
return Promise.resolve(unassignedPerson.cookie);
|
||||
} else {
|
||||
if (game.spectators.length === globals.MAX_SPECTATORS) {
|
||||
@@ -352,7 +349,7 @@ class GameManager {
|
||||
};
|
||||
|
||||
handleRequestForGameState = async (game, namespace, logger, gameRunner, accessCode, personCookie, ackFn, socketId) => {
|
||||
const matchingPerson = findPersonByField(game, 'cookie', personCookie);
|
||||
const matchingPerson = this.findPersonByField(game, 'cookie', personCookie);
|
||||
if (matchingPerson) {
|
||||
if (matchingPerson.socketId === socketId) {
|
||||
logger.debug('matching person found with an established connection to the room: ' + matchingPerson.name);
|
||||
@@ -363,6 +360,10 @@ class GameManager {
|
||||
logger.debug('matching person found with a new connection to the room: ' + matchingPerson.name);
|
||||
this.namespace.sockets.get(socketId).join(accessCode);
|
||||
matchingPerson.socketId = socketId;
|
||||
await this.publisher.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
game.accessCode + ';' + globals.EVENT_IDS.UPDATE_SOCKET + ';' + JSON.stringify({ personId: matchingPerson.id, socketId: socketId }) + ';' + this.instanceId
|
||||
);
|
||||
if (ackFn) {
|
||||
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, matchingPerson));
|
||||
}
|
||||
@@ -391,27 +392,40 @@ class GameManager {
|
||||
return array;
|
||||
};
|
||||
|
||||
pruneStaleGames = async () => {
|
||||
await this.activeGameRunner.refreshActiveGames();
|
||||
this.activeGameRunner.activeGames.forEach((key, value) => {
|
||||
if (value.createTime) {
|
||||
const createDate = new Date(value.createTime);
|
||||
if (createDate.setHours(createDate.getHours() + globals.STALE_GAME_HOURS) < Date.now()) {
|
||||
this.logger.info('PRUNING STALE GAME ' + key);
|
||||
this.activeGameRunner.activeGames.delete(key);
|
||||
if (this.activeGameRunner.timerThreads[key]) {
|
||||
this.logger.info('KILLING STALE TIMER PROCESS FOR ' + key);
|
||||
this.activeGameRunner.timerThreads[key].kill();
|
||||
delete this.activeGameRunner.timerThreads[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
// pruneStaleGames = async () => {
|
||||
// this.activeGameRunner.activeGames.forEach((key, value) => {
|
||||
// if (value.createTime) {
|
||||
// const createDate = new Date(value.createTime);
|
||||
// if (createDate.setHours(createDate.getHours() + globals.STALE_GAME_HOURS) < Date.now()) {
|
||||
// this.logger.info('PRUNING STALE GAME ' + key);
|
||||
// this.activeGameRunner.activeGames.delete(key);
|
||||
// if (this.activeGameRunner.timerThreads[key]) {
|
||||
// this.logger.info('KILLING STALE TIMER PROCESS FOR ' + key);
|
||||
// this.activeGameRunner.timerThreads[key].kill();
|
||||
// delete this.activeGameRunner.timerThreads[key];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
|
||||
isGameFull = (game) => {
|
||||
return game.moderator.assigned === true && !game.people.find((person) => person.assigned === false);
|
||||
}
|
||||
|
||||
findPersonByField = (game, fieldName, value) => {
|
||||
let person;
|
||||
if (value === game.moderator[fieldName]) {
|
||||
person = game.moderator;
|
||||
}
|
||||
if (!person) {
|
||||
person = game.people.find((person) => person[fieldName] === value);
|
||||
}
|
||||
if (!person) {
|
||||
person = game.spectators.find((spectator) => spectator[fieldName] === value);
|
||||
}
|
||||
return person;
|
||||
}
|
||||
}
|
||||
|
||||
function getRandomInt (max) {
|
||||
@@ -483,20 +497,6 @@ function findPlayerBySocketId (people, socketId) {
|
||||
return people.find((person) => person.socketId === socketId && person.userType === globals.USER_TYPES.PLAYER);
|
||||
}
|
||||
|
||||
function findPersonByField (game, fieldName, value) {
|
||||
let person;
|
||||
if (value === game.moderator[fieldName]) {
|
||||
person = game.moderator;
|
||||
}
|
||||
if (!person) {
|
||||
person = game.people.find((person) => person[fieldName] === value);
|
||||
}
|
||||
if (!person) {
|
||||
person = game.spectators.find((spectator) => spectator[fieldName] === value);
|
||||
}
|
||||
return person;
|
||||
}
|
||||
|
||||
function isNameTaken (game, name) {
|
||||
const processedName = name.toLowerCase().trim();
|
||||
return (game.people.find((person) => person.name.toLowerCase().trim() === processedName))
|
||||
@@ -513,6 +513,12 @@ function getGameSize (cards) {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
async function notifyPlayerInvolvedInModTransfer(game, namespace, person) {
|
||||
if (namespace.sockets.get(person.socketId)) {
|
||||
namespace.to(person.socketId).emit(globals.EVENTS.SYNC_GAME_STATE);
|
||||
}
|
||||
}
|
||||
|
||||
async function addSpectator (game, name, logger, namespace, publisher, instanceId, refreshGame) {
|
||||
const spectator = new Person(
|
||||
createRandomId(),
|
||||
@@ -527,9 +533,9 @@ async function addSpectator (game, name, logger, namespace, publisher, instanceI
|
||||
globals.EVENTS.UPDATE_SPECTATORS,
|
||||
game.spectators.map((spectator) => { return GameStateCurator.mapPerson(spectator); })
|
||||
);
|
||||
publisher.publish(
|
||||
await publisher.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
game.accessCode + ';' + globals.EVENT_IDS.SPECTATOR_JOINED + ';' + JSON.stringify(spectator) + ';' + instanceId
|
||||
game.accessCode + ';' + globals.EVENT_IDS.UPDATE_SPECTATORS + ';' + JSON.stringify(game.spectators) + ';' + instanceId
|
||||
);
|
||||
return Promise.resolve(spectator.cookie);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@ const EVENT_IDS = globals.EVENT_IDS;
|
||||
const { RateLimiterMemory } = require('rate-limiter-flexible');
|
||||
const redis = require('redis');
|
||||
const GameStateCurator = require("../GameStateCurator");
|
||||
const Events = require("../Events");
|
||||
|
||||
class SocketManager {
|
||||
constructor (logger, activeGameRunner, instanceId) {
|
||||
constructor (logger, instanceId) {
|
||||
if (SocketManager.instance) {
|
||||
throw new Error('The server attempted to instantiate more than one SocketManager.');
|
||||
}
|
||||
@@ -13,7 +14,8 @@ class SocketManager {
|
||||
this.logger = logger;
|
||||
this.io = null;
|
||||
this.publisher = null;
|
||||
this.activeGameRunner = activeGameRunner;
|
||||
this.activeGameRunner = null;
|
||||
this.gameManager = null;
|
||||
this.instanceId = instanceId;
|
||||
SocketManager.instance = this;
|
||||
}
|
||||
@@ -60,95 +62,95 @@ class SocketManager {
|
||||
return server.of('/in-game');
|
||||
};
|
||||
|
||||
registerHandlers = (namespace, socket, gameManager) => {
|
||||
registerHandlers = (namespace, socket) => {
|
||||
socket.on(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, async (eventId, accessCode, args = null, ackFn = null) => {
|
||||
const game = gameManager.activeGameRunner.activeGames.get(accessCode);
|
||||
const game = await this.activeGameRunner.getActiveGame(accessCode);
|
||||
if (game) {
|
||||
await this.handleEventById(eventId, game, socket.id, gameManager, accessCode, args, ackFn);
|
||||
/* This server should publish events initiated by a connected socket to Redis for consumption by other instances. */
|
||||
if (Object.values(EVENT_IDS).includes(eventId)) {
|
||||
await gameManager.refreshGame(game);
|
||||
this.publisher?.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
accessCode + ';' + eventId + ';' + JSON.stringify(args) + ';' + this.instanceId
|
||||
);
|
||||
}
|
||||
await this.handleAndSyncEvent(eventId, game, socket, args, ackFn);
|
||||
} else {
|
||||
ackFn(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleEventById = async (eventId, game, socketId, gameManager, accessCode, args, ackFn) => {
|
||||
this.logger.debug('ARGS TO HANDLER: ' + JSON.stringify(args));
|
||||
handleAndSyncEvent = async (eventId, game, socket, args, ackFn) => {
|
||||
await this.handleEventById(eventId, game, socket?.id, game.accessCode, args, ackFn, false);
|
||||
/* This server should publish events initiated by a connected socket to Redis for consumption by other instances. */
|
||||
if (globals.SYNCABLE_EVENTS().includes(eventId)) {
|
||||
await this.gameManager.refreshGame(game);
|
||||
this.publisher?.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
game.accessCode + ';' + eventId + ';' + JSON.stringify(args) + ';' + this.instanceId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
handleEventById = async (eventId, game, socketId, accessCode, args, ackFn, syncOnly) => {
|
||||
this.logger.trace('ARGS TO HANDLER: ' + JSON.stringify(args));
|
||||
const event = Events.find((event) => event.id === eventId);
|
||||
if (event) {
|
||||
if (!syncOnly) {
|
||||
event.stateChange(game, args, this.gameManager);
|
||||
}
|
||||
event.communicate(game, args, this.gameManager);
|
||||
}
|
||||
switch (eventId) {
|
||||
case EVENT_IDS.NEW_GAME:
|
||||
this.activeGameRunner.activeGames.set(accessCode, args.newGame);
|
||||
break;
|
||||
case EVENT_IDS.PLAYER_JOINED:
|
||||
let toBeAssignedIndex = game.people.findIndex((person) => person.id === args.id && person.assigned === false);
|
||||
if (toBeAssignedIndex >= 0) {
|
||||
game.people[toBeAssignedIndex] = args;
|
||||
game.isFull = gameManager.isGameFull(game);
|
||||
gameManager.namespace.in(game.accessCode).emit(
|
||||
globals.EVENTS.PLAYER_JOINED,
|
||||
GameStateCurator.mapPerson(args),
|
||||
game.isFull
|
||||
);
|
||||
}
|
||||
break;
|
||||
case EVENT_IDS.SPECTATOR_JOINED:
|
||||
if (!game.spectators.find((spectator) => spectator.id === args.id)) {
|
||||
game.spectators.push(args);
|
||||
}
|
||||
gameManager.namespace.in(game.accessCode).emit(
|
||||
globals.EVENTS.UPDATE_SPECTATORS,
|
||||
game.spectators.map((spectator) => { return GameStateCurator.mapPerson(spectator); })
|
||||
);
|
||||
break;
|
||||
case EVENT_IDS.FETCH_GAME_STATE:
|
||||
if (!socketId) break;
|
||||
await gameManager.handleRequestForGameState(
|
||||
await this.gameManager.handleRequestForGameState(
|
||||
game,
|
||||
this.namespace,
|
||||
this.logger,
|
||||
gameManager.activeGameRunner,
|
||||
this.activeGameRunner,
|
||||
accessCode,
|
||||
args.personId,
|
||||
ackFn,
|
||||
socketId
|
||||
);
|
||||
break;
|
||||
case EVENT_IDS.UPDATE_SOCKET:
|
||||
const matchingPerson = this.gameManager.findPersonByField(game, 'id', args.personId);
|
||||
if (matchingPerson) {
|
||||
matchingPerson.socketId = args.socketId;
|
||||
}
|
||||
break;
|
||||
case EVENT_IDS.SYNC_GAME_STATE:
|
||||
const personToSync = this.gameManager.findPersonByField(game, 'id', args.personId);
|
||||
if (personToSync) {
|
||||
this.gameManager.namespace.to(personToSync.socketId).emit(globals.EVENTS.SYNC_GAME_STATE);
|
||||
}
|
||||
break;
|
||||
case EVENT_IDS.START_GAME:
|
||||
await gameManager.startGame(game, gameManager.namespace);
|
||||
await this.gameManager.startGame(game, this.gameManager.namespace);
|
||||
if (ackFn) {
|
||||
ackFn();
|
||||
}
|
||||
break;
|
||||
case EVENT_IDS.PAUSE_TIMER:
|
||||
await gameManager.pauseTimer(game, this.logger);
|
||||
await this.gameManager.pauseTimer(game, this.logger);
|
||||
break;
|
||||
case EVENT_IDS.RESUME_TIMER:
|
||||
await gameManager.resumeTimer(game, this.logger);
|
||||
await this.gameManager.resumeTimer(game, this.logger);
|
||||
break;
|
||||
case EVENT_IDS.GET_TIME_REMAINING:
|
||||
await gameManager.getTimeRemaining(game, socketId);
|
||||
await this.gameManager.getTimeRemaining(game, socketId);
|
||||
break;
|
||||
case EVENT_IDS.KILL_PLAYER:
|
||||
await gameManager.killPlayer(socketId, game, game.people.find((person) => person.id === args.personId), gameManager.namespace, this.logger);
|
||||
await this.gameManager.killPlayer(socketId, game, game.people.find((person) => person.id === args.personId), this.gameManager.namespace, this.logger);
|
||||
break;
|
||||
case EVENT_IDS.REVEAL_PLAYER:
|
||||
await gameManager.revealPlayer(game, args.personId);
|
||||
await this.gameManager.revealPlayer(game, args.personId);
|
||||
break;
|
||||
case EVENT_IDS.TRANSFER_MODERATOR:
|
||||
let person = game.people.find((person) => person.id === args.personId);
|
||||
if (!person) {
|
||||
person = game.spectators.find((spectator) => spectator.id === args.personId);
|
||||
}
|
||||
await gameManager.transferModeratorPowers(socketId, game, person, gameManager.namespace, this.logger);
|
||||
await this.gameManager.transferModeratorPowers(
|
||||
socketId,
|
||||
game,
|
||||
this.gameManager?.findPersonByField(game, 'id', args.personId),
|
||||
this.gameManager.namespace,
|
||||
this.logger
|
||||
);
|
||||
break;
|
||||
case EVENT_IDS.END_GAME:
|
||||
await gameManager.endGame(game);
|
||||
await this.gameManager.endGame(game);
|
||||
if (ackFn) {
|
||||
ackFn();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user