From 25897f985f5378f499787f25c090bbf8b125a74b Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Tue, 13 Dec 2022 14:12:15 -0500 Subject: [PATCH] refactor GameStateRenderer, split functionality by game state --- client/src/modules/game_state/states/Ended.js | 102 ++++ .../InProgress.js} | 542 ++++++++---------- client/src/modules/game_state/states/Lobby.js | 222 +++++++ .../src/modules/page_handlers/gameHandler.js | 265 +-------- client/src/modules/timer/GameTimerManager.js | 2 +- karma.conf.js | 2 +- server/model/Game.js | 4 + server/modules/GameStateCurator.js | 4 + server/modules/SocketManager.js | 4 +- 9 files changed, 596 insertions(+), 551 deletions(-) create mode 100644 client/src/modules/game_state/states/Ended.js rename client/src/modules/game_state/{GameStateRenderer.js => states/InProgress.js} (65%) create mode 100644 client/src/modules/game_state/states/Lobby.js 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..d1c27e0 --- /dev/null +++ b/client/src/modules/game_state/states/Ended.js @@ -0,0 +1,102 @@ +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'; + +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 = (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'); + }); + }; + } + + 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.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..0c61820 --- /dev/null +++ b/client/src/modules/game_state/states/Lobby.js @@ -0,0 +1,222 @@ +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.components = { + HTML: + `
+
+ + +
+
+
+ +
+
+ clock +
+
+
+ person +
+
+
+
+
+ +
+
+
+
+ +
+
+ +
` + }; + + 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(); + } + ); + }); + }; + + this.container.innerHTML = this.components.HTML; + } + + 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 = '\'copy\'/'; + 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..0cabb91 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..1e6851b 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;