Merge pull request #170 from AlecM33/name-change

Edit name at any time
This commit is contained in:
Alec
2023-08-14 18:59:58 -04:00
committed by GitHub
19 changed files with 212 additions and 80 deletions

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 KiB

After

Width:  |  Height:  |  Size: 417 KiB

View File

@@ -1,6 +1,6 @@
import { toast } from './Toast.js';
export const Confirmation = (message, onYes = null, isDOMNode = false) => {
export const Confirmation = (message, onYes = null, isDOMNode = false, confirmButtonText = 'Yes') => {
document.querySelector('#confirmation')?.remove();
document.querySelector('#confirmation-background')?.remove();
@@ -11,7 +11,7 @@ export const Confirmation = (message, onYes = null, isDOMNode = false) => {
? `<div id="confirmation-message"></div>
<div class="confirmation-buttons">
<button id="confirmation-cancel-button" class="app-button cancel">Cancel</button>
<button id="confirmation-yes-button" class="app-button">Yes</button>
<button id="confirmation-yes-button" class="app-button">` + confirmButtonText + `</button>
</div>`
: `<div id="confirmation-message"></div>
<div class="confirmation-buttons-centered">

View File

@@ -94,7 +94,7 @@ export const HTMLFragments = {
</div>
<div id='game-people-container'>
<div id="current-moderator" class="moderator">
<div id="current-moderator-name"></div>
<div id="current-moderator-name" class="person-name-element"></div>
<div id="current-moderator-type"></div>
</div>
<label id='players-alive-label'></label>
@@ -128,7 +128,7 @@ export const HTMLFragments = {
</div>
<div id='game-people-container'>
<div id="current-moderator" class="moderator">
<div id="current-moderator-name"></div>
<div id="current-moderator-name" class="person-name-element"></div>
<div id="current-moderator-type"></div>
</div>
<label id='players-alive-label'></label>
@@ -245,7 +245,7 @@ export const HTMLFragments = {
</div>`,
MODERATOR_PLAYER:
`<div>
<div class='game-player-name'></div>
<div class='game-player-name person-name-element'></div>
<div class='game-player-role'></div>
</div>
<div class='player-action-buttons'>
@@ -254,7 +254,7 @@ export const HTMLFragments = {
</div>`,
GAME_PLAYER:
`<div>
<div class='game-player-name'></div>
<div class='game-player-name person-name-element'></div>
<div class='game-player-role'></div>
</div>`,
INITIAL_GAME_DOM:
@@ -265,6 +265,9 @@ export const HTMLFragments = {
<div id='client-name'></div>
<div id='client-user-type'></div>
</div>
<button id="edit-name-button">
<img alt="edit name" src="../../images/pencil.svg"/>
</button>
</div>
<div id='game-state-container'></div>`,
// via https://loading.io/css/
@@ -283,18 +286,10 @@ export const HTMLFragments = {
<div></div>
<div></div>
</div>`,
NAME_CHANGE_MODAL:
`<div id='change-name-modal-background' class='modal-background'></div>
<div tabindex='-1' id='change-name-modal' class='modal'>
<form id='change-name-form'>
<div id='transfer-mod-form-content'>
<label for='player-new-name'>Your name:</label>
<input id='player-new-name' autocomplete='given-name' type='text'/>
</div>
<div class='modal-button-container'>
<input type='submit' id='submit-new-name' value='Set Name'/>
</div>
</form>
NAME_CHANGE_FORM:
`<div id='change-name-form-content'>
<label for='client-new-name'>Your name:</label>
<input maxlength="40" id='client-new-name' autocomplete='off' type='text'/>
</div>`,
ROLE_INFO_MODAL:
`<div id='role-info-modal-background' class='modal-background'></div>
@@ -314,7 +309,7 @@ export const HTMLFragments = {
</div>
<div id='game-people-container'>
<div id="current-moderator" class="moderator">
<div id="current-moderator-name"></div>
<div id="current-moderator-name" class="person-name-element"></div>
<div id="current-moderator-type"></div>
</div>
<label id='players-alive-label'></label>

View File

@@ -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,8 +35,8 @@ export class GameCreationStepManager {
2: {
title: 'Create your deck (you can edit this later):',
forwardHandler: () => {
if (this.deckManager.getDeckSize() > 50) {
toast('Your deck is too large. The max is 50 cards.', 'error', true);
if (this.deckManager.getDeckSize() > PRIMITIVES.MAX_DECK_SIZE) {
toast('Your deck is too large. The max is ' + PRIMITIVES.MAX_DECK_SIZE + ' cards.', 'error', true);
} else {
this.currentGame.deck = this.deckManager.deck.filter((card) => card.quantity > 0);
cancelCurrentToast();
@@ -98,7 +98,7 @@ export class GameCreationStepManager {
this.incrementStep();
this.renderStep('creation-step-container', this.step);
} else {
toast('Name must be between 1 and 30 characters.', 'error', true);
toast('Name must be between 1 and ' + PRIMITIVES.MAX_PERSON_NAME_LENGTH + ' characters.', 'error', true);
}
}
},
@@ -569,12 +569,12 @@ function initializeRemainingEventListeners (deckManager, roleBox) {
}
function processNewCustomRoleSubmission (name, description, team, deckManager, isUpdate, roleBox, option = null) {
if (name.length > 40) {
toast('Your name is too long (max 40 characters).', 'error', true);
if (name.length > PRIMITIVES.MAX_CUSTOM_ROLE_NAME_LENGTH) {
toast('Your name is too long (max ' + PRIMITIVES.MAX_CUSTOM_ROLE_NAME_LENGTH + ' characters).', 'error', true);
return;
}
if (description.length > 500) {
toast('Your description is too long (max 500 characters).', 'error', true);
if (description.length > PRIMITIVES.MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH) {
toast('Your description is too long (max ' + PRIMITIVES.MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH + ' characters).', 'error', true);
return;
}
if (isUpdate) {
@@ -596,5 +596,5 @@ function hasTimer (hours, minutes) {
}
function validateName (name) {
return typeof name === 'string' && name.length > 0 && name.length <= 30;
return typeof name === 'string' && name.length > 0 && name.length <= PRIMITIVES.MAX_PERSON_NAME_LENGTH;
}

View File

@@ -53,6 +53,7 @@ function renderGroupOfPlayers (
for (const player of people) {
const playerEl = document.createElement('div');
playerEl.classList.add('game-player');
playerEl.dataset.pointer = player.id;
playerEl.innerHTML = HTMLFragments.GAME_PLAYER;
playerEl.querySelector('.game-player-name').innerText = player.name;

View File

@@ -322,6 +322,7 @@ export class InProgress {
for (const player of people) {
const playerEl = document.createElement('div');
playerEl.classList.add('game-player');
playerEl.dataset.pointer = player.id;
// add a reference to the player's id for each corresponding element in the list
if (moderatorType) {

View File

@@ -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();
@@ -354,8 +354,9 @@ function getTimeString (gameState) {
function renderLobbyPerson (person, gameState, socket) {
const el = document.createElement('div');
el.dataset.pointer = person.id;
const personNameEl = document.createElement('div');
personNameEl.classList.add('lobby-player-name');
personNameEl.classList.add('lobby-player-name', 'person-name-element');
const personTypeEl = document.createElement('div');
personNameEl.innerText = person.name;
personTypeEl.innerText = person.userType + USER_TYPE_ICONS[person.userType];

View File

@@ -100,8 +100,9 @@ export const SharedStateUtil = {
} else {
for (const spectator of spectators) {
const spectatorEl = document.createElement('div');
spectatorEl.dataset.pointer = spectator.id;
spectatorEl.classList.add('spectator');
spectatorEl.innerHTML = '<div class=\'spectator-name\'></div>' +
spectatorEl.innerHTML = '<div class=\'spectator-name person-name-element\'></div>' +
'<div>' + 'spectator' + USER_TYPE_ICONS.spectator + '</div>';
spectatorEl.querySelector('.spectator-name').innerText = spectator.name;
list.appendChild(spectatorEl);
@@ -123,6 +124,7 @@ export const SharedStateUtil = {
},
displayCurrentModerator: (moderator) => {
document.getElementById('current-moderator').dataset.pointer = moderator.id;
document.getElementById('current-moderator-name').innerText = moderator.name;
document.getElementById('current-moderator-type').innerText = moderator.userType + USER_TYPE_ICONS[moderator.userType];
},
@@ -183,9 +185,30 @@ export const SharedStateUtil = {
});
},
displayClientInfo: (name, userType) => {
document.getElementById('client-name').innerText = name;
document.getElementById('client-user-type').innerText = userType;
document.getElementById('client-user-type').innerText += USER_TYPE_ICONS[userType];
displayClientInfo: (gameState, socket) => {
document.getElementById('client-name').innerText = gameState.client.name;
document.getElementById('client-user-type').innerText = gameState.client.userType;
document.getElementById('client-user-type').innerText += USER_TYPE_ICONS[gameState.client.userType];
const nameForm = document.createElement('form');
nameForm.setAttribute('id', 'name-change-form');
nameForm.onsubmit = (e) => {
e.preventDefault();
document.getElementById('confirmation-yes-button').click();
};
nameForm.innerHTML = HTMLFragments.NAME_CHANGE_FORM;
nameForm.querySelector('#client-new-name').value = gameState.client.name;
document.getElementById('edit-name-button').addEventListener('click', () => {
Confirmation(nameForm, () => {
socket.emit(
SOCKET_EVENTS.IN_GAME_MESSAGE,
EVENT_IDS.CHANGE_NAME,
gameState.accessCode,
{ personId: gameState.client.id, newName: document.getElementById('client-new-name').value },
(response) => {
toast(response.message, response.errorFlag === 1 ? 'error' : 'success', true);
}
);
}, true, 'Update');
});
}
};

View File

@@ -135,7 +135,7 @@ function processGameState (
});
}
SharedStateUtil.displayClientInfo(currentGameState.client.name, currentGameState.client.userType);
SharedStateUtil.displayClientInfo(currentGameState, socket);
switch (currentGameState.status) {
case STATUS.LOBBY:
@@ -236,6 +236,20 @@ function setClientSocketHandlers (stateBucket, socket) {
);
});
socket.on(EVENT_IDS.CHANGE_NAME, (changedId, newName) => {
const person = stateBucket.currentGameState.people.find(person => person.id === changedId);
if (person) {
person.name = newName;
if (stateBucket.currentGameState.client.id === changedId) {
stateBucket.currentGameState.client.name = newName;
SharedStateUtil.displayClientInfo(stateBucket.currentGameState, socket);
}
document.querySelectorAll('[data-pointer="' + person.id + '"]').forEach((node) => {
node.querySelector('.person-name-element').innerText = newName;
});
}
});
socket.on(EVENT_IDS.END_GAME, (people) => {
stateBucket.currentGameState.people = people;
stateBucket.currentGameState.status = STATUS.ENDED;

View File

@@ -55,7 +55,7 @@ const joinHandler = (e) => {
resetJoinButtonState(e, joinHandler);
});
} else {
toast('Name must be between 1 and 30 characters.', 'error', true, true, 'long');
toast('Name must be between 1 and ' + PRIMITIVES.MAX_PERSON_NAME_LENGTH + ' characters.', 'error', true, true, 'long');
}
};
@@ -90,7 +90,7 @@ function resetJoinButtonState (e, joinHandler) {
}
function validateName (name) {
return typeof name === 'string' && name.length > 0 && name.length <= 30;
return typeof name === 'string' && name.length > 0 && name.length <= PRIMITIVES.MAX_PERSON_NAME_LENGTH;
}
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {

View File

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

View File

@@ -564,8 +564,34 @@ h1 {
}
#client-container {
max-width: 35em;
max-width: 25em;
width: 75%;
margin: 1em 0;
position: relative;
}
#client-container button {
background-color: transparent;
height: fit-content;
margin: 0 8px;
cursor: pointer;
padding: 5px;
border-radius: 5px;
border: 1px solid transparent;
position: absolute;
right: -50px;
top: 32px;
}
#client-container button:hover {
cursor: pointer;
filter: brightness(1.5);
background-color: #8080804d;
}
#client-container button img {
width: 22px;
pointer-events: none;
}
#client {
@@ -575,16 +601,19 @@ h1 {
align-items: center;
justify-content: space-between;
border-radius: 5px;
min-width: 15em;
border: 1px solid #46455299;
background: #4645525c;
}
#client-name {
max-width: 13em;
color: #e7e7e7;
font-family: 'signika-negative', sans-serif;
font-size: 25px;
margin: 0.25em 2em 0.25em 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#client-user-type {
@@ -920,8 +949,22 @@ canvas {
justify-content: center;
}
#change-name-modal-background {
cursor: default;
#change-name-form-content {
display: flex;
flex-direction: column;
margin: 0 auto;
max-width: 15em;
text-align: left;
margin: 2px auto;
}
#change-name-form-content input {
font-size: 20px;
}
#change-name-form-content label {
display: flex;
margin-bottom: 0.5em;
}
#lobby-people-container , #game-people-container {
@@ -954,6 +997,16 @@ canvas {
}
@media(max-width: 500px) {
#client-container button img {
width: 18px;
pointer-events: none;
}
#client-container button {
right: -46px;
top: 27px;
}
label {
font-size: 18px;
}
@@ -968,6 +1021,7 @@ canvas {
#client-name {
font-size: 20px;
margin: 0.25em 0 0.25em 0;
}
#client-user-type, #game-parameters {

View File

@@ -25,7 +25,7 @@
<div id="tutorial-links">
<ul>
<li>
<a href="#purpose-of-the-app">Purpose of the App</a>
<a href="#purpose-of-the-app">Purpose</a>
</li>
<li>
<a href="#creating-a-game">Creating a Game</a>
@@ -38,7 +38,7 @@
</li>
</ul>
</div>
<h1 class="how-to-use-header" id="purpose-of-the-app">Purpose of the Application</h1>
<h1 class="how-to-use-header" id="purpose-of-the-app">Purpose</h1>
<div class="how-to-use-section">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 @@
<div class="how-to-use-section">
Creating a game through the app has 3 main components:
<br>
<h3>Step One: Choosing a method of moderation</h3>
<h3>- Step One: Choosing a method of moderation</h3>
<br>
You have two options for moderation during the game. If the moderator isn't playing, you can choose the <span class="emphasized">Dedicated
Moderator</span> 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.
<br><br>
<h3>Step Two: Build your deck</h3>
<h3>- Step Two: Build your deck</h3>
<br>
There is a role box on this page that includes a list of <span class="emphasized">Default Roles</span> and a list
of <span class="emphasized">Custom Roles</span>, which can be displayed by selecting the appropriate button within the box.
@@ -79,7 +79,7 @@
<br><br>
Here I add 3 villagers to the game, and then remove them:
<br><br>
<img class='tutorial-image-small' src="../images/tutorial/add-role-to-deck.gif"/>
<img alt="adding a role to the deck" class='tutorial-image-small' src="../images/tutorial/add-role-to-deck.gif"/>
<br><br>
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 @@
<br><br>
<img alt="create-custom-role" class='tutorial-image-small' src="../images/tutorial/create-custom-role.gif"/>
<br><br>
<h3>Step Three: Set an optional timer</h3>
<h3>- Step Three: Set an optional timer</h3>
<br>
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 @@
</div>
<h1 class="how-to-use-header" id="being-the-moderator">Being the Moderator</h1>
<div class="how-to-use-section">
<h3>In the Lobby</h3>
<h3>- In the Lobby</h3>
<br>
In the Lobby, moderators can manage the people in the room and the cards in the game. By clicking
the <span class="emphasized">three vertical dots (AKA the "kebab menu")</span> next to a given player (<span class="emphasized">point A</span>
@@ -107,9 +107,9 @@
Saving any changes to the roles may affect the player count. If you wish to <span class="emphasized">start the game (point B)</span>, the number
of Players in the Lobby must equal the number of cards in the game.
<br><br>
<img class='tutorial-image-small-portrait' src="../images/tutorial/dedicated-mod-lobby-mobile.webp"/>
<img alt="moderator view in the lobby" class='tutorial-image-small-portrait' src="../images/tutorial/dedicated-mod-lobby-mobile.webp"/>
<br><br>
<h3>During the Game</h3>
<h3>- During the Game</h3>
<br>
<span class="emphasized">Dedicated Moderators</span> can see who is on which team and who is which role. The moderator
Kills and Reveals players (<span class="emphasized">Point A</span> below). They are separate actions. So, if you
@@ -120,33 +120,33 @@
play and pause the Timer (<span class="emphasized">Point B</span>), and can end the game (revealing everyone's role)
or return the game to the Lobby (<span class="emphasized">Point C</span>), where it can be started anew with different settings.
<br><br>
<img class='tutorial-image-small-portrait' src="../images/tutorial/dedicated-mod-in-progress-mobile.webp"/>
<img alt="moderator view during the game" class='tutorial-image-small-portrait' src="../images/tutorial/dedicated-mod-in-progress-mobile.webp"/>
<br><br>
Similarly, the <span class="emphasized">Temporary Moderator view</span> 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).
<br><br>
<img class='tutorial-image-small-portrait' src="../images/tutorial/temp-mod-in-progress-mobile.webp"/>
<img alt="temporary moderator view during the game" class='tutorial-image-small-portrait' src="../images/tutorial/temp-mod-in-progress-mobile.webp"/>
<br><br>
<h3>Transferring your moderator powers</h3>
<h3>- Transferring your moderator powers</h3>
<br>
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:
<br><br>
<img class='tutorial-image-small-portrait' src="../images/tutorial/mod-transfer.gif"/>
<img alt="transferring your moderator powers" class='tutorial-image-small-portrait' src="../images/tutorial/mod-transfer.gif"/>
<br><br>
</div>
<h1 class="how-to-use-header" id="being-a-player">Being a Player</h1>
<div class="how-to-use-section">
This is an example of what a <span class="emphasized">Player</span> is seeing. The timer is running, and they view their
role by double-clicking it:
This is an example of what a <span class="emphasized">Player</span> 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:
<br><br>
<img class='tutorial-image-small-portrait' src="../images/tutorial/player-view.gif"/>
<img alt='player-view' class='tutorial-image-small-portrait' src="../images/tutorial/player-view.gif"/>
<br><br>
There are three main things - the <span class="emphasized">Timer</span>, your <span class="emphasized">Role Card</span>
and the <span class="emphasized">Player List</span>. 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 <span class="emphasized">role info button</span> that,

View File

@@ -10,7 +10,8 @@ const PRIMITIVES = {
USER_SIGNATURE_LENGTH: 25,
INSTANCE_ID_LENGTH: 75,
MAX_SPECTATORS: 25,
MOCK_AUTH: 'mock_auth'
MOCK_AUTH: 'mock_auth',
MAX_PERSON_NAME_LENGTH: 40
};
const LOG_LEVEL = {
@@ -88,6 +89,7 @@ const SYNCABLE_EVENTS = function () {
EVENT_IDS.KILL_PLAYER,
EVENT_IDS.REVEAL_PLAYER,
EVENT_IDS.TRANSFER_MODERATOR,
EVENT_IDS.CHANGE_NAME,
EVENT_IDS.END_GAME,
EVENT_IDS.RESTART_GAME,
EVENT_IDS.PLAYER_JOINED,

View File

@@ -1,6 +1,6 @@
const GameStateCurator = require('./GameStateCurator');
const GameCreationRequest = require('../model/GameCreationRequest');
const { EVENT_IDS, STATUS, USER_TYPES, GAME_PROCESS_COMMANDS, REDIS_CHANNELS } = require('../config/globals');
const { EVENT_IDS, STATUS, USER_TYPES, GAME_PROCESS_COMMANDS, REDIS_CHANNELS, PRIMITIVES } = require('../config/globals');
const Events = [
{
@@ -55,6 +55,42 @@ const Events = [
);
}
},
{
id: EVENT_IDS.CHANGE_NAME,
stateChange: async (game, socketArgs, vars) => {
const toChangeIndex = game.people.findIndex(
(person) => person.id === socketArgs.personId
);
if (toChangeIndex >= 0) {
if (vars.gameManager.isNameTaken(game, socketArgs.newName)) {
vars.hasNameChanged = false;
if (game.people[toChangeIndex].name.toLowerCase().trim() === socketArgs.newName.toLowerCase().trim()) {
return;
}
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.hasNameChanged = false;
} else if (socketArgs.newName.length === 0) {
vars.ackFn({ errorFlag: 1, message: 'Your new name cannot be empty.' });
vars.hasNameChanged = false;
} else {
game.people[toChangeIndex].name = socketArgs.newName;
vars.ackFn({ errorFlag: 0, message: 'Name updated!' });
vars.hasNameChanged = true;
}
}
},
communicate: async (game, socketArgs, vars) => {
if (vars.hasNameChanged) {
vars.gameManager.namespace.in(game.accessCode).emit(
EVENT_IDS.CHANGE_NAME,
socketArgs.personId,
socketArgs.newName
);
}
}
},
{
id: EVENT_IDS.UPDATE_GAME_ROLES,
stateChange: async (game, socketArgs, vars) => {

View File

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

View File

@@ -182,7 +182,7 @@ class GameManager {
if (matchingPerson) {
return Promise.resolve(matchingPerson.cookie);
}
if (isNameTaken(game, name)) {
if (this.isNameTaken(game, name)) {
return Promise.reject({ status: 400, reason: 'This name is taken.' });
}
if (joinAsSpectator
@@ -334,6 +334,11 @@ class GameManager {
findPersonByField = (game, fieldName, value) => {
return game.people.find(person => person[fieldName] === value);
}
isNameTaken (game, name) {
const processedName = name.toLowerCase().trim();
return game.people.find((person) => person.name.toLowerCase().trim() === processedName);
}
}
function getRandomInt (max) {
@@ -383,11 +388,6 @@ function createRandomId () {
return id;
}
function isNameTaken (game, name) {
const processedName = name.toLowerCase().trim();
return game.people.find((person) => person.name.toLowerCase().trim() === processedName);
}
async function addSpectator (game, name, logger, namespace, eventManager, instanceId, refreshGame) {
const spectator = new Person(
createRandomId(),

View File

@@ -18,7 +18,7 @@ describe('GameManager', () => {
const inObj = { emit: () => {} };
namespace = { in: () => { return inObj; }, to: () => { return inObj; } };
socket = { id: '123', emit: () => {}, to: () => { return { emit: () => {} }; } };
gameManager = GameManager.instance ? GameManager.instance : new GameManager(logger, globals.ENVIRONMENT.PRODUCTION, 'test');
gameManager = GameManager.instance ? GameManager.instance : new GameManager(logger, globals.ENVIRONMENTS.PRODUCTION, 'test');
timerManager = TimerManager.instance ? TimerManager.instance : new TimerManager(logger, 'test');
eventManager = EventManager.instance ? EventManager.instance : new EventManager(logger, 'test');
eventManager.publisher = { publish: async (...a) => {} };