various refactors

This commit is contained in:
AlecM33
2023-08-13 17:51:41 -04:00
parent 29431565b4
commit 0b7dd9f4d7
34 changed files with 1097 additions and 1164 deletions

View File

@@ -1,4 +1,4 @@
export const globals = {
export const PRIMITIVES = {
CHAR_POOL: 'abcdefghijklmnopqrstuvwxyz0123456789',
USER_SIGNATURE_LENGTH: 75,
CLOCK_TICK_INTERVAL_MILLIS: 50,
@@ -6,100 +6,99 @@ export const globals = {
MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH: 1000,
TOAST_DURATION_DEFAULT: 6,
ACCESS_CODE_LENGTH: 4,
PLAYER_ID_COOKIE_KEY: 'play-werewolf-anon-id',
COMMANDS: {
FETCH_GAME_STATE: 'fetchGameState',
START_GAME: 'startGame',
PAUSE_TIMER: 'pauseTimer',
RESUME_TIMER: 'resumeTimer',
GET_TIME_REMAINING: 'getTimeRemaining',
KILL_PLAYER: 'killPlayer',
REVEAL_PLAYER: 'revealPlayer',
TRANSFER_MODERATOR: 'transferModerator',
CHANGE_NAME: 'changeName',
END_GAME: 'endGame',
END_TIMER: 'endTimer'
},
STATUS: {
LOBBY: 'lobby',
IN_PROGRESS: 'in progress',
ENDED: 'ended'
},
ALIGNMENT: {
GOOD: 'good',
EVIL: 'evil'
},
MESSAGES: {
ENTER_NAME: 'Client must enter name.'
},
SOCKET_EVENTS: {
IN_GAME_MESSAGE: 'inGameMessage'
},
EVENT_IDS: {
FETCH_GAME_STATE: 'fetchGameState',
START_GAME: 'startGame',
PAUSE_TIMER: 'pauseTimer',
RESUME_TIMER: 'resumeTimer',
GET_TIME_REMAINING: 'getTimeRemaining',
KILL_PLAYER: 'killPlayer',
REVEAL_PLAYER: 'revealPlayer',
TRANSFER_MODERATOR: 'transferModerator',
CHANGE_NAME: 'changeName',
END_GAME: 'endGame',
PLAYER_JOINED: 'playerJoined',
SYNC_GAME_STATE: 'syncGameState',
START_TIMER: 'startTimer',
PLAYER_LEFT: 'playerLeft',
ADD_SPECTATOR: 'addSpectator',
UPDATE_SPECTATORS: 'updateSpectators',
RESTART_GAME: 'restartGame',
ASSIGN_DEDICATED_MOD: 'assignDedicatedMod',
KICK_PERSON: 'kickPerson',
UPDATE_GAME_ROLES: 'updateGameRoles',
LEAVE_ROOM: 'leaveRoom'
},
TIMER_EVENTS: function () {
return [
this.EVENT_IDS.PAUSE_TIMER,
this.EVENT_IDS.RESUME_TIMER,
this.EVENT_IDS.GET_TIME_REMAINING,
this.EVENT_IDS.END_TIMER
];
},
LOBBY_EVENTS: function () {
return [
this.EVENT_IDS.PLAYER_JOINED,
this.EVENT_IDS.ADD_SPECTATOR,
this.EVENT_IDS.KICK_PERSON,
this.EVENT_IDS.UPDATE_GAME_ROLES,
this.EVENT_IDS.LEAVE_ROOM
];
},
IN_PROGRESS_EVENTS: function () {
return [
this.EVENT_IDS.KILL_PLAYER,
this.EVENT_IDS.REVEAL_PLAYER,
this.EVENT_IDS.ADD_SPECTATOR
];
},
USER_TYPES: {
MODERATOR: 'moderator',
PLAYER: 'player',
TEMPORARY_MODERATOR: 'temp mod',
KILLED_PLAYER: 'killed',
SPECTATOR: 'spectator',
BOT: 'bot'
},
ENVIRONMENT: {
LOCAL: 'local',
PRODUCTION: 'production'
},
USER_TYPE_ICONS: {
player: ' \uD83C\uDFAE',
moderator: ' \uD83D\uDC51',
'temp mod': ' \uD83C\uDFAE\uD83D\uDC51',
spectator: ' \uD83D\uDC7B',
killed: ' \uD83D\uDC80',
bot: ' \uD83E\uDD16'
}
PLAYER_ID_COOKIE_KEY: 'play-werewolf-anon-id'
};
export const STATUS = {
LOBBY: 'lobby',
IN_PROGRESS: 'in progress',
ENDED: 'ended'
};
export const ALIGNMENT = {
GOOD: 'good',
EVIL: 'evil'
};
export const MESSAGES = {
ENTER_NAME: 'Client must enter name.'
};
export const SOCKET_EVENTS = {
IN_GAME_MESSAGE: 'inGameMessage'
};
export const USER_TYPES = {
MODERATOR: 'moderator',
PLAYER: 'player',
TEMPORARY_MODERATOR: 'temp mod',
KILLED_PLAYER: 'killed',
SPECTATOR: 'spectator',
BOT: 'bot'
};
export const ENVIRONMENTS = {
LOCAL: 'local',
PRODUCTION: 'production'
};
export const USER_TYPE_ICONS = {
player: ' \uD83C\uDFAE',
moderator: ' \uD83D\uDC51',
'temp mod': ' \uD83C\uDFAE\uD83D\uDC51',
spectator: ' \uD83D\uDC7B',
killed: ' \uD83D\uDC80',
bot: ' \uD83E\uDD16'
};
export const EVENT_IDS = {
FETCH_GAME_STATE: 'fetchGameState',
START_GAME: 'startGame',
PAUSE_TIMER: 'pauseTimer',
RESUME_TIMER: 'resumeTimer',
END_TIMER: 'endTimer',
GET_TIME_REMAINING: 'getTimeRemaining',
KILL_PLAYER: 'killPlayer',
REVEAL_PLAYER: 'revealPlayer',
TRANSFER_MODERATOR: 'transferModerator',
CHANGE_NAME: 'changeName',
END_GAME: 'endGame',
PLAYER_JOINED: 'playerJoined',
SYNC_GAME_STATE: 'syncGameState',
START_TIMER: 'startTimer',
PLAYER_LEFT: 'playerLeft',
ADD_SPECTATOR: 'addSpectator',
UPDATE_SPECTATORS: 'updateSpectators',
RESTART_GAME: 'restartGame',
ASSIGN_DEDICATED_MOD: 'assignDedicatedMod',
KICK_PERSON: 'kickPerson',
UPDATE_GAME_ROLES: 'updateGameRoles',
LEAVE_ROOM: 'leaveRoom'
};
export const TIMER_EVENTS = function () {
return [
EVENT_IDS.PAUSE_TIMER,
EVENT_IDS.RESUME_TIMER,
EVENT_IDS.GET_TIME_REMAINING,
EVENT_IDS.END_TIMER
];
};
export const LOBBY_EVENTS = function () {
return [
EVENT_IDS.PLAYER_JOINED,
EVENT_IDS.ADD_SPECTATOR,
EVENT_IDS.KICK_PERSON,
EVENT_IDS.UPDATE_GAME_ROLES,
EVENT_IDS.LEAVE_ROOM
];
};
export const IN_PROGRESS_EVENTS = function () {
return [
EVENT_IDS.KILL_PLAYER,
EVENT_IDS.REVEAL_PLAYER,
EVENT_IDS.ADD_SPECTATOR
];
};

View File

@@ -1,5 +1,12 @@
export class Game {
constructor (deck, hasTimer, hasDedicatedModerator, moderatorName, timerParams = null, isTestGame = false) {
constructor (
deck,
hasTimer,
hasDedicatedModerator,
moderatorName,
timerParams = null,
isTestGame = false
) {
this.deck = deck;
this.hasTimer = hasTimer;
this.timerParams = timerParams;

View File

@@ -1,4 +1,4 @@
import { globals } from '../../config/globals.js';
import { ALIGNMENT } from '../../config/globals.js';
import { HTMLFragments } from '../front_end_components/HTMLFragments.js';
import { toast } from '../front_end_components/Toast.js';
import { ModalManager } from '../front_end_components/ModalManager.js';
@@ -147,7 +147,7 @@ export class DeckStateManager {
}
const sortedDeck = this.deck.sort((a, b) => {
if (a.team !== b.team) {
return a.team === globals.ALIGNMENT.GOOD ? -1 : 1;
return a.team === ALIGNMENT.GOOD ? -1 : 1;
}
return a.role.localeCompare(b.role);
});
@@ -187,10 +187,10 @@ export class DeckStateManager {
roleEl.dataset.roleId = sortedDeck[i].id;
roleEl.classList.add('added-role');
roleEl.innerHTML = HTMLFragments.DECK_SELECT_ROLE_ADDED_TO_DECK;
if (sortedDeck[i].team === globals.ALIGNMENT.GOOD) {
roleEl.classList.add(globals.ALIGNMENT.GOOD);
if (sortedDeck[i].team === ALIGNMENT.GOOD) {
roleEl.classList.add(ALIGNMENT.GOOD);
} else {
roleEl.classList.add(globals.ALIGNMENT.EVIL);
roleEl.classList.add(ALIGNMENT.EVIL);
}
populateRoleElementInfo(roleEl, sortedDeck, i);
document.getElementById('deck-list').appendChild(roleEl);
@@ -219,10 +219,10 @@ export class DeckStateManager {
if (e.type === 'click' || e.code === 'Enter') {
const alignmentEl = document.getElementById('custom-role-info-modal-alignment');
const nameEl = document.getElementById('custom-role-info-modal-name');
alignmentEl.classList.remove(globals.ALIGNMENT.GOOD);
alignmentEl.classList.remove(globals.ALIGNMENT.EVIL);
nameEl.classList.remove(globals.ALIGNMENT.GOOD);
nameEl.classList.remove(globals.ALIGNMENT.EVIL);
alignmentEl.classList.remove(ALIGNMENT.GOOD);
alignmentEl.classList.remove(ALIGNMENT.EVIL);
nameEl.classList.remove(ALIGNMENT.GOOD);
nameEl.classList.remove(ALIGNMENT.EVIL);
e.preventDefault();
nameEl.innerText = sortedDeck[i].role;
nameEl.classList.add(sortedDeck[i].team);

View File

@@ -1,8 +1,7 @@
import { Game } from '../../model/Game.js';
import { cancelCurrentToast, toast } from '../front_end_components/Toast.js';
import { ModalManager } from '../front_end_components/ModalManager.js';
import { XHRUtility } from '../utility/XHRUtility.js';
import { globals } from '../../config/globals.js';
import { ALIGNMENT } from '../../config/globals.js';
import { HTMLFragments } from '../front_end_components/HTMLFragments.js';
import { UserUtility } from '../utility/UserUtility.js';
import { RoleBox } from './RoleBox.js';
@@ -118,50 +117,42 @@ export class GameCreationStepManager {
button.classList.remove('submitted');
button.addEventListener('click', this.steps['5'].forwardHandler);
};
XHRUtility.xhr(
fetch(
'/api/games/create',
'POST',
null,
JSON.stringify(
new Game(
this.currentGame.deck.filter((card) => card.quantity > 0),
this.currentGame.hasTimer,
this.currentGame.hasDedicatedModerator,
this.currentGame.moderatorName,
this.currentGame.timerParams,
this.currentGame.isTestGame
{
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(
new Game(
this.currentGame.deck.filter((card) => card.quantity > 0),
this.currentGame.hasTimer,
this.currentGame.hasDedicatedModerator,
this.currentGame.moderatorName,
this.currentGame.timerParams,
this.currentGame.isTestGame
)
)
)
)
.then((res) => {
if (res.status === 201) {
try {
const json = JSON.parse(res.content);
UserUtility.setAnonymousUserId(json.cookie, json.environment);
window.location.replace(
window.location.protocol + '//' + window.location.host +
'/game/' + json.accessCode
);
} catch (e) {
restoreButton();
toast(
'There was a problem creating the game. Please contact the developers.',
'error',
true,
true
);
}
}
}).catch((e) => {
restoreButton();
if (e.status === 429) {
toast('You\'ve sent this request too many times.', 'error', true, true, 'medium');
} else if (e.status === 413) {
toast('Your request is too large.', 'error', true, true);
} else {
toast(e.content, 'error', true, true, 'medium');
}
restoreButton();
if (e.status === 429) {
toast('You\'ve sent this request too many times.', 'error', true, true, 'medium');
} else if (e.status === 413) {
toast('Your request is too large.', 'error', true, true);
} else {
toast(e.content, 'error', true, true, 'medium');
}
}).then(res => {
res.json().then(json => {
UserUtility.setAnonymousUserId(json.cookie, json.environment);
window.location.replace(
window.location.protocol + '//' + window.location.host +
'/game/' + json.accessCode
);
});
});
}
}
};
@@ -442,10 +433,10 @@ function renderReviewAndCreateStep (containerId, stepNumber, game, deckManager)
for (const card of game.deck) {
const roleEl = document.createElement('div');
roleEl.innerText = card.quantity + 'x ' + card.role;
if (card.team === globals.ALIGNMENT.GOOD) {
roleEl.classList.add(globals.ALIGNMENT.GOOD);
if (card.team === ALIGNMENT.GOOD) {
roleEl.classList.add(ALIGNMENT.GOOD);
} else {
roleEl.classList.add(globals.ALIGNMENT.EVIL);
roleEl.classList.add(ALIGNMENT.EVIL);
}
div.querySelector('#roles-option').appendChild(roleEl);
}
@@ -552,7 +543,7 @@ function initializeRemainingEventListeners (deckManager, roleBox) {
roleBox.createMode = true;
roleBox.currentlyEditingRoleName = null;
document.getElementById('role-name').value = '';
document.getElementById('role-alignment').value = globals.ALIGNMENT.GOOD;
document.getElementById('role-alignment').value = ALIGNMENT.GOOD;
document.getElementById('role-description').value = '';
ModalManager.displayModal(
'role-modal',

View File

@@ -1,5 +1,5 @@
import { HTMLFragments } from '../front_end_components/HTMLFragments.js';
import { globals } from '../../config/globals.js';
import { ALIGNMENT, PRIMITIVES } from '../../config/globals.js';
import { defaultRoles } from '../../config/defaultRoles.js';
import { toast } from '../front_end_components/Toast.js';
import { ModalManager } from '../front_end_components/ModalManager.js';
@@ -8,7 +8,6 @@ import { Confirmation } from '../front_end_components/Confirmation.js';
export class RoleBox {
constructor (container, deckManager) {
this.createMode = false;
this.currentlyEditingRoleName = null;
this.category = 'default';
this.deckManager = deckManager;
this.defaultRoles = [];
@@ -32,7 +31,7 @@ export class RoleBox {
loadDefaultRoles = () => {
this.defaultRoles = defaultRoles.sort((a, b) => {
if (a.team !== b.team) {
return a.team === globals.ALIGNMENT.GOOD ? -1 : 1;
return a.team === ALIGNMENT.GOOD ? -1 : 1;
}
return a.role.localeCompare(b.role);
}).map((role) => {
@@ -174,9 +173,9 @@ export class RoleBox {
defaultRole.innerHTML = HTMLFragments.DECK_SELECT_ROLE_DEFAULT;
defaultRole.classList.add('default-role');
defaultRole.dataset.roleId = this.defaultRoles[i].id;
const alignmentClass = this.defaultRoles[i].team === globals.ALIGNMENT.GOOD
? globals.ALIGNMENT.GOOD
: globals.ALIGNMENT.EVIL;
const alignmentClass = this.defaultRoles[i].team === ALIGNMENT.GOOD
? ALIGNMENT.GOOD
: ALIGNMENT.EVIL;
defaultRole.classList.add(alignmentClass);
defaultRole.querySelector('.role-name').innerText = this.defaultRoles[i].role;
selectEl.appendChild(defaultRole);
@@ -190,7 +189,7 @@ export class RoleBox {
this.categoryTransition.play();
this.customRoles.sort((a, b) => {
if (a.team !== b.team) {
return a.team === globals.ALIGNMENT.GOOD ? -1 : 1;
return a.team === ALIGNMENT.GOOD ? -1 : 1;
}
return a.role.localeCompare(b.role);
});
@@ -200,7 +199,7 @@ export class RoleBox {
customRole.innerHTML = HTMLFragments.DECK_SELECT_ROLE;
customRole.classList.add('custom-role');
customRole.dataset.roleId = this.customRoles[i].id;
const alignmentClass = this.customRoles[i].team === globals.ALIGNMENT.GOOD ? globals.ALIGNMENT.GOOD : globals.ALIGNMENT.EVIL;
const alignmentClass = this.customRoles[i].team === ALIGNMENT.GOOD ? ALIGNMENT.GOOD : ALIGNMENT.EVIL;
customRole.classList.add(alignmentClass);
customRole.querySelector('.role-name').innerText = this.customRoles[i].role;
selectEl.appendChild(customRole);
@@ -267,10 +266,10 @@ export class RoleBox {
if (e.type === 'click' || e.code === 'Enter') {
const alignmentEl = document.getElementById('custom-role-info-modal-alignment');
const nameEl = document.getElementById('custom-role-info-modal-name');
alignmentEl.classList.remove(globals.ALIGNMENT.GOOD);
alignmentEl.classList.remove(globals.ALIGNMENT.EVIL);
nameEl.classList.remove(globals.ALIGNMENT.GOOD);
nameEl.classList.remove(globals.ALIGNMENT.EVIL);
alignmentEl.classList.remove(ALIGNMENT.GOOD);
alignmentEl.classList.remove(ALIGNMENT.EVIL);
nameEl.classList.remove(ALIGNMENT.GOOD);
nameEl.classList.remove(ALIGNMENT.EVIL);
e.preventDefault();
let role;
if (isCustom) {
@@ -351,7 +350,7 @@ export class RoleBox {
function createRandomId () {
let id = '';
for (let i = 0; i < 50; i ++) {
id += globals.CHAR_POOL[Math.floor(Math.random() * globals.CHAR_POOL.length)];
id += PRIMITIVES.CHAR_POOL[Math.floor(Math.random() * PRIMITIVES.CHAR_POOL.length)];
}
return id;
}
@@ -365,9 +364,9 @@ function validateCustomRoleCookie (cookie) {
if (Array.isArray(cookieJSON)) {
for (const entry of cookieJSON) {
if (entry !== null && typeof entry === 'object') {
if (typeof entry.role !== 'string' || entry.role.length > globals.MAX_CUSTOM_ROLE_NAME_LENGTH
|| typeof entry.team !== 'string' || (entry.team !== globals.ALIGNMENT.GOOD && entry.team !== globals.ALIGNMENT.EVIL)
|| typeof entry.description !== 'string' || entry.description.length > globals.MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH
if (typeof entry.role !== 'string' || entry.role.length > PRIMITIVES.MAX_CUSTOM_ROLE_NAME_LENGTH
|| typeof entry.team !== 'string' || (entry.team !== ALIGNMENT.GOOD && entry.team !== ALIGNMENT.EVIL)
|| typeof entry.description !== 'string' || entry.description.length > PRIMITIVES.MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH
) {
return false;
}

View File

@@ -1,10 +1 @@
/* It started getting confusing where I am reading/writing to the game state, and thus the state started to get inconsistent.
Creating a bucket to hold it so I can overwrite the gameState object whilst still preserving a reference to the containing bucket.
Now several components can read a shared game state.
*/
export const stateBucket = {
joinRequestInFlight: true,
accessCode: null,
currentGameState: null,
environment: null
};
export const stateBucket = {};

View File

@@ -1,4 +1,4 @@
import { globals } from '../../../config/globals.js';
import { USER_TYPES } from '../../../config/globals.js';
import { HTMLFragments } from '../../front_end_components/HTMLFragments.js';
import { SharedStateUtil } from './shared/SharedStateUtil.js';
@@ -12,14 +12,14 @@ export class Ended {
renderEndOfGame (gameState) {
if (
gameState.client.userType === globals.USER_TYPES.MODERATOR
|| gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
gameState.client.userType === USER_TYPES.MODERATOR
|| gameState.client.userType === USER_TYPES.TEMPORARY_MODERATOR
) {
document.getElementById('end-of-game-buttons').prepend(SharedStateUtil.createReturnToLobbyButton(this.stateBucket));
}
SharedStateUtil.displayCurrentModerator(this.stateBucket.currentGameState.people
.find((person) => person.userType === globals.USER_TYPES.MODERATOR
|| person.userType === globals.USER_TYPES.TEMPORARY_MODERATOR));
.find((person) => person.userType === USER_TYPES.MODERATOR
|| person.userType === USER_TYPES.TEMPORARY_MODERATOR));
this.renderPlayersWithRoleInformation();
}
@@ -31,7 +31,7 @@ export class Ended {
const modType = tempMod ? this.stateBucket.currentGameState.moderator.userType : null;
renderGroupOfPlayers(
this.stateBucket.currentGameState.people.filter(
p => (p.userType !== globals.USER_TYPES.MODERATOR && p.userType !== globals.USER_TYPES.SPECTATOR)
p => (p.userType !== USER_TYPES.MODERATOR && p.userType !== USER_TYPES.SPECTATOR)
|| p.killed
),
this.stateBucket.currentGameState.accessCode,

View File

@@ -1,5 +1,12 @@
import { toast } from '../../front_end_components/Toast.js';
import { globals } from '../../../config/globals.js';
import {
STATUS,
EVENT_IDS,
SOCKET_EVENTS,
USER_TYPE_ICONS,
USER_TYPES,
ALIGNMENT
} 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';
@@ -18,25 +25,25 @@ export class InProgress {
setUserView (userType) {
switch (userType) {
case globals.USER_TYPES.PLAYER:
case USER_TYPES.PLAYER:
this.container.innerHTML = HTMLFragments.PLAYER_GAME_VIEW;
this.renderPlayerView();
break;
case globals.USER_TYPES.KILLED_PLAYER:
case USER_TYPES.KILLED_PLAYER:
this.container.innerHTML = HTMLFragments.PLAYER_GAME_VIEW;
this.renderPlayerView(true);
break;
case globals.USER_TYPES.MODERATOR:
case 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:
case 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:
case USER_TYPES.SPECTATOR:
this.container.innerHTML = HTMLFragments.SPECTATOR_GAME_VIEW;
this.renderSpectatorView();
break;
@@ -46,12 +53,12 @@ export class InProgress {
if (this.stateBucket.currentGameState.timerParams) {
this.socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.GET_TIME_REMAINING,
SOCKET_EVENTS.IN_GAME_MESSAGE,
EVENT_IDS.GET_TIME_REMAINING,
this.stateBucket.currentGameState.accessCode
);
setTimeout(() => {
if (this.socket.hasListeners(globals.EVENT_IDS.GET_TIME_REMAINING) && document.getElementById('game-timer') !== null) {
if (this.socket.hasListeners(EVENT_IDS.GET_TIME_REMAINING) && document.getElementById('game-timer') !== null) {
document.getElementById('game-timer').innerText = 'Timer not found.';
document.getElementById('game-timer').classList.add('timer-error');
}
@@ -68,7 +75,7 @@ export class InProgress {
Confirmation(
SharedStateUtil.buildSpectatorList(
this.stateBucket.currentGameState.people
.filter(p => p.userType === globals.USER_TYPES.SPECTATOR),
.filter(p => p.userType === USER_TYPES.SPECTATOR),
this.stateBucket.currentGameState.client,
this.socket,
this.stateBucket.currentGameState),
@@ -80,7 +87,7 @@ export class InProgress {
if (spectatorCount) {
SharedStateUtil.setNumberOfSpectators(
this.stateBucket.currentGameState.people.filter(p => p.userType === globals.USER_TYPES.SPECTATOR).length,
this.stateBucket.currentGameState.people.filter(p => p.userType === USER_TYPES.SPECTATOR).length,
spectatorCount
);
spectatorCount?.addEventListener('click', spectatorHandler);
@@ -92,13 +99,13 @@ export class InProgress {
if (isKilled) {
const clientUserType = document.getElementById('client-user-type');
if (clientUserType) {
clientUserType.innerText = globals.USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80';
clientUserType.innerText = USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80';
}
}
renderPlayerRole(this.stateBucket.currentGameState);
SharedStateUtil.displayCurrentModerator(this.stateBucket.currentGameState.people
.find((person) => person.userType === globals.USER_TYPES.MODERATOR
|| person.userType === globals.USER_TYPES.TEMPORARY_MODERATOR));
.find((person) => person.userType === USER_TYPES.MODERATOR
|| person.userType === USER_TYPES.TEMPORARY_MODERATOR));
this.renderPlayersWithNoRoleInformationUnlessRevealed(false);
}
@@ -116,7 +123,7 @@ export class InProgress {
: null;
this.renderGroupOfPlayers(
this.stateBucket.currentGameState.people.filter(
p => (p.userType !== globals.USER_TYPES.MODERATOR && p.userType !== globals.USER_TYPES.SPECTATOR)
p => (p.userType !== USER_TYPES.MODERATOR && p.userType !== USER_TYPES.SPECTATOR)
|| p.killed
),
this.killPlayerHandlers,
@@ -174,34 +181,34 @@ export class InProgress {
renderSpectatorView () {
SharedStateUtil.displayCurrentModerator(this.stateBucket.currentGameState.people
.find((person) => person.userType === globals.USER_TYPES.MODERATOR
|| person.userType === globals.USER_TYPES.TEMPORARY_MODERATOR));
.find((person) => person.userType === USER_TYPES.MODERATOR
|| person.userType === USER_TYPES.TEMPORARY_MODERATOR));
this.renderPlayersWithNoRoleInformationUnlessRevealed();
}
setSocketHandlers () {
this.socket.on(globals.EVENT_IDS.KILL_PLAYER, (killedPlayer) => {
this.socket.on(EVENT_IDS.KILL_PLAYER, (killedPlayer) => {
this.stateBucket.currentGameState.people = this.stateBucket.currentGameState.people
.map(person => person.id === killedPlayer.id ? killedPlayer : person);
if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) {
if (this.stateBucket.currentGameState.client.userType === USER_TYPES.MODERATOR) {
toast(killedPlayer.name + ' killed.', 'success', true, true, 'medium');
this.renderPlayersWithRoleAndAlignmentInfo(this.stateBucket.currentGameState.status === globals.STATUS.ENDED);
this.renderPlayersWithRoleAndAlignmentInfo(this.stateBucket.currentGameState.status === STATUS.ENDED);
} else {
if (killedPlayer.id === this.stateBucket.currentGameState.client.id) {
const clientUserType = document.getElementById('client-user-type');
if (clientUserType) {
clientUserType.innerText = globals.USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80';
clientUserType.innerText = USER_TYPES.KILLED_PLAYER + ' \uD83D\uDC80';
}
this.updatePlayerCardToKilledState();
toast('You have been killed!', 'warning', true, true, 'medium');
} else {
toast(killedPlayer.name + ' was killed!', 'warning', true, true, 'medium');
if (killedPlayer.userType === globals.USER_TYPES.MODERATOR
&& this.stateBucket.currentGameState.client.userType !== globals.USER_TYPES.TEMPORARY_MODERATOR) {
if (killedPlayer.userType === USER_TYPES.MODERATOR
&& this.stateBucket.currentGameState.client.userType !== USER_TYPES.TEMPORARY_MODERATOR) {
SharedStateUtil.displayCurrentModerator(killedPlayer);
}
}
if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
if (this.stateBucket.currentGameState.client.userType === USER_TYPES.TEMPORARY_MODERATOR) {
this.removePlayerListEventListeners(false);
} else {
this.renderPlayersWithNoRoleInformationUnlessRevealed(false);
@@ -209,27 +216,27 @@ export class InProgress {
}
});
this.socket.on(globals.EVENT_IDS.REVEAL_PLAYER, (revealData) => {
this.socket.on(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) {
if (this.stateBucket.currentGameState.client.userType === USER_TYPES.MODERATOR) {
if (revealedPerson.id === this.stateBucket.currentGameState.client.id) {
toast('You revealed your role.', 'success', true, true, 'medium');
} else {
toast(revealedPerson.name + ' revealed.', 'success', true, true, 'medium');
}
this.renderPlayersWithRoleAndAlignmentInfo(this.stateBucket.currentGameState.status === globals.STATUS.ENDED);
this.renderPlayersWithRoleAndAlignmentInfo(this.stateBucket.currentGameState.status === 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) {
if (this.stateBucket.currentGameState.client.userType === USER_TYPES.TEMPORARY_MODERATOR) {
this.renderPlayersWithNoRoleInformationUnlessRevealed(true);
} else {
this.renderPlayersWithNoRoleInformationUnlessRevealed(false);
@@ -238,14 +245,14 @@ export class InProgress {
}
});
this.socket.on(globals.EVENT_IDS.ADD_SPECTATOR, (spectator) => {
this.socket.on(EVENT_IDS.ADD_SPECTATOR, (spectator) => {
this.stateBucket.currentGameState.people.push(spectator);
SharedStateUtil.setNumberOfSpectators(
this.stateBucket.currentGameState.people.filter(p => p.userType === globals.USER_TYPES.SPECTATOR).length,
this.stateBucket.currentGameState.people.filter(p => p.userType === USER_TYPES.SPECTATOR).length,
document.getElementById('spectator-count')
);
if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR
|| this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
if (this.stateBucket.currentGameState.client.userType === USER_TYPES.MODERATOR
|| this.stateBucket.currentGameState.client.userType === USER_TYPES.TEMPORARY_MODERATOR) {
this.displayAvailableModerators();
}
});
@@ -269,13 +276,13 @@ export class InProgress {
return a.name >= b.name ? 1 : -1;
});
const teamGood = this.stateBucket.currentGameState.people.filter(
(p) => p.alignment === globals.ALIGNMENT.GOOD
&& ((p.userType !== globals.USER_TYPES.MODERATOR && p.userType !== globals.USER_TYPES.SPECTATOR)
(p) => p.alignment === ALIGNMENT.GOOD
&& ((p.userType !== USER_TYPES.MODERATOR && p.userType !== USER_TYPES.SPECTATOR)
|| p.killed)
);
const teamEvil = this.stateBucket.currentGameState.people.filter((p) => p.alignment === globals.ALIGNMENT.EVIL
&& ((p.userType !== globals.USER_TYPES.MODERATOR && p.userType !== globals.USER_TYPES.SPECTATOR)
const teamEvil = this.stateBucket.currentGameState.people.filter((p) => p.alignment === ALIGNMENT.EVIL
&& ((p.userType !== USER_TYPES.MODERATOR && p.userType !== USER_TYPES.SPECTATOR)
|| p.killed)
);
this.renderGroupOfPlayers(
@@ -283,7 +290,7 @@ export class InProgress {
this.killPlayerHandlers,
this.revealRoleHandlers,
this.stateBucket.currentGameState.accessCode,
globals.ALIGNMENT.EVIL,
ALIGNMENT.EVIL,
this.stateBucket.currentGameState.people.find(person =>
person.id === this.stateBucket.currentGameState.currentModeratorId).userType,
this.socket
@@ -293,7 +300,7 @@ export class InProgress {
this.killPlayerHandlers,
this.revealRoleHandlers,
this.stateBucket.currentGameState.accessCode,
globals.ALIGNMENT.GOOD,
ALIGNMENT.GOOD,
this.stateBucket.currentGameState.people.find(person =>
person.id === this.stateBucket.currentGameState.currentModeratorId).userType,
this.socket
@@ -328,7 +335,7 @@ export class InProgress {
const roleElement = playerEl.querySelector('.game-player-role');
// Add role/alignment indicators if necessary
if (moderatorType === globals.USER_TYPES.MODERATOR || player.revealed) {
if (moderatorType === USER_TYPES.MODERATOR || player.revealed) {
if (alignment === null) {
roleElement.classList.add(player.alignment);
} else {
@@ -349,10 +356,10 @@ export class InProgress {
} else if (!player.out && moderatorType) {
killPlayerHandlers[player.id] = () => {
Confirmation('Kill \'' + player.name + '\'?', () => {
if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.ASSIGN_DEDICATED_MOD, accessCode, { personId: player.id });
if (this.stateBucket.currentGameState.client.userType === USER_TYPES.TEMPORARY_MODERATOR) {
socket.emit(SOCKET_EVENTS.IN_GAME_MESSAGE, EVENT_IDS.ASSIGN_DEDICATED_MOD, accessCode, { personId: player.id });
} else {
socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.KILL_PLAYER, accessCode, { personId: player.id });
socket.emit(SOCKET_EVENTS.IN_GAME_MESSAGE, EVENT_IDS.KILL_PLAYER, accessCode, { personId: player.id });
}
});
};
@@ -368,13 +375,13 @@ export class InProgress {
} else if (!player.revealed && moderatorType) {
revealRoleHandlers[player.id] = () => {
Confirmation('Reveal \'' + player.name + '\'?', () => {
socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.REVEAL_PLAYER, accessCode, { personId: player.id });
socket.emit(SOCKET_EVENTS.IN_GAME_MESSAGE, EVENT_IDS.REVEAL_PLAYER, accessCode, { personId: player.id });
});
};
playerEl.querySelector('.reveal-role-button').addEventListener('click', revealRoleHandlers[player.id]);
}
const playerListContainerId = moderatorType === globals.USER_TYPES.MODERATOR
const playerListContainerId = moderatorType === USER_TYPES.MODERATOR
? 'player-list-moderator-team-' + alignment
: 'game-player-list';
@@ -417,7 +424,7 @@ export class InProgress {
function renderPlayerRole (gameState) {
const name = document.querySelector('#role-name');
name.innerText = gameState.client.gameRole;
if (gameState.client.alignment === globals.ALIGNMENT.GOOD) {
if (gameState.client.alignment === ALIGNMENT.GOOD) {
document.getElementById('game-role').classList.add('game-role-good');
name.classList.add('good');
} else {
@@ -498,8 +505,8 @@ function createEndGamePromptComponent (socket, stateBucket) {
e.preventDefault();
Confirmation('End the game?', () => {
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.END_GAME,
SOCKET_EVENTS.IN_GAME_MESSAGE,
EVENT_IDS.END_GAME,
stateBucket.currentGameState.accessCode,
null,
() => {
@@ -531,14 +538,14 @@ function insertPlaceholderButton (container, append, type) {
function renderPotentialMods (gameState, group, transferModHandlers, socket) {
const modalContent = document.getElementById('transfer-mod-modal-content');
for (const member of group) {
if ((member.userType === globals.USER_TYPES.KILLED_PLAYER || member.userType === globals.USER_TYPES.SPECTATOR) && !(member.id === gameState.client.id)) {
if ((member.userType === USER_TYPES.KILLED_PLAYER || member.userType === 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.innerHTML =
'<div class=\'potential-mod-name\'></div>' +
'<div>' + member.userType + ' ' + globals.USER_TYPE_ICONS[member.userType] + ' </div>';
'<div>' + member.userType + ' ' + USER_TYPE_ICONS[member.userType] + ' </div>';
container.querySelector('.potential-mod-name').innerText = member.name;
transferModHandlers[member.id] = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
@@ -549,16 +556,16 @@ function renderPotentialMods (gameState, group, transferModHandlers, socket) {
transferPrompt.innerHTML = '';
}
socket.timeout(5000).emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.TRANSFER_MODERATOR,
SOCKET_EVENTS.IN_GAME_MESSAGE,
EVENT_IDS.TRANSFER_MODERATOR,
gameState.accessCode,
{ personId: member.id },
(err) => {
if (err) {
console.error(err);
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.FETCH_GAME_STATE,
SOCKET_EVENTS.IN_GAME_MESSAGE,
EVENT_IDS.FETCH_GAME_STATE,
gameState.accessCode,
{ personId: gameState.client.cookie },
(gameState) => {

View File

@@ -1,6 +1,6 @@
import { QRCode } from '../../third_party/qrcode.js';
import { toast } from '../../front_end_components/Toast.js';
import { globals } from '../../../config/globals.js';
import { EVENT_IDS, SOCKET_EVENTS, USER_TYPE_ICONS, USER_TYPES } from '../../../config/globals.js';
import { HTMLFragments } from '../../front_end_components/HTMLFragments.js';
import { Confirmation } from '../../front_end_components/Confirmation.js';
import { SharedStateUtil } from './shared/SharedStateUtil.js';
@@ -25,15 +25,15 @@ export class Lobby {
}
Confirmation('Start game and deal roles?', () => {
socket.timeout(5000).emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.START_GAME,
SOCKET_EVENTS.IN_GAME_MESSAGE,
EVENT_IDS.START_GAME,
stateBucket.currentGameState.accessCode,
null,
(err) => {
if (err) {
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.FETCH_GAME_STATE,
SOCKET_EVENTS.IN_GAME_MESSAGE,
EVENT_IDS.FETCH_GAME_STATE,
stateBucket.currentGameState.accessCode,
{ personId: stateBucket.currentGameState.client.cookie },
(gameState) => {
@@ -52,8 +52,8 @@ export class Lobby {
e.preventDefault();
Confirmation('Leave the room?', () => {
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.LEAVE_ROOM,
SOCKET_EVENTS.IN_GAME_MESSAGE,
EVENT_IDS.LEAVE_ROOM,
stateBucket.currentGameState.accessCode,
{ personId: stateBucket.currentGameState.client.id }
);
@@ -86,8 +86,8 @@ export class Lobby {
document.querySelector('#role-edit-container-background')?.remove();
document.getElementById('game-content').style.display = 'flex';
this.socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.UPDATE_GAME_ROLES,
SOCKET_EVENTS.IN_GAME_MESSAGE,
EVENT_IDS.UPDATE_GAME_ROLES,
stateBucket.currentGameState.accessCode,
{ deck: this.gameCreationStepManager.deckManager.deck.filter((card) => card.quantity > 0) },
() => {
@@ -130,7 +130,7 @@ export class Lobby {
const playerCount = this.container.querySelector('#game-player-count');
playerCount.innerText = this.stateBucket.currentGameState.gameSize + ' Players';
const inLobbyCount = this.stateBucket.currentGameState.people.filter(
p => p.userType !== globals.USER_TYPES.MODERATOR && p.userType !== globals.USER_TYPES.SPECTATOR
p => p.userType !== USER_TYPES.MODERATOR && p.userType !== USER_TYPES.SPECTATOR
).length;
document.querySelector("label[for='lobby-players']").innerText =
'Participants (' + inLobbyCount + '/' + this.stateBucket.currentGameState.gameSize + ' Players)';
@@ -149,7 +149,7 @@ export class Lobby {
if (e.type === 'click' || e.code === 'Enter') {
Confirmation(
SharedStateUtil.buildSpectatorList(this.stateBucket.currentGameState.people
.filter(p => p.userType === globals.USER_TYPES.SPECTATOR),
.filter(p => p.userType === USER_TYPES.SPECTATOR),
this.stateBucket.currentGameState.client,
this.socket,
this.stateBucket.currentGameState),
@@ -162,7 +162,7 @@ export class Lobby {
this.container.querySelector('#spectator-count').addEventListener('keyup', spectatorHandler);
SharedStateUtil.setNumberOfSpectators(
this.stateBucket.currentGameState.people.filter(p => p.userType === globals.USER_TYPES.SPECTATOR).length,
this.stateBucket.currentGameState.people.filter(p => p.userType === USER_TYPES.SPECTATOR).length,
this.container.querySelector('#spectator-count')
);
@@ -180,54 +180,54 @@ export class Lobby {
const lobbyPlayersContainer = this.container.querySelector('#lobby-players');
const sorted = this.stateBucket.currentGameState.people.sort(
function (a, b) {
if (a.userType === globals.USER_TYPES.MODERATOR || a.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
if (a.userType === USER_TYPES.MODERATOR || a.userType === USER_TYPES.TEMPORARY_MODERATOR) {
return -1;
}
return 1;
}
);
for (const person of sorted.filter(p => p.userType !== globals.USER_TYPES.SPECTATOR)) {
for (const person of sorted.filter(p => p.userType !== USER_TYPES.SPECTATOR)) {
lobbyPlayersContainer.appendChild(renderLobbyPerson(person, this.stateBucket.currentGameState, this.socket));
}
const playerCount = this.stateBucket.currentGameState.people.filter(
p => p.userType !== globals.USER_TYPES.MODERATOR && p.userType !== globals.USER_TYPES.SPECTATOR
p => p.userType !== USER_TYPES.MODERATOR && p.userType !== USER_TYPES.SPECTATOR
).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, gameIsStartable) => {
this.socket.on(EVENT_IDS.PLAYER_JOINED, (player, gameIsStartable) => {
toast(player.name + ' joined!', 'success', true, true, 'short');
this.stateBucket.currentGameState.people.push(player);
this.stateBucket.currentGameState.isStartable = gameIsStartable;
this.populatePlayers();
if ((
this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR
|| this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
this.stateBucket.currentGameState.client.userType === USER_TYPES.MODERATOR
|| this.stateBucket.currentGameState.client.userType === USER_TYPES.TEMPORARY_MODERATOR
)
) {
this.displayStartGamePromptForModerators();
}
});
this.socket.on(globals.EVENT_IDS.ADD_SPECTATOR, (spectator) => {
this.socket.on(EVENT_IDS.ADD_SPECTATOR, (spectator) => {
this.stateBucket.currentGameState.people.push(spectator);
SharedStateUtil.setNumberOfSpectators(
this.stateBucket.currentGameState.people.filter(p => p.userType === globals.USER_TYPES.SPECTATOR).length,
this.stateBucket.currentGameState.people.filter(p => p.userType === USER_TYPES.SPECTATOR).length,
document.getElementById('spectator-count')
);
});
this.socket.on(globals.EVENT_IDS.KICK_PERSON, (kickedId, gameIsStartable) => {
this.socket.on(EVENT_IDS.KICK_PERSON, (kickedId, gameIsStartable) => {
if (kickedId === this.stateBucket.currentGameState.client.id) {
window.location = '/?message=' + encodeURIComponent('You were kicked by the moderator.');
} else {
this.handlePersonExiting(kickedId, gameIsStartable, globals.EVENT_IDS.KICK_PERSON);
this.handlePersonExiting(kickedId, gameIsStartable, EVENT_IDS.KICK_PERSON);
}
});
this.socket.on(globals.EVENT_IDS.UPDATE_GAME_ROLES, (deck, gameSize, isStartable) => {
this.socket.on(EVENT_IDS.UPDATE_GAME_ROLES, (deck, gameSize, isStartable) => {
this.stateBucket.currentGameState.deck = deck;
this.stateBucket.currentGameState.gameSize = gameSize;
this.stateBucket.currentGameState.isStartable = isStartable;
@@ -235,11 +235,11 @@ export class Lobby {
this.setPlayerCount();
});
this.socket.on(globals.EVENT_IDS.LEAVE_ROOM, (leftId, gameIsStartable) => {
this.socket.on(EVENT_IDS.LEAVE_ROOM, (leftId, gameIsStartable) => {
if (leftId === this.stateBucket.currentGameState.client.id) {
window.location = '/?message=' + encodeURIComponent('You left the room.');
} else {
this.handlePersonExiting(leftId, gameIsStartable, globals.EVENT_IDS.LEAVE_ROOM);
this.handlePersonExiting(leftId, gameIsStartable, EVENT_IDS.LEAVE_ROOM);
}
});
}
@@ -252,18 +252,18 @@ export class Lobby {
}
this.stateBucket.currentGameState.isStartable = gameIsStartable;
SharedStateUtil.setNumberOfSpectators(
this.stateBucket.currentGameState.people.filter(p => p.userType === globals.USER_TYPES.SPECTATOR).length,
this.stateBucket.currentGameState.people.filter(p => p.userType === USER_TYPES.SPECTATOR).length,
document.getElementById('spectator-count')
);
this.populatePlayers();
if ((
this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR
|| this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
this.stateBucket.currentGameState.client.userType === USER_TYPES.MODERATOR
|| this.stateBucket.currentGameState.client.userType === USER_TYPES.TEMPORARY_MODERATOR
)
) {
toast(
event === globals.EVENT_IDS.LEAVE_ROOM ? 'A player left.' : 'Player kicked.',
event === globals.EVENT_IDS.LEAVE_ROOM ? 'warning' : 'success',
event === EVENT_IDS.LEAVE_ROOM ? 'A player left.' : 'Player kicked.',
event === EVENT_IDS.LEAVE_ROOM ? 'warning' : 'success',
true,
true,
'short'
@@ -358,17 +358,17 @@ function renderLobbyPerson (person, gameState, socket) {
personNameEl.classList.add('lobby-player-name');
const personTypeEl = document.createElement('div');
personNameEl.innerText = person.name;
personTypeEl.innerText = person.userType + globals.USER_TYPE_ICONS[person.userType];
personTypeEl.innerText = person.userType + USER_TYPE_ICONS[person.userType];
el.classList.add('lobby-player');
if (person.userType === globals.USER_TYPES.MODERATOR || person.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
if (person.userType === USER_TYPES.MODERATOR || person.userType === USER_TYPES.TEMPORARY_MODERATOR) {
el.classList.add('moderator');
}
el.appendChild(personNameEl);
el.appendChild(personTypeEl);
if ((gameState.client.userType === globals.USER_TYPES.MODERATOR || gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR)
&& person.userType !== globals.USER_TYPES.MODERATOR && person.userType !== globals.USER_TYPES.TEMPORARY_MODERATOR) {
if ((gameState.client.userType === USER_TYPES.MODERATOR || gameState.client.userType === USER_TYPES.TEMPORARY_MODERATOR)
&& person.userType !== USER_TYPES.MODERATOR && person.userType !== USER_TYPES.TEMPORARY_MODERATOR) {
SharedStateUtil.addPlayerOptions(el, person, socket, gameState);
el.dataset.pointer = person.id;
}

View File

@@ -1,58 +1,41 @@
import { XHRUtility } from '../../../utility/XHRUtility.js';
import { UserUtility } from '../../../utility/UserUtility.js';
import { globals } from '../../../../config/globals.js';
import {
STATUS,
EVENT_IDS,
ENVIRONMENTS,
SOCKET_EVENTS,
USER_TYPE_ICONS,
USER_TYPES,
ALIGNMENT
} from '../../../../config/globals.js';
import { toast } from '../../../front_end_components/Toast.js';
import { Confirmation } from '../../../front_end_components/Confirmation.js';
import { Lobby } from '../Lobby.js';
import { stateBucket } from '../../StateBucket.js';
import { InProgress } from '../InProgress.js';
import { Ended } from '../Ended.js';
import { HTMLFragments } from '../../../front_end_components/HTMLFragments.js';
import { ModalManager } from '../../../front_end_components/ModalManager.js';
// This constant is meant to house logic that is utilized by more than one game state
export const SharedStateUtil = {
gameStateAckFn: (gameState, socket) => {
stateBucket.currentGameState = gameState;
processGameState(
stateBucket.currentGameState,
gameState.client.cookie,
socket,
true,
true
);
},
restartHandler: (stateBucket, status = globals.STATUS.IN_PROGRESS) => {
XHRUtility.xhr(
restartHandler: (stateBucket, status = STATUS.IN_PROGRESS) => {
fetch(
'/api/games/' + stateBucket.currentGameState.accessCode + '/restart?status=' + status,
'PATCH',
null,
JSON.stringify({
playerName: stateBucket.currentGameState.client.name,
accessCode: stateBucket.currentGameState.accessCode,
sessionCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.LOCAL),
localCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.PRODUCTION)
})
)
.then((res) => {})
.catch((res) => {
toast(res.content, 'error', true, true, 'medium');
});
},
createRestartButton: (stateBucket) => {
const restartGameButton = document.createElement('button');
restartGameButton.classList.add('app-button');
restartGameButton.setAttribute('id', 'restart-game-button');
restartGameButton.innerText = 'Quick Restart';
restartGameButton.addEventListener('click', () => {
Confirmation('Restart the game, dealing everyone new roles?', () => {
SharedStateUtil.restartHandler(stateBucket);
});
{
method: 'PATCH',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
playerName: stateBucket.currentGameState.client.name,
accessCode: stateBucket.currentGameState.accessCode,
sessionCookie: UserUtility.validateAnonUserSignature(ENVIRONMENTS.LOCAL),
localCookie: UserUtility.validateAnonUserSignature(ENVIRONMENTS.PRODUCTION)
})
}
).catch((res) => {
toast(res.content, 'error', true, true, 'medium');
});
return restartGameButton;
},
createReturnToLobbyButton: (stateBucket) => {
@@ -62,123 +45,18 @@ export const SharedStateUtil = {
returnToLobbyButton.innerText = 'Return to Lobby';
returnToLobbyButton.addEventListener('click', () => {
Confirmation('Return everyone to the Lobby?', () => {
SharedStateUtil.restartHandler(stateBucket, globals.STATUS.LOBBY);
SharedStateUtil.restartHandler(stateBucket, STATUS.LOBBY);
});
});
return returnToLobbyButton;
},
setClientSocketHandlers: (stateBucket, socket) => {
const startGameStateAckFn = (gameState) => {
SharedStateUtil.gameStateAckFn(gameState, socket);
toast('Game started!', 'success');
};
const restartGameStateAckFn = (gameState) => {
SharedStateUtil.gameStateAckFn(gameState, socket);
toast('Everyone has returned to the Lobby!', 'success');
};
const fetchGameStateHandler = (ackFn) => {
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.FETCH_GAME_STATE,
stateBucket.currentGameState.accessCode,
{ personId: stateBucket.currentGameState.client.cookie },
ackFn
);
};
socket.on(globals.EVENT_IDS.START_GAME, () => { fetchGameStateHandler(startGameStateAckFn); });
socket.on(globals.EVENT_IDS.RESTART_GAME, () => { fetchGameStateHandler(restartGameStateAckFn); });
socket.on(globals.EVENT_IDS.SYNC_GAME_STATE, () => {
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.FETCH_GAME_STATE,
stateBucket.currentGameState.accessCode,
{ personId: stateBucket.currentGameState.client.cookie },
function (gameState) {
const oldUserType = stateBucket.currentGameState.client.userType;
stateBucket.currentGameState = gameState;
processGameState(
stateBucket.currentGameState,
gameState.client.cookie,
socket,
true,
gameState.client.userType !== oldUserType
);
}
);
});
socket.on(globals.COMMANDS.END_GAME, (people) => {
stateBucket.currentGameState.people = people;
stateBucket.currentGameState.status = globals.STATUS.ENDED;
processGameState(
stateBucket.currentGameState,
stateBucket.currentGameState.client.cookie,
socket,
true,
true
);
});
},
syncWithGame: (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) {
socket.timeout(5000).emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.FETCH_GAME_STATE,
accessCode,
{ personId: cookie },
(err, gameState) => {
if (err) {
SharedStateUtil.retrySync(accessCode, socket, cookie);
} else {
SharedStateUtil.handleGameState(gameState, cookie, socket);
}
}
);
} else {
window.location = '/not-found?reason=' + encodeURIComponent('invalid-access-code');
}
},
retrySync: (accessCode, socket, cookie) => {
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.FETCH_GAME_STATE,
accessCode,
{ personId: cookie },
(gameState) => {
SharedStateUtil.handleGameState(gameState, cookie, socket);
}
);
},
handleGameState: (gameState, cookie, socket) => {
if (gameState === null) {
window.location = '/not-found?reason=' + encodeURIComponent('game-not-found');
} else {
stateBucket.currentGameState = gameState;
document.querySelector('.spinner-container')?.remove();
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, true, true);
}
},
addPlayerOptions: (personEl, person, socket, gameState) => {
const optionsButton = document.createElement('img');
const optionsHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
document.querySelector('#player-options-modal-title').innerText = person.name + globals.USER_TYPE_ICONS[person.userType];
document.querySelector('#player-options-modal-title').innerText = person.name + USER_TYPE_ICONS[person.userType];
document.getElementById('player-options-modal-content').innerHTML = '';
const kickOption = document.createElement('button');
kickOption.setAttribute('class', 'player-option');
@@ -187,8 +65,8 @@ export const SharedStateUtil = {
ModalManager.dispelModal('player-options-modal', 'player-options-modal-background');
Confirmation('Kick \'' + person.name + '\'?', () => {
socket.emit(
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
globals.EVENT_IDS.KICK_PERSON,
SOCKET_EVENTS.IN_GAME_MESSAGE,
EVENT_IDS.KICK_PERSON,
gameState.accessCode,
{ personId: person.id }
);
@@ -216,7 +94,7 @@ export const SharedStateUtil = {
buildSpectatorList (people, client, socket, gameState) {
const list = document.createElement('div');
const spectators = people.filter(p => p.userType === globals.USER_TYPES.SPECTATOR);
const spectators = people.filter(p => p.userType === USER_TYPES.SPECTATOR);
if (spectators.length === 0) {
list.innerHTML = '<div>Nobody currently spectating.</div>';
} else {
@@ -224,11 +102,11 @@ export const SharedStateUtil = {
const spectatorEl = document.createElement('div');
spectatorEl.classList.add('spectator');
spectatorEl.innerHTML = '<div class=\'spectator-name\'></div>' +
'<div>' + 'spectator' + globals.USER_TYPE_ICONS.spectator + '</div>';
'<div>' + 'spectator' + USER_TYPE_ICONS.spectator + '</div>';
spectatorEl.querySelector('.spectator-name').innerText = spectator.name;
list.appendChild(spectatorEl);
if (client.userType === globals.USER_TYPES.MODERATOR || client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
if (client.userType === USER_TYPES.MODERATOR || client.userType === USER_TYPES.TEMPORARY_MODERATOR) {
this.addPlayerOptions(spectatorEl, spectator, socket, gameState);
spectatorEl.dataset.pointer = spectator.id;
}
@@ -246,126 +124,68 @@ export const SharedStateUtil = {
displayCurrentModerator: (moderator) => {
document.getElementById('current-moderator-name').innerText = moderator.name;
document.getElementById('current-moderator-type').innerText = moderator.userType + globals.USER_TYPE_ICONS[moderator.userType];
document.getElementById('current-moderator-type').innerText = moderator.userType + USER_TYPE_ICONS[moderator.userType];
},
returnHumanReadableTime: (milliseconds, tenthsOfSeconds = false) => {
const tenths = Math.floor((milliseconds / 100) % 10);
let seconds = Math.floor((milliseconds / 1000) % 60);
let minutes = Math.floor((milliseconds / (1000 * 60)) % 60);
let hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);
hours = hours < 10 ? '0' + hours : hours;
minutes = minutes < 10 ? '0' + minutes : minutes;
seconds = seconds < 10 ? '0' + seconds : seconds;
return tenthsOfSeconds
? hours + ':' + minutes + ':' + seconds + '.' + tenths
: hours + ':' + minutes + ':' + seconds;
},
activateRoleInfoButton: () => {
document.getElementById('role-info-button').addEventListener('click', (e) => {
const deck = stateBucket.currentGameState.deck;
deck.sort((a, b) => {
return a.team === ALIGNMENT.GOOD ? -1 : 1;
});
e.preventDefault();
document.getElementById('role-info-prompt').innerHTML = HTMLFragments.ROLE_INFO_MODAL;
const modalContent = document.getElementById('game-role-info-container');
for (const card of deck) {
const roleDiv = document.createElement('div');
const roleNameDiv = document.createElement('div');
roleNameDiv.classList.add('role-info-name');
const roleName = document.createElement('h5');
const roleQuantity = document.createElement('h5');
const roleDescription = document.createElement('p');
roleDescription.innerText = card.description;
roleName.innerText = card.role;
roleQuantity.innerText = card.quantity + 'x';
if (card.team === ALIGNMENT.GOOD) {
roleName.classList.add(ALIGNMENT.GOOD);
} else {
roleName.classList.add(ALIGNMENT.EVIL);
}
roleNameDiv.appendChild(roleQuantity);
roleNameDiv.appendChild(roleName);
roleDiv.appendChild(roleNameDiv);
roleDiv.appendChild(roleDescription);
modalContent.appendChild(roleDiv);
}
ModalManager.displayModal('role-info-modal', 'role-info-modal-background', 'close-role-info-modal-button');
});
},
displayClientInfo: (name, userType) => {
document.getElementById('client-name').innerText = name;
document.getElementById('client-user-type').innerText = userType;
document.getElementById('client-user-type').innerText += USER_TYPE_ICONS[userType];
}
};
function processGameState (
currentGameState,
userId,
socket,
refreshPrompt = true,
animateContainer = false
) {
if (animateContainer) {
document.getElementById('game-state-container').animate(
[
{ opacity: '0', transform: 'translateY(10px)' },
{ opacity: '1', transform: 'translateY(0px)' }
], {
duration: 500,
easing: 'ease-in-out',
fill: 'both'
});
document.getElementById('client-container').animate([
{ opacity: '0' },
{ opacity: '1' }
], {
duration: 500,
easing: 'ease-out',
fill: 'both'
});
}
displayClientInfo(currentGameState.client.name, currentGameState.client.userType);
switch (currentGameState.status) {
case globals.STATUS.LOBBY:
const lobby = new Lobby('game-state-container', stateBucket, socket);
if (refreshPrompt) {
lobby.removeStartGameFunctionalityIfPresent();
}
lobby.populateHeader();
lobby.populatePlayers();
globals.LOBBY_EVENTS().forEach(e => socket.removeAllListeners(e));
lobby.setSocketHandlers();
if (currentGameState.client.userType === globals.USER_TYPES.MODERATOR
|| currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
if (refreshPrompt) {
lobby.displayStartGamePromptForModerators();
}
document.getElementById('player-options-prompt').innerHTML = HTMLFragments.PLAYER_OPTIONS_MODAL;
} else {
if (refreshPrompt) {
lobby.displayPlayerPrompt();
}
}
break;
case globals.STATUS.IN_PROGRESS:
if (refreshPrompt) {
document.querySelector('#game-control-prompt')?.remove();
document.querySelector('#leave-game-prompt')?.remove();
}
const inProgressGame = new InProgress('game-state-container', stateBucket, socket);
globals.IN_PROGRESS_EVENTS().forEach(e => socket.removeAllListeners(e));
inProgressGame.setSocketHandlers();
inProgressGame.setUserView(currentGameState.client.userType);
break;
case globals.STATUS.ENDED: {
const ended = new Ended('game-state-container', stateBucket, socket);
ended.renderEndOfGame(currentGameState);
break;
}
default:
break;
}
activateRoleInfoButton();
}
function activateRoleInfoButton () {
document.getElementById('role-info-button').addEventListener('click', (e) => {
const deck = stateBucket.currentGameState.deck;
deck.sort((a, b) => {
return a.team === globals.ALIGNMENT.GOOD ? -1 : 1;
});
e.preventDefault();
document.getElementById('role-info-prompt').innerHTML = HTMLFragments.ROLE_INFO_MODAL;
const modalContent = document.getElementById('game-role-info-container');
for (const card of deck) {
const roleDiv = document.createElement('div');
const roleNameDiv = document.createElement('div');
roleNameDiv.classList.add('role-info-name');
const roleName = document.createElement('h5');
const roleQuantity = document.createElement('h5');
const roleDescription = document.createElement('p');
roleDescription.innerText = card.description;
roleName.innerText = card.role;
roleQuantity.innerText = card.quantity + 'x';
if (card.team === globals.ALIGNMENT.GOOD) {
roleName.classList.add(globals.ALIGNMENT.GOOD);
} else {
roleName.classList.add(globals.ALIGNMENT.EVIL);
}
roleNameDiv.appendChild(roleQuantity);
roleNameDiv.appendChild(roleName);
roleDiv.appendChild(roleNameDiv);
roleDiv.appendChild(roleDescription);
modalContent.appendChild(roleDiv);
}
ModalManager.displayModal('role-info-modal', 'role-info-modal-background', 'close-role-info-modal-button');
});
}
function displayClientInfo (name, userType) {
document.getElementById('client-name').innerText = name;
document.getElementById('client-user-type').innerText = userType;
document.getElementById('client-user-type').innerText += globals.USER_TYPE_ICONS[userType];
}

View File

@@ -3,41 +3,244 @@ import { stateBucket } from '../game_state/StateBucket.js';
import { UserUtility } from '../utility/UserUtility.js';
import { toast } from '../front_end_components/Toast.js';
import { SharedStateUtil } from '../game_state/states/shared/SharedStateUtil.js';
import {
EVENT_IDS,
IN_PROGRESS_EVENTS,
LOBBY_EVENTS,
PRIMITIVES,
SOCKET_EVENTS,
STATUS,
USER_TYPES
} from '../../config/globals.js';
import { HTMLFragments } from '../front_end_components/HTMLFragments.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) => {
export const gameHandler = (socket, window, gameDOM) => {
document.body.innerHTML = gameDOM + document.body.innerHTML;
injectNavbar();
return new Promise((resolve, reject) => {
window.fetch(
'/api/games/environment',
{
method: 'GET',
mode: 'cors'
}
).catch((res) => {
reject(res.content);
}).then((response) => {
response.text().then((text) => {
stateBucket.environment = text;
socket.on('connect', () => {
if (stateBucket.timerWorker) {
stateBucket.timerWorker.terminate();
stateBucket.timerWorker = null;
}
syncWithGame(
socket,
UserUtility.validateAnonUserSignature(stateBucket.environment),
window
);
});
socket.on('connect_error', (err) => {
toast(err, 'error', true, false);
});
const response = await XHRUtility.xhr(
'/api/games/environment',
'GET',
null,
null
).catch((res) => {
toast(res.content, 'error', true);
socket.on('disconnect', () => {
toast('Disconnected. Attempting reconnect...', 'error', true, false);
});
setClientSocketHandlers(stateBucket, socket);
resolve();
});
});
});
};
stateBucket.environment = response.content;
function syncWithGame (socket, cookie, window) {
const splitUrl = window.location.href.split('/game/');
const accessCode = splitUrl[1];
if (/^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === PRIMITIVES.ACCESS_CODE_LENGTH) {
socket.timeout(5000).emit(
SOCKET_EVENTS.IN_GAME_MESSAGE,
EVENT_IDS.FETCH_GAME_STATE,
accessCode,
{ personId: cookie },
(err, gameState) => {
if (err) {
retrySync(accessCode, socket, cookie);
} else {
handleGameState(gameState, cookie, socket);
}
}
);
} else {
window.location = '/not-found?reason=' + encodeURIComponent('invalid-access-code');
}
}
socket.on('connect', function () {
if (stateBucket.timerWorker) {
stateBucket.timerWorker.terminate();
stateBucket.timerWorker = null;
function retrySync (accessCode, socket, cookie) {
socket.emit(
SOCKET_EVENTS.IN_GAME_MESSAGE,
EVENT_IDS.FETCH_GAME_STATE,
accessCode,
{ personId: cookie },
(gameState) => {
handleGameState(gameState, cookie, socket);
}
SharedStateUtil.syncWithGame(
);
}
function handleGameState (gameState, cookie, socket) {
if (gameState === null) {
window.location = '/not-found?reason=' + encodeURIComponent('game-not-found');
} else {
stateBucket.currentGameState = gameState;
document.querySelector('.spinner-container')?.remove();
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, true, true);
}
}
function processGameState (
currentGameState,
userId,
socket,
refreshPrompt = true,
animateContainer = false
) {
if (animateContainer) {
document.getElementById('game-state-container').animate(
[
{ opacity: '0', transform: 'translateY(10px)' },
{ opacity: '1', transform: 'translateY(0px)' }
], {
duration: 500,
easing: 'ease-in-out',
fill: 'both'
});
document.getElementById('client-container').animate([
{ opacity: '0' },
{ opacity: '1' }
], {
duration: 500,
easing: 'ease-out',
fill: 'both'
});
}
SharedStateUtil.displayClientInfo(currentGameState.client.name, currentGameState.client.userType);
switch (currentGameState.status) {
case STATUS.LOBBY:
const lobby = new Lobby('game-state-container', stateBucket, socket);
if (refreshPrompt) {
lobby.removeStartGameFunctionalityIfPresent();
}
lobby.populateHeader();
lobby.populatePlayers();
LOBBY_EVENTS().forEach(e => socket.removeAllListeners(e));
lobby.setSocketHandlers();
if (currentGameState.client.userType === USER_TYPES.MODERATOR
|| currentGameState.client.userType === USER_TYPES.TEMPORARY_MODERATOR) {
if (refreshPrompt) {
lobby.displayStartGamePromptForModerators();
}
document.getElementById('player-options-prompt').innerHTML = HTMLFragments.PLAYER_OPTIONS_MODAL;
} else {
if (refreshPrompt) {
lobby.displayPlayerPrompt();
}
}
break;
case STATUS.IN_PROGRESS:
if (refreshPrompt) {
document.querySelector('#game-control-prompt')?.remove();
document.querySelector('#leave-game-prompt')?.remove();
}
const inProgressGame = new InProgress('game-state-container', stateBucket, socket);
IN_PROGRESS_EVENTS().forEach(e => socket.removeAllListeners(e));
inProgressGame.setSocketHandlers();
inProgressGame.setUserView(currentGameState.client.userType);
break;
case STATUS.ENDED: {
const ended = new Ended('game-state-container', stateBucket, socket);
ended.renderEndOfGame(currentGameState);
break;
}
default:
break;
}
SharedStateUtil.activateRoleInfoButton();
}
function setClientSocketHandlers (stateBucket, socket) {
const commonGameStateAckFn = (gameState, socket) => {
stateBucket.currentGameState = gameState;
processGameState(
stateBucket.currentGameState,
gameState.client.cookie,
socket,
UserUtility.validateAnonUserSignature(response.content),
window
true,
true
);
};
const startGameStateAckFn = (gameState) => {
commonGameStateAckFn(gameState, socket);
toast('Game started!', 'success');
};
const restartGameStateAckFn = (gameState) => {
commonGameStateAckFn(gameState, socket);
toast('Everyone has returned to the Lobby!', 'success');
};
const fetchGameStateHandler = (ackFn) => {
socket.emit(
SOCKET_EVENTS.IN_GAME_MESSAGE,
EVENT_IDS.FETCH_GAME_STATE,
stateBucket.currentGameState.accessCode,
{ personId: stateBucket.currentGameState.client.cookie },
ackFn
);
};
socket.on(EVENT_IDS.START_GAME, () => { fetchGameStateHandler(startGameStateAckFn); });
socket.on(EVENT_IDS.RESTART_GAME, () => { fetchGameStateHandler(restartGameStateAckFn); });
socket.on(EVENT_IDS.SYNC_GAME_STATE, () => {
socket.emit(
SOCKET_EVENTS.IN_GAME_MESSAGE,
EVENT_IDS.FETCH_GAME_STATE,
stateBucket.currentGameState.accessCode,
{ personId: stateBucket.currentGameState.client.cookie },
function (gameState) {
const oldUserType = stateBucket.currentGameState.client.userType;
stateBucket.currentGameState = gameState;
processGameState(
stateBucket.currentGameState,
gameState.client.cookie,
socket,
true,
gameState.client.userType !== oldUserType
);
}
);
});
socket.on('connect_error', (err) => {
toast(err, 'error', true, false);
socket.on(EVENT_IDS.END_GAME, (people) => {
stateBucket.currentGameState.people = people;
stateBucket.currentGameState.status = STATUS.ENDED;
processGameState(
stateBucket.currentGameState,
stateBucket.currentGameState.client.cookie,
socket,
true,
true
);
});
socket.on('disconnect', () => {
toast('Disconnected. Attempting reconnect...', 'error', true, false);
});
SharedStateUtil.setClientSocketHandlers(stateBucket, socket);
};
}

View File

@@ -1,17 +1,18 @@
import { globals } from '../../config/globals.js';
import { EVENT_IDS, SOCKET_EVENTS, USER_TYPES, TIMER_EVENTS, PRIMITIVES } from '../../config/globals.js';
import { Confirmation } from '../front_end_components/Confirmation.js';
import { SharedStateUtil } from '../game_state/states/shared/SharedStateUtil.js';
export class GameTimerManager {
constructor (stateBucket, socket) {
this.stateBucket = stateBucket;
this.playListener = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.RESUME_TIMER, this.stateBucket.currentGameState.accessCode);
socket.emit(SOCKET_EVENTS.IN_GAME_MESSAGE, EVENT_IDS.RESUME_TIMER, this.stateBucket.currentGameState.accessCode);
}
};
this.pauseListener = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.PAUSE_TIMER, this.stateBucket.currentGameState.accessCode);
socket.emit(SOCKET_EVENTS.IN_GAME_MESSAGE, EVENT_IDS.PAUSE_TIMER, this.stateBucket.currentGameState.accessCode);
}
};
}
@@ -19,8 +20,8 @@ export class GameTimerManager {
resumeGameTimer (totalTime, tickRate, soundManager, timerWorker) {
if (window.Worker) {
if (
this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR
|| this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
this.stateBucket.currentGameState.client.userType === USER_TYPES.MODERATOR
|| this.stateBucket.currentGameState.client.userType === USER_TYPES.TEMPORARY_MODERATOR
) {
this.swapToPauseButton();
}
@@ -33,8 +34,8 @@ export class GameTimerManager {
timer.classList.add('low');
}
timer.innerText = totalTime < 60000
? returnHumanReadableTime(totalTime, true)
: returnHumanReadableTime(totalTime);
? SharedStateUtil.returnHumanReadableTime(totalTime, true)
: SharedStateUtil.returnHumanReadableTime(totalTime);
timerWorker.onmessage = function (e) {
if (e.data.hasOwnProperty('timeRemainingInMilliseconds') && e.data.timeRemainingInMilliseconds >= 0) {
if (e.data.timeRemainingInMilliseconds === 0) {
@@ -54,47 +55,31 @@ export class GameTimerManager {
pauseGameTimer (timerWorker, timeRemaining) {
if (window.Worker) {
if (
this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR
|| this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
this.stateBucket.currentGameState.client.userType === USER_TYPES.MODERATOR
|| this.stateBucket.currentGameState.client.userType === USER_TYPES.TEMPORARY_MODERATOR
) {
this.swapToPlayButton();
}
timerWorker.postMessage('stop');
const timer = document.getElementById('game-timer');
if (timeRemaining < 60000) {
timer.innerText = returnHumanReadableTime(timeRemaining, true);
timer.classList.add('paused-low');
timer.classList.add('low');
} else {
timer.innerText = returnHumanReadableTime(timeRemaining);
timer.classList.add('paused');
}
populateTimerElement(timeRemaining);
}
}
displayPausedTime (time) {
if (
this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR
|| this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
this.stateBucket.currentGameState.client.userType === USER_TYPES.MODERATOR
|| this.stateBucket.currentGameState.client.userType === USER_TYPES.TEMPORARY_MODERATOR
) {
this.swapToPlayButton();
}
const timer = document.getElementById('game-timer');
if (time < 60000) {
timer.innerText = returnHumanReadableTime(time, true);
timer.classList.add('paused-low');
timer.classList.add('low');
} else {
timer.innerText = returnHumanReadableTime(time);
timer.classList.add('paused');
}
populateTimerElement(time);
}
displayExpiredTime () {
if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
|| this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) {
if (this.stateBucket.currentGameState.client.userType === USER_TYPES.TEMPORARY_MODERATOR
|| this.stateBucket.currentGameState.client.userType === USER_TYPES.MODERATOR) {
const currentBtn = document.querySelector('#timer-container-moderator #play-pause img');
if (currentBtn) {
currentBtn.removeEventListener('click', this.pauseListener);
@@ -113,31 +98,31 @@ export class GameTimerManager {
}
const timer = document.getElementById('game-timer');
timer.innerText = returnHumanReadableTime(0, true);
timer.innerText = SharedStateUtil.returnHumanReadableTime(0, true);
}
attachTimerSocketListeners (socket, timerWorker) {
globals.TIMER_EVENTS().forEach(e => socket.removeAllListeners(e));
TIMER_EVENTS().forEach(e => socket.removeAllListeners(e));
socket.on(globals.COMMANDS.PAUSE_TIMER, (timeRemaining) => {
socket.on(EVENT_IDS.PAUSE_TIMER, (timeRemaining) => {
this.pauseGameTimer(timerWorker, timeRemaining);
});
socket.on(globals.COMMANDS.RESUME_TIMER, (timeRemaining) => {
this.resumeGameTimer(timeRemaining, globals.CLOCK_TICK_INTERVAL_MILLIS, null, timerWorker);
socket.on(EVENT_IDS.RESUME_TIMER, (timeRemaining) => {
this.resumeGameTimer(timeRemaining, PRIMITIVES.CLOCK_TICK_INTERVAL_MILLIS, null, timerWorker);
});
socket.once(globals.COMMANDS.GET_TIME_REMAINING, (timeRemaining, paused) => {
socket.once(EVENT_IDS.GET_TIME_REMAINING, (timeRemaining, paused) => {
if (paused) {
this.displayPausedTime(timeRemaining);
} else if (timeRemaining === 0) {
this.displayExpiredTime();
} else {
this.resumeGameTimer(timeRemaining, globals.CLOCK_TICK_INTERVAL_MILLIS, null, timerWorker);
this.resumeGameTimer(timeRemaining, PRIMITIVES.CLOCK_TICK_INTERVAL_MILLIS, null, timerWorker);
}
});
socket.on(globals.COMMANDS.END_TIMER, () => {
socket.on(EVENT_IDS.END_TIMER, () => {
Confirmation('The timer has expired!');
});
}
@@ -178,17 +163,14 @@ export class GameTimerManager {
}
}
function returnHumanReadableTime (milliseconds, tenthsOfSeconds = false) {
const tenths = Math.floor((milliseconds / 100) % 10);
let seconds = Math.floor((milliseconds / 1000) % 60);
let minutes = Math.floor((milliseconds / (1000 * 60)) % 60);
let hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);
hours = hours < 10 ? '0' + hours : hours;
minutes = minutes < 10 ? '0' + minutes : minutes;
seconds = seconds < 10 ? '0' + seconds : seconds;
return tenthsOfSeconds
? hours + ':' + minutes + ':' + seconds + '.' + tenths
: hours + ':' + minutes + ':' + seconds;
function populateTimerElement (timeRemaining) {
const timer = document.getElementById('game-timer');
if (timeRemaining < 60000) {
timer.innerText = SharedStateUtil.returnHumanReadableTime(timeRemaining, true);
timer.classList.add('paused-low');
timer.classList.add('low');
} else {
timer.innerText = SharedStateUtil.returnHumanReadableTime(timeRemaining);
timer.classList.add('paused');
}
}

View File

@@ -80,6 +80,20 @@ class Timer {
}
}
}
function returnHumanReadableTime (milliseconds, tenthsOfSeconds = false) {
const tenths = Math.floor((milliseconds / 100) % 10);
let seconds = Math.floor((milliseconds / 1000) % 60);
let minutes = Math.floor((milliseconds / (1000 * 60)) % 60);
let hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);
hours = hours < 10 ? '0' + hours : hours;
minutes = minutes < 10 ? '0' + minutes : minutes;
seconds = seconds < 10 ? '0' + seconds : seconds;
return tenthsOfSeconds
? hours + ':' + minutes + ':' + seconds + '.' + tenths
: hours + ':' + minutes + ':' + seconds;
}
class Singleton {
constructor (totalTime, tickInterval) {
@@ -105,18 +119,3 @@ class Singleton {
}
}
}
function returnHumanReadableTime (milliseconds, tenthsOfSeconds = false) {
const tenths = Math.floor((milliseconds / 100) % 10);
let seconds = Math.floor((milliseconds / 1000) % 60);
let minutes = Math.floor((milliseconds / (1000 * 60)) % 60);
let hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);
hours = hours < 10 ? '0' + hours : hours;
minutes = minutes < 10 ? '0' + minutes : minutes;
seconds = seconds < 10 ? '0' + seconds : seconds;
return tenthsOfSeconds
? hours + ':' + minutes + ':' + seconds + '.' + tenths
: hours + ':' + minutes + ':' + seconds;
}

View File

@@ -1,4 +1,4 @@
import { globals } from '../../config/globals.js';
import { ENVIRONMENTS, PRIMITIVES } from '../../config/globals.js';
/*
we will use sessionStorage during local development to aid in testing, vs. localStorage for production.
@@ -7,25 +7,25 @@ import { globals } from '../../config/globals.js';
export const UserUtility = {
setAnonymousUserId (id, environment) {
if (environment === globals.ENVIRONMENT.LOCAL) {
sessionStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, id);
if (environment === ENVIRONMENTS.LOCAL) {
sessionStorage.setItem(PRIMITIVES.PLAYER_ID_COOKIE_KEY, id);
} else {
localStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, id);
localStorage.setItem(PRIMITIVES.PLAYER_ID_COOKIE_KEY, id);
}
},
validateAnonUserSignature (environment) {
let userSig;
if (environment === globals.ENVIRONMENT.LOCAL) {
userSig = sessionStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
if (environment === ENVIRONMENTS.LOCAL) {
userSig = sessionStorage.getItem(PRIMITIVES.PLAYER_ID_COOKIE_KEY);
} else {
userSig = localStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
userSig = localStorage.getItem(PRIMITIVES.PLAYER_ID_COOKIE_KEY);
}
return (
userSig
&& typeof userSig === 'string'
&& /^[a-zA-Z0-9]+$/.test(userSig)
&& userSig.length === globals.USER_SIGNATURE_LENGTH
&& userSig.length === PRIMITIVES.USER_SIGNATURE_LENGTH
)
? userSig
: false;

View File

@@ -1,39 +0,0 @@
export const XHRUtility =
{
standardHeaders: [['Content-Type', 'application/json'], ['Accept', 'application/json'], ['X-Requested-With', 'XMLHttpRequest']],
// Easily make XHR calls with a promise wrapper. Defaults to GET and MIME type application/JSON
xhr (url, method = 'GET', headers, body = null) {
if (headers === undefined || headers === null) {
headers = this.standardHeaders;
}
if (typeof url !== 'string' || url.trim().length < 1) {
return Promise.reject('Cannot request with empty URL: ' + url);
}
const req = new XMLHttpRequest();
req.open(method, url.trim());
for (const hdr of headers) {
if (hdr.length !== 2) continue;
req.setRequestHeader(hdr[0], hdr[1]);
}
return new Promise((resolve, reject) => {
req.onload = function () {
const response = {
status: this.status,
statusText: this.statusText,
content: this.responseText
};
if (this.status >= 200 && this.status < 400) {
resolve(response);
} else {
reject(response);
}
};
body ? req.send(body) : req.send();
});
}
};

View File

@@ -1,8 +1,10 @@
import 'core-js/stable';
import 'regenerator-runtime/runtime';
import { gameHandler } from '../modules/page_handlers/gameHandler';
import { gameHandler } from '../modules/page_handlers/gameHandler.js';
import { io } from 'socket.io-client';
import { XHRUtility } from '../modules/utility/XHRUtility.js';
import gameTemplate from '../view_templates/GameTemplate.js';
import { toast } from '../modules/front_end_components/Toast.js';
await gameHandler(io('/in-game'), XHRUtility, window, gameTemplate);
gameHandler(io('/in-game'), window, gameTemplate).then(() => {
toast('Connecting...', 'warning', true, false);
}).catch((e) => {
toast(e, 'error', true);
});

View File

@@ -1,4 +1,3 @@
import { XHRUtility } from '../modules/utility/XHRUtility.js';
import { toast } from '../modules/front_end_components/Toast.js';
import { injectNavbar } from '../modules/front_end_components/Navbar.js';
import { Confirmation } from '../modules/front_end_components/Confirmation.js';
@@ -25,32 +24,33 @@ function attemptToJoinGame (event) {
form.removeEventListener('submit', attemptToJoinGame);
form.classList.add('submitted');
document.getElementById('join-button')?.setAttribute('value', 'Joining...');
XHRUtility.xhr(
fetch(
'/api/games/' + userCode.toUpperCase().trim() + '/availability',
'GET',
null,
null
)
.then((res) => {
if (res.status === 200) {
const json = JSON.parse(res.content);
{
method: 'GET',
mode: 'cors'
}
).then((res) => {
if (res.status === 200) {
res.json().then(json => {
window.location = window.location.protocol + '//' + window.location.host +
'/join/' + encodeURIComponent(json.accessCode) +
'?playerCount=' + encodeURIComponent(json.playerCount) +
'&timer=' + encodeURIComponent(getTimeString(json.timerParams));
}
}).catch((res) => {
form.addEventListener('submit', attemptToJoinGame);
form.classList.remove('submitted');
document.getElementById('join-button')?.setAttribute('value', 'Join');
if (res.status === 404) {
toast('Game not found', 'error', true);
} else if (res.status === 400) {
toast(res.content, 'error', true);
} else {
toast('An unknown error occurred. Please try again later.', 'error', true);
}
});
});
}
}).catch((res) => {
form.addEventListener('submit', attemptToJoinGame);
form.classList.remove('submitted');
document.getElementById('join-button')?.setAttribute('value', 'Join');
if (res.status === 404) {
toast('Game not found', 'error', true);
} else if (res.status === 400) {
toast(res.content, 'error', true);
} else {
toast('An unknown error occurred. Please try again later.', 'error', true);
}
});
} else {
toast('Invalid code. Codes are 4 numbers or letters.', 'error', true, true);
}

View File

@@ -1,14 +1,13 @@
import { injectNavbar } from '../modules/front_end_components/Navbar.js';
import { toast } from '../modules/front_end_components/Toast.js';
import { XHRUtility } from '../modules/utility/XHRUtility.js';
import { UserUtility } from '../modules/utility/UserUtility.js';
import { globals } from '../config/globals.js';
import { ENVIRONMENTS, PRIMITIVES } from '../config/globals.js';
const join = () => {
injectNavbar();
const splitUrl = window.location.pathname.split('/join/');
const accessCode = splitUrl[1];
if (/^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === globals.ACCESS_CODE_LENGTH) {
if (/^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === PRIMITIVES.ACCESS_CODE_LENGTH) {
document.getElementById('game-code').innerText = accessCode;
document.getElementById('game-time').innerText =
decodeURIComponent((new URL(document.location)).searchParams.get('timer'));
@@ -29,10 +28,11 @@ const joinHandler = (e) => {
if (validateName(name)) {
sendJoinRequest(e, name, accessCode)
.then((res) => {
const json = JSON.parse(res.content);
UserUtility.setAnonymousUserId(json.cookie, json.environment);
resetJoinButtonState(e, res, joinHandler);
window.location = '/game/' + accessCode;
res.json().then(json => {
UserUtility.setAnonymousUserId(json.cookie, json.environment);
resetJoinButtonState(e, res, joinHandler);
window.location = '/game/' + accessCode;
});
}).catch((res) => {
handleJoinError(e, res, joinHandler);
});
@@ -46,17 +46,22 @@ function sendJoinRequest (e, name, accessCode) {
document.getElementById('join-submit').classList.add('submitted');
document.getElementById('join-submit').setAttribute('value', '...');
return XHRUtility.xhr(
return fetch(
'/api/games/' + accessCode + '/players',
'PATCH',
null,
JSON.stringify({
playerName: name,
accessCode: accessCode,
sessionCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.LOCAL),
localCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.PRODUCTION),
joinAsSpectator: document.getElementById('join-as-spectator').checked
})
{
method: 'PATCH',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
playerName: name,
accessCode: accessCode,
sessionCookie: UserUtility.validateAnonUserSignature(ENVIRONMENTS.LOCAL),
localCookie: UserUtility.validateAnonUserSignature(ENVIRONMENTS.PRODUCTION),
joinAsSpectator: document.getElementById('join-as-spectator').checked
})
}
);
}

View File

@@ -18,7 +18,7 @@ const template =
<div></div>
<div></div>
</div>
<p>Connecting to game...</p>
<p>Waiting for connection to Room...</p>
</div>
<div id="mobile-menu-background-overlay"></div>
<div id="navbar"></div>