From 230e657368ca1b94ec38f747efc8a5247b89ab92 Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Tue, 15 Aug 2023 14:17:48 -0400 Subject: [PATCH 1/6] start of functionality --- client/src/config/globals.js | 3 + .../front_end_components/HTMLFragments.js | 6 + .../game_creation/GameCreationStepManager.js | 123 ++++++++++-------- client/src/modules/game_state/states/Lobby.js | 60 ++++++++- .../src/modules/page_handlers/gameHandler.js | 30 +++-- client/src/scripts/home.js | 2 +- client/src/scripts/join.js | 2 +- client/src/styles/game.css | 28 +++- client/src/view_templates/GameTemplate.js | 2 +- 9 files changed, 180 insertions(+), 76 deletions(-) diff --git a/client/src/config/globals.js b/client/src/config/globals.js index 0c9cf28..98dc8da 100644 --- a/client/src/config/globals.js +++ b/client/src/config/globals.js @@ -8,6 +8,8 @@ export const PRIMITIVES = { MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH: 1000, TOAST_DURATION_DEFAULT: 6, ACCESS_CODE_LENGTH: 4, + MAX_MINUTES: 59, + MAX_HOURS: 5, PLAYER_ID_COOKIE_KEY: 'play-werewolf-anon-id' }; @@ -75,6 +77,7 @@ export const EVENT_IDS = { ASSIGN_DEDICATED_MOD: 'assignDedicatedMod', KICK_PERSON: 'kickPerson', UPDATE_GAME_ROLES: 'updateGameRoles', + UPDATE_GAME_TIMER: 'updateGameTimer', LEAVE_ROOM: 'leaveRoom' }; diff --git a/client/src/modules/front_end_components/HTMLFragments.js b/client/src/modules/front_end_components/HTMLFragments.js index 09f9c2e..2f6ff18 100644 --- a/client/src/modules/front_end_components/HTMLFragments.js +++ b/client/src/modules/front_end_components/HTMLFragments.js @@ -46,6 +46,7 @@ export const HTMLFragments = { `, START_GAME_PROMPT: ` + `, LEAVE_GAME_PROMPT: '', @@ -58,6 +59,11 @@ export const HTMLFragments = { `, + TIMER_EDIT_BUTTONS: + ` + `, PLAYER_GAME_VIEW: `
diff --git a/client/src/modules/game_creation/GameCreationStepManager.js b/client/src/modules/game_creation/GameCreationStepManager.js index ec2e473..2de6cec 100644 --- a/client/src/modules/game_creation/GameCreationStepManager.js +++ b/client/src/modules/game_creation/GameCreationStepManager.js @@ -53,16 +53,10 @@ export class GameCreationStepManager { if (e.type === 'click' || e.code === 'Enter') { let hours = parseInt(document.getElementById('game-hours').value); let minutes = parseInt(document.getElementById('game-minutes').value); - hours = isNaN(hours) ? null : hours; - minutes = isNaN(minutes) ? null : minutes; - if ((hours === null && minutes === null) - || (hours === null && minutes > 0 && minutes < 60) - || (minutes === null && hours > 0 && hours < 6) - || (hours === 0 && minutes > 0 && minutes < 60) - || (minutes === 0 && hours > 0 && hours < 6) - || (hours > 0 && hours < 6 && minutes >= 0 && minutes < 60) - ) { - if (hasTimer(hours, minutes)) { + hours = this.standardizeNumberInput(hours); + minutes = this.standardizeNumberInput(minutes) + if (this.timerIsValid(hours, minutes)) { + if (this.hasTimer(hours, minutes)) { this.currentGame.hasTimer = true; this.currentGame.timerParams = { hours: hours, @@ -77,11 +71,7 @@ export class GameCreationStepManager { this.incrementStep(); this.renderStep('creation-step-container', this.step); } else { - if (hours === 0 && minutes === 0) { - toast('You must enter a non-zero amount of time.', 'error', true); - } else { - toast('Invalid timer options. Hours can be a max of 5, Minutes a max of 59.', 'error', true); - } + toast('Invalid timer options. Hours can be a max of 5, Minutes a max of 59.', 'error', true); } } }, @@ -198,7 +188,7 @@ export class GameCreationStepManager { showButtons(true, true, this.steps[step].forwardHandler, this.steps[step].backHandler); break; case 3: - renderTimerStep(containerId, step, this.currentGame, this.steps); + this.renderTimerStep(containerId, step, this.currentGame, this.steps); showButtons(true, true, this.steps[step].forwardHandler, this.steps[step].backHandler); break; case 4: @@ -297,6 +287,68 @@ export class GameCreationStepManager { initializeRemainingEventListeners(this.deckManager, this.roleBox); }; + + renderTimerStep (containerId, stepNumber, game, steps) { + const div = document.createElement('div'); + div.setAttribute('id', 'step-' + stepNumber); + div.classList.add('step'); + + const timeContainer = document.createElement('div'); + timeContainer.setAttribute('id', 'game-time'); + + const hoursDiv = document.createElement('div'); + const hoursLabel = document.createElement('label'); + hoursLabel.setAttribute('for', 'game-hours'); + hoursLabel.innerText = 'Hours'; + const hours = document.createElement('input'); + hours.addEventListener('keyup', steps[stepNumber].forwardHandler); + setAttributes(hours, { type: 'number', id: 'game-hours', name: 'game-hours', min: '0', max: '5', value: game.timerParams?.hours }); + + const minutesDiv = document.createElement('div'); + const minsLabel = document.createElement('label'); + minsLabel.setAttribute('for', 'game-minutes'); + minsLabel.innerText = 'Minutes'; + const minutes = document.createElement('input'); + minutes.addEventListener('keyup', steps[stepNumber].forwardHandler); + setAttributes(minutes, { type: 'number', id: 'game-minutes', name: 'game-minutes', min: '1', max: '60', value: game.timerParams?.minutes }); + + hoursDiv.appendChild(hoursLabel); + hoursDiv.appendChild(hours); + minutesDiv.appendChild(minsLabel); + minutesDiv.appendChild(minutes); + timeContainer.appendChild(hoursDiv); + timeContainer.appendChild(minutesDiv); + div.appendChild(timeContainer); + + document.getElementById(containerId).appendChild(div); + } + + timerIsValid(hours, minutes) { + let valid = true; + + if (hours === null && minutes === null) { + return valid; + } + + if (hours !== null) { + valid = hours <= PRIMITIVES.MAX_HOURS; + } + + if (minutes !== null) { + valid = minutes <= PRIMITIVES.MAX_MINUTES; + } + + + return valid; + } + + hasTimer(hours, minutes) { + return hours !== null || minutes !== null; + } + + standardizeNumberInput(input) { + return (isNaN(input) || input === 0) ? null : input; + } } function renderNameStep (containerId, step, game, steps) { @@ -358,41 +410,6 @@ function renderModerationTypeStep (game, containerId, stepNumber) { document.getElementById(containerId).appendChild(stepContainer); } -function renderTimerStep (containerId, stepNumber, game, steps) { - const div = document.createElement('div'); - div.setAttribute('id', 'step-' + stepNumber); - div.classList.add('step'); - - const timeContainer = document.createElement('div'); - timeContainer.setAttribute('id', 'game-time'); - - const hoursDiv = document.createElement('div'); - const hoursLabel = document.createElement('label'); - hoursLabel.setAttribute('for', 'game-hours'); - hoursLabel.innerText = 'Hours'; - const hours = document.createElement('input'); - hours.addEventListener('keyup', steps[stepNumber].forwardHandler); - setAttributes(hours, { type: 'number', id: 'game-hours', name: 'game-hours', min: '0', max: '5', value: game.timerParams?.hours }); - - const minutesDiv = document.createElement('div'); - const minsLabel = document.createElement('label'); - minsLabel.setAttribute('for', 'game-minutes'); - minsLabel.innerText = 'Minutes'; - const minutes = document.createElement('input'); - minutes.addEventListener('keyup', steps[stepNumber].forwardHandler); - setAttributes(minutes, { type: 'number', id: 'game-minutes', name: 'game-minutes', min: '1', max: '60', value: game.timerParams?.minutes }); - - hoursDiv.appendChild(hoursLabel); - hoursDiv.appendChild(hours); - minutesDiv.appendChild(minsLabel); - minutesDiv.appendChild(minutes); - timeContainer.appendChild(hoursDiv); - timeContainer.appendChild(minutesDiv); - div.appendChild(timeContainer); - - document.getElementById(containerId).appendChild(div); -} - function renderReviewAndCreateStep (containerId, stepNumber, game, deckManager) { const div = document.createElement('div'); div.setAttribute('id', 'step-' + stepNumber); @@ -591,10 +608,6 @@ function processNewCustomRoleSubmission (name, description, team, deckManager, i } } -function hasTimer (hours, minutes) { - return hours !== null || minutes !== null; -} - function validateName (name) { return typeof name === 'string' && name.length > 0 && name.length <= PRIMITIVES.MAX_PERSON_NAME_LENGTH; } diff --git a/client/src/modules/game_state/states/Lobby.js b/client/src/modules/game_state/states/Lobby.js index e6dabbd..fada5c6 100644 --- a/client/src/modules/game_state/states/Lobby.js +++ b/client/src/modules/game_state/states/Lobby.js @@ -1,5 +1,5 @@ import { QRCode } from '../../third_party/qrcode.js'; -import { toast } from '../../front_end_components/Toast.js'; +import {cancelCurrentToast, toast} from '../../front_end_components/Toast.js'; import { EVENT_IDS, PRIMITIVES, SOCKET_EVENTS, USER_TYPE_ICONS, USER_TYPES } from '../../../config/globals.js'; import { HTMLFragments } from '../../front_end_components/HTMLFragments.js'; import { Confirmation } from '../../front_end_components/Confirmation.js'; @@ -60,6 +60,64 @@ export class Lobby { }); }; + this.editTimerHandler = (e) => { + e.preventDefault(); + document.querySelector('#mid-game-timer-editor')?.remove(); + const timerEditContainer = document.createElement('div'); + const timerEditContainerBackground = document.createElement('div'); + timerEditContainerBackground.setAttribute('id', 'timer-edit-container-background'); + timerEditContainer.setAttribute('id', 'mid-game-timer-editor'); + document.getElementById('game-content').style.display = 'none'; + document.body.appendChild(timerEditContainer); + document.body.appendChild(timerEditContainerBackground); + this.gameCreationStepManager + .renderTimerStep(this.stateBucket.currentGameState, '2', this.stateBucket.currentGameState, this.gameCreationStepManager.steps); + const timerEditPrompt = document.createElement('div'); + timerEditPrompt.setAttribute('id', 'timer-edit-prompt'); + timerEditPrompt.innerHTML = HTMLFragments.TIMER_EDIT_BUTTONS; + timerEditPrompt.querySelector('#save-timer-changes-button').addEventListener('click', () => { + let hours = parseInt(document.getElementById('game-hours').value); + let minutes = parseInt(document.getElementById('game-minutes').value); + hours = this.gameCreationStepManager.standardizeNumberInput(hours); + minutes = this.gameCreationStepManager.standardizeNumberInput(minutes) + if (this.gameCreationStepManager.timerIsValid(hours, minutes)) { + let hasTimer, timerParams; + if (this.gameCreationStepManager.hasTimer(hours, minutes)) { + hasTimer = true; + timerParams = { + hours: hours, + minutes: minutes + }; + } else { + hasTimer = false; + timerParams = null; + } + document.querySelector('#mid-game-timer-editor')?.remove(); + document.querySelector('#timer-edit-container-background')?.remove(); + document.getElementById('game-content').style.display = 'flex'; + this.socket.emit( + SOCKET_EVENTS.IN_GAME_MESSAGE, + EVENT_IDS.UPDATE_GAME_TIMER, + stateBucket.currentGameState.accessCode, + { hasTimer: hasTimer, timerParams: timerParams }, + () => { + toast('Timer updated successfully!', 'success'); + } + ); + } else { + toast('Invalid timer options. Hours can be a max of 5, Minutes a max of 59.', 'error', true); + } + }); + + timerEditPrompt.querySelector('#cancel-timer-changes-button').addEventListener('click', () => { + document.querySelector('#mid-game-timer-editor')?.remove(); + document.querySelector('#timer-edit-container-background')?.remove(); + document.getElementById('game-content').style.display = 'flex'; + }); + + timerEditContainer.appendChild(timerEditPrompt); + } + this.editRolesHandler = (e) => { e.preventDefault(); document.querySelector('#mid-game-role-editor')?.remove(); diff --git a/client/src/modules/page_handlers/gameHandler.js b/client/src/modules/page_handlers/gameHandler.js index a2445af..ff9d5eb 100644 --- a/client/src/modules/page_handlers/gameHandler.js +++ b/client/src/modules/page_handlers/gameHandler.js @@ -20,6 +20,17 @@ import { Ended } from '../game_state/states/Ended.js'; export const gameHandler = (socket, window, gameDOM) => { document.body.innerHTML = gameDOM + document.body.innerHTML; injectNavbar(); + const connectionHandler = () => { + if (stateBucket.timerWorker) { + stateBucket.timerWorker.terminate(); + stateBucket.timerWorker = null; + } + syncWithGame( + socket, + UserUtility.validateAnonUserSignature(stateBucket.environment), + window + ); + } return new Promise((resolve, reject) => { window.fetch( '/api/games/environment', @@ -30,22 +41,18 @@ export const gameHandler = (socket, window, gameDOM) => { ).catch(() => { reject(new Error('There was a problem connecting to the room.')); }).then((response) => { - if (!response.ok) { - reject(new Error('HTTP ' + response.status + ': Could not connect to the room')); + if (!response.ok && !(response.status === 304)) { + console.log('too many requests! returning...'); + reject(new Error('Could not connect to the room: HTTP ' + response.status + ': ' + response.statusText)); return; } response.text().then((text) => { stateBucket.environment = text; + if (socket.connected) { + connectionHandler(); + } socket.on('connect', () => { - if (stateBucket.timerWorker) { - stateBucket.timerWorker.terminate(); - stateBucket.timerWorker = null; - } - syncWithGame( - socket, - UserUtility.validateAnonUserSignature(stateBucket.environment), - window - ); + connectionHandler(); }); socket.on('connect_error', (err) => { toast(err, 'error', true, false); @@ -72,6 +79,7 @@ function syncWithGame (socket, cookie, window) { { personId: cookie }, (err, gameState) => { if (err) { + console.log(err); retrySync(accessCode, socket, cookie); } else { handleGameState(gameState, cookie, socket); diff --git a/client/src/scripts/home.js b/client/src/scripts/home.js index 3736cd0..3839f23 100644 --- a/client/src/scripts/home.js +++ b/client/src/scripts/home.js @@ -31,7 +31,7 @@ function attemptToJoinGame (event) { mode: 'cors' } ).then((res) => { - if (!res.ok) { + if (!res.ok && !(res.status === 304)) { switch (res.status) { case 404: toast('Game not found', 'error', true); diff --git a/client/src/scripts/join.js b/client/src/scripts/join.js index fd9afa7..b9326ae 100644 --- a/client/src/scripts/join.js +++ b/client/src/scripts/join.js @@ -28,7 +28,7 @@ const joinHandler = (e) => { if (validateName(name)) { sendJoinRequest(e, name, accessCode) .then((res) => { - if (!res.ok) { + if (!res.ok && !(res.status === 304)) { switch (res.status) { case 404: toast('Game not found', 'error', true); diff --git a/client/src/styles/game.css b/client/src/styles/game.css index c200c22..c5d4c85 100644 --- a/client/src/styles/game.css +++ b/client/src/styles/game.css @@ -136,7 +136,8 @@ #end-of-game-buttons #return-to-lobby-button, #mod-transfer-button, #edit-roles-button, -#save-role-changes-button { +#save-role-changes-button, +#edit-timer-button { background-color: #045ea6; border: 2px solid #024070; } @@ -145,7 +146,8 @@ #end-of-game-buttons #return-to-lobby-button:hover, #mod-transfer-button:hover, #edit-roles-button:hover, -#save-role-changes-button:hover { +#save-role-changes-button:hover, +#edit-timer-button:hover { background-color: rgba(0, 120, 215, 0.45); border: 2px solid #045EA6; } @@ -678,16 +680,21 @@ label[for='moderator'] { box-shadow: 0 -6px 40px black; } -#start-game-button, #end-game-button, #return-to-lobby-button, #edit-roles-button, #leave-game-button { +#start-game-button, +#end-game-button, +#return-to-lobby-button, +#edit-roles-button, +#leave-game-button, +#edit-timer-button { font-family: 'signika-negative', sans-serif !important; - padding: 10px; + padding: 7px; border-radius: 5px; color: #e7e7e7; cursor: pointer; border: 2px solid transparent; transition: background-color, border 0.3s ease-out; text-shadow: 0 3px 4px rgb(0 0 0 / 85%); - font-size: 25px; + font-size: 22px; user-select: none; -ms-user-select: none; -webkit-user-select: none; @@ -997,6 +1004,10 @@ canvas { } @media(max-width: 500px) { + #game-control-prompt button, #start-game-prompt button, #leave-game-prompt button { + margin: 0 10px; + } + #client-container button img { width: 18px; pointer-events: none; @@ -1068,7 +1079,12 @@ canvas { height: 65px; } - #start-game-button, #end-game-button, #return-to-lobby-button, #edit-roles-button, #leave-game-button { + #start-game-button, + #end-game-button, + #return-to-lobby-button, + #edit-roles-button, + #leave-game-button, + #edit-timer-button { font-size: 20px; padding: 5px; } diff --git a/client/src/view_templates/GameTemplate.js b/client/src/view_templates/GameTemplate.js index acd83c1..f2a914d 100644 --- a/client/src/view_templates/GameTemplate.js +++ b/client/src/view_templates/GameTemplate.js @@ -18,7 +18,7 @@ const template =
-

Connecting to the Room...

+

Waiting for Connection to Room...

From 1d22aebbb97f30e27c82b6352af8e292cf43979f Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Wed, 16 Aug 2023 13:43:22 -0400 Subject: [PATCH 2/6] built out rest of functionality; add placeholder when there are no custom roles --- client/src/config/globals.js | 1 + .../game_creation/GameCreationStepManager.js | 9 ++-- client/src/modules/game_creation/RoleBox.js | 13 ++++++ client/src/modules/game_state/states/Lobby.js | 33 +++++++++++--- .../src/modules/page_handlers/gameHandler.js | 5 ++- client/src/styles/create.css | 4 +- client/src/styles/game.css | 44 ++++++++++++------- server/config/globals.js | 2 + server/model/GameCreationRequest.js | 34 +++++++------- server/modules/Events.js | 21 ++++++++- 10 files changed, 117 insertions(+), 49 deletions(-) diff --git a/client/src/config/globals.js b/client/src/config/globals.js index 98dc8da..041fa74 100644 --- a/client/src/config/globals.js +++ b/client/src/config/globals.js @@ -96,6 +96,7 @@ export const LOBBY_EVENTS = function () { EVENT_IDS.ADD_SPECTATOR, EVENT_IDS.KICK_PERSON, EVENT_IDS.UPDATE_GAME_ROLES, + EVENT_IDS.UPDATE_GAME_TIMER, EVENT_IDS.LEAVE_ROOM ]; }; diff --git a/client/src/modules/game_creation/GameCreationStepManager.js b/client/src/modules/game_creation/GameCreationStepManager.js index 2de6cec..063eb64 100644 --- a/client/src/modules/game_creation/GameCreationStepManager.js +++ b/client/src/modules/game_creation/GameCreationStepManager.js @@ -54,7 +54,7 @@ export class GameCreationStepManager { let hours = parseInt(document.getElementById('game-hours').value); let minutes = parseInt(document.getElementById('game-minutes').value); hours = this.standardizeNumberInput(hours); - minutes = this.standardizeNumberInput(minutes) + minutes = this.standardizeNumberInput(minutes); if (this.timerIsValid(hours, minutes)) { if (this.hasTimer(hours, minutes)) { this.currentGame.hasTimer = true; @@ -323,7 +323,7 @@ export class GameCreationStepManager { document.getElementById(containerId).appendChild(div); } - timerIsValid(hours, minutes) { + timerIsValid (hours, minutes) { let valid = true; if (hours === null && minutes === null) { @@ -338,15 +338,14 @@ export class GameCreationStepManager { valid = minutes <= PRIMITIVES.MAX_MINUTES; } - return valid; } - hasTimer(hours, minutes) { + hasTimer (hours, minutes) { return hours !== null || minutes !== null; } - standardizeNumberInput(input) { + standardizeNumberInput (input) { return (isNaN(input) || input === 0) ? null : input; } } diff --git a/client/src/modules/game_creation/RoleBox.js b/client/src/modules/game_creation/RoleBox.js index e074a5f..7b996f2 100644 --- a/client/src/modules/game_creation/RoleBox.js +++ b/client/src/modules/game_creation/RoleBox.js @@ -166,6 +166,7 @@ export class RoleBox { }; displayDefaultRoles = (selectEl) => { + document.querySelector('#custom-role-placeholder')?.remove(); document.querySelectorAll('#role-select .default-role, #role-select .custom-role').forEach(e => e.remove()); this.categoryTransition.play(); for (let i = 0; i < this.defaultRoles.length; i ++) { @@ -184,9 +185,21 @@ export class RoleBox { this.addRoleEventListeners(selectEl, true, true, false, false, false); }; + displayCustomRolePlaceHolder = () => { + const placeholder = document.createElement('div'); + placeholder.setAttribute('id', 'custom-role-placeholder'); + placeholder.innerText = 'Create a role with the button below.'; + document.getElementById('role-select').appendChild(placeholder); + }; + displayCustomRoles = (selectEl) => { + document.querySelector('#custom-role-placeholder')?.remove(); document.querySelectorAll('#role-select .default-role, #role-select .custom-role').forEach(e => e.remove()); this.categoryTransition.play(); + if (this.customRoles.length === 0) { + this.displayCustomRolePlaceHolder(); + return; + } this.customRoles.sort((a, b) => { if (a.team !== b.team) { return a.team === ALIGNMENT.GOOD ? -1 : 1; diff --git a/client/src/modules/game_state/states/Lobby.js b/client/src/modules/game_state/states/Lobby.js index fada5c6..dc29961 100644 --- a/client/src/modules/game_state/states/Lobby.js +++ b/client/src/modules/game_state/states/Lobby.js @@ -1,5 +1,5 @@ import { QRCode } from '../../third_party/qrcode.js'; -import {cancelCurrentToast, toast} from '../../front_end_components/Toast.js'; +import { toast } from '../../front_end_components/Toast.js'; import { EVENT_IDS, PRIMITIVES, SOCKET_EVENTS, USER_TYPE_ICONS, USER_TYPES } from '../../../config/globals.js'; import { HTMLFragments } from '../../front_end_components/HTMLFragments.js'; import { Confirmation } from '../../front_end_components/Confirmation.js'; @@ -70,16 +70,22 @@ export class Lobby { document.getElementById('game-content').style.display = 'none'; document.body.appendChild(timerEditContainer); document.body.appendChild(timerEditContainerBackground); - this.gameCreationStepManager - .renderTimerStep(this.stateBucket.currentGameState, '2', this.stateBucket.currentGameState, this.gameCreationStepManager.steps); const timerEditPrompt = document.createElement('div'); timerEditPrompt.setAttribute('id', 'timer-edit-prompt'); timerEditPrompt.innerHTML = HTMLFragments.TIMER_EDIT_BUTTONS; + this.gameCreationStepManager.steps['3'].forwardHandler = (e) => { + e.preventDefault(); + if (e.type === 'click' || e.code === 'Enter') { + timerEditPrompt.querySelector('#save-timer-changes-button')?.click(); + } + }; + this.gameCreationStepManager + .renderTimerStep('mid-game-timer-editor', '3', this.stateBucket.currentGameState, this.gameCreationStepManager.steps); timerEditPrompt.querySelector('#save-timer-changes-button').addEventListener('click', () => { let hours = parseInt(document.getElementById('game-hours').value); let minutes = parseInt(document.getElementById('game-minutes').value); hours = this.gameCreationStepManager.standardizeNumberInput(hours); - minutes = this.gameCreationStepManager.standardizeNumberInput(minutes) + minutes = this.gameCreationStepManager.standardizeNumberInput(minutes); if (this.gameCreationStepManager.timerIsValid(hours, minutes)) { let hasTimer, timerParams; if (this.gameCreationStepManager.hasTimer(hours, minutes)) { @@ -116,7 +122,7 @@ export class Lobby { }); timerEditContainer.appendChild(timerEditPrompt); - } + }; this.editRolesHandler = (e) => { e.preventDefault(); @@ -194,11 +200,17 @@ export class Lobby { 'Participants (' + inLobbyCount + '/' + this.stateBucket.currentGameState.gameSize + ' Players)'; } - populateHeader () { + setTimer () { const timeString = getTimeString(this.stateBucket.currentGameState); const time = this.container.querySelector('#game-time'); time.innerText = timeString; + return timeString; + } + + populateHeader () { + const timeString = this.setTimer(); + const link = this.setLink(timeString); this.setPlayerCount(); @@ -293,6 +305,13 @@ export class Lobby { this.setPlayerCount(); }); + this.socket.on(EVENT_IDS.UPDATE_GAME_TIMER, (hasTimer, timerParams) => { + this.stateBucket.currentGameState.hasTimer = hasTimer; + this.stateBucket.currentGameState.timerParams = timerParams; + const timeString = this.setTimer(); + this.setLink(timeString); + }); + this.socket.on(EVENT_IDS.LEAVE_ROOM, (leftId, gameIsStartable) => { if (leftId === this.stateBucket.currentGameState.client.id) { window.location = '/?message=' + encodeURIComponent('You left the room.'); @@ -335,6 +354,7 @@ export class Lobby { if (existingPrompt) { enableStartButton(existingPrompt, this.startGameHandler); document.getElementById('edit-roles-button').addEventListener('click', this.editRolesHandler); + document.getElementById('edit-timer-button').addEventListener('click', this.editTimerHandler); } else { const newPrompt = document.createElement('div'); newPrompt.setAttribute('id', 'start-game-prompt'); @@ -343,6 +363,7 @@ export class Lobby { document.body.appendChild(newPrompt); enableStartButton(newPrompt, this.startGameHandler); document.getElementById('edit-roles-button').addEventListener('click', this.editRolesHandler); + document.getElementById('edit-timer-button').addEventListener('click', this.editTimerHandler); } } diff --git a/client/src/modules/page_handlers/gameHandler.js b/client/src/modules/page_handlers/gameHandler.js index 3704fae..c30a495 100644 --- a/client/src/modules/page_handlers/gameHandler.js +++ b/client/src/modules/page_handlers/gameHandler.js @@ -221,7 +221,10 @@ function setClientSocketHandlers (stateBucket, socket) { socket.on(EVENT_IDS.START_GAME, () => { fetchGameStateHandler(startGameStateAckFn); }); - socket.on(EVENT_IDS.RESTART_GAME, () => { fetchGameStateHandler(restartGameStateAckFn); }); + socket.on(EVENT_IDS.RESTART_GAME, () => { + document.querySelector('#game-control-prompt')?.remove(); + fetchGameStateHandler(restartGameStateAckFn); + }); socket.on(EVENT_IDS.SYNC_GAME_STATE, () => { socket.emit( diff --git a/client/src/styles/create.css b/client/src/styles/create.css index 7b4eb94..b34080e 100644 --- a/client/src/styles/create.css +++ b/client/src/styles/create.css @@ -191,14 +191,14 @@ margin-top: 0.5em; } -#deck-list-placeholder { +#deck-list-placeholder, #custom-role-placeholder { margin: auto; position: absolute; top: 0; left: 0; bottom: 0; right: 0; - width: 290px; + width: fit-content; height: 50px; font-size: 20px; text-align: center; diff --git a/client/src/styles/game.css b/client/src/styles/game.css index c5d4c85..5f2894f 100644 --- a/client/src/styles/game.css +++ b/client/src/styles/game.css @@ -106,13 +106,13 @@ max-width: 17em; } -#save-role-changes-button, #cancel-role-changes-button { +#save-role-changes-button, #cancel-role-changes-button, #save-timer-changes-button, #cancel-timer-changes-button { padding: 10px; font-size: 25px; margin: 0.5em 0; } -#role-edit-prompt { +#role-edit-prompt, #timer-edit-prompt { display: flex; margin: 10px 0; padding: 10px 0; @@ -123,11 +123,15 @@ background-color: #16141e; } -#role-edit-prompt button { +#timer-edit-prompt { + margin-top: 2em; +} + +#role-edit-prompt button, #timer-edit-prompt button { margin: 0 20px; } -#save-role-changes-button img { +#save-role-changes-button img, #save-timer-changes-button img { width: 20px; margin-left: 10px; } @@ -137,7 +141,8 @@ #mod-transfer-button, #edit-roles-button, #save-role-changes-button, -#edit-timer-button { +#edit-timer-button, +#save-timer-changes-button { background-color: #045ea6; border: 2px solid #024070; } @@ -147,12 +152,13 @@ #mod-transfer-button:hover, #edit-roles-button:hover, #save-role-changes-button:hover, -#edit-timer-button:hover { +#edit-timer-button:hover, +#save-timer-changes-button:hover { background-color: rgba(0, 120, 215, 0.45); border: 2px solid #045EA6; } -#mid-game-role-editor { +#mid-game-role-editor, #mid-game-timer-editor { display: flex; border-radius: 5px; position: fixed; @@ -166,7 +172,16 @@ overflow: auto; } -#role-edit-container-background { +#mid-game-timer-editor #game-time { + display: flex; + flex-wrap: wrap; + border-radius: 5px; + margin: 1em; + background-color: #16141e; + border: 2px solid #3b3a4a; +} + +#role-edit-container-background, #timer-edit-container-background { position: fixed; top: 0; left: 0; @@ -177,17 +192,15 @@ cursor: pointer; } -#mid-game-role-editor #step-2 { +#mid-game-role-editor #step-2 , #mid-game-timer-editor #step-3 { width: 100%; display: flex; justify-content: center; overflow: auto; align-items: center; + padding: 0; } -#save-button { - -} #mid-game-role-editor #custom-roles-container { height: fit-content; @@ -641,6 +654,7 @@ label[for='moderator'] { display: flex; flex-direction: row; align-items: center; + animation: fade-in-slide-up 0.5s ease-in-out; justify-content: center; position: fixed; z-index: 3; @@ -1167,11 +1181,7 @@ canvas { @keyframes fade-in-slide-up { 0% { opacity: 0; - transform: translateY(20px); - } - 5% { - opacity: 1; - transform: translateY(0px); + transform: translateY(10px); } 100% { opacity: 1; diff --git a/server/config/globals.js b/server/config/globals.js index e49bbe2..ec01331 100644 --- a/server/config/globals.js +++ b/server/config/globals.js @@ -78,6 +78,7 @@ const EVENT_IDS = { TIMER_EVENT: 'timerEvent', KICK_PERSON: 'kickPerson', UPDATE_GAME_ROLES: 'updateGameRoles', + UPDATE_GAME_TIMER: 'updateGameTimer', LEAVE_ROOM: 'leaveRoom', BROADCAST: 'broadcast' }; @@ -103,6 +104,7 @@ const SYNCABLE_EVENTS = function () { EVENT_IDS.END_TIMER, EVENT_IDS.KICK_PERSON, EVENT_IDS.UPDATE_GAME_ROLES, + EVENT_IDS.UPDATE_GAME_TIMER, EVENT_IDS.LEAVE_ROOM ]; }; diff --git a/server/model/GameCreationRequest.js b/server/model/GameCreationRequest.js index 9301c8f..b6ba6cc 100644 --- a/server/model/GameCreationRequest.js +++ b/server/model/GameCreationRequest.js @@ -56,6 +56,22 @@ class GameCreationRequest { } return false; } + + static timerParamsAreValid = (hasTimer, timerParams) => { + if (hasTimer === false) { + return timerParams === null; + } else { + if (timerParams === null || typeof timerParams !== 'object') { + return false; + } + + return (timerParams.hours === null && timerParams.minutes > 0 && timerParams.minutes < 60) + || (timerParams.minutes === null && timerParams.hours > 0 && timerParams.hours < 6) + || (timerParams.hours === 0 && timerParams.minutes > 0 && timerParams.minutes < 60) + || (timerParams.minutes === 0 && timerParams.hours > 0 && timerParams.hours < 6) + || (timerParams.hours > 0 && timerParams.hours < 6 && timerParams.minutes >= 0 && timerParams.minutes < 60); + } + } } function valid (gameParams) { @@ -65,24 +81,8 @@ function valid (gameParams) { && typeof gameParams.moderatorName === 'string' && gameParams.moderatorName.length > 0 && gameParams.moderatorName.length <= 30 - && timerParamsAreValid(gameParams.hasTimer, gameParams.timerParams) + && GameCreationRequest.timerParamsAreValid(gameParams.hasTimer, gameParams.timerParams) && GameCreationRequest.deckIsValid(gameParams.deck); } -function timerParamsAreValid (hasTimer, timerParams) { - if (hasTimer === false) { - return timerParams === null; - } else { - if (timerParams === null || typeof timerParams !== 'object') { - return false; - } - - return (timerParams.hours === null && timerParams.minutes > 0 && timerParams.minutes < 60) - || (timerParams.minutes === null && timerParams.hours > 0 && timerParams.hours < 6) - || (timerParams.hours === 0 && timerParams.minutes > 0 && timerParams.minutes < 60) - || (timerParams.minutes === 0 && timerParams.hours > 0 && timerParams.hours < 6) - || (timerParams.hours > 0 && timerParams.hours < 6 && timerParams.minutes >= 0 && timerParams.minutes < 60); - } -} - module.exports = GameCreationRequest; diff --git a/server/modules/Events.js b/server/modules/Events.js index 8b81797..6648ff7 100644 --- a/server/modules/Events.js +++ b/server/modules/Events.js @@ -69,7 +69,7 @@ const Events = [ } vars.ackFn({ errorFlag: 1, message: 'This name is taken.' }); } else if (socketArgs.newName.length > PRIMITIVES.MAX_PERSON_NAME_LENGTH) { - vars.ackFn({ errorFlag: 1, message: 'Your new name is too long - the max is' + PRIMITIVES.MAX_PERSON_NAME_LENGTH + ' characters.' }); + vars.ackFn({ errorFlag: 1, message: 'Your new name is too long - the max is ' + PRIMITIVES.MAX_PERSON_NAME_LENGTH + ' characters.' }); vars.hasNameChanged = false; } else if (socketArgs.newName.length === 0) { vars.ackFn({ errorFlag: 1, message: 'Your new name cannot be empty.' }); @@ -381,6 +381,25 @@ const Events = [ } } }, + { + id: EVENT_IDS.UPDATE_GAME_TIMER, + stateChange: async (game, socketArgs, vars) => { + if (GameCreationRequest.timerParamsAreValid(socketArgs.hasTimer, socketArgs.timerParams)) { + game.hasTimer = socketArgs.hasTimer; + game.timerParams = socketArgs.timerParams; + } + }, + communicate: async (game, socketArgs, vars) => { + if (vars.ackFn) { + vars.ackFn(); + } + vars.gameManager.namespace.in(game.accessCode).emit( + EVENT_IDS.UPDATE_GAME_TIMER, + game.hasTimer, + game.timerParams + ); + } + }, { id: EVENT_IDS.END_TIMER, stateChange: async (game, socketArgs, vars) => { From e015f1baeac09a8e49da186f7641a4efef3810e3 Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Wed, 16 Aug 2023 13:57:03 -0400 Subject: [PATCH 3/6] fix timer validation, make error toasts brighter --- .../game_creation/GameCreationStepManager.js | 6 +++--- client/src/styles/GLOBAL.css | 17 +++++++++-------- client/src/styles/create.css | 4 ++-- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/client/src/modules/game_creation/GameCreationStepManager.js b/client/src/modules/game_creation/GameCreationStepManager.js index 063eb64..51b612a 100644 --- a/client/src/modules/game_creation/GameCreationStepManager.js +++ b/client/src/modules/game_creation/GameCreationStepManager.js @@ -139,7 +139,7 @@ export class GameCreationStepManager { restoreButton(); break; case 400: - toast('Your game has invalid parameters..', 'error', true, true); + toast('Your game has invalid parameters.', 'error', true, true); restoreButton(); break; case 201: @@ -331,11 +331,11 @@ export class GameCreationStepManager { } if (hours !== null) { - valid = hours <= PRIMITIVES.MAX_HOURS; + valid = hours > 0 && hours <= PRIMITIVES.MAX_HOURS; } if (minutes !== null) { - valid = minutes <= PRIMITIVES.MAX_MINUTES; + valid = minutes > 0 && minutes <= PRIMITIVES.MAX_MINUTES; } return valid; diff --git a/client/src/styles/GLOBAL.css b/client/src/styles/GLOBAL.css index e7a2493..7466ee8 100644 --- a/client/src/styles/GLOBAL.css +++ b/client/src/styles/GLOBAL.css @@ -87,8 +87,8 @@ textarea { } .toast-error { - background-color: #fdaeb7; - border: 3px solid #c78a8a; + background-color: #f98e9a; + border: 3px solid #c57272; } .toast-neutral { @@ -338,12 +338,13 @@ input { border-radius: 5px; font-family: 'signika-negative', sans-serif; font-weight: 100; - box-shadow: 0 1px 1px rgba(0,0,0,0.11), - 0 2px 2px rgba(0,0,0,0.11), - 0 4px 4px rgba(0,0,0,0.11), - 0 8px 8px rgba(0,0,0,0.11), - 0 16px 16px rgba(0,0,0,0.11), - 0 32px 32px rgba(0,0,0,0.11); + box-shadow: + 0 1px 1px rgba(0,0,0,0.11), + 0 2px 2px rgba(0,0,0,0.11), + 0 4px 4px rgba(0,0,0,0.11), + 0 8px 8px rgba(0,0,0,0.11), + 0 16px 16px rgba(0,0,0,0.11), + 0 32px 32px rgba(0,0,0,0.11); left: 0; right: 0; width: fit-content; diff --git a/client/src/styles/create.css b/client/src/styles/create.css index b34080e..53dc8ae 100644 --- a/client/src/styles/create.css +++ b/client/src/styles/create.css @@ -164,7 +164,7 @@ } #deck-status-container { - width: 20em; + width: 25em; max-width: 95%; height: 20em; overflow-y: auto; @@ -457,7 +457,7 @@ input[type="number"] { #role-select { margin: 0.5em 1em 1.5em 0; overflow-y: auto; - height: 16em; + height: 20em; } .default-role, .custom-role, .added-role { From 531c093bbc923352196686414fe8b1d38e5c894a Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Wed, 16 Aug 2023 14:08:03 -0400 Subject: [PATCH 4/6] prevent duplicate HTML id --- .../src/modules/front_end_components/HTMLFragments.js | 2 +- client/src/modules/game_state/states/Lobby.js | 10 +++------- client/src/styles/game.css | 4 ++++ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client/src/modules/front_end_components/HTMLFragments.js b/client/src/modules/front_end_components/HTMLFragments.js index 2f6ff18..35fa91d 100644 --- a/client/src/modules/front_end_components/HTMLFragments.js +++ b/client/src/modules/front_end_components/HTMLFragments.js @@ -11,7 +11,7 @@ export const HTMLFragments = {
clock -
+
person diff --git a/client/src/modules/game_state/states/Lobby.js b/client/src/modules/game_state/states/Lobby.js index dc29961..2c87a95 100644 --- a/client/src/modules/game_state/states/Lobby.js +++ b/client/src/modules/game_state/states/Lobby.js @@ -202,7 +202,7 @@ export class Lobby { setTimer () { const timeString = getTimeString(this.stateBucket.currentGameState); - const time = this.container.querySelector('#game-time'); + const time = this.container.querySelector('#timer-parameters'); time.innerText = timeString; return timeString; @@ -416,14 +416,10 @@ function getTimeString (gameState) { const hours = gameState.timerParams.hours; const minutes = gameState.timerParams.minutes; if (hours) { - timeString += hours > 1 - ? hours + ' hours ' - : hours + ' hour '; + timeString += hours + 'h ' } if (minutes) { - timeString += minutes > 1 - ? minutes + ' minutes ' - : minutes + ' minute '; + timeString += minutes + 'm' } return timeString; } else { diff --git a/client/src/styles/game.css b/client/src/styles/game.css index 5f2894f..fcfe87f 100644 --- a/client/src/styles/game.css +++ b/client/src/styles/game.css @@ -181,6 +181,10 @@ border: 2px solid #3b3a4a; } +#timer-parameters { + width: 65px; +} + #role-edit-container-background, #timer-edit-container-background { position: fixed; top: 0; From a986eb4c62e6a04d1b2eb21616eefe67a28bdf62 Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Wed, 16 Aug 2023 14:10:40 -0400 Subject: [PATCH 5/6] lint --- client/src/modules/game_state/states/Lobby.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/modules/game_state/states/Lobby.js b/client/src/modules/game_state/states/Lobby.js index 2c87a95..61ed3c7 100644 --- a/client/src/modules/game_state/states/Lobby.js +++ b/client/src/modules/game_state/states/Lobby.js @@ -416,10 +416,10 @@ function getTimeString (gameState) { const hours = gameState.timerParams.hours; const minutes = gameState.timerParams.minutes; if (hours) { - timeString += hours + 'h ' + timeString += hours + 'h '; } if (minutes) { - timeString += minutes + 'm' + timeString += minutes + 'm'; } return timeString; } else { From 54d16ff565fc37d96d26d819ec8c18e291a9d0fd Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Wed, 16 Aug 2023 14:14:07 -0400 Subject: [PATCH 6/6] fix description --- client/src/config/defaultRoles.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/config/defaultRoles.js b/client/src/config/defaultRoles.js index 44ceda4..96f7bff 100644 --- a/client/src/config/defaultRoles.js +++ b/client/src/config/defaultRoles.js @@ -37,8 +37,8 @@ export const defaultRoles = [ { role: 'Doctor', team: 'good', - description: `Each night, choose a player to protect from the Werewolves. This can be yourself. If the Werewolves - target this person, they still survive to the following day.` + description: 'Each night, choose a player to protect from the Werewolves. This can be yourself. If the Werewolves ' + + 'target this person, they still survive to the following day.' }, { role: 'Parity Hunter',