From 0c92455e0ea622aa406917ca30ff5f2979d5c99a Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Fri, 4 Aug 2023 00:15:29 -0400 Subject: [PATCH] fix various small gaps, give option for players to leave --- client/src/config/globals.js | 3 +- .../front_end_components/HTMLFragments.js | 7 + client/src/modules/game_creation/RoleBox.js | 6 +- client/src/modules/game_state/states/Lobby.js | 129 +++++++++++++----- .../states/shared/SharedStateUtil.js | 5 + client/src/styles/GLOBAL.css | 4 +- client/src/styles/game.css | 48 +++++-- client/src/styles/join.css | 2 + server/config/globals.js | 6 +- server/modules/Events.js | 22 ++- server/modules/singletons/GameManager.js | 3 +- 11 files changed, 182 insertions(+), 53 deletions(-) diff --git a/client/src/config/globals.js b/client/src/config/globals.js index 53e9894..b9d2d2d 100644 --- a/client/src/config/globals.js +++ b/client/src/config/globals.js @@ -55,7 +55,8 @@ export const globals = { RESTART_GAME: 'restartGame', ASSIGN_DEDICATED_MOD: 'assignDedicatedMod', KICK_PERSON: 'kickPerson', - UPDATE_GAME_ROLES: 'updateGameRoles' + UPDATE_GAME_ROLES: 'updateGameRoles', + LEAVE_ROOM: 'leaveRoom' }, TIMER_EVENTS: function () { return [ diff --git a/client/src/modules/front_end_components/HTMLFragments.js b/client/src/modules/front_end_components/HTMLFragments.js index bb4fc8c..0dbec17 100644 --- a/client/src/modules/front_end_components/HTMLFragments.js +++ b/client/src/modules/front_end_components/HTMLFragments.js @@ -47,10 +47,17 @@ export const HTMLFragments = { START_GAME_PROMPT: ` `, + LEAVE_GAME_PROMPT: + '', GAME_CONTROL_PROMPT: `
`, + ROLE_EDIT_BUTTONS: + ` + `, PLAYER_GAME_VIEW: `
diff --git a/client/src/modules/game_creation/RoleBox.js b/client/src/modules/game_creation/RoleBox.js index ea4c555..9cd1d58 100644 --- a/client/src/modules/game_creation/RoleBox.js +++ b/client/src/modules/game_creation/RoleBox.js @@ -117,8 +117,10 @@ export class RoleBox { const role = this.getDefaultRole(card.role) ? this.getDefaultRole(card.role) : this.getCustomRole(card.role); - role.id = card.id; - this.deckManager.addToDeck(role); + if (role) { + role.id = card.id; + this.deckManager.addToDeck(role); + } } else { this.deckManager.addCopyOfCard(card.role); } diff --git a/client/src/modules/game_state/states/Lobby.js b/client/src/modules/game_state/states/Lobby.js index 2a8ae80..815da69 100644 --- a/client/src/modules/game_state/states/Lobby.js +++ b/client/src/modules/game_state/states/Lobby.js @@ -20,7 +20,7 @@ export class Lobby { e.preventDefault(); if (!stateBucket.currentGameState.isStartable) { toast('The number of players does not match the number of cards. ' + - 'You must either add/remove players or edit roles and their quantities.', 'error'); + 'You must either add/remove players or edit roles and their quantities.', 'error', true, true, 'long'); return; } Confirmation('Start game and deal roles?', () => { @@ -48,28 +48,45 @@ export class Lobby { }); }; + this.leaveGameHandler = (e) => { + e.preventDefault(); + Confirmation('Leave the room?', () => { + socket.emit( + globals.SOCKET_EVENTS.IN_GAME_MESSAGE, + globals.EVENT_IDS.LEAVE_ROOM, + stateBucket.currentGameState.accessCode, + { personId: stateBucket.currentGameState.client.id } + ); + }); + }; + this.editRolesHandler = (e) => { e.preventDefault(); document.querySelector('#mid-game-role-editor')?.remove(); const roleEditContainer = document.createElement('div'); + const roleEditContainerBackground = document.createElement('div'); + roleEditContainerBackground.setAttribute('id', 'role-edit-container-background'); roleEditContainer.setAttribute('id', 'mid-game-role-editor'); roleEditContainer.innerHTML = hiddenMenus; + document.getElementById('game-content').style.display = 'none'; document.body.appendChild(roleEditContainer); + document.body.appendChild(roleEditContainerBackground); this.gameCreationStepManager.deckManager.deck = []; this.gameCreationStepManager .renderRoleSelectionStep(this.stateBucket.currentGameState, 'mid-game-role-editor', '2'); this.gameCreationStepManager.roleBox.loadSelectedRolesFromCurrentGame(this.stateBucket.currentGameState); - const saveButton = document.createElement('button'); - saveButton.classList.add('app-button'); - saveButton.setAttribute('id', 'save-role-changes-button'); - saveButton.innerHTML = '

Save

'; - saveButton.addEventListener('click', () => { + const roleEditPrompt = document.createElement('div'); + roleEditPrompt.setAttribute('id', 'role-edit-prompt'); + roleEditPrompt.innerHTML = HTMLFragments.ROLE_EDIT_BUTTONS; + roleEditPrompt.querySelector('#save-role-changes-button').addEventListener('click', () => { if (this.gameCreationStepManager.deckManager.getDeckSize() > 50) { toast('Your deck is too large. The max is 50 cards.', 'error', true); } else if (this.gameCreationStepManager.deckManager.getDeckSize() < 1) { toast('You must add at least one card', 'error', true); } else { document.querySelector('#mid-game-role-editor')?.remove(); + document.querySelector('#role-edit-container-background')?.remove(); + document.getElementById('game-content').style.display = 'flex'; this.socket.emit( globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.UPDATE_GAME_ROLES, @@ -81,7 +98,14 @@ export class Lobby { ); } }); - roleEditContainer.appendChild(saveButton); + + roleEditPrompt.querySelector('#cancel-role-changes-button').addEventListener('click', () => { + document.querySelector('#mid-game-role-editor')?.remove(); + document.querySelector('#role-edit-container-background')?.remove(); + document.getElementById('game-content').style.display = 'flex'; + }); + + roleEditContainer.appendChild(roleEditPrompt); }; } @@ -97,6 +121,10 @@ export class Lobby { linkContainer.prepend(linkDiv); activateLink(linkContainer, link); + QRCode.toCanvas(document.getElementById('canvas'), link, { scale: 3 }, function (error) { + if (error) console.error(error); + }); + return link; } @@ -171,10 +199,10 @@ export class Lobby { } setSocketHandlers () { - this.socket.on(globals.EVENT_IDS.PLAYER_JOINED, (player, gameisStartable) => { + this.socket.on(globals.EVENT_IDS.PLAYER_JOINED, (player, gameIsStartable) => { toast(player.name + ' joined!', 'success', true, true, 'short'); this.stateBucket.currentGameState.people.push(player); - this.stateBucket.currentGameState.isStartable = gameisStartable; + this.stateBucket.currentGameState.isStartable = gameIsStartable; this.populatePlayers(); if (( this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR @@ -193,41 +221,57 @@ export class Lobby { ); }); - this.socket.on(globals.EVENT_IDS.KICK_PERSON, (kickedId, gameisStartable) => { + this.socket.on(globals.EVENT_IDS.KICK_PERSON, (kickedId, gameIsStartable) => { if (kickedId === this.stateBucket.currentGameState.client.id) { window.location = '/?message=' + encodeURIComponent('You were kicked by the moderator.'); } else { - const kickedIndex = this.stateBucket.currentGameState.people.findIndex(person => person.id === kickedId); - if (kickedIndex >= 0) { - this.stateBucket.currentGameState.people - .splice(kickedIndex, 1); - } - this.stateBucket.currentGameState.isStartable = gameisStartable; - SharedStateUtil.setNumberOfSpectators( - this.stateBucket.currentGameState.people.filter(p => p.userType === globals.USER_TYPES.SPECTATOR).length, - document.getElementById('spectator-count') - ); - this.populatePlayers(); - if (( - this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR - || this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR - ) - ) { - toast('player kicked.', 'success', true, true, 'short'); - this.displayStartGamePromptForModerators(); - } + this.handlePersonExiting(kickedId, gameIsStartable, globals.EVENT_IDS.KICK_PERSON); } }); - this.socket.on(globals.EVENT_IDS.UPDATE_GAME_ROLES, (deck, gameSize) => { + this.socket.on(globals.EVENT_IDS.UPDATE_GAME_ROLES, (deck, gameSize, isStartable) => { this.stateBucket.currentGameState.deck = deck; this.stateBucket.currentGameState.gameSize = gameSize; - this.stateBucket.currentGameState.isStartable = this.stateBucket.currentGameState.people - .filter(person => person.userType === globals.USER_TYPES.PLAYER - || person.userType === globals.USER_TYPES.TEMPORARY_MODERATOR).length === gameSize; + this.stateBucket.currentGameState.isStartable = isStartable; this.setLink(getTimeString(this.stateBucket.currentGameState)); this.setPlayerCount(); }); + + this.socket.on(globals.EVENT_IDS.LEAVE_ROOM, (leftId, gameIsStartable) => { + if (leftId === this.stateBucket.currentGameState.client.id) { + window.location = '/?message=' + encodeURIComponent('You left the room.'); + } else { + this.handlePersonExiting(leftId, gameIsStartable, globals.EVENT_IDS.LEAVE_ROOM); + } + }); + } + + handlePersonExiting (id, gameIsStartable, event) { + const index = this.stateBucket.currentGameState.people.findIndex(person => person.id === id); + if (index >= 0) { + this.stateBucket.currentGameState.people + .splice(index, 1); + } + this.stateBucket.currentGameState.isStartable = gameIsStartable; + SharedStateUtil.setNumberOfSpectators( + this.stateBucket.currentGameState.people.filter(p => p.userType === globals.USER_TYPES.SPECTATOR).length, + document.getElementById('spectator-count') + ); + this.populatePlayers(); + if (( + this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR + || this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR + ) + ) { + toast( + event === globals.EVENT_IDS.LEAVE_ROOM ? 'A player left.' : 'Player kicked.', + event === globals.EVENT_IDS.LEAVE_ROOM ? 'warning' : 'success', + true, + true, + 'short' + ); + this.displayStartGamePromptForModerators(); + } } displayStartGamePromptForModerators () { @@ -246,6 +290,20 @@ export class Lobby { } } + displayPlayerPrompt () { + const existingPrompt = document.getElementById('leave-game-prompt'); + if (existingPrompt) { + enableLeaveButton(existingPrompt, this.leaveGameHandler); + } else { + const newPrompt = document.createElement('div'); + newPrompt.setAttribute('id', 'leave-game-prompt'); + newPrompt.innerHTML = HTMLFragments.LEAVE_GAME_PROMPT; + + document.body.appendChild(newPrompt); + enableLeaveButton(newPrompt, this.leaveGameHandler); + } + } + removeStartGameFunctionalityIfPresent () { document.querySelector('#start-game-prompt')?.removeEventListener('click', this.startGameHandler); document.querySelector('#start-game-prompt')?.remove(); @@ -257,6 +315,11 @@ function enableStartButton (buttonContainer, handler) { buttonContainer.querySelector('#start-game-button').classList.remove('disabled'); } +function enableLeaveButton (buttonContainer, handler) { + buttonContainer.querySelector('#leave-game-button').addEventListener('click', handler); + buttonContainer.querySelector('#leave-game-button').classList.remove('disabled'); +} + function activateLink (linkContainer, link) { const linkCopyHandler = (e) => { if (e.type === 'click' || e.code === 'Enter') { diff --git a/client/src/modules/game_state/states/shared/SharedStateUtil.js b/client/src/modules/game_state/states/shared/SharedStateUtil.js index 15e7f49..0dc8433 100644 --- a/client/src/modules/game_state/states/shared/SharedStateUtil.js +++ b/client/src/modules/game_state/states/shared/SharedStateUtil.js @@ -295,11 +295,16 @@ function processGameState ( lobby.displayStartGamePromptForModerators(); } document.getElementById('player-options-prompt').innerHTML = HTMLFragments.PLAYER_OPTIONS_MODAL; + } else { + if (refreshPrompt) { + lobby.displayPlayerPrompt(); + } } break; case globals.STATUS.IN_PROGRESS: if (refreshPrompt) { document.querySelector('#game-control-prompt')?.remove(); + document.querySelector('#leave-game-prompt')?.remove(); } const inProgressGame = new InProgress('game-state-container', stateBucket, socket); globals.IN_PROGRESS_EVENTS().forEach(e => socket.removeAllListeners(e)); diff --git a/client/src/styles/GLOBAL.css b/client/src/styles/GLOBAL.css index d07e0da..61da69d 100644 --- a/client/src/styles/GLOBAL.css +++ b/client/src/styles/GLOBAL.css @@ -663,8 +663,8 @@ input { } .info-message { - padding: 10px; - font-size: 18px; + padding: 5px; + font-size: 16px; } } diff --git a/client/src/styles/game.css b/client/src/styles/game.css index b11c5d5..f4ca675 100644 --- a/client/src/styles/game.css +++ b/client/src/styles/game.css @@ -106,12 +106,27 @@ max-width: 17em; } -#save-role-changes-button { +#save-role-changes-button, #cancel-role-changes-button { padding: 10px; font-size: 25px; margin: 0.5em 0; } +#role-edit-prompt { + display: flex; + margin: 10px 0; + padding: 10px 0; + width: 100%; + max-width: 16em; + border-radius: 5px; + justify-content: center; + background-color: #16141e; +} + +#role-edit-prompt button { + margin: 0 20px; +} + #save-role-changes-button img { width: 20px; margin-left: 10px; @@ -142,7 +157,6 @@ width: 100%; height: 100%; z-index: 100001; - background-color: #000000d4; align-items: center; justify-content: center; font-family: 'signika-negative', sans-serif; @@ -150,6 +164,17 @@ overflow: auto; } +#role-edit-container-background { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: calc(100% + 100px); + background-color: rgba(0, 0, 0, 1); + z-index: 100000; + cursor: pointer; +} + #mid-game-role-editor #step-2 { width: 100%; display: flex; @@ -271,7 +296,7 @@ h1 { #game-code span { font-family: "Courier New", monospace; color: #21ba45; - font-size: 20px; + font-size: 30px; } #game-link > div { @@ -302,6 +327,7 @@ h1 { margin: 1em 0; overflow-y: auto; max-height: 35em; + min-width: 19em; } #game-role-info-container > div { @@ -579,7 +605,7 @@ label[for='moderator'] { font-size: 30px; } -#start-game-prompt, #game-control-prompt { +#start-game-prompt, #game-control-prompt, #leave-game-prompt { padding: 0.5em 0; display: flex; flex-direction: row; @@ -602,7 +628,7 @@ label[for='moderator'] { background-color: #1b1a24; } -#game-control-prompt button, #start-game-prompt button { +#game-control-prompt button, #start-game-prompt button, #leave-game-prompt button { margin: 0 15px; min-width: 5em; } @@ -623,7 +649,7 @@ label[for='moderator'] { box-shadow: 0 -6px 40px black; } -#start-game-button, #end-game-button, #return-to-lobby-button, #edit-roles-button { +#start-game-button, #end-game-button, #return-to-lobby-button, #edit-roles-button, #leave-game-button { font-family: 'signika-negative', sans-serif !important; padding: 10px; border-radius: 5px; @@ -643,7 +669,7 @@ label[for='moderator'] { background-color: #1c8a36; } -#end-game-button { +#end-game-button, #leave-game-button { background-color: #8a1c1c; } @@ -652,7 +678,7 @@ label[for='moderator'] { border: 2px solid #1c8a36; } -#end-game-button:hover { +#end-game-button:hover, #leave-game-button:hover { background-color: #623232; border: 2px solid #8a1c1c; } @@ -920,7 +946,7 @@ canvas { } @media(max-width: 800px) { - #start-game-prompt, #game-control-prompt { + #start-game-prompt, #game-control-prompt, #leave-game-prompt { border-radius: 0; width: 100%; bottom: 0; @@ -984,11 +1010,11 @@ canvas { font-size: 20px; } - #start-game-prompt, #game-control-prompt { + #start-game-prompt, #game-control-prompt, #leave-game-prompt { height: 65px; } - #start-game-button, #end-game-button, #return-to-lobby-button, #edit-roles-button { + #start-game-button, #end-game-button, #return-to-lobby-button, #edit-roles-button, #leave-game-button { font-size: 20px; padding: 5px; } diff --git a/client/src/styles/join.css b/client/src/styles/join.css index ad3da5e..b311545 100644 --- a/client/src/styles/join.css +++ b/client/src/styles/join.css @@ -40,8 +40,10 @@ #game-parameters div:nth-child(1) { margin-bottom: 20px; + font-size: 35px; } #game-code { font-family: "Courier New", monospace; + color: #21ba45; } diff --git a/server/config/globals.js b/server/config/globals.js index 26fac56..244231c 100644 --- a/server/config/globals.js +++ b/server/config/globals.js @@ -57,7 +57,8 @@ const globals = { ASSIGN_DEDICATED_MOD: 'assignDedicatedMod', TIMER_EVENT: 'timerEvent', KICK_PERSON: 'kickPerson', - UPDATE_GAME_ROLES: 'updateGameRoles' + UPDATE_GAME_ROLES: 'updateGameRoles', + LEAVE_ROOM: 'leaveRoom' }, SYNCABLE_EVENTS: function () { return [ @@ -78,7 +79,8 @@ const globals = { this.EVENT_IDS.PAUSE_TIMER, this.EVENT_IDS.END_TIMER, this.EVENT_IDS.KICK_PERSON, - this.EVENT_IDS.UPDATE_GAME_ROLES + this.EVENT_IDS.UPDATE_GAME_ROLES, + this.EVENT_IDS.LEAVE_ROOM ]; }, TIMER_EVENTS: function () { diff --git a/server/modules/Events.js b/server/modules/Events.js index 97d521e..4b10ab9 100644 --- a/server/modules/Events.js +++ b/server/modules/Events.js @@ -37,6 +37,25 @@ const Events = [ ); } }, + { + id: EVENT_IDS.LEAVE_ROOM, + stateChange: async (game, socketArgs, vars) => { + const toBeClearedIndex = game.people.findIndex( + (person) => person.id === socketArgs.personId && person.assigned === true + ); + if (toBeClearedIndex >= 0) { + game.people.splice(toBeClearedIndex, 1); + game.isStartable = vars.gameManager.isGameStartable(game); + } + }, + communicate: async (game, socketArgs, vars) => { + vars.gameManager.namespace.in(game.accessCode).emit( + EVENT_IDS.LEAVE_ROOM, + socketArgs.personId, + game.isStartable + ); + } + }, { id: EVENT_IDS.UPDATE_GAME_ROLES, stateChange: async (game, socketArgs, vars) => { @@ -56,7 +75,8 @@ const Events = [ vars.gameManager.namespace.in(game.accessCode).emit( EVENT_IDS.UPDATE_GAME_ROLES, game.deck, - game.gameSize + game.gameSize, + game.isStartable ); } }, diff --git a/server/modules/singletons/GameManager.js b/server/modules/singletons/GameManager.js index c0fd078..47af957 100644 --- a/server/modules/singletons/GameManager.js +++ b/server/modules/singletons/GameManager.js @@ -320,7 +320,8 @@ class GameManager { isGameStartable = (game) => { return game.people.filter(person => person.userType === globals.USER_TYPES.PLAYER - || person.userType === globals.USER_TYPES.TEMPORARY_MODERATOR).length === game.gameSize; + || person.userType === globals.USER_TYPES.TEMPORARY_MODERATOR + || person.userType === globals.USER_TYPES.BOT).length === game.gameSize; } findPersonByField = (game, fieldName, value) => {