diff --git a/client/modules/GameStateRenderer.js b/client/modules/GameStateRenderer.js index 79fe67c..ed237ff 100644 --- a/client/modules/GameStateRenderer.js +++ b/client/modules/GameStateRenderer.js @@ -4,27 +4,33 @@ import {templates} from "./Templates.js"; import {ModalManager} from "./ModalManager.js"; export class GameStateRenderer { - constructor(gameState, socket) { - this.gameState = gameState; + constructor(stateBucket, socket) { + this.stateBucket = stateBucket; this.socket = socket; this.killPlayerHandlers = {}; this.revealRoleHandlers = {}; this.transferModHandlers = {}; - this.cardFlipped = false; } renderLobbyPlayers() { document.querySelectorAll('.lobby-player').forEach((el) => el.remove()) let lobbyPlayersContainer = document.getElementById("lobby-players"); - if (this.gameState.client.userType === globals.USER_TYPES.PLAYER && this.gameState.moderator.userType === globals.USER_TYPES.MODERATOR) { - lobbyPlayersContainer.appendChild(renderLobbyPerson(this.gameState.moderator.name, this.gameState.moderator.userType)) + if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.PLAYER + && this.stateBucket.currentGameState.moderator.userType === globals.USER_TYPES.MODERATOR + ) { + lobbyPlayersContainer.appendChild( + renderLobbyPerson( + this.stateBucket.currentGameState.moderator.name, + this.stateBucket.currentGameState.moderator.userType + ) + ) } - for (let person of this.gameState.people) { + for (let person of this.stateBucket.currentGameState.people) { lobbyPlayersContainer.appendChild(renderLobbyPerson(person.name,person.userType)) } - let playerCount = this.gameState.people.length; + let playerCount = this.stateBucket.currentGameState.people.length; document.querySelector("label[for='lobby-players']").innerText = - "People (" + playerCount + "/" + getGameSize(this.gameState.deck) + " Players)"; + "People (" + playerCount + "/" + getGameSize(this.stateBucket.currentGameState.deck) + " Players)"; } renderLobbyHeader() { @@ -46,7 +52,7 @@ export class GameStateRenderer { renderLobbyFooter() { let gameDeckContainer = document.getElementById("game-deck"); - for (let card of this.gameState.deck) { + for (let card of this.stateBucket.currentGameState.deck) { let cardEl = document.createElement("div"); cardEl.innerText = card.quantity + 'x ' + card.role; cardEl.classList.add('lobby-card') @@ -84,7 +90,7 @@ export class GameStateRenderer { div.innerHTML = templates.END_GAME_PROMPT; document.body.appendChild(div); - renderPlayerRole(this.gameState); + renderPlayerRole(this.stateBucket.currentGameState); this.renderPlayersWithNoRoleInformationUnlessRevealed(true); } @@ -95,7 +101,7 @@ export class GameStateRenderer { clientUserType.innerText = globals.USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80' } } - renderPlayerRole(this.gameState); + renderPlayerRole(this.stateBucket.currentGameState); this.renderPlayersWithNoRoleInformationUnlessRevealed(false); } @@ -124,30 +130,31 @@ export class GameStateRenderer { } el.remove(); }); - this.gameState.people.sort((a, b) => { + this.stateBucket.currentGameState.people.sort((a, b) => { return a.name >= b.name ? 1 : -1; }); - let teamGood = this.gameState.people.filter((person) => person.alignment === globals.ALIGNMENT.GOOD); - let teamEvil = this.gameState.people.filter((person) => person.alignment === globals.ALIGNMENT.EVIL); + let teamGood = this.stateBucket.currentGameState.people.filter((person) => person.alignment === globals.ALIGNMENT.GOOD); + let teamEvil = this.stateBucket.currentGameState.people.filter((person) => person.alignment === globals.ALIGNMENT.EVIL); renderGroupOfPlayers( teamEvil, this.killPlayerHandlers, this.revealRoleHandlers, - this.gameState.accessCode, + this.stateBucket.currentGameState.accessCode, globals.ALIGNMENT.EVIL, - this.gameState.moderator.userType, + this.stateBucket.currentGameState.moderator.userType, this.socket ); renderGroupOfPlayers( teamGood, this.killPlayerHandlers, this.revealRoleHandlers, - this.gameState.accessCode, + this.stateBucket.currentGameState.accessCode, globals.ALIGNMENT.GOOD, - this.gameState.moderator.userType, + this.stateBucket.currentGameState.moderator.userType, this.socket); document.getElementById("players-alive-label").innerText = - 'Players: ' + this.gameState.people.filter((person) => !person.out).length + ' / ' + this.gameState.people.length + ' Alive'; + 'Players: ' + this.stateBucket.currentGameState.people.filter((person) => !person.out).length + ' / ' + + this.stateBucket.currentGameState.people.length + ' Alive'; } @@ -167,19 +174,20 @@ export class GameStateRenderer { }); } document.querySelectorAll('.game-player').forEach((el) => el.remove()); - sortPeopleByStatus(this.gameState.people); - let modType = tempMod ? this.gameState.moderator.userType : null; + sortPeopleByStatus(this.stateBucket.currentGameState.people); + let modType = tempMod ? this.stateBucket.currentGameState.moderator.userType : null; renderGroupOfPlayers( - this.gameState.people, + this.stateBucket.currentGameState.people, this.killPlayerHandlers, this.revealRoleHandlers, - this.gameState.accessCode, + this.stateBucket.currentGameState.accessCode, null, modType, this.socket ); document.getElementById("players-alive-label").innerText = - 'Players: ' + this.gameState.people.filter((person) => !person.out).length + ' / ' + this.gameState.people.length + ' Alive'; + 'Players: ' + this.stateBucket.currentGameState.people.filter((person) => !person.out).length + ' / ' + + this.stateBucket.currentGameState.people.length + ' Alive'; } @@ -202,8 +210,20 @@ export class GameStateRenderer { }); let modalContent = document.getElementById("transfer-mod-form-content"); if (modalContent) { - renderPotentialMods(this.gameState, this.gameState.people, this.transferModHandlers, modalContent, this.socket); - renderPotentialMods(this.gameState, this.gameState.spectators, this.transferModHandlers, modalContent, this.socket); + renderPotentialMods( + this.stateBucket.currentGameState, + this.stateBucket.currentGameState.people, + this.transferModHandlers, + modalContent, + this.socket + ); + renderPotentialMods( // spectators can also be made mods. + this.stateBucket.currentGameState, + this.stateBucket.currentGameState.spectators, + this.transferModHandlers, + modalContent, + this.socket + ); } } @@ -272,7 +292,15 @@ function removeExistingTitle() { } // TODO: refactor to reduce the cyclomatic complexity of this function -function renderGroupOfPlayers(people, killPlayerHandlers, revealRoleHandlers, accessCode=null, alignment=null, moderatorType, socket=null) { +function renderGroupOfPlayers( + people, + killPlayerHandlers, + revealRoleHandlers, + accessCode=null, + alignment=null, + moderatorType, + socket=null +) { for (let player of people) { let container = document.createElement("div"); container.classList.add('game-player'); diff --git a/client/modules/GameTimerManager.js b/client/modules/GameTimerManager.js index b350e51..cbd73e0 100644 --- a/client/modules/GameTimerManager.js +++ b/client/modules/GameTimerManager.js @@ -1,21 +1,21 @@ import {globals} from "../config/globals.js"; export class GameTimerManager { - constructor(gameState, socket) { - this.gameState = gameState; + constructor(stateBucket, socket) { + this.stateBucket = stateBucket; this.playListener = () => { - socket.emit(globals.COMMANDS.RESUME_TIMER, this.gameState.accessCode); + socket.emit(globals.COMMANDS.RESUME_TIMER, this.stateBucket.currentGameState.accessCode); } this.pauseListener = () => { - socket.emit(globals.COMMANDS.PAUSE_TIMER, this.gameState.accessCode); + socket.emit(globals.COMMANDS.PAUSE_TIMER, this.stateBucket.currentGameState.accessCode); } } resumeGameTimer(totalTime, tickRate, soundManager, timerWorker) { if (window.Worker) { if ( - this.gameState.client.userType === globals.USER_TYPES.MODERATOR - || this.gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR + this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR + || this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR ) { this.swapToPauseButton(); } @@ -41,8 +41,8 @@ export class GameTimerManager { pauseGameTimer(timerWorker, timeRemaining) { if (window.Worker) { if ( - this.gameState.client.userType === globals.USER_TYPES.MODERATOR - || this.gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR + this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR + || this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR ) { this.swapToPlayButton(); } @@ -58,8 +58,8 @@ export class GameTimerManager { displayPausedTime(time) { if ( - this.gameState.client.userType === globals.USER_TYPES.MODERATOR - || this.gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR + this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR + || this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR ) { this.swapToPlayButton(); } diff --git a/client/modules/StateBucket.js b/client/modules/StateBucket.js new file mode 100644 index 0000000..62c624a --- /dev/null +++ b/client/modules/StateBucket.js @@ -0,0 +1,7 @@ +/* It started getting confusing where I am reading/writing to the game state, and thus the state started to get inconsistent. + Creating a bucket to hold it so I can overwrite the gameState object whilst still preserving a reference to the containing bucket. + */ +export const stateBucket = { + currentGameState: null, + timerWorker: null +} diff --git a/client/scripts/game.js b/client/scripts/game.js index 62618a5..ddc253a 100644 --- a/client/scripts/game.js +++ b/client/scripts/game.js @@ -5,6 +5,7 @@ import {GameStateRenderer} from "../modules/GameStateRenderer.js"; import {cancelCurrentToast, toast} from "../modules/Toast.js"; import {GameTimerManager} from "../modules/GameTimerManager.js"; import {ModalManager} from "../modules/ModalManager.js"; +import {stateBucket} from "../modules/StateBucket.js"; export const game = () => { let timerWorker; @@ -29,15 +30,28 @@ function prepareGamePage(environment, socket, timerWorker) { const accessCode = splitUrl[1]; if (/^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === globals.ACCESS_CODE_LENGTH) { socket.emit(globals.COMMANDS.FETCH_GAME_STATE, accessCode, userId, function (gameState) { - let currentGameState = gameState; + stateBucket.currentGameState = gameState; document.querySelector('.spinner-container')?.remove(); document.querySelector('.spinner-background')?.remove(); + if (gameState === null) { window.location = '/not-found?reason=' + encodeURIComponent('game-not-found'); - } else if (!gameState.client.hasEnteredName) { - userId = gameState.client.cookie; - UserUtility.setAnonymousUserId(userId, environment); - document.getElementById("game-content").innerHTML = templates.NAME_CHANGE_MODAL; + } + + document.getElementById("game-content").innerHTML = templates.INITIAL_GAME_DOM; + toast('You are connected.', 'success', true, true, 2); + console.log(gameState); + userId = gameState.client.cookie; + UserUtility.setAnonymousUserId(userId, environment); + let gameStateRenderer = new GameStateRenderer(stateBucket, socket); + let gameTimerManager; + if (stateBucket.currentGameState.timerParams) { + gameTimerManager = new GameTimerManager(stateBucket, socket); + } + initializeGame(stateBucket, socket, timerWorker, userId, gameStateRenderer, gameTimerManager); + + if (!gameState.client.hasEnteredName) { + document.getElementById("prompt").innerHTML = templates.NAME_CHANGE_MODAL; document.getElementById("change-name-form").onsubmit = (e) => { e.preventDefault(); let name = document.getElementById("player-new-name").value; @@ -50,22 +64,14 @@ function prepareGamePage(environment, socket, timerWorker) { case "changed": ModalManager.dispelModal("change-name-modal", "change-name-modal-background") toast('Name set.', 'success', true, true, 5); - document.getElementById("game-content").innerHTML = templates.INITIAL_GAME_DOM; - propagateNameChange(currentGameState, name, currentGameState.client.id); - initializeGame(currentGameState, socket, timerWorker, userId); + propagateNameChange(stateBucket.currentGameState, name, stateBucket.currentGameState.client.id); + processGameState(stateBucket.currentGameState, userId, socket, gameStateRenderer); } }) } else { toast("Name must be fewer than 30 characters.", 'error', true, true, 8); } } - } else { - document.getElementById("game-content").innerHTML = templates.INITIAL_GAME_DOM; - toast('You are connected.', 'success', true, true, 2); - console.log(gameState); - userId = gameState.client.cookie; - UserUtility.setAnonymousUserId(userId, environment); - initializeGame(gameState, socket, timerWorker, userId); } }); } else { @@ -73,14 +79,9 @@ function prepareGamePage(environment, socket, timerWorker) { } } -function initializeGame(currentGameState, socket, timerWorker, userId) { - let gameStateRenderer = new GameStateRenderer(currentGameState, socket); - let gameTimerManager; - if (currentGameState.timerParams) { - gameTimerManager = new GameTimerManager(currentGameState, socket); - } - setClientSocketHandlers(currentGameState, gameStateRenderer, socket, timerWorker, gameTimerManager); - processGameState(currentGameState, userId, socket, gameStateRenderer); +function initializeGame(stateBucket, socket, timerWorker, userId, gameStateRenderer, gameTimerManager) { + setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWorker, gameTimerManager); + processGameState(stateBucket.currentGameState, userId, socket, gameStateRenderer); } function processGameState (currentGameState, userId, socket, gameStateRenderer) { @@ -97,7 +98,7 @@ function processGameState (currentGameState, userId, socket, gameStateRenderer) || currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR ) ) { - displayStartGamePromptForModerators(gameStateRenderer, socket); + displayStartGamePromptForModerators(currentGameState, socket); } break; case globals.STATUS.IN_PROGRESS: @@ -144,20 +145,20 @@ function displayClientInfo(name, userType) { document.getElementById("client-user-type").innerText += globals.USER_TYPE_ICONS[userType]; } -function setClientSocketHandlers(currentGameState, gameStateRenderer, socket, timerWorker, gameTimerManager) { +function setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWorker, gameTimerManager) { if (!socket.hasListeners(globals.EVENTS.PLAYER_JOINED)) { socket.on(globals.EVENTS.PLAYER_JOINED, (player, gameIsFull) => { toast(player.name + " joined!", "success", false); - currentGameState.people.push(player); + stateBucket.currentGameState.people.push(player); gameStateRenderer.renderLobbyPlayers(); if ( gameIsFull && ( - currentGameState.client.userType === globals.USER_TYPES.MODERATOR - || currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR + stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR + || stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR ) ) { - displayStartGamePromptForModerators(gameStateRenderer, socket); + displayStartGamePromptForModerators(stateBucket.currentGameState, socket); } }); } @@ -165,13 +166,11 @@ function setClientSocketHandlers(currentGameState, gameStateRenderer, socket, ti socket.on(globals.EVENTS.SYNC_GAME_STATE, () => { socket.emit( globals.COMMANDS.FETCH_GAME_STATE, - currentGameState.accessCode, - currentGameState.client.cookie, + stateBucket.currentGameState.accessCode, + stateBucket.currentGameState.client.cookie, function (gameState) { - currentGameState = gameState; - gameStateRenderer.gameState = currentGameState; - gameTimerManager.gameState = currentGameState; - processGameState(currentGameState, gameState.client.cookie, socket, gameStateRenderer); + stateBucket.currentGameState = gameState; + processGameState(stateBucket.currentGameState, gameState.client.cookie, socket, gameStateRenderer); } ); }); @@ -183,14 +182,14 @@ function setClientSocketHandlers(currentGameState, gameStateRenderer, socket, ti if (!socket.hasListeners(globals.EVENTS.KILL_PLAYER)) { socket.on(globals.EVENTS.KILL_PLAYER, (id) => { - let killedPerson = currentGameState.people.find((person) => person.id === id); + let killedPerson = stateBucket.currentGameState.people.find((person) => person.id === id); if (killedPerson) { killedPerson.out = true; - if (currentGameState.client.userType === globals.USER_TYPES.MODERATOR) { + if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) { toast(killedPerson.name + ' killed.', 'success', true, true, 6); gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo() } else { - if (killedPerson.id === currentGameState.client.id) { + if (killedPerson.id === stateBucket.currentGameState.client.id) { let clientUserType = document.getElementById("client-user-type"); if (clientUserType) { clientUserType.innerText = globals.USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80' @@ -200,7 +199,7 @@ function setClientSocketHandlers(currentGameState, gameStateRenderer, socket, ti } else { toast(killedPerson.name + ' was killed!', 'warning', false, true, 6); } - if (currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { + if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true); } else { gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(false); @@ -212,21 +211,21 @@ function setClientSocketHandlers(currentGameState, gameStateRenderer, socket, ti if (!socket.hasListeners(globals.EVENTS.REVEAL_PLAYER)) { socket.on(globals.EVENTS.REVEAL_PLAYER, (revealData) => { - let revealedPerson = currentGameState.people.find((person) => person.id === revealData.id); + let revealedPerson = stateBucket.currentGameState.people.find((person) => person.id === revealData.id); if (revealedPerson) { revealedPerson.revealed = true; revealedPerson.gameRole = revealData.gameRole; revealedPerson.alignment = revealData.alignment; - if (currentGameState.client.userType === globals.USER_TYPES.MODERATOR) { + if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) { toast(revealedPerson.name + ' revealed.', 'success', true, true, 6); gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo() } else { - if (revealedPerson.id === currentGameState.client.id) { + if (revealedPerson.id === stateBucket.currentGameState.client.id) { toast('Your role has been revealed!', 'warning', false, true, 6); } else { toast(revealedPerson.name + ' was revealed as a ' + revealedPerson.gameRole + '!', 'warning', false, true, 6); } - if (currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { + if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true); } else { gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(false); @@ -238,20 +237,21 @@ function setClientSocketHandlers(currentGameState, gameStateRenderer, socket, ti if (!socket.hasListeners(globals.EVENTS.CHANGE_NAME)) { socket.on(globals.EVENTS.CHANGE_NAME, (personId, name) => { - propagateNameChange(currentGameState, name, personId); - processGameState(currentGameState, currentGameState.client.cookie, socket, gameStateRenderer); + propagateNameChange(stateBucket.currentGameState, name, personId); + updateDOMWithNameChange(stateBucket.currentGameState, gameStateRenderer); + processGameState(stateBucket.currentGameState, stateBucket.currentGameState.client.cookie, socket, gameStateRenderer); }); } } -function displayStartGamePromptForModerators(gameStateRenderer, socket) { +function displayStartGamePromptForModerators(gameState, socket) { let div = document.createElement("div"); div.innerHTML = templates.START_GAME_PROMPT; document.body.appendChild(div); document.getElementById("start-game-button").addEventListener('click', (e) => { e.preventDefault(); if (confirm("Start the game and deal roles?")) { - socket.emit(globals.COMMANDS.START_GAME, gameStateRenderer.gameState.accessCode,gameStateRenderer.gameState.client.cookie); + socket.emit(globals.COMMANDS.START_GAME, gameState.accessCode, gameState.client.cookie); } }); @@ -288,3 +288,21 @@ function propagateNameChange(gameState, name, personId) { matchingSpectator.name = name; } } + +function updateDOMWithNameChange(gameState, gameStateRenderer) { + switch (gameState.client.userType) { + case globals.USER_TYPES.PLAYER: + case globals.USER_TYPES.KILLED_PLAYER: + case globals.USER_TYPES.SPECTATOR: + gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(false); + break; + case globals.USER_TYPES.MODERATOR: + gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(); + break; + case globals.USER_TYPES.TEMPORARY_MODERATOR: + gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true); + break; + default: + break; + } +} diff --git a/client/styles/create.css b/client/styles/create.css index 0903409..5224e5e 100644 --- a/client/styles/create.css +++ b/client/styles/create.css @@ -176,12 +176,18 @@ input[type="number"] { margin-bottom: 1em; } -#create-game{ - color: #45a445; +#create-game { + background-color: #1c8a36; + color: whitesmoke; font-size: 30px; padding: 10px 50px; } +#create-game:hover { + background-color: #326243; + border: 2px solid #1c8a36; +} + .dropdown { margin: 0.5em; } diff --git a/client/views/game.html b/client/views/game.html index f17d6f1..f4cea66 100644 --- a/client/views/game.html +++ b/client/views/game.html @@ -20,6 +20,7 @@ +