clean up state management

This commit is contained in:
AlecM33
2022-01-10 21:02:29 -05:00
parent 7ca184cfee
commit 3b14ae3978
16 changed files with 349 additions and 129 deletions

View File

@@ -18,7 +18,8 @@ export const globals = {
REVEAL_PLAYER: 'revealPlayer',
TRANSFER_MODERATOR: 'transferModerator',
CHANGE_NAME: 'changeName',
END_GAME: 'endGame'
END_GAME: 'endGame',
FETCH_IN_PROGRESS_STATE: 'fetchInitialInProgressState'
},
STATUS: {
LOBBY: "lobby",
@@ -38,7 +39,9 @@ export const globals = {
START_TIMER: "startTimer",
KILL_PLAYER: "killPlayer",
REVEAL_PLAYER: 'revealPlayer',
CHANGE_NAME: 'changeName'
CHANGE_NAME: 'changeName',
START_GAME: 'startGame',
PLAYER_LEFT: 'playerLeft'
},
USER_TYPES: {
MODERATOR: "moderator",

View File

@@ -10,6 +10,12 @@ export class GameStateRenderer {
this.killPlayerHandlers = {};
this.revealRoleHandlers = {};
this.transferModHandlers = {};
this.startGameHandler = (e) => {
e.preventDefault();
if (confirm("Start the game and deal roles?")) {
socket.emit(globals.COMMANDS.START_GAME, this.stateBucket.currentGameState.accessCode);
}
}
}
renderLobbyPlayers() {
@@ -465,16 +471,18 @@ function removeExistingPlayerElements(killPlayerHandlers, revealRoleHandlers) {
}
function createEndGamePromptComponent(socket, stateBucket) {
let div = document.createElement("div");
div.innerHTML = templates.END_GAME_PROMPT;
div.querySelector("#end-game-button").addEventListener('click', (e) => {
e.preventDefault();
if (confirm("End the game?")) {
socket.emit(
globals.COMMANDS.END_GAME,
stateBucket.currentGameState.accessCode
);
}
});
document.getElementById("game-content").appendChild(div);
if (document.querySelector("#end-game-prompt") === null) {
let div = document.createElement("div");
div.innerHTML = templates.END_GAME_PROMPT;
div.querySelector("#end-game-button").addEventListener('click', (e) => {
e.preventDefault();
if (confirm("End the game?")) {
socket.emit(
globals.COMMANDS.END_GAME,
stateBucket.currentGameState.accessCode
);
}
});
document.getElementById("game-content").appendChild(div);
}
}

View File

@@ -42,7 +42,7 @@ function getNavbarLinks (page=null, device) {
'</a>' +
'<a class="' + linkClass + '" href="/">Home</a>' +
'<a class="' + linkClass + '" href="/create">Create</a>' +
'<a class="' + linkClass + '" href="/">How to Use</a>' +
'<a class="' + linkClass + '" href="/how-to-use">How to Use</a>' +
'<a class="' + linkClass + ' "href="mailto:play.werewolf.contact@gmail.com?Subject=Werewolf App" target="_top">Contact</a>' +
'<a class="' + linkClass + '" href="https://www.buymeacoffee.com/alecm33">Support the App</a>'
}

View File

@@ -4,5 +4,6 @@
*/
export const stateBucket = {
currentGameState: null,
timerWorker: null
timerWorker: null,
gameStateRequestInFlight: false
}

View File

@@ -52,7 +52,7 @@ export const templates = {
"<p id='role-description'></p>" +
"</div>" +
"<div id='game-role-back'>" +
"<h4>Click to reveal your role</h4>" +
"<h4>Click to show your role</h4>" +
"<p>(click again to hide)</p>" +
"</div>" +
"<div id='game-people-container'>" +
@@ -138,7 +138,7 @@ export const templates = {
"<p id='role-description'></p>" +
"</div>" +
"<div id='game-role-back'>" +
"<h4>Click to reveal your role</h4>" +
"<h4>Click to show your role</h4>" +
"<p>(click again to hide)</p>" +
"</div>" +
"<div id='game-people-container'>" +

View File

@@ -13,16 +13,21 @@ const game = () => {
injectNavbar();
let timerWorker;
const socket = io('/in-game');
stateBucket.gameStateRequestInFlight = false;
socket.on('disconnect', () => {
stateBucket.gameStateRequestInFlight = false;
if (timerWorker) {
timerWorker.terminate();
}
toast('Disconnected. Attempting reconnect...', 'error', true, false);
});
socket.on('connect', () => {
socket.emit(globals.COMMANDS.GET_ENVIRONMENT, function(returnedEnvironment) {
timerWorker = new Worker(new URL('../modules/Timer.js', import.meta.url));
prepareGamePage(returnedEnvironment, socket, timerWorker);
console.log('fired connect event');
socket.emit(globals.COMMANDS.GET_ENVIRONMENT, function (returnedEnvironment) {
if (!stateBucket.gameStateRequestInFlight) {
timerWorker = new Worker(new URL('../modules/Timer.js', import.meta.url));
prepareGamePage(returnedEnvironment, socket, timerWorker);
}
});
})
};
@@ -32,46 +37,51 @@ function prepareGamePage(environment, socket, timerWorker) {
const splitUrl = window.location.href.split('/game/');
const accessCode = splitUrl[1];
if (/^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === globals.ACCESS_CODE_LENGTH) {
stateBucket.gameStateRequestInFlight = true;
socket.emit(globals.COMMANDS.FETCH_GAME_STATE, accessCode, userId, function (gameState) {
stateBucket.gameStateRequestInFlight = false;
stateBucket.currentGameState = gameState;
document.querySelector('.spinner-container')?.remove();
document.querySelector('.spinner-background')?.remove();
if (gameState === null) {
window.location = '/not-found?reason=' + encodeURIComponent('game-not-found');
}
} else {
document.getElementById("game-content").innerHTML = templates.INITIAL_GAME_DOM;
toast('You are connected.', 'success', true, true, 2);
userId = gameState.client.cookie;
UserUtility.setAnonymousUserId(userId, environment);
let gameStateRenderer = new GameStateRenderer(stateBucket, socket);
let gameTimerManager;
if (stateBucket.currentGameState.timerParams) {
gameTimerManager = new GameTimerManager(stateBucket, socket);
}
initializeGame(stateBucket, socket, timerWorker, userId, gameStateRenderer, gameTimerManager);
document.getElementById("game-content").innerHTML = templates.INITIAL_GAME_DOM;
toast('You are connected.', 'success', true, true, 2);
userId = gameState.client.cookie;
UserUtility.setAnonymousUserId(userId, environment);
let gameStateRenderer = new GameStateRenderer(stateBucket, socket);
let gameTimerManager;
if (stateBucket.currentGameState.timerParams) {
gameTimerManager = new GameTimerManager(stateBucket, socket);
}
initializeGame(stateBucket, socket, timerWorker, userId, gameStateRenderer, gameTimerManager);
if (!gameState.client.hasEnteredName) {
document.getElementById("prompt").innerHTML = templates.NAME_CHANGE_MODAL;
document.getElementById("change-name-form").onsubmit = (e) => {
e.preventDefault();
let name = document.getElementById("player-new-name").value;
if (validateName(name)) {
socket.emit(globals.COMMANDS.CHANGE_NAME, gameState.accessCode, { name: name, personId: gameState.client.id }, (result) => {
switch (result) {
case "taken":
toast('This name is already taken.', 'error', true, true, 8);
break;
case "changed":
ModalManager.dispelModal("change-name-modal", "change-name-modal-background")
toast('Name set.', 'success', true, true, 5);
propagateNameChange(stateBucket.currentGameState, name, stateBucket.currentGameState.client.id);
processGameState(stateBucket.currentGameState, userId, socket, gameStateRenderer, gameTimerManager, timerWorker);
}
})
} else {
toast("Name must be between 1 and 30 characters.", 'error', true, true, 8);
if (!gameState.client.hasEnteredName) {
document.getElementById("prompt").innerHTML = templates.NAME_CHANGE_MODAL;
document.getElementById("change-name-form").onsubmit = (e) => {
e.preventDefault();
let name = document.getElementById("player-new-name").value;
if (validateName(name)) {
socket.emit(globals.COMMANDS.CHANGE_NAME, gameState.accessCode, {
name: name,
personId: gameState.client.id
}, (result) => {
switch (result) {
case "taken":
toast('This name is already taken.', 'error', true, true, 8);
break;
case "changed":
ModalManager.dispelModal("change-name-modal", "change-name-modal-background")
toast('Name set.', 'success', true, true, 5);
propagateNameChange(stateBucket.currentGameState, name, stateBucket.currentGameState.client.id);
processGameState(stateBucket.currentGameState, userId, socket, gameStateRenderer, gameTimerManager, timerWorker);
}
})
} else {
toast("Name must be between 1 and 30 characters.", 'error', true, true, 8);
}
}
}
}
@@ -86,10 +96,12 @@ function initializeGame(stateBucket, socket, timerWorker, userId, gameStateRende
processGameState(stateBucket.currentGameState, userId, socket, gameStateRenderer, gameTimerManager, timerWorker);
}
function processGameState (currentGameState, userId, socket, gameStateRenderer, gameTimerManager, timerWorker) {
function processGameState (currentGameState, userId, socket, gameStateRenderer, gameTimerManager, timerWorker, refreshPrompt=true) {
displayClientInfo(currentGameState.client.name, currentGameState.client.userType);
document.querySelector("#start-game-prompt")?.remove();
document.querySelector("#end-game-prompt")?.remove();
if (refreshPrompt) {
removeStartGameFunctionalityIfPresent(gameStateRenderer);
document.querySelector("#end-game-prompt")?.remove();
}
switch (currentGameState.status) {
case globals.STATUS.LOBBY:
document.getElementById("game-state-container").innerHTML = templates.LOBBY;
@@ -101,8 +113,9 @@ function processGameState (currentGameState, userId, socket, gameStateRenderer,
currentGameState.client.userType === globals.USER_TYPES.MODERATOR
|| currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
)
&& refreshPrompt
) {
displayStartGamePromptForModerators(currentGameState, socket);
displayStartGamePromptForModerators(currentGameState, gameStateRenderer);
}
break;
case globals.STATUS.IN_PROGRESS:
@@ -174,21 +187,69 @@ function setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWo
|| stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
)
) {
displayStartGamePromptForModerators(stateBucket.currentGameState, socket);
displayStartGamePromptForModerators(stateBucket.currentGameState, gameStateRenderer);
}
});
}
if (!socket.hasListeners(globals.EVENTS.PLAYER_LEFT)) {
socket.on(globals.EVENTS.PLAYER_LEFT, (player) => {
removeStartGameFunctionalityIfPresent(gameStateRenderer);
toast(player.name + " has left!", "error", false, true, 3);
let index = stateBucket.currentGameState.people.findIndex(person => person.id === player.id);
if (index >= 0) {
stateBucket.currentGameState.people.splice(
index,
1
);
gameStateRenderer.renderLobbyPlayers();
}
});
}
if (!socket.hasListeners(globals.EVENTS.START_GAME)) {
socket.on(globals.EVENTS.START_GAME, () => {
socket.emit(
globals.COMMANDS.FETCH_IN_PROGRESS_STATE,
stateBucket.currentGameState.accessCode,
stateBucket.currentGameState.client.cookie,
function (gameState) {
stateBucket.gameStateRequestInFlight = false;
stateBucket.currentGameState = gameState;
processGameState(
stateBucket.currentGameState,
gameState.client.cookie,
socket,
gameStateRenderer,
gameTimerManager,
timerWorker
);
}
);
});
}
if (!socket.hasListeners(globals.EVENTS.SYNC_GAME_STATE)) {
socket.on(globals.EVENTS.SYNC_GAME_STATE, () => {
socket.emit(
globals.COMMANDS.FETCH_GAME_STATE,
stateBucket.currentGameState.accessCode,
stateBucket.currentGameState.client.cookie,
function (gameState) {
stateBucket.currentGameState = gameState;
processGameState(stateBucket.currentGameState, gameState.client.cookie, socket, gameStateRenderer, gameTimerManager, timerWorker);
}
);
if (!stateBucket.gameStateRequestInFlight) {
stateBucket.gameStateRequestInFlight = true;
socket.emit(
globals.COMMANDS.FETCH_IN_PROGRESS_STATE,
stateBucket.currentGameState.accessCode,
stateBucket.currentGameState.client.cookie,
function (gameState) {
stateBucket.gameStateRequestInFlight = false;
stateBucket.currentGameState = gameState;
processGameState(
stateBucket.currentGameState,
gameState.client.cookie,
socket,
gameStateRenderer,
gameTimerManager,
timerWorker
);
}
);
}
});
}
@@ -255,7 +316,15 @@ function setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWo
socket.on(globals.EVENTS.CHANGE_NAME, (personId, name) => {
propagateNameChange(stateBucket.currentGameState, name, personId);
updateDOMWithNameChange(stateBucket.currentGameState, gameStateRenderer);
processGameState(stateBucket.currentGameState, stateBucket.currentGameState.client.cookie, socket, gameStateRenderer, gameTimerManager, timerWorker);
processGameState(
stateBucket.currentGameState,
stateBucket.currentGameState.client.cookie,
socket,
gameStateRenderer,
gameTimerManager,
timerWorker,
false
);
});
}
@@ -263,21 +332,22 @@ function setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWo
socket.on(globals.COMMANDS.END_GAME, (people) => {
stateBucket.currentGameState.people = people;
stateBucket.currentGameState.status = globals.STATUS.ENDED;
processGameState(stateBucket.currentGameState, stateBucket.currentGameState.client.cookie, socket, gameStateRenderer, gameTimerManager, timerWorker);
processGameState(
stateBucket.currentGameState,
stateBucket.currentGameState.client.cookie,
socket,
gameStateRenderer,
gameTimerManager,
timerWorker
);
});
}
}
function displayStartGamePromptForModerators(gameState, socket) {
function displayStartGamePromptForModerators(gameState, gameStateRenderer) {
let div = document.createElement("div");
div.innerHTML = templates.START_GAME_PROMPT;
div.querySelector('#start-game-button').addEventListener('click', (e) => {
e.preventDefault();
if (confirm("Start the game and deal roles?")) {
socket.emit(globals.COMMANDS.START_GAME, gameState.accessCode);
}
});
div.querySelector('#start-game-button').addEventListener('click', gameStateRenderer.startGameHandler);
document.body.appendChild(div);
}
@@ -296,8 +366,15 @@ function validateName(name) {
return typeof name === 'string' && name.length > 0 && name.length <= 30;
}
function removeStartGameFunctionalityIfPresent(gameStateRenderer) {
document.querySelector("#start-game-prompt")?.removeEventListener('click', gameStateRenderer.startGameHandler);
document.querySelector("#start-game-prompt")?.remove();
}
function propagateNameChange(gameState, name, personId) {
gameState.client.name = name;
if (gameState.client.id === personId) {
gameState.client.name = name;
}
let matchingPerson = gameState.people.find((person) => person.id === personId);
if (matchingPerson) {
matchingPerson.name = name;
@@ -314,20 +391,24 @@ function propagateNameChange(gameState, name, personId) {
}
function updateDOMWithNameChange(gameState, gameStateRenderer) {
switch (gameState.client.userType) {
case globals.USER_TYPES.PLAYER:
case globals.USER_TYPES.KILLED_PLAYER:
case globals.USER_TYPES.SPECTATOR:
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(false);
break;
case globals.USER_TYPES.MODERATOR:
gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(gameState.status === globals.STATUS.ENDED);
break;
case globals.USER_TYPES.TEMPORARY_MODERATOR:
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true);
break;
default:
break;
if (gameState.status === globals.STATUS.IN_PROGRESS) {
switch (gameState.client.userType) {
case globals.USER_TYPES.PLAYER:
case globals.USER_TYPES.KILLED_PLAYER:
case globals.USER_TYPES.SPECTATOR:
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(false);
break;
case globals.USER_TYPES.MODERATOR:
gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(gameState.status === globals.STATUS.ENDED);
break;
case globals.USER_TYPES.TEMPORARY_MODERATOR:
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true);
break;
default:
break;
}
} else {
gameStateRenderer.renderLobbyPlayers();
}
}

View File

@@ -0,0 +1,9 @@
import { injectNavbar } from "../modules/Navbar.js";
const howToUse = () => { injectNavbar(); };
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = howToUse;
} else {
howToUse();
}

View File

@@ -221,6 +221,16 @@ input {
z-index: 53000;
}
#how-to-use-container {
color: #d7d7d7;
display: flex;
flex-direction: column;
margin: 0 auto;
width: 95%;
max-width: 70em;
line-height: 1.5;
}
#desktop-links > a:nth-child(1), #mobile-links a:nth-child(1) {
margin: 0 0.5em;
width: 50px;

View File

@@ -0,0 +1,47 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Create A Game</title>
<meta name="description" content="How to use this app.">
<meta property="og:title" content="How to use">
<meta property="og:type" content="website">
<meta property="og:url" content="https://playwerewolf.uk.r.appspot.com/how-to-use">
<meta property="og:description" content="How to use this app.">
<meta property="og:image" content="image.png">
<link rel="icon" href="/favicon.ico">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="stylesheet" href="/styles/GLOBAL.css">
<link rel="stylesheet" href="/styles/create.css">
<link rel="stylesheet" href="/styles/modal.css">
<link rel="stylesheet" href="/styles/hamburgers.css">
</head>
<body>
<div id="mobile-menu-background-overlay"></div>
<div id="navbar"></div>
<div id="how-to-use-container">
<h1 class="how-to-use-header">Purpose of the Application</h1>
<div class="how-to-use-section">This app serves as a means of running games in a social setting where a traditional
running of the game is hindered. This might be when people are meeting virtually, and thus roles can't be handed
out in-person, or when people are in-person but don't have Werewolf cards with them. You can use a deck of regular
playing cards, but it can be difficult for players to remember which card signifies which role, especially if
you want to build a crazy game with many different roles. Even when people are together and have cards, there's
information that would be great to centralize for everyone - a timer, role descriptions, and the in/out status of
players. This app attempts to provide the utilities necessary to run Werewolf with all the different roles you want,
wherever you can access the internet.
</div>
<h1 class="">Creating a Game</h1>
<div class="how-to-use-section">
Creating a game through the app is a 4-step process:
<br>
<h3>Step One: Choosing method of moderation</h3>
<br>
You have two options for moderation during the game. If the moderator isn't playing, you can choose the "dedicated
moderator" option.
</div>
</div>
<script src="/dist/howToUse-bundle.js"></script>
</body>
</html>

View File

@@ -5,7 +5,8 @@ module.exports = {
game: './client/src/scripts/game.js',
home: './client/src/scripts/home.js',
create: './client/src/scripts/create.js',
notFound: './client/src/scripts/notFound.js'
notFound: './client/src/scripts/notFound.js',
howToUse: './client/src/scripts/howToUse.js'
},
output: {
path: path.resolve(__dirname, '../dist'),

View File

@@ -11,7 +11,7 @@
"start:dev:no-hot-reload": "NODE_ENV=development && node server/main.js",
"start:dev:windows": "SET NODE_ENV=development && nodemon server/main.js",
"start:dev:windows:no-hot-reload": "SET NODE_ENV=development && node server/main.js",
"start": "NODE_ENV=production node server/main.js -- loglevel=debug port=8080",
"start": "NODE_ENV=production node server/main.js -- loglevel=trace port=8080",
"start:windows": "SET NODE_ENV=production && node server/main.js -- loglevel=warn port=8080",
"test": "jasmine"
},

View File

@@ -14,7 +14,8 @@ const globals = {
REVEAL_PLAYER: 'revealPlayer',
TRANSFER_MODERATOR: 'transferModerator',
CHANGE_NAME: 'changeName',
END_GAME: 'endGame'
END_GAME: 'endGame',
FETCH_IN_PROGRESS_STATE: 'fetchInitialInProgressState'
},
MESSAGES: {
ENTER_NAME: "Client must enter name."
@@ -38,6 +39,7 @@ const globals = {
},
EVENTS: {
PLAYER_JOINED: "playerJoined",
PLAYER_LEFT: "playerLeft",
SYNC_GAME_STATE: "syncGameState"
},
ENVIRONMENT: {

View File

@@ -23,6 +23,10 @@ app.set('port', args.port);
const inGameSocketServer = ServerBootstrapper.createSocketServer(main, app, args.port);
inGameSocketServer.on('connection', function (socket) {
socket.on("disconnecting", (reason) => {
logger.trace('client socket disconnecting because: ' + reason)
gameManager.removeClientFromLobbyIfApplicable(socket);
});
gameManager.addGameSocketHandlers(inGameSocketServer, socket);
});

View File

@@ -11,7 +11,6 @@ class GameManager {
this.environment = environment;
this.activeGameRunner = new ActiveGameRunner(logger).getInstance();
this.namespace = null;
//this.gameSocketUtility = GameSocketUtility;
}
addGameSocketHandlers = (namespace, socket) => {
@@ -29,19 +28,36 @@ class GameManager {
);
});
/* this event handler will call handleRequestForGameState() with the 'handleNoMatch' arg as false - only
connections that match a participant in the game at that time will have the game state sent to them.
*/
socket.on(globals.CLIENT_COMMANDS.FETCH_IN_PROGRESS_STATE, (accessCode, personId, ackFn) => {
this.logger.trace('request for game state for accessCode ' + accessCode + ', person ' + personId);
this.handleRequestForGameState(
this.namespace,
this.logger,
this.activeGameRunner,
accessCode,
personId,
ackFn,
socket,
false
);
});
socket.on(globals.CLIENT_COMMANDS.GET_ENVIRONMENT, (ackFn) => {
ackFn(this.environment);
});
socket.on(globals.CLIENT_COMMANDS.START_GAME, (accessCode) => {
let game = this.activeGameRunner.activeGames[accessCode];
if (game) {
if (game && game.isFull) {
game.status = globals.STATUS.IN_PROGRESS;
namespace.in(accessCode).emit(globals.EVENTS.SYNC_GAME_STATE);
if (game.hasTimer) {
game.timerParams.paused = true;
this.activeGameRunner.runGame(game, namespace);
}
namespace.in(accessCode).emit(globals.CLIENT_COMMANDS.START_GAME);
}
});
@@ -142,7 +158,7 @@ class GameManager {
ackFn("changed");
person.name = data.name.trim();
person.hasEnteredName = true;
socket.to(accessCode).emit(globals.EVENTS.SYNC_GAME_STATE);
namespace.in(accessCode).emit(globals.CLIENT_COMMANDS.CHANGE_NAME, person.id, person.name);
} else {
ackFn("taken");
}
@@ -261,14 +277,14 @@ class GameManager {
}
handleRequestForGameState = (namespace, logger, gameRunner, accessCode, personCookie, ackFn, socket) => {
handleRequestForGameState = (namespace, logger, gameRunner, accessCode, personCookie, ackFn, socket, handleNoMatch=true) => {
const game = gameRunner.activeGames[accessCode];
if (game) {
let matchingPerson = game.people.find((person) => person.cookie === personCookie);
if (!matchingPerson) {
matchingPerson = game.spectators.find((spectator) => spectator.cookie === personCookie);
}
if (game.moderator.cookie === personCookie) {
if (!matchingPerson && game.moderator.cookie === personCookie) {
matchingPerson = game.moderator;
}
if (matchingPerson) {
@@ -281,7 +297,7 @@ class GameManager {
matchingPerson.socketId = socket.id;
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, matchingPerson, gameRunner, socket, logger));
}
} else {
} else if (handleNoMatch) {
this.handleRequestFromNonMatchingPerson(game, socket, gameRunner, ackFn, logger);
}
} else {
@@ -291,7 +307,7 @@ class GameManager {
}
handleRequestFromNonMatchingPerson = (game, socket, gameRunner, ackFn, logger) => {
let personWithMatchingSocketId = findPersonWithMatchingSocketId(game.people, socket.id);
let personWithMatchingSocketId = findPersonWithMatchingSocketId(game, socket.id);
if (personWithMatchingSocketId) {
logger.trace("matching person found whose cookie got cleared after establishing a connection to the room: " + personWithMatchingSocketId.name);
ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, personWithMatchingSocketId, gameRunner, socket, logger));
@@ -308,7 +324,7 @@ class GameManager {
game.isFull = isFull;
socket.to(game.accessCode).emit(
globals.EVENTS.PLAYER_JOINED,
{name: unassignedPerson.name, userType: unassignedPerson.userType},
GameStateCurator.mapPerson(unassignedPerson),
isFull
);
} else { // if the game is full, make them a spectator.
@@ -326,6 +342,30 @@ class GameManager {
}
}
removeClientFromLobbyIfApplicable(socket) {
socket.rooms.forEach((room) => {
if (this.activeGameRunner.activeGames[room]) {
this.logger.trace('disconnected socket is in a game');
let game = this.activeGameRunner.activeGames[room];
if (game.status === globals.STATUS.LOBBY) {
let matchingPlayer = findPlayerBySocketId(game.people, socket.id);
if (matchingPlayer) {
this.logger.trace("un-assigning disconnected player: " + matchingPlayer.name);
matchingPlayer.assigned = false;
matchingPlayer.socketId = null;
matchingPlayer.cookie = createRandomId();
matchingPlayer.hasEnteredName = false;
socket.to(game.accessCode).emit(
globals.EVENTS.PLAYER_LEFT,
GameStateCurator.mapPerson(matchingPlayer)
);
game.isFull = isGameFull(game);
matchingPlayer.name = UsernameGenerator.generate();
}
}
}
})
}
}
function getRandomInt (max) {
@@ -403,8 +443,19 @@ function rejectClientRequestForGameState(acknowledgementFunction) {
return acknowledgementFunction(null);
}
function findPersonWithMatchingSocketId(people, socketId) {
return people.find((person) => person.socketId === socketId);
function findPersonWithMatchingSocketId(game, socketId) {
let person = game.people.find((person) => person.socketId === socketId);
if (!person) {
person = game.spectators.find((spectator) => spectator.socketId === socketId);
}
if (!person && game.moderator.socketId === socketId) {
person = game.moderator;
}
return person;
}
function findPlayerBySocketId(people, socketId) {
return people.find((person) => person.socketId === socketId && person.userType === globals.USER_TYPES.PLAYER);
}
function isGameFull(game) {

View File

@@ -24,6 +24,21 @@ const GameStateCurator = {
out: person.out,
revealed: person.revealed
}));
},
mapPerson: (person) => {
if (person.revealed) {
return {
name: person.name,
id: person.id,
userType: person.userType,
out: person.out,
revealed: person.revealed,
gameRole: person.gameRole,
alignment: person.alignment
};
} else {
return { name: person.name, id: person.id, userType: person.userType, out: person.out, revealed: person.revealed };
}
}
}
@@ -48,7 +63,7 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) {
let state = {
accessCode: game.accessCode,
status: game.status,
moderator: mapPerson(game.moderator),
moderator: GameStateCurator.mapPerson(game.moderator),
client: client,
deck: game.deck,
people: game.people
@@ -56,7 +71,7 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) {
return person.assigned === true
})
.map((filteredPerson) =>
mapPerson(filteredPerson)
GameStateCurator.mapPerson(filteredPerson)
),
timerParams: game.timerParams,
isFull: game.isFull,
@@ -69,7 +84,7 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) {
return {
accessCode: game.accessCode,
status: game.status,
moderator: mapPerson(game.moderator),
moderator: GameStateCurator.mapPerson(game.moderator),
client: client,
deck: game.deck,
people: GameStateCurator.mapPeopleForModerator(game.people, client),
@@ -81,14 +96,14 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) {
return {
accessCode: game.accessCode,
status: game.status,
moderator: mapPerson(game.moderator),
moderator: GameStateCurator.mapPerson(game.moderator),
client: client,
deck: game.deck,
people: game.people
.filter((person) => {
return person.assigned === true
})
.map((filteredPerson) => mapPerson(filteredPerson)),
.map((filteredPerson) => GameStateCurator.mapPerson(filteredPerson)),
timerParams: game.timerParams,
isFull: game.isFull
}
@@ -96,14 +111,14 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) {
return {
accessCode: game.accessCode,
status: game.status,
moderator: mapPerson(game.moderator),
moderator: GameStateCurator.mapPerson(game.moderator),
client: client,
deck: game.deck,
people: game.people
.filter((person) => {
return person.assigned === true
})
.map((filteredPerson) => mapPerson(filteredPerson)),
.map((filteredPerson) => GameStateCurator.mapPerson(filteredPerson)),
timerParams: game.timerParams,
isFull: game.isFull,
}
@@ -112,20 +127,4 @@ function getGameStateBasedOnPermissions(game, person, gameRunner) {
}
}
function mapPerson(person) {
if (person.revealed) {
return {
name: person.name,
id: person.id,
userType: person.userType,
out: person.out,
revealed: person.revealed,
gameRole: person.gameRole,
alignment: person.alignment
};
} else {
return { name: person.name, id: person.id, userType: person.userType, out: person.out, revealed: person.revealed };
}
}
module.exports = GameStateCurator;

View File

@@ -10,6 +10,10 @@ router.get('/create', function (request, response) {
response.sendFile(path.join(__dirname, '../../client/src/views/create.html'));
});
router.get('/how-to-use', function (request, response) {
response.sendFile(path.join(__dirname, '../../client/src/views/how-to-use.html'));
});
router.get('/game/:code', function (request, response) {
response.sendFile(path.join(__dirname, '../../client/src/views/game.html'));
});