mirror of
https://github.com/AlecM33/Werewolf.git
synced 2025-12-26 07:47:50 +01:00
refactor GameStateRenderer, split functionality by game state
This commit is contained in:
102
client/src/modules/game_state/states/Ended.js
Normal file
102
client/src/modules/game_state/states/Ended.js
Normal file
@@ -0,0 +1,102 @@
|
||||
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';
|
||||
|
||||
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 = (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');
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
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.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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
222
client/src/modules/game_state/states/Lobby.js
Normal file
222
client/src/modules/game_state/states/Lobby.js
Normal file
@@ -0,0 +1,222 @@
|
||||
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.components = {
|
||||
HTML:
|
||||
`<div id='lobby-header'>
|
||||
<div>
|
||||
<label for='game-link'>Share Link</label>
|
||||
<div tabindex='0' id='game-link'></div>
|
||||
<div id='game-code'></div>
|
||||
</div>
|
||||
<div>
|
||||
<canvas id="canvas"></canvas>
|
||||
<div id='game-parameters'>
|
||||
<div>
|
||||
<img alt='clock' src='/images/clock.svg'/>
|
||||
<div id='game-time'></div>
|
||||
</div>
|
||||
<div>
|
||||
<img alt='person' src='/images/person.svg'/>
|
||||
<div id='game-player-count'></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button id='role-info-button' class='app-button'>Roles in This Game <img src='/images/info.svg'/></button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div id='lobby-people-container'>
|
||||
<label for='lobby-players'>Other People</label>
|
||||
<div id='lobby-players'></div>
|
||||
</div>
|
||||
<div id='lobby-footer'>
|
||||
<div id='game-deck'></div>
|
||||
</div>
|
||||
</div>`
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
this.container.innerHTML = this.components.HTML;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user