diff --git a/client/src/modules/game_state/states/Ended.js b/client/src/modules/game_state/states/Ended.js
new file mode 100644
index 0000000..ea4e9fd
--- /dev/null
+++ b/client/src/modules/game_state/states/Ended.js
@@ -0,0 +1,98 @@
+import { globals } from '../../../config/globals.js';
+import { HTMLFragments } from '../../front_end_components/HTMLFragments.js';
+import { XHRUtility } from '../../utility/XHRUtility.js';
+import { UserUtility } from '../../utility/UserUtility.js';
+import { toast } from '../../front_end_components/Toast.js';
+import { Confirmation } from '../../front_end_components/Confirmation.js';
+
+export class Ended {
+ constructor (containerId, stateBucket, socket) {
+ this.stateBucket = stateBucket;
+ this.socket = socket;
+ this.container = document.getElementById(containerId);
+ this.container.innerHTML = HTMLFragments.END_OF_GAME_VIEW;
+ this.restartGameHandler = () => {
+ XHRUtility.xhr(
+ '/api/games/' + this.stateBucket.currentGameState.accessCode + '/restart',
+ 'PATCH',
+ null,
+ JSON.stringify({
+ playerName: this.stateBucket.currentGameState.client.name,
+ accessCode: this.stateBucket.currentGameState.accessCode,
+ sessionCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.LOCAL),
+ localCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.PRODUCTION)
+ })
+ )
+ .then((res) => {
+ toast('Game restarted!', 'success', true, true, 'medium');
+ })
+ .catch((res) => {
+ toast(res.content, 'error', true, true, 'medium');
+ });
+ };
+ }
+
+ renderEndOfGame (gameState) {
+ if (
+ gameState.client.userType === globals.USER_TYPES.MODERATOR
+ || gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
+ ) {
+ const restartGameContainer = document.createElement('div');
+ restartGameContainer.innerHTML = HTMLFragments.RESTART_GAME_BUTTON;
+ const button = restartGameContainer.querySelector('#restart-game');
+ button.addEventListener('click', () => {
+ Confirmation('Restart the game, dealing everyone new roles?', () => {
+ this.restartGameHandler();
+ });
+ });
+ document.getElementById('end-of-game-buttons').prepend(restartGameContainer);
+ }
+ this.renderPlayersWithRoleInformation();
+ }
+
+ renderPlayersWithRoleInformation (tempMod = false) {
+ document.querySelectorAll('.game-player').forEach((el) => el.remove());
+ /* TODO: UX issue - it's easier to parse visually when players are sorted this way,
+ but shifting players around when they are killed or revealed is bad UX for the moderator. */
+ // sortPeopleByStatus(this.stateBucket.currentGameState.people);
+ const modType = tempMod ? this.stateBucket.currentGameState.moderator.userType : null;
+ renderGroupOfPlayers(
+ this.stateBucket.currentGameState.people,
+ this.stateBucket.currentGameState.accessCode,
+ null,
+ modType,
+ this.socket
+ );
+ document.getElementById('players-alive-label').innerText =
+ 'Players: ' + this.stateBucket.currentGameState.people.filter((person) => !person.out).length + ' / ' +
+ this.stateBucket.currentGameState.people.length + ' Alive';
+ }
+}
+
+function renderGroupOfPlayers (
+ people,
+ accessCode = null,
+ alignment = null
+) {
+ for (const player of people) {
+ const playerEl = document.createElement('div');
+ playerEl.classList.add('game-player');
+ playerEl.innerHTML = HTMLFragments.GAME_PLAYER;
+
+ playerEl.querySelector('.game-player-name').innerText = player.name;
+ const roleElement = playerEl.querySelector('.game-player-role');
+
+ if (alignment === null) {
+ roleElement.classList.add(player.alignment);
+ } else {
+ roleElement.classList.add(alignment);
+ }
+ roleElement.innerText = player.gameRole;
+
+ if (player.out) {
+ playerEl.classList.add('killed');
+ }
+
+ document.getElementById('game-player-list').appendChild(playerEl);
+ }
+}
diff --git a/client/src/modules/game_state/GameStateRenderer.js b/client/src/modules/game_state/states/InProgress.js
similarity index 65%
rename from client/src/modules/game_state/GameStateRenderer.js
rename to client/src/modules/game_state/states/InProgress.js
index 97935e0..1bd83d5 100644
--- a/client/src/modules/game_state/GameStateRenderer.js
+++ b/client/src/modules/game_state/states/InProgress.js
@@ -1,148 +1,114 @@
-import { globals } from '../../config/globals.js';
-import { toast } from '../front_end_components/Toast.js';
-import { HTMLFragments } from '../front_end_components/HTMLFragments.js';
-import { ModalManager } from '../front_end_components/ModalManager.js';
-import { XHRUtility } from '../utility/XHRUtility.js';
-import { UserUtility } from '../utility/UserUtility.js';
-// QRCode module via: https://github.com/soldair/node-qrcode
-import { QRCode } from '../third_party/qrcode.js';
-import { Confirmation } from '../front_end_components/Confirmation.js';
+import { toast } from '../../front_end_components/Toast.js';
+import { globals } from '../../../config/globals.js';
+import { HTMLFragments } from '../../front_end_components/HTMLFragments.js';
+import { Confirmation } from '../../front_end_components/Confirmation.js';
+import { ModalManager } from '../../front_end_components/ModalManager.js';
+import { GameTimerManager } from '../../timer/GameTimerManager.js';
+import { stateBucket } from '../StateBucket.js';
-export class GameStateRenderer {
- constructor (stateBucket, socket) {
+export class InProgress {
+ constructor (containerId, stateBucket, socket) {
this.stateBucket = stateBucket;
this.socket = socket;
+ this.container = document.getElementById(containerId);
+ this.components = {
+
+ };
this.killPlayerHandlers = {};
this.revealRoleHandlers = {};
this.transferModHandlers = {};
- this.startGameHandler = (e) => { // TODO: prevent multiple emissions of this event (recommend converting to XHR)
- e.preventDefault();
- Confirmation('Start game and deal roles?', () => {
- socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.START_GAME, stateBucket.currentGameState.accessCode);
- });
- };
- this.restartGameHandler = (e) => {
- e.preventDefault();
- const button = document.getElementById('restart-game');
- button.removeEventListener('click', this.restartGameHandler);
- button.classList.add('submitted');
- button.innerText = 'Restarting...';
- XHRUtility.xhr(
- '/api/games/' + this.stateBucket.currentGameState.accessCode + '/restart',
- 'PATCH',
- null,
- JSON.stringify({
- playerName: this.stateBucket.currentGameState.client.name,
- accessCode: this.stateBucket.currentGameState.accessCode,
- sessionCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.LOCAL),
- localCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.PRODUCTION)
- })
- )
- .then((res) => {
- toast('Game restarted!', 'success', true, true, 'medium');
- })
- .catch((res) => {
- const button = document.getElementById('restart-game');
- button.innerText = 'Restart Game 🔄';
- button.classList.remove('submitted');
- button.addEventListener('click', this.restartGameHandler);
- toast(res.content, 'error', true, true, 'medium');
- });
- };
}
- renderLobbyPlayers () {
- document.querySelectorAll('.lobby-player').forEach((el) => el.remove());
- const lobbyPlayersContainer = document.getElementById('lobby-players');
- if (this.stateBucket.currentGameState.moderator.userType === globals.USER_TYPES.MODERATOR) {
- lobbyPlayersContainer.appendChild(
- renderLobbyPerson(
- this.stateBucket.currentGameState.moderator.name,
- this.stateBucket.currentGameState.moderator.userType
- )
- );
+ setUserView (userType) {
+ switch (userType) {
+ case globals.USER_TYPES.PLAYER:
+ this.container.innerHTML = HTMLFragments.PLAYER_GAME_VIEW;
+ this.renderPlayerView();
+ break;
+ case globals.USER_TYPES.KILLED_PLAYER:
+ this.container.innerHTML = HTMLFragments.PLAYER_GAME_VIEW;
+ this.renderPlayerView(true);
+ break;
+ case globals.USER_TYPES.MODERATOR:
+ document.getElementById('transfer-mod-prompt').innerHTML = HTMLFragments.TRANSFER_MOD_MODAL;
+ this.container.innerHTML = HTMLFragments.MODERATOR_GAME_VIEW;
+ this.renderModeratorView();
+ break;
+ case globals.USER_TYPES.TEMPORARY_MODERATOR:
+ document.getElementById('transfer-mod-prompt').innerHTML = HTMLFragments.TRANSFER_MOD_MODAL;
+ this.container.innerHTML = HTMLFragments.TEMP_MOD_GAME_VIEW;
+ this.renderTempModView();
+ break;
+ case globals.USER_TYPES.SPECTATOR:
+ this.container.innerHTML = HTMLFragments.SPECTATOR_GAME_VIEW;
+ this.renderSpectatorView();
+ break;
+ default:
+ break;
}
- for (const person of this.stateBucket.currentGameState.people) {
- lobbyPlayersContainer.appendChild(renderLobbyPerson(person.name, person.userType));
- }
- const playerCount = this.stateBucket.currentGameState.people.length;
- document.querySelector("label[for='lobby-players']").innerText =
- 'Participants (' + playerCount + '/' + getGameSize(this.stateBucket.currentGameState.deck) + ' Players)';
- }
- renderLobbyHeader () {
- removeExistingTitle();
- const gameLinkContainer = document.getElementById('game-link');
-
- const copyImg = document.createElement('img');
- copyImg.setAttribute('src', '../images/copy.svg');
- gameLinkContainer.appendChild(copyImg);
-
- const time = document.getElementById('game-time');
- const playerCount = document.getElementById('game-player-count');
- const gameCode = document.getElementById('game-code');
- playerCount.innerText = getGameSize(this.stateBucket.currentGameState.deck) + ' Players';
- gameCode.innerHTML = 'Or enter this code on the homepage:
' + this.stateBucket.currentGameState.accessCode + '';
-
- let timeString = '';
if (this.stateBucket.currentGameState.timerParams) {
- const hours = this.stateBucket.currentGameState.timerParams.hours;
- const minutes = this.stateBucket.currentGameState.timerParams.minutes;
- if (hours) {
- timeString += hours > 1
- ? hours + ' hours '
- : hours + ' hour ';
- }
- if (minutes) {
- timeString += minutes > 1
- ? minutes + ' minutes '
- : minutes + ' minute ';
- }
- time.innerText = timeString;
+ this.socket.emit(
+ globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
+ globals.EVENT_IDS.GET_TIME_REMAINING,
+ this.stateBucket.currentGameState.accessCode
+ );
} else {
- timeString = 'untimed';
- time.innerText = timeString;
+ document.querySelector('#game-timer')?.remove();
+ document.querySelector('#timer-container-moderator')?.remove();
+ document.querySelector('label[for="game-timer"]')?.remove();
}
+ }
- const link = window.location.protocol + '//' + window.location.host +
- '/join/' + this.stateBucket.currentGameState.accessCode +
- '?playerCount=' + getGameSize(this.stateBucket.currentGameState.deck) +
- '&timer=' + encodeURIComponent(timeString);
-
- QRCode.toCanvas(document.getElementById('canvas'), link, { scale: 2 }, function (error) {
- if (error) console.error(error);
- });
-
- const linkCopyHandler = (e) => {
- if (e.type === 'click' || e.code === 'Enter') {
- navigator.clipboard.writeText(link)
- .then(() => {
- toast('Link copied!', 'success', true);
- });
+ renderPlayerView (isKilled = false) {
+ if (isKilled) {
+ const clientUserType = document.getElementById('client-user-type');
+ if (clientUserType) {
+ clientUserType.innerText = globals.USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80';
}
- };
- gameLinkContainer.addEventListener('click', linkCopyHandler);
- gameLinkContainer.addEventListener('keyup', linkCopyHandler);
-
- const linkDiv = document.createElement('div');
- linkDiv.innerText = link;
-
- gameLinkContainer.prepend(linkDiv);
- }
-
- renderLobbyFooter () {
- for (const card of this.stateBucket.currentGameState.deck) {
- const cardEl = document.createElement('div');
- cardEl.innerText = card.quantity + 'x ' + card.role;
- cardEl.classList.add('lobby-card');
}
+ renderPlayerRole(this.stateBucket.currentGameState);
+ this.renderPlayersWithNoRoleInformationUnlessRevealed(false);
}
- renderGameHeader () {
- removeExistingTitle();
- // let title = document.createElement("h1");
- // title.innerText = "Game";
- // document.getElementById("game-title").appendChild(title);
+ renderPlayersWithNoRoleInformationUnlessRevealed (tempMod = false) {
+ if (tempMod) {
+ this.removePlayerListEventListeners();
+ }
+ document.querySelectorAll('.game-player').forEach((el) => el.remove());
+ /* TODO: UX issue - it's easier to parse visually when players are sorted this way,
+ but shifting players around when they are killed or revealed is bad UX for the moderator. */
+ // sortPeopleByStatus(this.stateBucket.currentGameState.people);
+ const modType = tempMod ? this.stateBucket.currentGameState.moderator.userType : null;
+ this.renderGroupOfPlayers(
+ this.stateBucket.currentGameState.people,
+ this.killPlayerHandlers,
+ this.revealRoleHandlers,
+ this.stateBucket.currentGameState.accessCode,
+ null,
+ modType,
+ this.socket
+ );
+ document.getElementById('players-alive-label').innerText =
+ 'Players: ' + this.stateBucket.currentGameState.people.filter((person) => !person.out).length + ' / ' +
+ this.stateBucket.currentGameState.people.length + ' Alive';
+ }
+
+ removePlayerListEventListeners (removeEl = true) {
+ document.querySelectorAll('.game-player').forEach((el) => {
+ const pointer = el.dataset.pointer;
+ if (pointer && this.killPlayerHandlers[pointer]) {
+ el.removeEventListener('click', this.killPlayerHandlers[pointer]);
+ delete this.killPlayerHandlers[pointer];
+ }
+ if (pointer && this.revealRoleHandlers[pointer]) {
+ el.removeEventListener('click', this.revealRoleHandlers[pointer]);
+ delete this.revealRoleHandlers[pointer];
+ }
+ if (removeEl) {
+ el.remove();
+ }
+ });
}
renderModeratorView () {
@@ -169,26 +135,66 @@ export class GameStateRenderer {
this.renderPlayersWithNoRoleInformationUnlessRevealed(true);
}
- renderPlayerView (isKilled = false) {
- if (isKilled) {
- const clientUserType = document.getElementById('client-user-type');
- if (clientUserType) {
- clientUserType.innerText = globals.USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80';
- }
- }
- renderPlayerRole(this.stateBucket.currentGameState);
- this.renderPlayersWithNoRoleInformationUnlessRevealed(false);
- }
-
renderSpectatorView () {
this.renderPlayersWithNoRoleInformationUnlessRevealed();
}
- refreshPlayerList (isModerator) {
- if (isModerator) {
- this.renderPlayersWithRoleAndAlignmentInfo();
- } else {
- this.renderPlayersWithNoRoleInformationUnlessRevealed();
+ setSocketHandlers () {
+ this.socket.on(globals.EVENT_IDS.KILL_PLAYER, (id) => {
+ const killedPerson = this.stateBucket.currentGameState.people.find((person) => person.id === id);
+ if (killedPerson) {
+ killedPerson.out = true;
+ if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) {
+ toast(killedPerson.name + ' killed.', 'success', true, true, 'medium');
+ this.renderPlayersWithRoleAndAlignmentInfo(this.stateBucket.currentGameState.status === globals.STATUS.ENDED);
+ } else {
+ if (killedPerson.id === this.stateBucket.currentGameState.client.id) {
+ const clientUserType = document.getElementById('client-user-type');
+ if (clientUserType) {
+ clientUserType.innerText = globals.USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80';
+ }
+ this.updatePlayerCardToKilledState();
+ toast('You have been killed!', 'warning', true, true, 'medium');
+ } else {
+ toast(killedPerson.name + ' was killed!', 'warning', true, true, 'medium');
+ }
+ if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
+ this.removePlayerListEventListeners(false);
+ } else {
+ this.renderPlayersWithNoRoleInformationUnlessRevealed(false);
+ }
+ }
+ }
+ });
+
+ this.socket.on(globals.EVENT_IDS.REVEAL_PLAYER, (revealData) => {
+ const revealedPerson = this.stateBucket.currentGameState.people.find((person) => person.id === revealData.id);
+ if (revealedPerson) {
+ revealedPerson.revealed = true;
+ revealedPerson.gameRole = revealData.gameRole;
+ revealedPerson.alignment = revealData.alignment;
+ if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) {
+ toast(revealedPerson.name + ' revealed.', 'success', true, true, 'medium');
+ this.renderPlayersWithRoleAndAlignmentInfo(this.stateBucket.currentGameState.status === globals.STATUS.ENDED);
+ } else {
+ if (revealedPerson.id === this.stateBucket.currentGameState.client.id) {
+ toast('Your role has been revealed!', 'warning', true, true, 'medium');
+ } else {
+ toast(revealedPerson.name + ' was revealed as a ' + revealedPerson.gameRole + '!', 'warning', true, true, 'medium');
+ }
+ if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
+ this.renderPlayersWithNoRoleInformationUnlessRevealed(true);
+ } else {
+ this.renderPlayersWithNoRoleInformationUnlessRevealed(false);
+ }
+ }
+ }
+ });
+
+ if (this.stateBucket.currentGameState.timerParams) {
+ const timerWorker = new Worker(new URL('../../timer/Timer.js', import.meta.url));
+ const gameTimerManager = new GameTimerManager(stateBucket, this.socket);
+ gameTimerManager.attachTimerSocketListeners(this.socket, timerWorker);
}
}
@@ -222,96 +228,6 @@ export class GameStateRenderer {
this.stateBucket.currentGameState.people.length + ' Alive';
}
- removePlayerListEventListeners (removeEl = true) {
- document.querySelectorAll('.game-player').forEach((el) => {
- const pointer = el.dataset.pointer;
- if (pointer && this.killPlayerHandlers[pointer]) {
- el.removeEventListener('click', this.killPlayerHandlers[pointer]);
- delete this.killPlayerHandlers[pointer];
- }
- if (pointer && this.revealRoleHandlers[pointer]) {
- el.removeEventListener('click', this.revealRoleHandlers[pointer]);
- delete this.revealRoleHandlers[pointer];
- }
- if (removeEl) {
- el.remove();
- }
- });
- }
-
- renderPlayersWithNoRoleInformationUnlessRevealed (tempMod = false) {
- if (tempMod) {
- this.removePlayerListEventListeners();
- }
- document.querySelectorAll('.game-player').forEach((el) => el.remove());
- /* TODO: UX issue - it's easier to parse visually when players are sorted this way,
- but shifting players around when they are killed or revealed is bad UX for the moderator. */
- // sortPeopleByStatus(this.stateBucket.currentGameState.people);
- const modType = tempMod ? this.stateBucket.currentGameState.moderator.userType : null;
- this.renderGroupOfPlayers(
- this.stateBucket.currentGameState.people,
- this.killPlayerHandlers,
- this.revealRoleHandlers,
- this.stateBucket.currentGameState.accessCode,
- null,
- modType,
- this.socket
- );
- document.getElementById('players-alive-label').innerText =
- 'Players: ' + this.stateBucket.currentGameState.people.filter((person) => !person.out).length + ' / ' +
- this.stateBucket.currentGameState.people.length + ' Alive';
- }
-
- updatePlayerCardToKilledState () {
- document.querySelector('#role-image').classList.add('killed-card');
- document.getElementById('role-image').setAttribute(
- 'src',
- '../images/tombstone.png'
- );
- }
-
- displayAvailableModerators () {
- document.getElementById('transfer-mod-modal-content').innerText = '';
- document.querySelectorAll('.potential-moderator').forEach((el) => {
- const pointer = el.dataset.pointer;
- if (pointer && this.transferModHandlers[pointer]) {
- el.removeEventListener('click', this.transferModHandlers[pointer]);
- delete this.transferModHandlers[pointer];
- }
- el.remove();
- });
- renderPotentialMods(
- this.stateBucket.currentGameState,
- this.stateBucket.currentGameState.people,
- this.transferModHandlers,
- this.socket
- );
- renderPotentialMods( // spectators can also be made mods.
- this.stateBucket.currentGameState,
- this.stateBucket.currentGameState.spectators,
- this.transferModHandlers,
- this.socket
- );
-
- if (document.querySelectorAll('.potential-moderator').length === 0) {
- document.getElementById('transfer-mod-modal-content').innerText = 'There is nobody available to transfer to.';
- }
- }
-
- renderEndOfGame (gameState) {
- if (
- gameState.client.userType === globals.USER_TYPES.MODERATOR
- || gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
- ) {
- const restartGameContainer = document.createElement('div');
- restartGameContainer.innerHTML = HTMLFragments.RESTART_GAME_BUTTON;
- const button = restartGameContainer.querySelector('#restart-game');
- button.addEventListener('click', this.restartGameHandler);
- document.getElementById('end-of-game-buttons').prepend(restartGameContainer);
- }
- this.renderPlayersWithNoRoleInformationUnlessRevealed();
- }
-
renderGroupOfPlayers (
people,
killPlayerHandlers,
@@ -337,7 +253,7 @@ export class GameStateRenderer {
const roleElement = playerEl.querySelector('.game-player-role');
// Add role/alignment indicators if necessary
- if (moderatorType === globals.USER_TYPES.MODERATOR || player.revealed || this.stateBucket.currentGameState.status === globals.STATUS.ENDED) {
+ if (moderatorType === globals.USER_TYPES.MODERATOR || player.revealed) {
if (alignment === null) {
roleElement.classList.add(player.alignment);
} else {
@@ -386,69 +302,41 @@ export class GameStateRenderer {
document.getElementById(playerListContainerId).appendChild(playerEl);
}
}
-}
-function renderPotentialMods (gameState, group, transferModHandlers, socket) {
- const modalContent = document.getElementById('transfer-mod-modal-content');
- for (const member of group) {
- if ((member.out || member.userType === globals.USER_TYPES.SPECTATOR) && !(member.id === gameState.client.id)) {
- const container = document.createElement('div');
- container.classList.add('potential-moderator');
- container.setAttribute('tabindex', '0');
- container.dataset.pointer = member.id;
- container.innerText = member.name;
- transferModHandlers[member.id] = (e) => {
- if (e.type === 'click' || e.code === 'Enter') {
- ModalManager.dispelModal('transfer-mod-modal', 'transfer-mod-modal-background');
- Confirmation('Transfer moderator powers to \'' + member.name + '\'?', () => {
- const transferPrompt = document.getElementById('transfer-mod-prompt');
- if (transferPrompt !== null) {
- transferPrompt.innerHTML = '';
- }
- socket.emit(
- globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
- globals.EVENT_IDS.TRANSFER_MODERATOR,
- gameState.accessCode,
- { personId: member.id }
- );
- });
- }
- };
+ displayAvailableModerators () {
+ document.getElementById('transfer-mod-modal-content').innerText = '';
+ document.querySelectorAll('.potential-moderator').forEach((el) => {
+ const pointer = el.dataset.pointer;
+ if (pointer && this.transferModHandlers[pointer]) {
+ el.removeEventListener('click', this.transferModHandlers[pointer]);
+ delete this.transferModHandlers[pointer];
+ }
+ el.remove();
+ });
+ renderPotentialMods(
+ this.stateBucket.currentGameState,
+ this.stateBucket.currentGameState.people,
+ this.transferModHandlers,
+ this.socket
+ );
+ renderPotentialMods( // spectators can also be made mods.
+ this.stateBucket.currentGameState,
+ this.stateBucket.currentGameState.spectators,
+ this.transferModHandlers,
+ this.socket
+ );
- container.addEventListener('click', transferModHandlers[member.id]);
- container.addEventListener('keyup', transferModHandlers[member.id]);
- modalContent.appendChild(container);
+ if (document.querySelectorAll('.potential-moderator').length === 0) {
+ document.getElementById('transfer-mod-modal-content').innerText = 'There is nobody available to transfer to.';
}
}
-}
-function renderLobbyPerson (name, userType) {
- const el = document.createElement('div');
- const personNameEl = document.createElement('div');
- const personTypeEl = document.createElement('div');
- personNameEl.innerText = name;
- personTypeEl.innerText = userType + globals.USER_TYPE_ICONS[userType];
- el.classList.add('lobby-player');
-
- el.appendChild(personNameEl);
- el.appendChild(personTypeEl);
-
- return el;
-}
-
-function getGameSize (cards) {
- let quantity = 0;
- for (const card of cards) {
- quantity += card.quantity;
- }
-
- return quantity;
-}
-
-function removeExistingTitle () {
- const existingTitle = document.querySelector('#game-title h1');
- if (existingTitle) {
- existingTitle.remove();
+ updatePlayerCardToKilledState () {
+ document.querySelector('#role-image').classList.add('killed-card');
+ document.getElementById('role-image').setAttribute(
+ 'src',
+ '../images/tombstone.png'
+ );
}
}
@@ -503,21 +391,6 @@ function renderPlayerRole (gameState) {
});
}
-function insertPlaceholderButton (container, append, type) {
- const button = document.createElement('div');
- button.classList.add('placeholder-button');
- if (type === 'killed') {
- button.innerText = 'Killed';
- } else {
- button.innerText = 'Revealed';
- }
- if (append) {
- container.querySelector('.player-action-buttons').appendChild(button);
- } else {
- container.querySelector('.player-action-buttons').prepend(button);
- }
-}
-
function removeExistingPlayerElements (killPlayerHandlers, revealRoleHandlers) {
document.querySelectorAll('.game-player').forEach((el) => {
const pointer = el.dataset.pointer;
@@ -543,10 +416,63 @@ function createEndGamePromptComponent (socket, stateBucket) {
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.END_GAME,
- stateBucket.currentGameState.accessCode
+ stateBucket.currentGameState.accessCode,
+ null,
+ () => {
+ document.querySelector('#end-game-prompt')?.remove();
+ }
);
});
});
document.getElementById('game-content').appendChild(div);
}
}
+
+function insertPlaceholderButton (container, append, type) {
+ const button = document.createElement('div');
+ button.classList.add('placeholder-button');
+ if (type === 'killed') {
+ button.innerText = 'Killed';
+ } else {
+ button.innerText = 'Revealed';
+ }
+ if (append) {
+ container.querySelector('.player-action-buttons').appendChild(button);
+ } else {
+ container.querySelector('.player-action-buttons').prepend(button);
+ }
+}
+
+function renderPotentialMods (gameState, group, transferModHandlers, socket) {
+ const modalContent = document.getElementById('transfer-mod-modal-content');
+ for (const member of group) {
+ if ((member.out || member.userType === globals.USER_TYPES.SPECTATOR) && !(member.id === gameState.client.id)) {
+ const container = document.createElement('div');
+ container.classList.add('potential-moderator');
+ container.setAttribute('tabindex', '0');
+ container.dataset.pointer = member.id;
+ container.innerText = member.name;
+ transferModHandlers[member.id] = (e) => {
+ if (e.type === 'click' || e.code === 'Enter') {
+ ModalManager.dispelModal('transfer-mod-modal', 'transfer-mod-modal-background');
+ Confirmation('Transfer moderator powers to \'' + member.name + '\'?', () => {
+ const transferPrompt = document.getElementById('transfer-mod-prompt');
+ if (transferPrompt !== null) {
+ transferPrompt.innerHTML = '';
+ }
+ socket.emit(
+ globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
+ globals.EVENT_IDS.TRANSFER_MODERATOR,
+ gameState.accessCode,
+ { personId: member.id }
+ );
+ });
+ }
+ };
+
+ container.addEventListener('click', transferModHandlers[member.id]);
+ container.addEventListener('keyup', transferModHandlers[member.id]);
+ modalContent.appendChild(container);
+ }
+ }
+}
diff --git a/client/src/modules/game_state/states/Lobby.js b/client/src/modules/game_state/states/Lobby.js
new file mode 100644
index 0000000..10ce765
--- /dev/null
+++ b/client/src/modules/game_state/states/Lobby.js
@@ -0,0 +1,186 @@
+import { QRCode } from '../../third_party/qrcode.js';
+import { toast } from '../../front_end_components/Toast.js';
+import { globals } from '../../../config/globals.js';
+import { HTMLFragments } from '../../front_end_components/HTMLFragments.js';
+import { Confirmation } from '../../front_end_components/Confirmation.js';
+
+export class Lobby {
+ constructor (containerId, stateBucket, socket) {
+ this.stateBucket = stateBucket;
+ this.socket = socket;
+ this.container = document.getElementById(containerId);
+ this.container.innerHTML = HTMLFragments.LOBBY;
+
+ this.startGameHandler = (e) => {
+ e.preventDefault();
+ Confirmation('Start game and deal roles?', () => {
+ socket.emit(
+ globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
+ globals.EVENT_IDS.START_GAME,
+ stateBucket.currentGameState.accessCode,
+ null,
+ () => {
+ this.removeStartGameFunctionalityIfPresent();
+ }
+ );
+ });
+ };
+ }
+
+ populateHeader () {
+ const timeString = getTimeString(this.stateBucket.currentGameState);
+ const time = this.container.querySelector('#game-time');
+ time.innerText = timeString;
+
+ const linkContainer = this.container.querySelector('#game-link');
+ linkContainer.innerHTML = '

';
+ const link = window.location.protocol + '//' + window.location.host +
+ '/join/' + this.stateBucket.currentGameState.accessCode +
+ '?playerCount=' + this.stateBucket.currentGameState.gameSize +
+ '&timer=' + encodeURIComponent(timeString);
+ const linkDiv = document.createElement('div');
+ linkDiv.innerText = link;
+ linkContainer.prepend(linkDiv);
+ activateLink(linkContainer, link);
+
+ const playerCount = this.container.querySelector('#game-player-count');
+ playerCount.innerText = this.stateBucket.currentGameState.gameSize + ' Players';
+
+ const gameCode = this.container.querySelector('#game-code');
+ gameCode.innerHTML = 'Or enter this code on the homepage:
' +
+ this.stateBucket.currentGameState.accessCode + '';
+
+ QRCode.toCanvas(document.getElementById('canvas'), link, { scale: 2 }, function (error) {
+ if (error) console.error(error);
+ });
+ }
+
+ populatePlayers () {
+ document.querySelectorAll('.lobby-player').forEach((el) => el.remove());
+ const lobbyPlayersContainer = this.container.querySelector('#lobby-players');
+ if (this.stateBucket.currentGameState.moderator.userType === globals.USER_TYPES.MODERATOR) {
+ lobbyPlayersContainer.appendChild(
+ renderLobbyPerson(
+ this.stateBucket.currentGameState.moderator.name,
+ this.stateBucket.currentGameState.moderator.userType
+ )
+ );
+ }
+ for (const person of this.stateBucket.currentGameState.people) {
+ lobbyPlayersContainer.appendChild(renderLobbyPerson(person.name, person.userType));
+ }
+ const playerCount = this.stateBucket.currentGameState.people.length;
+ document.querySelector("label[for='lobby-players']").innerText =
+ 'Participants (' + playerCount + '/' + this.stateBucket.currentGameState.gameSize + ' Players)';
+ }
+
+ setSocketHandlers () {
+ this.socket.on(globals.EVENT_IDS.PLAYER_JOINED, (player, gameIsFull) => {
+ toast(player.name + ' joined!', 'success', false, true, 'short');
+ this.stateBucket.currentGameState.people.push(player);
+ this.stateBucket.currentGameState.isFull = gameIsFull;
+ this.populatePlayers();
+ if ((
+ this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR
+ || this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
+ )
+ ) {
+ this.displayStartGamePromptForModerators();
+ }
+ });
+
+ this.socket.on(globals.EVENT_IDS.NEW_SPECTATOR, (spectator) => {
+ this.stateBucket.currentGameState.spectators.push(spectator);
+ });
+
+ // this.socket.on(globals.EVENT_IDS.PLAYER_LEFT, (player) => {
+ // removeStartGameFunctionalityIfPresent(this.stateBucket.currentGameState, this.startGameHandler);
+ // toast(player.name + ' has left!', 'error', false, true, 'short');
+ // const index = this.stateBucket.currentGameState.people.findIndex(person => person.id === player.id);
+ // if (index >= 0) {
+ // this.stateBucket.currentGameState.people.splice(
+ // index,
+ // 1
+ // );
+ // this.populatePlayers();
+ // }
+ // });
+ }
+
+ displayStartGamePromptForModerators () {
+ const existingPrompt = document.getElementById('start-game-prompt');
+ if (existingPrompt) {
+ enableOrDisableStartButton(this.stateBucket.currentGameState, existingPrompt, this.startGameHandler);
+ } else {
+ const newPrompt = document.createElement('div');
+ newPrompt.setAttribute('id', 'start-game-prompt');
+ newPrompt.innerHTML = HTMLFragments.START_GAME_PROMPT;
+
+ document.body.appendChild(newPrompt);
+ enableOrDisableStartButton(this.stateBucket.currentGameState, newPrompt, this.startGameHandler);
+ }
+ }
+
+ removeStartGameFunctionalityIfPresent () {
+ document.querySelector('#start-game-prompt')?.removeEventListener('click', this.startGameHandler);
+ document.querySelector('#start-game-prompt')?.remove();
+ }
+}
+
+function enableOrDisableStartButton (gameState, buttonContainer, handler) {
+ if (gameState.isFull) {
+ buttonContainer.querySelector('#start-game-button').addEventListener('click', handler);
+ buttonContainer.querySelector('#start-game-button').classList.remove('disabled');
+ } else {
+ buttonContainer.querySelector('#start-game-button').removeEventListener('click', handler);
+ buttonContainer.querySelector('#start-game-button').classList.add('disabled');
+ }
+}
+
+function activateLink (linkContainer, link) {
+ const linkCopyHandler = (e) => {
+ if (e.type === 'click' || e.code === 'Enter') {
+ navigator.clipboard.writeText(link)
+ .then(() => {
+ toast('Link copied!', 'success', true);
+ });
+ }
+ };
+ linkContainer.addEventListener('click', linkCopyHandler);
+ linkContainer.addEventListener('keyup', linkCopyHandler);
+}
+
+function getTimeString (gameState) {
+ let timeString = '';
+ if (gameState.timerParams) {
+ const hours = gameState.timerParams.hours;
+ const minutes = gameState.timerParams.minutes;
+ if (hours) {
+ timeString += hours > 1
+ ? hours + ' hours '
+ : hours + ' hour ';
+ }
+ if (minutes) {
+ timeString += minutes > 1
+ ? minutes + ' minutes '
+ : minutes + ' minute ';
+ }
+ return timeString;
+ } else {
+ return 'untimed';
+ }
+}
+
+function renderLobbyPerson (name, userType) {
+ const el = document.createElement('div');
+ const personNameEl = document.createElement('div');
+ const personTypeEl = document.createElement('div');
+ personNameEl.innerText = name;
+ personTypeEl.innerText = userType + globals.USER_TYPE_ICONS[userType];
+ el.classList.add('lobby-player');
+
+ el.appendChild(personNameEl);
+ el.appendChild(personTypeEl);
+
+ return el;
+}
diff --git a/client/src/modules/page_handlers/gameHandler.js b/client/src/modules/page_handlers/gameHandler.js
index 69c71b9..b2421ea 100644
--- a/client/src/modules/page_handlers/gameHandler.js
+++ b/client/src/modules/page_handlers/gameHandler.js
@@ -1,12 +1,13 @@
import { injectNavbar } from '../front_end_components/Navbar.js';
import { stateBucket } from '../game_state/StateBucket.js';
-import { GameTimerManager } from '../timer/GameTimerManager.js';
-import { GameStateRenderer } from '../game_state/GameStateRenderer.js';
import { UserUtility } from '../utility/UserUtility.js';
import { toast } from '../front_end_components/Toast.js';
import { globals } from '../../config/globals.js';
import { HTMLFragments } from '../front_end_components/HTMLFragments.js';
import { ModalManager } from '../front_end_components/ModalManager.js';
+import { Lobby } from '../game_state/states/Lobby.js';
+import { InProgress } from '../game_state/states/InProgress.js';
+import { Ended } from '../game_state/states/Ended.js';
export const gameHandler = async (socket, XHRUtility, window, gameDOM) => {
document.body.innerHTML = gameDOM + document.body.innerHTML;
@@ -22,16 +23,10 @@ export const gameHandler = async (socket, XHRUtility, window, gameDOM) => {
});
stateBucket.environment = response.content;
- const timerWorker = new Worker(new URL('../timer/Timer.js', import.meta.url));
- const gameTimerManager = new GameTimerManager(stateBucket, socket);
- const gameStateRenderer = new GameStateRenderer(stateBucket, socket);
socket.on('connect', function () {
syncWithGame(
stateBucket,
- gameTimerManager,
- gameStateRenderer,
- timerWorker,
socket,
UserUtility.validateAnonUserSignature(response.content),
window
@@ -46,10 +41,10 @@ export const gameHandler = async (socket, XHRUtility, window, gameDOM) => {
toast('Disconnected. Attempting reconnect...', 'error', true, false);
});
- setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWorker, gameTimerManager);
+ setClientSocketHandlers(stateBucket, socket);
};
-function syncWithGame (stateBucket, gameTimerManager, gameStateRenderer, timerWorker, socket, cookie, window) {
+function syncWithGame (stateBucket, socket, cookie, window) {
const splitUrl = window.location.href.split('/game/');
const accessCode = splitUrl[1];
if (/^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === globals.ACCESS_CODE_LENGTH) {
@@ -62,7 +57,7 @@ function syncWithGame (stateBucket, gameTimerManager, gameStateRenderer, timerWo
document.querySelector('.spinner-background')?.remove();
document.getElementById('game-content').innerHTML = HTMLFragments.INITIAL_GAME_DOM;
toast('You are connected.', 'success', true, true, 'short');
- processGameState(stateBucket.currentGameState, cookie, socket, gameStateRenderer, gameTimerManager, timerWorker, true, true);
+ processGameState(stateBucket.currentGameState, cookie, socket, true, true);
}
});
} else {
@@ -74,9 +69,6 @@ function processGameState (
currentGameState,
userId,
socket,
- gameStateRenderer,
- gameTimerManager,
- timerWorker,
refreshPrompt = true,
animateContainer = false
) {
@@ -92,66 +84,38 @@ function processGameState (
if (animateContainer) {
containerAnimation.play();
}
+
displayClientInfo(currentGameState.client.name, currentGameState.client.userType);
- if (refreshPrompt) {
- removeStartGameFunctionalityIfPresent(gameStateRenderer);
- document.querySelector('#end-game-prompt')?.remove();
- }
+
switch (currentGameState.status) {
case globals.STATUS.LOBBY:
- document.getElementById('game-state-container').innerHTML = HTMLFragments.LOBBY;
- gameStateRenderer.renderLobbyHeader();
- gameStateRenderer.renderLobbyPlayers();
+ const lobby = new Lobby('game-state-container', stateBucket, socket);
+ if (refreshPrompt) {
+ lobby.removeStartGameFunctionalityIfPresent();
+ }
+ lobby.populateHeader();
+ lobby.populatePlayers();
+ lobby.setSocketHandlers();
if ((
currentGameState.client.userType === globals.USER_TYPES.MODERATOR
|| currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
)
&& refreshPrompt
) {
- displayStartGamePromptForModerators(currentGameState, gameStateRenderer);
+ lobby.displayStartGamePromptForModerators();
}
break;
case globals.STATUS.IN_PROGRESS:
- gameStateRenderer.renderGameHeader();
- switch (currentGameState.client.userType) {
- case globals.USER_TYPES.PLAYER:
- document.getElementById('game-state-container').innerHTML = HTMLFragments.PLAYER_GAME_VIEW;
- gameStateRenderer.renderPlayerView();
- break;
- case globals.USER_TYPES.KILLED_PLAYER:
-
- document.getElementById('game-state-container').innerHTML = HTMLFragments.PLAYER_GAME_VIEW;
- gameStateRenderer.renderPlayerView(true);
- break;
- case globals.USER_TYPES.MODERATOR:
- document.getElementById('transfer-mod-prompt').innerHTML = HTMLFragments.TRANSFER_MOD_MODAL;
- document.getElementById('game-state-container').innerHTML = HTMLFragments.MODERATOR_GAME_VIEW;
- gameStateRenderer.renderModeratorView();
- break;
- case globals.USER_TYPES.TEMPORARY_MODERATOR:
- document.getElementById('transfer-mod-prompt').innerHTML = HTMLFragments.TRANSFER_MOD_MODAL;
- document.getElementById('game-state-container').innerHTML = HTMLFragments.TEMP_MOD_GAME_VIEW;
- gameStateRenderer.renderTempModView();
- break;
- case globals.USER_TYPES.SPECTATOR:
- document.getElementById('game-state-container').innerHTML = HTMLFragments.SPECTATOR_GAME_VIEW;
- gameStateRenderer.renderSpectatorView();
- break;
- default:
- break;
- }
- if (currentGameState.timerParams) {
- socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.GET_TIME_REMAINING, currentGameState.accessCode);
- } else {
- document.querySelector('#game-timer')?.remove();
- document.querySelector('#timer-container-moderator')?.remove();
- document.querySelector('label[for="game-timer"]')?.remove();
+ if (refreshPrompt) {
+ document.querySelector('#end-game-prompt')?.remove();
}
+ const inProgressGame = new InProgress('game-state-container', stateBucket, socket);
+ inProgressGame.setSocketHandlers();
+ inProgressGame.setUserView(currentGameState.client.userType);
break;
case globals.STATUS.ENDED: {
- const container = document.getElementById('game-state-container');
- container.innerHTML = HTMLFragments.END_OF_GAME_VIEW;
- gameStateRenderer.renderEndOfGame(currentGameState);
+ const ended = new Ended('game-state-container', stateBucket, socket);
+ ended.renderEndOfGame(currentGameState);
break;
}
default:
@@ -207,38 +171,8 @@ function displayClientInfo (name, userType) {
document.getElementById('client-user-type').innerText += globals.USER_TYPE_ICONS[userType];
}
-function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerWorker, gameTimerManager) {
- socket.on(globals.EVENT_IDS.PLAYER_JOINED, (player, gameIsFull) => {
- toast(player.name + ' joined!', 'success', false, true, 'short');
- stateBucket.currentGameState.people.push(player);
- stateBucket.currentGameState.isFull = gameIsFull;
- gameStateRenderer.renderLobbyPlayers();
- if ((
- stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR
- || stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
- )
- ) {
- displayStartGamePromptForModerators(stateBucket.currentGameState, gameStateRenderer);
- }
- });
-
- socket.on(globals.EVENT_IDS.NEW_SPECTATOR, (spectator) => {
- stateBucket.currentGameState.spectators.push(spectator);
- });
-
- socket.on(globals.EVENT_IDS.PLAYER_LEFT, (player) => {
- removeStartGameFunctionalityIfPresent(gameStateRenderer);
- toast(player.name + ' has left!', 'error', false, true, 'short');
- const index = stateBucket.currentGameState.people.findIndex(person => person.id === player.id);
- if (index >= 0) {
- stateBucket.currentGameState.people.splice(
- index,
- 1
- );
- gameStateRenderer.renderLobbyPlayers();
- }
- });
-
+// Should be reserved for socket events not specific to any one game state (Lobby, In Progress, etc.)
+function setClientSocketHandlers (stateBucket, socket) {
socket.on(globals.EVENT_IDS.START_GAME, () => {
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
@@ -251,9 +185,6 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW
stateBucket.currentGameState,
gameState.client.cookie,
socket,
- gameStateRenderer,
- gameTimerManager,
- timerWorker,
true,
true
);
@@ -273,9 +204,6 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW
stateBucket.currentGameState,
gameState.client.cookie,
socket,
- gameStateRenderer,
- gameTimerManager,
- timerWorker,
true,
true
);
@@ -283,76 +211,6 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW
);
});
- if (timerWorker && gameTimerManager) {
- gameTimerManager.attachTimerSocketListeners(socket, timerWorker, gameStateRenderer);
- }
-
- socket.on(globals.EVENT_IDS.KILL_PLAYER, (id) => {
- const killedPerson = stateBucket.currentGameState.people.find((person) => person.id === id);
- if (killedPerson) {
- killedPerson.out = true;
- if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) {
- toast(killedPerson.name + ' killed.', 'success', true, true, 'medium');
- gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(stateBucket.currentGameState.status === globals.STATUS.ENDED);
- } else {
- if (killedPerson.id === stateBucket.currentGameState.client.id) {
- const clientUserType = document.getElementById('client-user-type');
- if (clientUserType) {
- clientUserType.innerText = globals.USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80';
- }
- gameStateRenderer.updatePlayerCardToKilledState();
- toast('You have been killed!', 'warning', true, true, 'medium');
- } else {
- toast(killedPerson.name + ' was killed!', 'warning', true, true, 'medium');
- }
- if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
- gameStateRenderer.removePlayerListEventListeners(false);
- } else {
- gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(false);
- }
- }
- }
- });
-
- socket.on(globals.EVENT_IDS.REVEAL_PLAYER, (revealData) => {
- const revealedPerson = stateBucket.currentGameState.people.find((person) => person.id === revealData.id);
- if (revealedPerson) {
- revealedPerson.revealed = true;
- revealedPerson.gameRole = revealData.gameRole;
- revealedPerson.alignment = revealData.alignment;
- if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) {
- toast(revealedPerson.name + ' revealed.', 'success', true, true, 'medium');
- gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(stateBucket.currentGameState.status === globals.STATUS.ENDED);
- } else {
- if (revealedPerson.id === stateBucket.currentGameState.client.id) {
- toast('Your role has been revealed!', 'warning', true, true, 'medium');
- } else {
- toast(revealedPerson.name + ' was revealed as a ' + revealedPerson.gameRole + '!', 'warning', true, true, 'medium');
- }
- if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
- gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true);
- } else {
- gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(false);
- }
- }
- }
- });
-
- socket.on(globals.EVENT_IDS.CHANGE_NAME, (personId, name) => {
- propagateNameChange(stateBucket.currentGameState, name, personId);
- updateDOMWithNameChange(stateBucket.currentGameState, gameStateRenderer);
- processGameState(
- stateBucket.currentGameState,
- stateBucket.currentGameState.client.cookie,
- socket,
- gameStateRenderer,
- gameTimerManager,
- timerWorker,
- false,
- false
- );
- });
-
socket.on(globals.COMMANDS.END_GAME, (people) => {
stateBucket.currentGameState.people = people;
stateBucket.currentGameState.status = globals.STATUS.ENDED;
@@ -360,81 +218,8 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW
stateBucket.currentGameState,
stateBucket.currentGameState.client.cookie,
socket,
- gameStateRenderer,
- gameTimerManager,
- timerWorker,
true,
true
);
});
}
-
-function displayStartGamePromptForModerators (gameState, gameStateRenderer) {
- const existingPrompt = document.getElementById('start-game-prompt');
- if (existingPrompt) {
- enableOrDisableStartButton(gameState, existingPrompt, gameStateRenderer.startGameHandler);
- } else {
- const newPrompt = document.createElement('div');
- newPrompt.setAttribute('id', 'start-game-prompt');
- newPrompt.innerHTML = HTMLFragments.START_GAME_PROMPT;
-
- document.body.appendChild(newPrompt);
- enableOrDisableStartButton(gameState, newPrompt, gameStateRenderer.startGameHandler);
- }
-}
-
-function enableOrDisableStartButton (gameState, buttonContainer, handler) {
- if (gameState.isFull) {
- buttonContainer.querySelector('#start-game-button').addEventListener('click', handler);
- buttonContainer.querySelector('#start-game-button').classList.remove('disabled');
- } else {
- buttonContainer.querySelector('#start-game-button').removeEventListener('click', handler);
- buttonContainer.querySelector('#start-game-button').classList.add('disabled');
- }
-}
-
-function removeStartGameFunctionalityIfPresent (gameStateRenderer) {
- document.querySelector('#start-game-prompt')?.removeEventListener('click', gameStateRenderer.startGameHandler);
- document.querySelector('#start-game-prompt')?.remove();
-}
-
-function propagateNameChange (gameState, name, personId) {
- if (gameState.client.id === personId) {
- gameState.client.name = name;
- }
- const matchingPerson = gameState.people.find((person) => person.id === personId);
- if (matchingPerson) {
- matchingPerson.name = name;
- }
-
- if (gameState.moderator.id === personId) {
- gameState.moderator.name = name;
- }
-
- const matchingSpectator = gameState.spectators?.find((spectator) => spectator.id === personId);
- if (matchingSpectator) {
- matchingSpectator.name = name;
- }
-}
-
-function updateDOMWithNameChange (gameState, gameStateRenderer) {
- 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/modules/timer/GameTimerManager.js b/client/src/modules/timer/GameTimerManager.js
index 71defae..ef43c5a 100644
--- a/client/src/modules/timer/GameTimerManager.js
+++ b/client/src/modules/timer/GameTimerManager.js
@@ -99,7 +99,7 @@ export class GameTimerManager {
timer.innerText = returnHumanReadableTime(0, true);
}
- attachTimerSocketListeners (socket, timerWorker, gameStateRenderer) {
+ attachTimerSocketListeners (socket, timerWorker) {
if (!socket.hasListeners(globals.COMMANDS.PAUSE_TIMER)) {
socket.on(globals.COMMANDS.PAUSE_TIMER, (timeRemaining) => {
this.pauseGameTimer(timerWorker, timeRemaining);
diff --git a/karma.conf.js b/karma.conf.js
index 5c29d9e..2ee5482 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -5,7 +5,7 @@ module.exports = function(config) {
files: [
{ pattern: 'spec/e2e/*.js', type: 'module' },
{ pattern: 'spec/support/*.js', type: 'module' },
- { pattern: 'client/src/modules/*/*.js', type: 'module', included: true, served: true },
+ { pattern: 'client/src/modules/**/*.js', type: 'module', included: true, served: true },
{ pattern: 'client/src/config/*.js', type: 'module', included: true, served: true },
{ pattern: 'client/src/model/*.js', type: 'module', included: true, served: true },
{ pattern: 'client/src/view_templates/*.js', type: 'module', included: true, served: true }
diff --git a/server/model/Game.js b/server/model/Game.js
index 58d70a1..c6a5bcc 100644
--- a/server/model/Game.js
+++ b/server/model/Game.js
@@ -16,6 +16,10 @@ class Game {
this.moderator = moderator;
this.people = people;
this.deck = deck;
+ this.gameSize = deck.reduce(
+ (accumulator, currentValue) => accumulator + currentValue.quantity,
+ 0
+ );
this.hasTimer = hasTimer;
this.hasDedicatedModerator = hasDedicatedModerator;
this.originalModeratorId = originalModeratorId;
diff --git a/server/modules/GameStateCurator.js b/server/modules/GameStateCurator.js
index 8d24ceb..81a8a66 100644
--- a/server/modules/GameStateCurator.js
+++ b/server/modules/GameStateCurator.js
@@ -66,6 +66,7 @@ function getGameStateBasedOnPermissions (game, person, gameRunner) {
moderator: GameStateCurator.mapPerson(game.moderator),
client: client,
deck: game.deck,
+ gameSize: game.gameSize,
people: game.people
.filter((person) => {
return person.assigned === true;
@@ -88,6 +89,7 @@ function getGameStateBasedOnPermissions (game, person, gameRunner) {
moderator: GameStateCurator.mapPerson(game.moderator),
client: client,
deck: game.deck,
+ gameSize: game.gameSize,
people: GameStateCurator.mapPeopleForModerator(game.people, client),
timerParams: game.timerParams,
isFull: game.isFull,
@@ -100,6 +102,7 @@ function getGameStateBasedOnPermissions (game, person, gameRunner) {
moderator: GameStateCurator.mapPerson(game.moderator),
client: client,
deck: game.deck,
+ gameSize: game.gameSize,
people: game.people
.filter((person) => {
return person.assigned === true;
@@ -115,6 +118,7 @@ function getGameStateBasedOnPermissions (game, person, gameRunner) {
moderator: GameStateCurator.mapPerson(game.moderator),
client: client,
deck: game.deck,
+ gameSize: game.gameSize,
people: game.people
.filter((person) => {
return person.assigned === true;
diff --git a/server/modules/SocketManager.js b/server/modules/SocketManager.js
index ad2133b..515431c 100644
--- a/server/modules/SocketManager.js
+++ b/server/modules/SocketManager.js
@@ -45,7 +45,7 @@ class SocketManager {
};
registerHandlers = (namespace, socket, gameManager) => {
- socket.on(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, async (eventId, accessCode, args, ackFn) => {
+ socket.on(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, async (eventId, accessCode, args = null, ackFn = null) => {
const game = gameManager.activeGameRunner.activeGames.get(accessCode);
if (game) {
switch (eventId) {
@@ -63,6 +63,7 @@ class SocketManager {
break;
case EVENT_IDS.START_GAME:
gameManager.startGame(game, namespace);
+ ackFn();
break;
case EVENT_IDS.PAUSE_TIMER:
gameManager.pauseTimer(game, this.logger);
@@ -91,6 +92,7 @@ class SocketManager {
break;
case EVENT_IDS.END_GAME:
gameManager.endGame(game);
+ ackFn();
break;
default:
break;