diff --git a/client/src/config/globals.js b/client/src/config/globals.js
index 47b16e9..e50b2f7 100644
--- a/client/src/config/globals.js
+++ b/client/src/config/globals.js
@@ -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",
diff --git a/client/src/modules/GameStateRenderer.js b/client/src/modules/GameStateRenderer.js
index ad16d30..3b95c6d 100644
--- a/client/src/modules/GameStateRenderer.js
+++ b/client/src/modules/GameStateRenderer.js
@@ -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);
+ }
}
diff --git a/client/src/modules/Navbar.js b/client/src/modules/Navbar.js
index 2d3472e..c22411a 100644
--- a/client/src/modules/Navbar.js
+++ b/client/src/modules/Navbar.js
@@ -42,7 +42,7 @@ function getNavbarLinks (page=null, device) {
'' +
'Home ' +
'Create ' +
- 'How to Use ' +
+ 'How to Use ' +
'Contact ' +
'Support the App '
}
diff --git a/client/src/modules/StateBucket.js b/client/src/modules/StateBucket.js
index 7571fc2..fa71c56 100644
--- a/client/src/modules/StateBucket.js
+++ b/client/src/modules/StateBucket.js
@@ -4,5 +4,6 @@
*/
export const stateBucket = {
currentGameState: null,
- timerWorker: null
+ timerWorker: null,
+ gameStateRequestInFlight: false
}
diff --git a/client/src/modules/Templates.js b/client/src/modules/Templates.js
index f3bd3b1..4fe03b8 100644
--- a/client/src/modules/Templates.js
+++ b/client/src/modules/Templates.js
@@ -52,7 +52,7 @@ export const templates = {
"
" +
diff --git a/client/src/scripts/game.js b/client/src/scripts/game.js
index 9f75c87..299f199 100644
--- a/client/src/scripts/game.js
+++ b/client/src/scripts/game.js
@@ -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();
}
}
diff --git a/client/src/scripts/howToUse.js b/client/src/scripts/howToUse.js
new file mode 100644
index 0000000..ee25480
--- /dev/null
+++ b/client/src/scripts/howToUse.js
@@ -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();
+}
diff --git a/client/src/styles/GLOBAL.css b/client/src/styles/GLOBAL.css
index 17957de..04dbae5 100644
--- a/client/src/styles/GLOBAL.css
+++ b/client/src/styles/GLOBAL.css
@@ -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;
diff --git a/client/src/views/how-to-use.html b/client/src/views/how-to-use.html
new file mode 100644
index 0000000..5d8d770
--- /dev/null
+++ b/client/src/views/how-to-use.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+
Create A Game
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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.
+
+
Creating a Game
+
+ Creating a game through the app is a 4-step process:
+
+
Step One: Choosing method of moderation
+
+ You have two options for moderation during the game. If the moderator isn't playing, you can choose the "dedicated
+ moderator" option.
+
+
+
+
+
diff --git a/client/webpack/webpack-dev.config.js b/client/webpack/webpack-dev.config.js
index 07ad9c0..a68125c 100644
--- a/client/webpack/webpack-dev.config.js
+++ b/client/webpack/webpack-dev.config.js
@@ -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'),
diff --git a/package.json b/package.json
index 42b1d8d..463e854 100644
--- a/package.json
+++ b/package.json
@@ -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"
},
diff --git a/server/config/globals.js b/server/config/globals.js
index 131659e..4ee35bc 100644
--- a/server/config/globals.js
+++ b/server/config/globals.js
@@ -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: {
diff --git a/server/main.js b/server/main.js
index 94f97e5..cb742a9 100644
--- a/server/main.js
+++ b/server/main.js
@@ -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);
});
diff --git a/server/modules/GameManager.js b/server/modules/GameManager.js
index fe9dd23..6438a62 100644
--- a/server/modules/GameManager.js
+++ b/server/modules/GameManager.js
@@ -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) {
diff --git a/server/modules/GameStateCurator.js b/server/modules/GameStateCurator.js
index b7f4fa6..74859f7 100644
--- a/server/modules/GameStateCurator.js
+++ b/server/modules/GameStateCurator.js
@@ -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;
diff --git a/server/routes/router.js b/server/routes/router.js
index 473368a..d60c298 100644
--- a/server/routes/router.js
+++ b/server/routes/router.js
@@ -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'));
});