Merge pull request #133 from AlecM33/refactor-game-state-renderer

Refactor GameStateRenderer to modular game state components
This commit is contained in:
Alec
2022-12-13 19:38:00 -05:00
committed by GitHub
10 changed files with 557 additions and 552 deletions

View File

@@ -230,7 +230,7 @@ export const HTMLFragments = {
<div id='game-player-list'></div>
</div>`,
RESTART_GAME_BUTTON:
'<button id=\'restart-game\' class=\'app-button\'>Restart Game 🔄</button>',
'<button id=\'restart-game\' class=\'app-button\'>Play Again 🔄</button>',
CREATE_GAME_DECK:
`<div id='deck-container'>
<div>

View File

@@ -0,0 +1,98 @@
import { globals } from '../../../config/globals.js';
import { HTMLFragments } from '../../front_end_components/HTMLFragments.js';
import { XHRUtility } from '../../utility/XHRUtility.js';
import { UserUtility } from '../../utility/UserUtility.js';
import { toast } from '../../front_end_components/Toast.js';
import { Confirmation } from '../../front_end_components/Confirmation.js';
export class Ended {
constructor (containerId, stateBucket, socket) {
this.stateBucket = stateBucket;
this.socket = socket;
this.container = document.getElementById(containerId);
this.container.innerHTML = HTMLFragments.END_OF_GAME_VIEW;
this.restartGameHandler = () => {
XHRUtility.xhr(
'/api/games/' + this.stateBucket.currentGameState.accessCode + '/restart',
'PATCH',
null,
JSON.stringify({
playerName: this.stateBucket.currentGameState.client.name,
accessCode: this.stateBucket.currentGameState.accessCode,
sessionCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.LOCAL),
localCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.PRODUCTION)
})
)
.then((res) => {
toast('Game restarted!', 'success', true, true, 'medium');
})
.catch((res) => {
toast(res.content, 'error', true, true, 'medium');
});
};
}
renderEndOfGame (gameState) {
if (
gameState.client.userType === globals.USER_TYPES.MODERATOR
|| gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
) {
const restartGameContainer = document.createElement('div');
restartGameContainer.innerHTML = HTMLFragments.RESTART_GAME_BUTTON;
const button = restartGameContainer.querySelector('#restart-game');
button.addEventListener('click', () => {
Confirmation('Restart the game, dealing everyone new roles?', () => {
this.restartGameHandler();
});
});
document.getElementById('end-of-game-buttons').prepend(restartGameContainer);
}
this.renderPlayersWithRoleInformation();
}
renderPlayersWithRoleInformation (tempMod = false) {
document.querySelectorAll('.game-player').forEach((el) => el.remove());
/* TODO: UX issue - it's easier to parse visually when players are sorted this way,
but shifting players around when they are killed or revealed is bad UX for the moderator. */
// sortPeopleByStatus(this.stateBucket.currentGameState.people);
const modType = tempMod ? this.stateBucket.currentGameState.moderator.userType : null;
renderGroupOfPlayers(
this.stateBucket.currentGameState.people,
this.stateBucket.currentGameState.accessCode,
null,
modType,
this.socket
);
document.getElementById('players-alive-label').innerText =
'Players: ' + this.stateBucket.currentGameState.people.filter((person) => !person.out).length + ' / ' +
this.stateBucket.currentGameState.people.length + ' Alive';
}
}
function renderGroupOfPlayers (
people,
accessCode = null,
alignment = null
) {
for (const player of people) {
const playerEl = document.createElement('div');
playerEl.classList.add('game-player');
playerEl.innerHTML = HTMLFragments.GAME_PLAYER;
playerEl.querySelector('.game-player-name').innerText = player.name;
const roleElement = playerEl.querySelector('.game-player-role');
if (alignment === null) {
roleElement.classList.add(player.alignment);
} else {
roleElement.classList.add(alignment);
}
roleElement.innerText = player.gameRole;
if (player.out) {
playerEl.classList.add('killed');
}
document.getElementById('game-player-list').appendChild(playerEl);
}
}

View File

@@ -1,148 +1,114 @@
import { globals } from '../../config/globals.js';
import { toast } from '../front_end_components/Toast.js';
import { HTMLFragments } from '../front_end_components/HTMLFragments.js';
import { ModalManager } from '../front_end_components/ModalManager.js';
import { XHRUtility } from '../utility/XHRUtility.js';
import { UserUtility } from '../utility/UserUtility.js';
// QRCode module via: https://github.com/soldair/node-qrcode
import { QRCode } from '../third_party/qrcode.js';
import { Confirmation } from '../front_end_components/Confirmation.js';
import { toast } from '../../front_end_components/Toast.js';
import { globals } from '../../../config/globals.js';
import { HTMLFragments } from '../../front_end_components/HTMLFragments.js';
import { Confirmation } from '../../front_end_components/Confirmation.js';
import { ModalManager } from '../../front_end_components/ModalManager.js';
import { GameTimerManager } from '../../timer/GameTimerManager.js';
import { stateBucket } from '../StateBucket.js';
export class GameStateRenderer {
constructor (stateBucket, socket) {
export class InProgress {
constructor (containerId, stateBucket, socket) {
this.stateBucket = stateBucket;
this.socket = socket;
this.container = document.getElementById(containerId);
this.components = {
};
this.killPlayerHandlers = {};
this.revealRoleHandlers = {};
this.transferModHandlers = {};
this.startGameHandler = (e) => { // TODO: prevent multiple emissions of this event (recommend converting to XHR)
e.preventDefault();
Confirmation('Start game and deal roles?', () => {
socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.START_GAME, stateBucket.currentGameState.accessCode);
});
};
this.restartGameHandler = (e) => {
e.preventDefault();
const button = document.getElementById('restart-game');
button.removeEventListener('click', this.restartGameHandler);
button.classList.add('submitted');
button.innerText = 'Restarting...';
XHRUtility.xhr(
'/api/games/' + this.stateBucket.currentGameState.accessCode + '/restart',
'PATCH',
null,
JSON.stringify({
playerName: this.stateBucket.currentGameState.client.name,
accessCode: this.stateBucket.currentGameState.accessCode,
sessionCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.LOCAL),
localCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.PRODUCTION)
})
)
.then((res) => {
toast('Game restarted!', 'success', true, true, 'medium');
})
.catch((res) => {
const button = document.getElementById('restart-game');
button.innerText = 'Restart Game 🔄';
button.classList.remove('submitted');
button.addEventListener('click', this.restartGameHandler);
toast(res.content, 'error', true, true, 'medium');
});
};
}
renderLobbyPlayers () {
document.querySelectorAll('.lobby-player').forEach((el) => el.remove());
const lobbyPlayersContainer = document.getElementById('lobby-players');
if (this.stateBucket.currentGameState.moderator.userType === globals.USER_TYPES.MODERATOR) {
lobbyPlayersContainer.appendChild(
renderLobbyPerson(
this.stateBucket.currentGameState.moderator.name,
this.stateBucket.currentGameState.moderator.userType
)
);
setUserView (userType) {
switch (userType) {
case globals.USER_TYPES.PLAYER:
this.container.innerHTML = HTMLFragments.PLAYER_GAME_VIEW;
this.renderPlayerView();
break;
case globals.USER_TYPES.KILLED_PLAYER:
this.container.innerHTML = HTMLFragments.PLAYER_GAME_VIEW;
this.renderPlayerView(true);
break;
case globals.USER_TYPES.MODERATOR:
document.getElementById('transfer-mod-prompt').innerHTML = HTMLFragments.TRANSFER_MOD_MODAL;
this.container.innerHTML = HTMLFragments.MODERATOR_GAME_VIEW;
this.renderModeratorView();
break;
case globals.USER_TYPES.TEMPORARY_MODERATOR:
document.getElementById('transfer-mod-prompt').innerHTML = HTMLFragments.TRANSFER_MOD_MODAL;
this.container.innerHTML = HTMLFragments.TEMP_MOD_GAME_VIEW;
this.renderTempModView();
break;
case globals.USER_TYPES.SPECTATOR:
this.container.innerHTML = HTMLFragments.SPECTATOR_GAME_VIEW;
this.renderSpectatorView();
break;
default:
break;
}
for (const person of this.stateBucket.currentGameState.people) {
lobbyPlayersContainer.appendChild(renderLobbyPerson(person.name, person.userType));
}
const playerCount = this.stateBucket.currentGameState.people.length;
document.querySelector("label[for='lobby-players']").innerText =
'Participants (' + playerCount + '/' + getGameSize(this.stateBucket.currentGameState.deck) + ' Players)';
}
renderLobbyHeader () {
removeExistingTitle();
const gameLinkContainer = document.getElementById('game-link');
const copyImg = document.createElement('img');
copyImg.setAttribute('src', '../images/copy.svg');
gameLinkContainer.appendChild(copyImg);
const time = document.getElementById('game-time');
const playerCount = document.getElementById('game-player-count');
const gameCode = document.getElementById('game-code');
playerCount.innerText = getGameSize(this.stateBucket.currentGameState.deck) + ' Players';
gameCode.innerHTML = 'Or enter this code on the homepage: <span>' + this.stateBucket.currentGameState.accessCode + '</span>';
let timeString = '';
if (this.stateBucket.currentGameState.timerParams) {
const hours = this.stateBucket.currentGameState.timerParams.hours;
const minutes = this.stateBucket.currentGameState.timerParams.minutes;
if (hours) {
timeString += hours > 1
? hours + ' hours '
: hours + ' hour ';
}
if (minutes) {
timeString += minutes > 1
? minutes + ' minutes '
: minutes + ' minute ';
}
time.innerText = timeString;
this.socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.GET_TIME_REMAINING,
this.stateBucket.currentGameState.accessCode
);
} else {
timeString = 'untimed';
time.innerText = timeString;
document.querySelector('#game-timer')?.remove();
document.querySelector('#timer-container-moderator')?.remove();
document.querySelector('label[for="game-timer"]')?.remove();
}
}
const link = window.location.protocol + '//' + window.location.host +
'/join/' + this.stateBucket.currentGameState.accessCode +
'?playerCount=' + getGameSize(this.stateBucket.currentGameState.deck) +
'&timer=' + encodeURIComponent(timeString);
QRCode.toCanvas(document.getElementById('canvas'), link, { scale: 2 }, function (error) {
if (error) console.error(error);
});
const linkCopyHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
navigator.clipboard.writeText(link)
.then(() => {
toast('Link copied!', 'success', true);
});
renderPlayerView (isKilled = false) {
if (isKilled) {
const clientUserType = document.getElementById('client-user-type');
if (clientUserType) {
clientUserType.innerText = globals.USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80';
}
};
gameLinkContainer.addEventListener('click', linkCopyHandler);
gameLinkContainer.addEventListener('keyup', linkCopyHandler);
const linkDiv = document.createElement('div');
linkDiv.innerText = link;
gameLinkContainer.prepend(linkDiv);
}
renderLobbyFooter () {
for (const card of this.stateBucket.currentGameState.deck) {
const cardEl = document.createElement('div');
cardEl.innerText = card.quantity + 'x ' + card.role;
cardEl.classList.add('lobby-card');
}
renderPlayerRole(this.stateBucket.currentGameState);
this.renderPlayersWithNoRoleInformationUnlessRevealed(false);
}
renderGameHeader () {
removeExistingTitle();
// let title = document.createElement("h1");
// title.innerText = "Game";
// document.getElementById("game-title").appendChild(title);
renderPlayersWithNoRoleInformationUnlessRevealed (tempMod = false) {
if (tempMod) {
this.removePlayerListEventListeners();
}
document.querySelectorAll('.game-player').forEach((el) => el.remove());
/* TODO: UX issue - it's easier to parse visually when players are sorted this way,
but shifting players around when they are killed or revealed is bad UX for the moderator. */
// sortPeopleByStatus(this.stateBucket.currentGameState.people);
const modType = tempMod ? this.stateBucket.currentGameState.moderator.userType : null;
this.renderGroupOfPlayers(
this.stateBucket.currentGameState.people,
this.killPlayerHandlers,
this.revealRoleHandlers,
this.stateBucket.currentGameState.accessCode,
null,
modType,
this.socket
);
document.getElementById('players-alive-label').innerText =
'Players: ' + this.stateBucket.currentGameState.people.filter((person) => !person.out).length + ' / ' +
this.stateBucket.currentGameState.people.length + ' Alive';
}
removePlayerListEventListeners (removeEl = true) {
document.querySelectorAll('.game-player').forEach((el) => {
const pointer = el.dataset.pointer;
if (pointer && this.killPlayerHandlers[pointer]) {
el.removeEventListener('click', this.killPlayerHandlers[pointer]);
delete this.killPlayerHandlers[pointer];
}
if (pointer && this.revealRoleHandlers[pointer]) {
el.removeEventListener('click', this.revealRoleHandlers[pointer]);
delete this.revealRoleHandlers[pointer];
}
if (removeEl) {
el.remove();
}
});
}
renderModeratorView () {
@@ -169,26 +135,66 @@ export class GameStateRenderer {
this.renderPlayersWithNoRoleInformationUnlessRevealed(true);
}
renderPlayerView (isKilled = false) {
if (isKilled) {
const clientUserType = document.getElementById('client-user-type');
if (clientUserType) {
clientUserType.innerText = globals.USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80';
}
}
renderPlayerRole(this.stateBucket.currentGameState);
this.renderPlayersWithNoRoleInformationUnlessRevealed(false);
}
renderSpectatorView () {
this.renderPlayersWithNoRoleInformationUnlessRevealed();
}
refreshPlayerList (isModerator) {
if (isModerator) {
this.renderPlayersWithRoleAndAlignmentInfo();
} else {
this.renderPlayersWithNoRoleInformationUnlessRevealed();
setSocketHandlers () {
this.socket.on(globals.EVENT_IDS.KILL_PLAYER, (id) => {
const killedPerson = this.stateBucket.currentGameState.people.find((person) => person.id === id);
if (killedPerson) {
killedPerson.out = true;
if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) {
toast(killedPerson.name + ' killed.', 'success', true, true, 'medium');
this.renderPlayersWithRoleAndAlignmentInfo(this.stateBucket.currentGameState.status === globals.STATUS.ENDED);
} else {
if (killedPerson.id === this.stateBucket.currentGameState.client.id) {
const clientUserType = document.getElementById('client-user-type');
if (clientUserType) {
clientUserType.innerText = globals.USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80';
}
this.updatePlayerCardToKilledState();
toast('You have been killed!', 'warning', true, true, 'medium');
} else {
toast(killedPerson.name + ' was killed!', 'warning', true, true, 'medium');
}
if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
this.removePlayerListEventListeners(false);
} else {
this.renderPlayersWithNoRoleInformationUnlessRevealed(false);
}
}
}
});
this.socket.on(globals.EVENT_IDS.REVEAL_PLAYER, (revealData) => {
const revealedPerson = this.stateBucket.currentGameState.people.find((person) => person.id === revealData.id);
if (revealedPerson) {
revealedPerson.revealed = true;
revealedPerson.gameRole = revealData.gameRole;
revealedPerson.alignment = revealData.alignment;
if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) {
toast(revealedPerson.name + ' revealed.', 'success', true, true, 'medium');
this.renderPlayersWithRoleAndAlignmentInfo(this.stateBucket.currentGameState.status === globals.STATUS.ENDED);
} else {
if (revealedPerson.id === this.stateBucket.currentGameState.client.id) {
toast('Your role has been revealed!', 'warning', true, true, 'medium');
} else {
toast(revealedPerson.name + ' was revealed as a ' + revealedPerson.gameRole + '!', 'warning', true, true, 'medium');
}
if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
this.renderPlayersWithNoRoleInformationUnlessRevealed(true);
} else {
this.renderPlayersWithNoRoleInformationUnlessRevealed(false);
}
}
}
});
if (this.stateBucket.currentGameState.timerParams) {
const timerWorker = new Worker(new URL('../../timer/Timer.js', import.meta.url));
const gameTimerManager = new GameTimerManager(stateBucket, this.socket);
gameTimerManager.attachTimerSocketListeners(this.socket, timerWorker);
}
}
@@ -222,96 +228,6 @@ export class GameStateRenderer {
this.stateBucket.currentGameState.people.length + ' Alive';
}
removePlayerListEventListeners (removeEl = true) {
document.querySelectorAll('.game-player').forEach((el) => {
const pointer = el.dataset.pointer;
if (pointer && this.killPlayerHandlers[pointer]) {
el.removeEventListener('click', this.killPlayerHandlers[pointer]);
delete this.killPlayerHandlers[pointer];
}
if (pointer && this.revealRoleHandlers[pointer]) {
el.removeEventListener('click', this.revealRoleHandlers[pointer]);
delete this.revealRoleHandlers[pointer];
}
if (removeEl) {
el.remove();
}
});
}
renderPlayersWithNoRoleInformationUnlessRevealed (tempMod = false) {
if (tempMod) {
this.removePlayerListEventListeners();
}
document.querySelectorAll('.game-player').forEach((el) => el.remove());
/* TODO: UX issue - it's easier to parse visually when players are sorted this way,
but shifting players around when they are killed or revealed is bad UX for the moderator. */
// sortPeopleByStatus(this.stateBucket.currentGameState.people);
const modType = tempMod ? this.stateBucket.currentGameState.moderator.userType : null;
this.renderGroupOfPlayers(
this.stateBucket.currentGameState.people,
this.killPlayerHandlers,
this.revealRoleHandlers,
this.stateBucket.currentGameState.accessCode,
null,
modType,
this.socket
);
document.getElementById('players-alive-label').innerText =
'Players: ' + this.stateBucket.currentGameState.people.filter((person) => !person.out).length + ' / ' +
this.stateBucket.currentGameState.people.length + ' Alive';
}
updatePlayerCardToKilledState () {
document.querySelector('#role-image').classList.add('killed-card');
document.getElementById('role-image').setAttribute(
'src',
'../images/tombstone.png'
);
}
displayAvailableModerators () {
document.getElementById('transfer-mod-modal-content').innerText = '';
document.querySelectorAll('.potential-moderator').forEach((el) => {
const pointer = el.dataset.pointer;
if (pointer && this.transferModHandlers[pointer]) {
el.removeEventListener('click', this.transferModHandlers[pointer]);
delete this.transferModHandlers[pointer];
}
el.remove();
});
renderPotentialMods(
this.stateBucket.currentGameState,
this.stateBucket.currentGameState.people,
this.transferModHandlers,
this.socket
);
renderPotentialMods( // spectators can also be made mods.
this.stateBucket.currentGameState,
this.stateBucket.currentGameState.spectators,
this.transferModHandlers,
this.socket
);
if (document.querySelectorAll('.potential-moderator').length === 0) {
document.getElementById('transfer-mod-modal-content').innerText = 'There is nobody available to transfer to.';
}
}
renderEndOfGame (gameState) {
if (
gameState.client.userType === globals.USER_TYPES.MODERATOR
|| gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
) {
const restartGameContainer = document.createElement('div');
restartGameContainer.innerHTML = HTMLFragments.RESTART_GAME_BUTTON;
const button = restartGameContainer.querySelector('#restart-game');
button.addEventListener('click', this.restartGameHandler);
document.getElementById('end-of-game-buttons').prepend(restartGameContainer);
}
this.renderPlayersWithNoRoleInformationUnlessRevealed();
}
renderGroupOfPlayers (
people,
killPlayerHandlers,
@@ -337,7 +253,7 @@ export class GameStateRenderer {
const roleElement = playerEl.querySelector('.game-player-role');
// Add role/alignment indicators if necessary
if (moderatorType === globals.USER_TYPES.MODERATOR || player.revealed || this.stateBucket.currentGameState.status === globals.STATUS.ENDED) {
if (moderatorType === globals.USER_TYPES.MODERATOR || player.revealed) {
if (alignment === null) {
roleElement.classList.add(player.alignment);
} else {
@@ -386,69 +302,41 @@ export class GameStateRenderer {
document.getElementById(playerListContainerId).appendChild(playerEl);
}
}
}
function renderPotentialMods (gameState, group, transferModHandlers, socket) {
const modalContent = document.getElementById('transfer-mod-modal-content');
for (const member of group) {
if ((member.out || member.userType === globals.USER_TYPES.SPECTATOR) && !(member.id === gameState.client.id)) {
const container = document.createElement('div');
container.classList.add('potential-moderator');
container.setAttribute('tabindex', '0');
container.dataset.pointer = member.id;
container.innerText = member.name;
transferModHandlers[member.id] = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
ModalManager.dispelModal('transfer-mod-modal', 'transfer-mod-modal-background');
Confirmation('Transfer moderator powers to \'' + member.name + '\'?', () => {
const transferPrompt = document.getElementById('transfer-mod-prompt');
if (transferPrompt !== null) {
transferPrompt.innerHTML = '';
}
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.TRANSFER_MODERATOR,
gameState.accessCode,
{ personId: member.id }
);
});
}
};
displayAvailableModerators () {
document.getElementById('transfer-mod-modal-content').innerText = '';
document.querySelectorAll('.potential-moderator').forEach((el) => {
const pointer = el.dataset.pointer;
if (pointer && this.transferModHandlers[pointer]) {
el.removeEventListener('click', this.transferModHandlers[pointer]);
delete this.transferModHandlers[pointer];
}
el.remove();
});
renderPotentialMods(
this.stateBucket.currentGameState,
this.stateBucket.currentGameState.people,
this.transferModHandlers,
this.socket
);
renderPotentialMods( // spectators can also be made mods.
this.stateBucket.currentGameState,
this.stateBucket.currentGameState.spectators,
this.transferModHandlers,
this.socket
);
container.addEventListener('click', transferModHandlers[member.id]);
container.addEventListener('keyup', transferModHandlers[member.id]);
modalContent.appendChild(container);
if (document.querySelectorAll('.potential-moderator').length === 0) {
document.getElementById('transfer-mod-modal-content').innerText = 'There is nobody available to transfer to.';
}
}
}
function renderLobbyPerson (name, userType) {
const el = document.createElement('div');
const personNameEl = document.createElement('div');
const personTypeEl = document.createElement('div');
personNameEl.innerText = name;
personTypeEl.innerText = userType + globals.USER_TYPE_ICONS[userType];
el.classList.add('lobby-player');
el.appendChild(personNameEl);
el.appendChild(personTypeEl);
return el;
}
function getGameSize (cards) {
let quantity = 0;
for (const card of cards) {
quantity += card.quantity;
}
return quantity;
}
function removeExistingTitle () {
const existingTitle = document.querySelector('#game-title h1');
if (existingTitle) {
existingTitle.remove();
updatePlayerCardToKilledState () {
document.querySelector('#role-image').classList.add('killed-card');
document.getElementById('role-image').setAttribute(
'src',
'../images/tombstone.png'
);
}
}
@@ -503,21 +391,6 @@ function renderPlayerRole (gameState) {
});
}
function insertPlaceholderButton (container, append, type) {
const button = document.createElement('div');
button.classList.add('placeholder-button');
if (type === 'killed') {
button.innerText = 'Killed';
} else {
button.innerText = 'Revealed';
}
if (append) {
container.querySelector('.player-action-buttons').appendChild(button);
} else {
container.querySelector('.player-action-buttons').prepend(button);
}
}
function removeExistingPlayerElements (killPlayerHandlers, revealRoleHandlers) {
document.querySelectorAll('.game-player').forEach((el) => {
const pointer = el.dataset.pointer;
@@ -543,10 +416,63 @@ function createEndGamePromptComponent (socket, stateBucket) {
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.END_GAME,
stateBucket.currentGameState.accessCode
stateBucket.currentGameState.accessCode,
null,
() => {
document.querySelector('#end-game-prompt')?.remove();
}
);
});
});
document.getElementById('game-content').appendChild(div);
}
}
function insertPlaceholderButton (container, append, type) {
const button = document.createElement('div');
button.classList.add('placeholder-button');
if (type === 'killed') {
button.innerText = 'Killed';
} else {
button.innerText = 'Revealed';
}
if (append) {
container.querySelector('.player-action-buttons').appendChild(button);
} else {
container.querySelector('.player-action-buttons').prepend(button);
}
}
function renderPotentialMods (gameState, group, transferModHandlers, socket) {
const modalContent = document.getElementById('transfer-mod-modal-content');
for (const member of group) {
if ((member.out || member.userType === globals.USER_TYPES.SPECTATOR) && !(member.id === gameState.client.id)) {
const container = document.createElement('div');
container.classList.add('potential-moderator');
container.setAttribute('tabindex', '0');
container.dataset.pointer = member.id;
container.innerText = member.name;
transferModHandlers[member.id] = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
ModalManager.dispelModal('transfer-mod-modal', 'transfer-mod-modal-background');
Confirmation('Transfer moderator powers to \'' + member.name + '\'?', () => {
const transferPrompt = document.getElementById('transfer-mod-prompt');
if (transferPrompt !== null) {
transferPrompt.innerHTML = '';
}
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.TRANSFER_MODERATOR,
gameState.accessCode,
{ personId: member.id }
);
});
}
};
container.addEventListener('click', transferModHandlers[member.id]);
container.addEventListener('keyup', transferModHandlers[member.id]);
modalContent.appendChild(container);
}
}
}

View File

@@ -0,0 +1,186 @@
import { QRCode } from '../../third_party/qrcode.js';
import { toast } from '../../front_end_components/Toast.js';
import { globals } from '../../../config/globals.js';
import { HTMLFragments } from '../../front_end_components/HTMLFragments.js';
import { Confirmation } from '../../front_end_components/Confirmation.js';
export class Lobby {
constructor (containerId, stateBucket, socket) {
this.stateBucket = stateBucket;
this.socket = socket;
this.container = document.getElementById(containerId);
this.container.innerHTML = HTMLFragments.LOBBY;
this.startGameHandler = (e) => {
e.preventDefault();
Confirmation('Start game and deal roles?', () => {
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.START_GAME,
stateBucket.currentGameState.accessCode,
null,
() => {
this.removeStartGameFunctionalityIfPresent();
}
);
});
};
}
populateHeader () {
const timeString = getTimeString(this.stateBucket.currentGameState);
const time = this.container.querySelector('#game-time');
time.innerText = timeString;
const linkContainer = this.container.querySelector('#game-link');
linkContainer.innerHTML = '<img src=\'../images/copy.svg\' alt=\'copy\'/>';
const link = window.location.protocol + '//' + window.location.host +
'/join/' + this.stateBucket.currentGameState.accessCode +
'?playerCount=' + this.stateBucket.currentGameState.gameSize +
'&timer=' + encodeURIComponent(timeString);
const linkDiv = document.createElement('div');
linkDiv.innerText = link;
linkContainer.prepend(linkDiv);
activateLink(linkContainer, link);
const playerCount = this.container.querySelector('#game-player-count');
playerCount.innerText = this.stateBucket.currentGameState.gameSize + ' Players';
const gameCode = this.container.querySelector('#game-code');
gameCode.innerHTML = 'Or enter this code on the homepage: <span>' +
this.stateBucket.currentGameState.accessCode + '</span>';
QRCode.toCanvas(document.getElementById('canvas'), link, { scale: 2 }, function (error) {
if (error) console.error(error);
});
}
populatePlayers () {
document.querySelectorAll('.lobby-player').forEach((el) => el.remove());
const lobbyPlayersContainer = this.container.querySelector('#lobby-players');
if (this.stateBucket.currentGameState.moderator.userType === globals.USER_TYPES.MODERATOR) {
lobbyPlayersContainer.appendChild(
renderLobbyPerson(
this.stateBucket.currentGameState.moderator.name,
this.stateBucket.currentGameState.moderator.userType
)
);
}
for (const person of this.stateBucket.currentGameState.people) {
lobbyPlayersContainer.appendChild(renderLobbyPerson(person.name, person.userType));
}
const playerCount = this.stateBucket.currentGameState.people.length;
document.querySelector("label[for='lobby-players']").innerText =
'Participants (' + playerCount + '/' + this.stateBucket.currentGameState.gameSize + ' Players)';
}
setSocketHandlers () {
this.socket.on(globals.EVENT_IDS.PLAYER_JOINED, (player, gameIsFull) => {
toast(player.name + ' joined!', 'success', false, true, 'short');
this.stateBucket.currentGameState.people.push(player);
this.stateBucket.currentGameState.isFull = gameIsFull;
this.populatePlayers();
if ((
this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR
|| this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
)
) {
this.displayStartGamePromptForModerators();
}
});
this.socket.on(globals.EVENT_IDS.NEW_SPECTATOR, (spectator) => {
this.stateBucket.currentGameState.spectators.push(spectator);
});
// this.socket.on(globals.EVENT_IDS.PLAYER_LEFT, (player) => {
// removeStartGameFunctionalityIfPresent(this.stateBucket.currentGameState, this.startGameHandler);
// toast(player.name + ' has left!', 'error', false, true, 'short');
// const index = this.stateBucket.currentGameState.people.findIndex(person => person.id === player.id);
// if (index >= 0) {
// this.stateBucket.currentGameState.people.splice(
// index,
// 1
// );
// this.populatePlayers();
// }
// });
}
displayStartGamePromptForModerators () {
const existingPrompt = document.getElementById('start-game-prompt');
if (existingPrompt) {
enableOrDisableStartButton(this.stateBucket.currentGameState, existingPrompt, this.startGameHandler);
} else {
const newPrompt = document.createElement('div');
newPrompt.setAttribute('id', 'start-game-prompt');
newPrompt.innerHTML = HTMLFragments.START_GAME_PROMPT;
document.body.appendChild(newPrompt);
enableOrDisableStartButton(this.stateBucket.currentGameState, newPrompt, this.startGameHandler);
}
}
removeStartGameFunctionalityIfPresent () {
document.querySelector('#start-game-prompt')?.removeEventListener('click', this.startGameHandler);
document.querySelector('#start-game-prompt')?.remove();
}
}
function enableOrDisableStartButton (gameState, buttonContainer, handler) {
if (gameState.isFull) {
buttonContainer.querySelector('#start-game-button').addEventListener('click', handler);
buttonContainer.querySelector('#start-game-button').classList.remove('disabled');
} else {
buttonContainer.querySelector('#start-game-button').removeEventListener('click', handler);
buttonContainer.querySelector('#start-game-button').classList.add('disabled');
}
}
function activateLink (linkContainer, link) {
const linkCopyHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
navigator.clipboard.writeText(link)
.then(() => {
toast('Link copied!', 'success', true);
});
}
};
linkContainer.addEventListener('click', linkCopyHandler);
linkContainer.addEventListener('keyup', linkCopyHandler);
}
function getTimeString (gameState) {
let timeString = '';
if (gameState.timerParams) {
const hours = gameState.timerParams.hours;
const minutes = gameState.timerParams.minutes;
if (hours) {
timeString += hours > 1
? hours + ' hours '
: hours + ' hour ';
}
if (minutes) {
timeString += minutes > 1
? minutes + ' minutes '
: minutes + ' minute ';
}
return timeString;
} else {
return 'untimed';
}
}
function renderLobbyPerson (name, userType) {
const el = document.createElement('div');
const personNameEl = document.createElement('div');
const personTypeEl = document.createElement('div');
personNameEl.innerText = name;
personTypeEl.innerText = userType + globals.USER_TYPE_ICONS[userType];
el.classList.add('lobby-player');
el.appendChild(personNameEl);
el.appendChild(personTypeEl);
return el;
}

View File

@@ -1,12 +1,13 @@
import { injectNavbar } from '../front_end_components/Navbar.js';
import { stateBucket } from '../game_state/StateBucket.js';
import { GameTimerManager } from '../timer/GameTimerManager.js';
import { GameStateRenderer } from '../game_state/GameStateRenderer.js';
import { UserUtility } from '../utility/UserUtility.js';
import { toast } from '../front_end_components/Toast.js';
import { globals } from '../../config/globals.js';
import { HTMLFragments } from '../front_end_components/HTMLFragments.js';
import { ModalManager } from '../front_end_components/ModalManager.js';
import { Lobby } from '../game_state/states/Lobby.js';
import { InProgress } from '../game_state/states/InProgress.js';
import { Ended } from '../game_state/states/Ended.js';
export const gameHandler = async (socket, XHRUtility, window, gameDOM) => {
document.body.innerHTML = gameDOM + document.body.innerHTML;
@@ -22,16 +23,10 @@ export const gameHandler = async (socket, XHRUtility, window, gameDOM) => {
});
stateBucket.environment = response.content;
const timerWorker = new Worker(new URL('../timer/Timer.js', import.meta.url));
const gameTimerManager = new GameTimerManager(stateBucket, socket);
const gameStateRenderer = new GameStateRenderer(stateBucket, socket);
socket.on('connect', function () {
syncWithGame(
stateBucket,
gameTimerManager,
gameStateRenderer,
timerWorker,
socket,
UserUtility.validateAnonUserSignature(response.content),
window
@@ -46,10 +41,10 @@ export const gameHandler = async (socket, XHRUtility, window, gameDOM) => {
toast('Disconnected. Attempting reconnect...', 'error', true, false);
});
setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWorker, gameTimerManager);
setClientSocketHandlers(stateBucket, socket);
};
function syncWithGame (stateBucket, gameTimerManager, gameStateRenderer, timerWorker, socket, cookie, window) {
function syncWithGame (stateBucket, socket, cookie, window) {
const splitUrl = window.location.href.split('/game/');
const accessCode = splitUrl[1];
if (/^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === globals.ACCESS_CODE_LENGTH) {
@@ -62,7 +57,7 @@ function syncWithGame (stateBucket, gameTimerManager, gameStateRenderer, timerWo
document.querySelector('.spinner-background')?.remove();
document.getElementById('game-content').innerHTML = HTMLFragments.INITIAL_GAME_DOM;
toast('You are connected.', 'success', true, true, 'short');
processGameState(stateBucket.currentGameState, cookie, socket, gameStateRenderer, gameTimerManager, timerWorker, true, true);
processGameState(stateBucket.currentGameState, cookie, socket, true, true);
}
});
} else {
@@ -74,9 +69,6 @@ function processGameState (
currentGameState,
userId,
socket,
gameStateRenderer,
gameTimerManager,
timerWorker,
refreshPrompt = true,
animateContainer = false
) {
@@ -92,66 +84,38 @@ function processGameState (
if (animateContainer) {
containerAnimation.play();
}
displayClientInfo(currentGameState.client.name, currentGameState.client.userType);
if (refreshPrompt) {
removeStartGameFunctionalityIfPresent(gameStateRenderer);
document.querySelector('#end-game-prompt')?.remove();
}
switch (currentGameState.status) {
case globals.STATUS.LOBBY:
document.getElementById('game-state-container').innerHTML = HTMLFragments.LOBBY;
gameStateRenderer.renderLobbyHeader();
gameStateRenderer.renderLobbyPlayers();
const lobby = new Lobby('game-state-container', stateBucket, socket);
if (refreshPrompt) {
lobby.removeStartGameFunctionalityIfPresent();
}
lobby.populateHeader();
lobby.populatePlayers();
lobby.setSocketHandlers();
if ((
currentGameState.client.userType === globals.USER_TYPES.MODERATOR
|| currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
)
&& refreshPrompt
) {
displayStartGamePromptForModerators(currentGameState, gameStateRenderer);
lobby.displayStartGamePromptForModerators();
}
break;
case globals.STATUS.IN_PROGRESS:
gameStateRenderer.renderGameHeader();
switch (currentGameState.client.userType) {
case globals.USER_TYPES.PLAYER:
document.getElementById('game-state-container').innerHTML = HTMLFragments.PLAYER_GAME_VIEW;
gameStateRenderer.renderPlayerView();
break;
case globals.USER_TYPES.KILLED_PLAYER:
document.getElementById('game-state-container').innerHTML = HTMLFragments.PLAYER_GAME_VIEW;
gameStateRenderer.renderPlayerView(true);
break;
case globals.USER_TYPES.MODERATOR:
document.getElementById('transfer-mod-prompt').innerHTML = HTMLFragments.TRANSFER_MOD_MODAL;
document.getElementById('game-state-container').innerHTML = HTMLFragments.MODERATOR_GAME_VIEW;
gameStateRenderer.renderModeratorView();
break;
case globals.USER_TYPES.TEMPORARY_MODERATOR:
document.getElementById('transfer-mod-prompt').innerHTML = HTMLFragments.TRANSFER_MOD_MODAL;
document.getElementById('game-state-container').innerHTML = HTMLFragments.TEMP_MOD_GAME_VIEW;
gameStateRenderer.renderTempModView();
break;
case globals.USER_TYPES.SPECTATOR:
document.getElementById('game-state-container').innerHTML = HTMLFragments.SPECTATOR_GAME_VIEW;
gameStateRenderer.renderSpectatorView();
break;
default:
break;
}
if (currentGameState.timerParams) {
socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.GET_TIME_REMAINING, currentGameState.accessCode);
} else {
document.querySelector('#game-timer')?.remove();
document.querySelector('#timer-container-moderator')?.remove();
document.querySelector('label[for="game-timer"]')?.remove();
if (refreshPrompt) {
document.querySelector('#end-game-prompt')?.remove();
}
const inProgressGame = new InProgress('game-state-container', stateBucket, socket);
inProgressGame.setSocketHandlers();
inProgressGame.setUserView(currentGameState.client.userType);
break;
case globals.STATUS.ENDED: {
const container = document.getElementById('game-state-container');
container.innerHTML = HTMLFragments.END_OF_GAME_VIEW;
gameStateRenderer.renderEndOfGame(currentGameState);
const ended = new Ended('game-state-container', stateBucket, socket);
ended.renderEndOfGame(currentGameState);
break;
}
default:
@@ -207,38 +171,8 @@ function displayClientInfo (name, userType) {
document.getElementById('client-user-type').innerText += globals.USER_TYPE_ICONS[userType];
}
function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerWorker, gameTimerManager) {
socket.on(globals.EVENT_IDS.PLAYER_JOINED, (player, gameIsFull) => {
toast(player.name + ' joined!', 'success', false, true, 'short');
stateBucket.currentGameState.people.push(player);
stateBucket.currentGameState.isFull = gameIsFull;
gameStateRenderer.renderLobbyPlayers();
if ((
stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR
|| stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
)
) {
displayStartGamePromptForModerators(stateBucket.currentGameState, gameStateRenderer);
}
});
socket.on(globals.EVENT_IDS.NEW_SPECTATOR, (spectator) => {
stateBucket.currentGameState.spectators.push(spectator);
});
socket.on(globals.EVENT_IDS.PLAYER_LEFT, (player) => {
removeStartGameFunctionalityIfPresent(gameStateRenderer);
toast(player.name + ' has left!', 'error', false, true, 'short');
const index = stateBucket.currentGameState.people.findIndex(person => person.id === player.id);
if (index >= 0) {
stateBucket.currentGameState.people.splice(
index,
1
);
gameStateRenderer.renderLobbyPlayers();
}
});
// Should be reserved for socket events not specific to any one game state (Lobby, In Progress, etc.)
function setClientSocketHandlers (stateBucket, socket) {
socket.on(globals.EVENT_IDS.START_GAME, () => {
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
@@ -251,9 +185,6 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW
stateBucket.currentGameState,
gameState.client.cookie,
socket,
gameStateRenderer,
gameTimerManager,
timerWorker,
true,
true
);
@@ -273,9 +204,6 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW
stateBucket.currentGameState,
gameState.client.cookie,
socket,
gameStateRenderer,
gameTimerManager,
timerWorker,
true,
true
);
@@ -283,76 +211,6 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW
);
});
if (timerWorker && gameTimerManager) {
gameTimerManager.attachTimerSocketListeners(socket, timerWorker, gameStateRenderer);
}
socket.on(globals.EVENT_IDS.KILL_PLAYER, (id) => {
const killedPerson = stateBucket.currentGameState.people.find((person) => person.id === id);
if (killedPerson) {
killedPerson.out = true;
if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) {
toast(killedPerson.name + ' killed.', 'success', true, true, 'medium');
gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(stateBucket.currentGameState.status === globals.STATUS.ENDED);
} else {
if (killedPerson.id === stateBucket.currentGameState.client.id) {
const clientUserType = document.getElementById('client-user-type');
if (clientUserType) {
clientUserType.innerText = globals.USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80';
}
gameStateRenderer.updatePlayerCardToKilledState();
toast('You have been killed!', 'warning', true, true, 'medium');
} else {
toast(killedPerson.name + ' was killed!', 'warning', true, true, 'medium');
}
if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
gameStateRenderer.removePlayerListEventListeners(false);
} else {
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(false);
}
}
}
});
socket.on(globals.EVENT_IDS.REVEAL_PLAYER, (revealData) => {
const revealedPerson = stateBucket.currentGameState.people.find((person) => person.id === revealData.id);
if (revealedPerson) {
revealedPerson.revealed = true;
revealedPerson.gameRole = revealData.gameRole;
revealedPerson.alignment = revealData.alignment;
if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) {
toast(revealedPerson.name + ' revealed.', 'success', true, true, 'medium');
gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(stateBucket.currentGameState.status === globals.STATUS.ENDED);
} else {
if (revealedPerson.id === stateBucket.currentGameState.client.id) {
toast('Your role has been revealed!', 'warning', true, true, 'medium');
} else {
toast(revealedPerson.name + ' was revealed as a ' + revealedPerson.gameRole + '!', 'warning', true, true, 'medium');
}
if (stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true);
} else {
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(false);
}
}
}
});
socket.on(globals.EVENT_IDS.CHANGE_NAME, (personId, name) => {
propagateNameChange(stateBucket.currentGameState, name, personId);
updateDOMWithNameChange(stateBucket.currentGameState, gameStateRenderer);
processGameState(
stateBucket.currentGameState,
stateBucket.currentGameState.client.cookie,
socket,
gameStateRenderer,
gameTimerManager,
timerWorker,
false,
false
);
});
socket.on(globals.COMMANDS.END_GAME, (people) => {
stateBucket.currentGameState.people = people;
stateBucket.currentGameState.status = globals.STATUS.ENDED;
@@ -360,81 +218,8 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW
stateBucket.currentGameState,
stateBucket.currentGameState.client.cookie,
socket,
gameStateRenderer,
gameTimerManager,
timerWorker,
true,
true
);
});
}
function displayStartGamePromptForModerators (gameState, gameStateRenderer) {
const existingPrompt = document.getElementById('start-game-prompt');
if (existingPrompt) {
enableOrDisableStartButton(gameState, existingPrompt, gameStateRenderer.startGameHandler);
} else {
const newPrompt = document.createElement('div');
newPrompt.setAttribute('id', 'start-game-prompt');
newPrompt.innerHTML = HTMLFragments.START_GAME_PROMPT;
document.body.appendChild(newPrompt);
enableOrDisableStartButton(gameState, newPrompt, gameStateRenderer.startGameHandler);
}
}
function enableOrDisableStartButton (gameState, buttonContainer, handler) {
if (gameState.isFull) {
buttonContainer.querySelector('#start-game-button').addEventListener('click', handler);
buttonContainer.querySelector('#start-game-button').classList.remove('disabled');
} else {
buttonContainer.querySelector('#start-game-button').removeEventListener('click', handler);
buttonContainer.querySelector('#start-game-button').classList.add('disabled');
}
}
function removeStartGameFunctionalityIfPresent (gameStateRenderer) {
document.querySelector('#start-game-prompt')?.removeEventListener('click', gameStateRenderer.startGameHandler);
document.querySelector('#start-game-prompt')?.remove();
}
function propagateNameChange (gameState, name, personId) {
if (gameState.client.id === personId) {
gameState.client.name = name;
}
const matchingPerson = gameState.people.find((person) => person.id === personId);
if (matchingPerson) {
matchingPerson.name = name;
}
if (gameState.moderator.id === personId) {
gameState.moderator.name = name;
}
const matchingSpectator = gameState.spectators?.find((spectator) => spectator.id === personId);
if (matchingSpectator) {
matchingSpectator.name = name;
}
}
function updateDOMWithNameChange (gameState, gameStateRenderer) {
if (gameState.status === globals.STATUS.IN_PROGRESS) {
switch (gameState.client.userType) {
case globals.USER_TYPES.PLAYER:
case globals.USER_TYPES.KILLED_PLAYER:
case globals.USER_TYPES.SPECTATOR:
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(false);
break;
case globals.USER_TYPES.MODERATOR:
gameStateRenderer.renderPlayersWithRoleAndAlignmentInfo(gameState.status === globals.STATUS.ENDED);
break;
case globals.USER_TYPES.TEMPORARY_MODERATOR:
gameStateRenderer.renderPlayersWithNoRoleInformationUnlessRevealed(true);
break;
default:
break;
}
} else {
gameStateRenderer.renderLobbyPlayers();
}
}

View File

@@ -99,7 +99,7 @@ export class GameTimerManager {
timer.innerText = returnHumanReadableTime(0, true);
}
attachTimerSocketListeners (socket, timerWorker, gameStateRenderer) {
attachTimerSocketListeners (socket, timerWorker) {
if (!socket.hasListeners(globals.COMMANDS.PAUSE_TIMER)) {
socket.on(globals.COMMANDS.PAUSE_TIMER, (timeRemaining) => {
this.pauseGameTimer(timerWorker, timeRemaining);

View File

@@ -5,7 +5,7 @@ module.exports = function(config) {
files: [
{ pattern: 'spec/e2e/*.js', type: 'module' },
{ pattern: 'spec/support/*.js', type: 'module' },
{ pattern: 'client/src/modules/*/*.js', type: 'module', included: true, served: true },
{ pattern: 'client/src/modules/**/*.js', type: 'module', included: true, served: true },
{ pattern: 'client/src/config/*.js', type: 'module', included: true, served: true },
{ pattern: 'client/src/model/*.js', type: 'module', included: true, served: true },
{ pattern: 'client/src/view_templates/*.js', type: 'module', included: true, served: true }

View File

@@ -16,6 +16,10 @@ class Game {
this.moderator = moderator;
this.people = people;
this.deck = deck;
this.gameSize = deck.reduce(
(accumulator, currentValue) => accumulator + currentValue.quantity,
0
);
this.hasTimer = hasTimer;
this.hasDedicatedModerator = hasDedicatedModerator;
this.originalModeratorId = originalModeratorId;

View File

@@ -66,6 +66,7 @@ function getGameStateBasedOnPermissions (game, person, gameRunner) {
moderator: GameStateCurator.mapPerson(game.moderator),
client: client,
deck: game.deck,
gameSize: game.gameSize,
people: game.people
.filter((person) => {
return person.assigned === true;
@@ -88,6 +89,7 @@ function getGameStateBasedOnPermissions (game, person, gameRunner) {
moderator: GameStateCurator.mapPerson(game.moderator),
client: client,
deck: game.deck,
gameSize: game.gameSize,
people: GameStateCurator.mapPeopleForModerator(game.people, client),
timerParams: game.timerParams,
isFull: game.isFull,
@@ -100,6 +102,7 @@ function getGameStateBasedOnPermissions (game, person, gameRunner) {
moderator: GameStateCurator.mapPerson(game.moderator),
client: client,
deck: game.deck,
gameSize: game.gameSize,
people: game.people
.filter((person) => {
return person.assigned === true;
@@ -115,6 +118,7 @@ function getGameStateBasedOnPermissions (game, person, gameRunner) {
moderator: GameStateCurator.mapPerson(game.moderator),
client: client,
deck: game.deck,
gameSize: game.gameSize,
people: game.people
.filter((person) => {
return person.assigned === true;

View File

@@ -45,7 +45,7 @@ class SocketManager {
};
registerHandlers = (namespace, socket, gameManager) => {
socket.on(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, async (eventId, accessCode, args, ackFn) => {
socket.on(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, async (eventId, accessCode, args = null, ackFn = null) => {
const game = gameManager.activeGameRunner.activeGames.get(accessCode);
if (game) {
switch (eventId) {
@@ -63,6 +63,7 @@ class SocketManager {
break;
case EVENT_IDS.START_GAME:
gameManager.startGame(game, namespace);
ackFn();
break;
case EVENT_IDS.PAUSE_TIMER:
gameManager.pauseTimer(game, this.logger);
@@ -91,6 +92,7 @@ class SocketManager {
break;
case EVENT_IDS.END_GAME:
gameManager.endGame(game);
ackFn();
break;
default:
break;