diff --git a/client/src/config/globals.js b/client/src/config/globals.js index f428f49..0c9cf28 100644 --- a/client/src/config/globals.js +++ b/client/src/config/globals.js @@ -3,6 +3,8 @@ export const PRIMITIVES = { USER_SIGNATURE_LENGTH: 75, CLOCK_TICK_INTERVAL_MILLIS: 50, MAX_CUSTOM_ROLE_NAME_LENGTH: 50, + MAX_PERSON_NAME_LENGTH: 40, + MAX_DECK_SIZE: 50, MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH: 1000, TOAST_DURATION_DEFAULT: 6, ACCESS_CODE_LENGTH: 4, diff --git a/client/src/images/tutorial/player-view.gif b/client/src/images/tutorial/player-view.gif index 66424b6..501c04c 100644 Binary files a/client/src/images/tutorial/player-view.gif and b/client/src/images/tutorial/player-view.gif differ diff --git a/client/src/modules/game_creation/GameCreationStepManager.js b/client/src/modules/game_creation/GameCreationStepManager.js index 67133f9..c968fc0 100644 --- a/client/src/modules/game_creation/GameCreationStepManager.js +++ b/client/src/modules/game_creation/GameCreationStepManager.js @@ -1,7 +1,7 @@ import { Game } from '../../model/Game.js'; import { cancelCurrentToast, toast } from '../front_end_components/Toast.js'; import { ModalManager } from '../front_end_components/ModalManager.js'; -import { ALIGNMENT } from '../../config/globals.js'; +import { ALIGNMENT, PRIMITIVES } from '../../config/globals.js'; import { HTMLFragments } from '../front_end_components/HTMLFragments.js'; import { UserUtility } from '../utility/UserUtility.js'; import { RoleBox } from './RoleBox.js'; @@ -35,7 +35,7 @@ export class GameCreationStepManager { 2: { title: 'Create your deck (you can edit this later):', forwardHandler: () => { - if (this.deckManager.getDeckSize() > 50) { + if (this.deckManager.getDeckSize() > PRIMITIVES.MAX_DECK_SIZE) { toast('Your deck is too large. The max is 50 cards.', 'error', true); } else { this.currentGame.deck = this.deckManager.deck.filter((card) => card.quantity > 0); @@ -569,11 +569,11 @@ function initializeRemainingEventListeners (deckManager, roleBox) { } function processNewCustomRoleSubmission (name, description, team, deckManager, isUpdate, roleBox, option = null) { - if (name.length > 50) { + if (name.length > PRIMITIVES.MAX_CUSTOM_ROLE_NAME_LENGTH) { toast('Your name is too long (max 50 characters).', 'error', true); return; } - if (description.length > 500) { + if (description.length > PRIMITIVES.MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH) { toast('Your description is too long (max 500 characters).', 'error', true); return; } @@ -596,5 +596,5 @@ function hasTimer (hours, minutes) { } function validateName (name) { - return typeof name === 'string' && name.length > 0 && name.length <= 40; + 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 92e878c..e6dabbd 100644 --- a/client/src/modules/game_state/states/Lobby.js +++ b/client/src/modules/game_state/states/Lobby.js @@ -1,6 +1,6 @@ import { QRCode } from '../../third_party/qrcode.js'; import { toast } from '../../front_end_components/Toast.js'; -import { EVENT_IDS, SOCKET_EVENTS, USER_TYPE_ICONS, USER_TYPES } from '../../../config/globals.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'; import { SharedStateUtil } from './shared/SharedStateUtil.js'; @@ -79,7 +79,7 @@ export class Lobby { 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) { + if (this.gameCreationStepManager.deckManager.getDeckSize() > PRIMITIVES.MAX_DECK_SIZE) { toast('Your deck is too large. The max is 50 cards.', 'error', true); } else { document.querySelector('#mid-game-role-editor')?.remove(); diff --git a/client/src/scripts/join.js b/client/src/scripts/join.js index 452f9c1..c1d8867 100644 --- a/client/src/scripts/join.js +++ b/client/src/scripts/join.js @@ -90,7 +90,7 @@ function resetJoinButtonState (e, joinHandler) { } function validateName (name) { - return typeof name === 'string' && name.length > 0 && name.length <= 40; + return typeof name === 'string' && name.length > 0 && name.length <= PRIMITIVES.MAX_PERSON_NAME_LENGTH; } if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { diff --git a/client/src/styles/GLOBAL.css b/client/src/styles/GLOBAL.css index 0e55971..e7a2493 100644 --- a/client/src/styles/GLOBAL.css +++ b/client/src/styles/GLOBAL.css @@ -315,7 +315,7 @@ button { } #how-to-use-container h1 { - color: #d7d7d7; + color: #21ba45; font-family: signika-negative, sans-serif; background-color: #1e1b26; width: fit-content; @@ -376,6 +376,11 @@ input { #how-to-use-container h3 { color: #b1afcd; font-weight: bold; + font-family: signika-negative, sans-serif; + background-color: #1e1b26; + width: fit-content; + padding: 0 5px; + border-radius: 5px; } #tutorial-links {text-align: left; @@ -829,7 +834,7 @@ input { @media(max-width: 550px) { .how-to-use-header { - font-size: 25px; + font-size: 30px; } #how-to-use-container h3 { font-size: 20px; diff --git a/client/src/views/how-to-use.html b/client/src/views/how-to-use.html index 039854d..8edb961 100644 --- a/client/src/views/how-to-use.html +++ b/client/src/views/how-to-use.html @@ -25,7 +25,7 @@ -

Purpose of the Application

+

Purpose

This app serves as a means of running games in a social setting where a traditional running of the game is hindered. This might be when people are meeting virtually, and thus roles can't be handed out in-person, or when people are in-person but don't have Werewolf/Mafia cards with them. You can use a deck of regular @@ -52,7 +52,7 @@
Creating a game through the app has 3 main components:
-

Step One: Choosing a method of moderation

+

- Step One: Choosing a method of moderation


You have two options for moderation during the game. If the moderator isn't playing, you can choose the Dedicated Moderator option. Dedicated Moderators are not dealt into the game. Once they start the game, they will know @@ -68,7 +68,7 @@ or to a spectator. That way, if the current Dedicated Moderator has to leave, or simply does not want to moderate anymore, they can easily delegate.

-

Step Two: Build your deck

+

- Step Two: Build your deck


There is a role box on this page that includes a list of Default Roles and a list of Custom Roles, which can be displayed by selecting the appropriate button within the box. @@ -79,7 +79,7 @@

Here I add 3 villagers to the game, and then remove them:

- + adding a role to the deck

You can add, edit, and remove Custom Roles. You can also import and export them via a formatted text file. Click the hamburger menu on the role box to see the import/export options. Here I create a new Custom Role and observe @@ -87,7 +87,7 @@

create-custom-role

-

Step Three: Set an optional timer

+

- Step Three: Set an optional timer


If you don't fill in these fields, the game will be untimed. If you do, you can use a time between 1 minute and 5 hours. The timer can be played and paused by the current moderator. Importantly, when the timer expires, @@ -96,7 +96,7 @@

Being the Moderator

-

In the Lobby

+

- In the Lobby


In the Lobby, moderators can manage the people in the room and the cards in the game. By clicking the three vertical dots (AKA the "kebab menu") next to a given player (point A @@ -107,9 +107,9 @@ Saving any changes to the roles may affect the player count. If you wish to start the game (point B), the number of Players in the Lobby must equal the number of cards in the game.

- + moderator view in the lobby

-

During the Game

+

- During the Game


Dedicated Moderators can see who is on which team and who is which role. The moderator Kills and Reveals players (Point A below). They are separate actions. So, if you @@ -120,33 +120,33 @@ play and pause the Timer (Point B), and can end the game (revealing everyone's role) or return the game to the Lobby (Point C), where it can be started anew with different settings.

- + moderator view during the game

Similarly, the Temporary Moderator view looks like the below image. They have much the same abilities as a dedicated moderator, except they don't know role or alignment information and cannot transfer their powers. Their powers will be transferred automatically to the first person they remove from the game (which can be themselves).

- + temporary moderator view during the game

-

Transferring your moderator powers

+

- Transferring your moderator powers


You can transfer your moderator abilities to anyone that has been removed from the game, or to anyone that happens to be spectating. Here we select a killed player and transfer our powers to them:

- + transferring your moderator powers

Being a Player

- This is an example of what a Player is seeing. The timer is running, and they view their - role by double-clicking it: + This is an example of what a Player is seeing, including the running timer, + their role card, and the player list. You can also edit your name for the Room by clicking the pencil next to it. + Below, we flip our role card up and down by double-clicking it, and then we bring up the prompt to edit our name:

- + player-view

- There are three main things - the Timer, your Role Card - and the Player List. Players can view the timer, but only the current moderator can play and pause it. - Your role card starts flipped over - this is useful if you are in-person and don't want someone else accidentally seeing your role as + Players can view the timer, but only the current moderator can play and pause it. Your role card starts flipped over + - this is useful if you are in-person and don't want someone else accidentally seeing your role as it is dealt. You can view your role at any time by double-clicking/double-tapping it. Requiring a double-click guards against the possibility of accidentally flipping your role when tapping other things. Within the Player List, you can see who is alive or dead and who has had their role revealed. There is also a role info button that, diff --git a/server/modules/ServerTimer.js b/server/modules/ServerTimer.js index 8d6b69e..6b87204 100644 --- a/server/modules/ServerTimer.js +++ b/server/modules/ServerTimer.js @@ -7,25 +7,23 @@ This timer is accurate to within a few ms for any amount of time provided. */ function stepFn (serverTimerInstance, expected) { - const now = Date.now(); // - serverTimerInstance.currentTimeInMillis = serverTimerInstance.totalTime - (now - serverTimerInstance.start); - if (now - serverTimerInstance.start >= serverTimerInstance.totalTime) { // check if the time has elapsed + serverTimerInstance.currentTimeInMillis = serverTimerInstance.totalTime - (Date.now() - serverTimerInstance.start); + if (Date.now() - serverTimerInstance.start >= serverTimerInstance.totalTime) { // check if the time has elapsed serverTimerInstance.logger.debug( - 'ELAPSED: ' + (now - serverTimerInstance.start) + 'ms (~' + - (Math.abs(serverTimerInstance.totalTime - (now - serverTimerInstance.start)) / serverTimerInstance.totalTime).toFixed(3) + '% error).' + 'ELAPSED: ' + (Date.now() - serverTimerInstance.start) + 'ms (~' + + (Math.abs(serverTimerInstance.totalTime - (Date.now() - serverTimerInstance.start)) / serverTimerInstance.totalTime).toFixed(3) + '% error).' ); serverTimerInstance.timesUpResolver(); // this is a reference to the callback defined in the construction of the promise in runTimer() clearTimeout(serverTimerInstance.ticking); return; } - const delta = now - expected; expected += serverTimerInstance.interval; serverTimerInstance.ticking = setTimeout(function () { stepFn( serverTimerInstance, expected ); - }, Math.max(0, serverTimerInstance.interval - delta)); // take into account drift + }, Math.max(0, serverTimerInstance.interval - (Date.now() - expected))); // take into account drift } class ServerTimer {