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 @@
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:
-

+
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 @@
-
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 @@
-
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.
-

+
-
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.
-

+
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).
-

+
-
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:
-

+
- 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:
-

+
- 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 {