mirror of
https://github.com/AlecM33/Werewolf.git
synced 2025-12-26 15:57:50 +01:00
various refactors
This commit is contained in:
@@ -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
|
||||
];
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -3,13 +3,13 @@ const router = express.Router();
|
||||
const debugMode = Array.from(process.argv.map((arg) => arg.trim().toLowerCase())).includes('debug');
|
||||
const logger = require('../modules/Logger')(debugMode);
|
||||
const eventManager = (require('../modules/singletons/EventManager.js')).instance;
|
||||
const globals = require('../config/globals.js');
|
||||
const cors = require('cors');
|
||||
const { CORS_OPTIONS, CONTENT_TYPE_VALIDATOR } = require('../config/globals');
|
||||
|
||||
router.use(cors(globals.CORS));
|
||||
router.use(cors(CORS_OPTIONS));
|
||||
|
||||
router.post('/sockets/broadcast', (req, res, next) => {
|
||||
globals.CONTENT_TYPE_VALIDATOR(req, res, next);
|
||||
CONTENT_TYPE_VALIDATOR(req, res, next);
|
||||
});
|
||||
|
||||
// TODO: implement client-side display of this message.
|
||||
|
||||
@@ -4,8 +4,8 @@ const debugMode = Array.from(process.argv.map((arg) => arg.trim().toLowerCase())
|
||||
const logger = require('../modules/Logger')(debugMode);
|
||||
const GameManager = require('../modules/singletons/GameManager.js');
|
||||
const rateLimit = require('express-rate-limit').default;
|
||||
const globals = require('../config/globals.js');
|
||||
const cors = require('cors');
|
||||
const { CORS_OPTIONS, CONTENT_TYPE_VALIDATOR, PRIMITIVES, ERROR_MESSAGES, ENVIRONMENTS } = require('../config/globals');
|
||||
|
||||
const gameManager = GameManager.instance;
|
||||
|
||||
@@ -20,19 +20,19 @@ const gameEndpointLimiter = rateLimit({
|
||||
legacyHeaders: false
|
||||
});
|
||||
|
||||
router.use(cors(globals.CORS));
|
||||
router.options('/:code/players', cors(globals.CORS));
|
||||
router.options('/create', cors(globals.CORS));
|
||||
router.options('/restart', cors(globals.CORS));
|
||||
router.use(cors(CORS_OPTIONS));
|
||||
router.options('/:code/players', cors(CORS_OPTIONS));
|
||||
router.options('/create', cors(CORS_OPTIONS));
|
||||
router.options('/restart', cors(CORS_OPTIONS));
|
||||
|
||||
router.post('/create', (req, res, next) => {
|
||||
globals.CONTENT_TYPE_VALIDATOR(req, res, next);
|
||||
CONTENT_TYPE_VALIDATOR(req, res, next);
|
||||
});
|
||||
router.patch('/players', (req, res, next) => {
|
||||
globals.CONTENT_TYPE_VALIDATOR(req, res, next);
|
||||
CONTENT_TYPE_VALIDATOR(req, res, next);
|
||||
});
|
||||
router.patch('/restart', (req, res, next) => {
|
||||
globals.CONTENT_TYPE_VALIDATOR(req, res, next);
|
||||
CONTENT_TYPE_VALIDATOR(req, res, next);
|
||||
});
|
||||
|
||||
router.post('/create', gameEndpointLimiter, function (req, res) {
|
||||
@@ -43,8 +43,8 @@ router.post('/create', gameEndpointLimiter, function (req, res) {
|
||||
res.status(201).send(result); // game was created successfully, and access code was returned
|
||||
}
|
||||
}).catch((e) => {
|
||||
if (e === globals.ERROR_MESSAGE.BAD_CREATE_REQUEST) {
|
||||
res.status(400).send(globals.ERROR_MESSAGE.BAD_CREATE_REQUEST);
|
||||
if (e === ERROR_MESSAGES.BAD_CREATE_REQUEST) {
|
||||
res.status(400).send(ERROR_MESSAGES.BAD_CREATE_REQUEST);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -78,7 +78,7 @@ router.patch('/:code/players', async function (req, res) {
|
||||
} else {
|
||||
const game = await gameManager.getActiveGame(req.body.accessCode);
|
||||
if (game) {
|
||||
const inUseCookie = gameManager.environment === globals.ENVIRONMENT.PRODUCTION ? req.body.localCookie : req.body.sessionCookie;
|
||||
const inUseCookie = gameManager.environment === ENVIRONMENTS.PRODUCTION ? req.body.localCookie : req.body.sessionCookie;
|
||||
gameManager.joinGame(game, req.body.playerName, inUseCookie, req.body.joinAsSpectator).then((data) => {
|
||||
res.status(200).send({ cookie: data, environment: gameManager.environment });
|
||||
}).catch((data) => {
|
||||
@@ -103,7 +103,7 @@ router.patch('/:code/restart', async function (req, res) {
|
||||
} else {
|
||||
const game = await gameManager.getActiveGame(req.body.accessCode);
|
||||
if (game) {
|
||||
gameManager.restartGame(game, gameManager.namespace, req.query.status).then((data) => {
|
||||
gameManager.restartGame(game, gameManager.namespace).then((data) => {
|
||||
res.status(200).send();
|
||||
}).catch((code) => {
|
||||
res.status(code).send();
|
||||
@@ -123,11 +123,11 @@ function validateName (name) {
|
||||
}
|
||||
|
||||
function validateCookie (cookie) {
|
||||
return cookie === null || cookie === false || (typeof cookie === 'string' && cookie.length === globals.INSTANCE_ID_LENGTH);
|
||||
return cookie === null || cookie === false || (typeof cookie === 'string' && cookie.length === PRIMITIVES.INSTANCE_ID_LENGTH);
|
||||
}
|
||||
|
||||
function validateAccessCode (accessCode) {
|
||||
return /^[a-zA-Z0-9]+$/.test(accessCode) && accessCode?.length === globals.ACCESS_CODE_LENGTH;
|
||||
return /^[a-zA-Z0-9]+$/.test(accessCode) && accessCode?.length === PRIMITIVES.ACCESS_CODE_LENGTH;
|
||||
}
|
||||
|
||||
function validateSpectatorFlag (spectatorFlag) {
|
||||
@@ -1,4 +1,4 @@
|
||||
const globals = {
|
||||
const PRIMITIVES = {
|
||||
ACCESS_CODE_CHAR_POOL: 'BCDFGHJLMNPQRSTVWXYZ23456789',
|
||||
INSTANCE_ID_CHAR_POOL: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
|
||||
ACCESS_CODE_LENGTH: 4,
|
||||
@@ -6,143 +6,169 @@ const globals = {
|
||||
CLOCK_TICK_INTERVAL_MILLIS: 100,
|
||||
MAX_CUSTOM_ROLE_NAME_LENGTH: 50,
|
||||
MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH: 1000,
|
||||
ALIGNMENT: {
|
||||
GOOD: 'good',
|
||||
EVIL: 'evil'
|
||||
},
|
||||
REDIS_CHANNELS: {
|
||||
ACTIVE_GAME_STREAM: 'active_game_stream'
|
||||
},
|
||||
CORS: process.env.NODE_ENV?.trim() === 'development'
|
||||
? {
|
||||
origin: '*',
|
||||
optionsSuccessStatus: 200
|
||||
}
|
||||
: {
|
||||
origin: 'https://play-werewolf.app',
|
||||
optionsSuccessStatus: 200
|
||||
},
|
||||
CONTENT_TYPE_VALIDATOR: (req, res, next) => {
|
||||
req.accepts();
|
||||
if (req.is('application/json')) {
|
||||
next();
|
||||
} else {
|
||||
res.status(400).send('Request has invalid content type.');
|
||||
}
|
||||
},
|
||||
STALE_GAME_SECONDS: 86400,
|
||||
SOCKET_EVENTS: {
|
||||
IN_GAME_MESSAGE: 'inGameMessage'
|
||||
},
|
||||
EVENT_IDS: {
|
||||
NEW_GAME: 'newGame',
|
||||
FETCH_GAME_STATE: 'fetchGameState',
|
||||
START_GAME: 'startGame',
|
||||
PAUSE_TIMER: 'pauseTimer',
|
||||
RESUME_TIMER: 'resumeTimer',
|
||||
END_TIMER: 'endTimer',
|
||||
GET_TIME_REMAINING: 'getTimeRemaining',
|
||||
SOURCE_TIMER_EVENT: 'sourceTimerEvent',
|
||||
KILL_PLAYER: 'killPlayer',
|
||||
REVEAL_PLAYER: 'revealPlayer',
|
||||
TRANSFER_MODERATOR: 'transferModerator',
|
||||
CHANGE_NAME: 'changeName',
|
||||
END_GAME: 'endGame',
|
||||
RESTART_GAME: 'restartGame',
|
||||
PLAYER_JOINED: 'playerJoined',
|
||||
UPDATE_SPECTATORS: 'updateSpectators',
|
||||
ADD_SPECTATOR: 'addSpectator',
|
||||
SYNC_GAME_STATE: 'syncGameState',
|
||||
UPDATE_SOCKET: 'updateSocket',
|
||||
ASSIGN_DEDICATED_MOD: 'assignDedicatedMod',
|
||||
TIMER_EVENT: 'timerEvent',
|
||||
KICK_PERSON: 'kickPerson',
|
||||
UPDATE_GAME_ROLES: 'updateGameRoles',
|
||||
LEAVE_ROOM: 'leaveRoom'
|
||||
},
|
||||
SYNCABLE_EVENTS: function () {
|
||||
return [
|
||||
this.EVENT_IDS.NEW_GAME,
|
||||
this.EVENT_IDS.START_GAME,
|
||||
this.EVENT_IDS.KILL_PLAYER,
|
||||
this.EVENT_IDS.REVEAL_PLAYER,
|
||||
this.EVENT_IDS.TRANSFER_MODERATOR,
|
||||
this.EVENT_IDS.END_GAME,
|
||||
this.EVENT_IDS.RESTART_GAME,
|
||||
this.EVENT_IDS.PLAYER_JOINED,
|
||||
this.EVENT_IDS.ADD_SPECTATOR,
|
||||
this.EVENT_IDS.SYNC_GAME_STATE,
|
||||
this.EVENT_IDS.UPDATE_SOCKET,
|
||||
this.EVENT_IDS.FETCH_GAME_STATE,
|
||||
this.EVENT_IDS.ASSIGN_DEDICATED_MOD,
|
||||
this.EVENT_IDS.RESUME_TIMER,
|
||||
this.EVENT_IDS.PAUSE_TIMER,
|
||||
this.EVENT_IDS.END_TIMER,
|
||||
this.EVENT_IDS.KICK_PERSON,
|
||||
this.EVENT_IDS.UPDATE_GAME_ROLES,
|
||||
this.EVENT_IDS.LEAVE_ROOM
|
||||
];
|
||||
},
|
||||
TIMER_EVENTS: function () {
|
||||
return [
|
||||
this.EVENT_IDS.RESUME_TIMER,
|
||||
this.EVENT_IDS.PAUSE_TIMER,
|
||||
this.EVENT_IDS.END_TIMER,
|
||||
this.EVENT_IDS.GET_TIME_REMAINING
|
||||
];
|
||||
},
|
||||
MESSAGES: {
|
||||
ENTER_NAME: 'Client must enter name.'
|
||||
},
|
||||
STATUS: {
|
||||
LOBBY: 'lobby',
|
||||
IN_PROGRESS: 'in progress',
|
||||
ENDED: 'ended'
|
||||
},
|
||||
USER_SIGNATURE_LENGTH: 25,
|
||||
INSTANCE_ID_LENGTH: 75,
|
||||
USER_TYPES: {
|
||||
MODERATOR: 'moderator',
|
||||
PLAYER: 'player',
|
||||
TEMPORARY_MODERATOR: 'temp mod',
|
||||
KILLED_PLAYER: 'killed',
|
||||
KILLED_BOT: 'killed bot',
|
||||
SPECTATOR: 'spectator',
|
||||
BOT: 'bot'
|
||||
},
|
||||
ERROR_MESSAGE: {
|
||||
GAME_IS_FULL: 'This game is full',
|
||||
BAD_CREATE_REQUEST: 'Game has invalid options.',
|
||||
NO_UNIQUE_ACCESS_CODE: 'Could not generate a unique access code.'
|
||||
},
|
||||
EVENTS: {
|
||||
PLAYER_JOINED: 'playerJoined',
|
||||
PLAYER_LEFT: 'playerLeft',
|
||||
SYNC_GAME_STATE: 'syncGameState',
|
||||
UPDATE_SPECTATORS: 'newSpectator',
|
||||
BROADCAST: 'broadcast'
|
||||
},
|
||||
ENVIRONMENT: {
|
||||
LOCAL: 'local',
|
||||
PRODUCTION: 'production'
|
||||
},
|
||||
LOG_LEVEL: {
|
||||
INFO: 'info',
|
||||
DEBUG: 'debug',
|
||||
ERROR: 'error',
|
||||
WARN: 'warn',
|
||||
TRACE: 'trace'
|
||||
},
|
||||
GAME_PROCESS_COMMANDS: {
|
||||
END_TIMER: 'endTimer',
|
||||
START_GAME: 'startGame',
|
||||
START_TIMER: 'startTimer',
|
||||
PAUSE_TIMER: 'pauseTimer',
|
||||
RESUME_TIMER: 'resumeTimer',
|
||||
GET_TIME_REMAINING: 'getTimeRemaining'
|
||||
},
|
||||
MOCK_AUTH: 'mock_auth',
|
||||
MAX_SPECTATORS: 25
|
||||
MAX_SPECTATORS: 25,
|
||||
MOCK_AUTH: 'mock_auth'
|
||||
};
|
||||
|
||||
module.exports = globals;
|
||||
const LOG_LEVEL = {
|
||||
INFO: 'info',
|
||||
DEBUG: 'debug',
|
||||
ERROR: 'error',
|
||||
WARN: 'warn',
|
||||
TRACE: 'trace'
|
||||
};
|
||||
|
||||
const ALIGNMENT = {
|
||||
GOOD: 'good',
|
||||
EVIL: 'evil'
|
||||
};
|
||||
|
||||
const REDIS_CHANNELS = {
|
||||
ACTIVE_GAME_STREAM: 'active_game_stream'
|
||||
};
|
||||
|
||||
const CORS_OPTIONS = process.env.NODE_ENV?.trim() === 'development'
|
||||
? {
|
||||
origin: '*',
|
||||
optionsSuccessStatus: 200
|
||||
}
|
||||
: {
|
||||
origin: 'https://play-werewolf.app',
|
||||
optionsSuccessStatus: 200
|
||||
};
|
||||
|
||||
const CONTENT_TYPE_VALIDATOR = (req, res, next) => {
|
||||
req.accepts();
|
||||
if (req.is('application/json')) {
|
||||
next();
|
||||
} else {
|
||||
res.status(400).send('Request has invalid content type.');
|
||||
}
|
||||
};
|
||||
|
||||
const SOCKET_EVENTS = {
|
||||
IN_GAME_MESSAGE: 'inGameMessage'
|
||||
};
|
||||
|
||||
const EVENT_IDS = {
|
||||
NEW_GAME: 'newGame',
|
||||
FETCH_GAME_STATE: 'fetchGameState',
|
||||
START_GAME: 'startGame',
|
||||
PAUSE_TIMER: 'pauseTimer',
|
||||
RESUME_TIMER: 'resumeTimer',
|
||||
END_TIMER: 'endTimer',
|
||||
GET_TIME_REMAINING: 'getTimeRemaining',
|
||||
SOURCE_TIMER_EVENT: 'sourceTimerEvent',
|
||||
KILL_PLAYER: 'killPlayer',
|
||||
REVEAL_PLAYER: 'revealPlayer',
|
||||
TRANSFER_MODERATOR: 'transferModerator',
|
||||
CHANGE_NAME: 'changeName',
|
||||
END_GAME: 'endGame',
|
||||
RESTART_GAME: 'restartGame',
|
||||
PLAYER_JOINED: 'playerJoined',
|
||||
UPDATE_SPECTATORS: 'updateSpectators',
|
||||
ADD_SPECTATOR: 'addSpectator',
|
||||
SYNC_GAME_STATE: 'syncGameState',
|
||||
UPDATE_SOCKET: 'updateSocket',
|
||||
ASSIGN_DEDICATED_MOD: 'assignDedicatedMod',
|
||||
TIMER_EVENT: 'timerEvent',
|
||||
KICK_PERSON: 'kickPerson',
|
||||
UPDATE_GAME_ROLES: 'updateGameRoles',
|
||||
LEAVE_ROOM: 'leaveRoom',
|
||||
BROADCAST: 'broadcast'
|
||||
};
|
||||
|
||||
const SYNCABLE_EVENTS = function () {
|
||||
return [
|
||||
EVENT_IDS.NEW_GAME,
|
||||
EVENT_IDS.START_GAME,
|
||||
EVENT_IDS.KILL_PLAYER,
|
||||
EVENT_IDS.REVEAL_PLAYER,
|
||||
EVENT_IDS.TRANSFER_MODERATOR,
|
||||
EVENT_IDS.END_GAME,
|
||||
EVENT_IDS.RESTART_GAME,
|
||||
EVENT_IDS.PLAYER_JOINED,
|
||||
EVENT_IDS.ADD_SPECTATOR,
|
||||
EVENT_IDS.SYNC_GAME_STATE,
|
||||
EVENT_IDS.UPDATE_SOCKET,
|
||||
EVENT_IDS.FETCH_GAME_STATE,
|
||||
EVENT_IDS.ASSIGN_DEDICATED_MOD,
|
||||
EVENT_IDS.RESUME_TIMER,
|
||||
EVENT_IDS.PAUSE_TIMER,
|
||||
EVENT_IDS.END_TIMER,
|
||||
EVENT_IDS.KICK_PERSON,
|
||||
EVENT_IDS.UPDATE_GAME_ROLES,
|
||||
EVENT_IDS.LEAVE_ROOM
|
||||
];
|
||||
};
|
||||
|
||||
const TIMER_EVENTS = function () {
|
||||
return [
|
||||
EVENT_IDS.RESUME_TIMER,
|
||||
EVENT_IDS.PAUSE_TIMER,
|
||||
EVENT_IDS.END_TIMER,
|
||||
EVENT_IDS.GET_TIME_REMAINING
|
||||
];
|
||||
};
|
||||
|
||||
const MESSAGES = {
|
||||
ENTER_NAME: 'Client must enter name.'
|
||||
};
|
||||
|
||||
const STATUS = {
|
||||
LOBBY: 'lobby',
|
||||
IN_PROGRESS: 'in progress',
|
||||
ENDED: 'ended'
|
||||
};
|
||||
|
||||
const USER_TYPES = {
|
||||
MODERATOR: 'moderator',
|
||||
PLAYER: 'player',
|
||||
TEMPORARY_MODERATOR: 'temp mod',
|
||||
KILLED_PLAYER: 'killed',
|
||||
KILLED_BOT: 'killed bot',
|
||||
SPECTATOR: 'spectator',
|
||||
BOT: 'bot'
|
||||
};
|
||||
|
||||
const ERROR_MESSAGES = {
|
||||
GAME_IS_FULL: 'This game is full',
|
||||
BAD_CREATE_REQUEST: 'Game has invalid options.',
|
||||
NO_UNIQUE_ACCESS_CODE: 'Could not generate a unique access code.'
|
||||
};
|
||||
|
||||
const ENVIRONMENTS = {
|
||||
LOCAL: 'local',
|
||||
PRODUCTION: 'production'
|
||||
};
|
||||
|
||||
const GAME_PROCESS_COMMANDS = {
|
||||
END_TIMER: 'endTimer',
|
||||
START_GAME: 'startGame',
|
||||
START_TIMER: 'startTimer',
|
||||
PAUSE_TIMER: 'pauseTimer',
|
||||
RESUME_TIMER: 'resumeTimer',
|
||||
GET_TIME_REMAINING: 'getTimeRemaining'
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
PRIMITIVES,
|
||||
LOG_LEVEL,
|
||||
ALIGNMENT,
|
||||
REDIS_CHANNELS,
|
||||
CORS_OPTIONS,
|
||||
CONTENT_TYPE_VALIDATOR,
|
||||
SOCKET_EVENTS,
|
||||
EVENT_IDS,
|
||||
SYNCABLE_EVENTS,
|
||||
TIMER_EVENTS,
|
||||
MESSAGES,
|
||||
STATUS,
|
||||
USER_TYPES,
|
||||
ERROR_MESSAGES,
|
||||
ENVIRONMENTS,
|
||||
GAME_PROCESS_COMMANDS
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const globals = require('../config/globals');
|
||||
const { ERROR_MESSAGES, PRIMITIVES, ALIGNMENT } = require('../config/globals');
|
||||
|
||||
class GameCreationRequest {
|
||||
constructor (
|
||||
@@ -24,7 +24,7 @@ class GameCreationRequest {
|
||||
|| expectedKeys.some((key) => !Object.keys(gameParams).includes(key))
|
||||
|| !valid(gameParams)
|
||||
) {
|
||||
return Promise.reject(globals.ERROR_MESSAGE.BAD_CREATE_REQUEST);
|
||||
return Promise.reject(ERROR_MESSAGES.BAD_CREATE_REQUEST);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -37,12 +37,12 @@ class GameCreationRequest {
|
||||
&& typeof entry === 'object'
|
||||
&& typeof entry.role === 'string'
|
||||
&& entry.role.length > 0
|
||||
&& entry.role.length <= globals.MAX_CUSTOM_ROLE_NAME_LENGTH
|
||||
&& entry.role.length <= PRIMITIVES.MAX_CUSTOM_ROLE_NAME_LENGTH
|
||||
&& typeof entry.team === 'string'
|
||||
&& (entry.team === globals.ALIGNMENT.GOOD || entry.team === globals.ALIGNMENT.EVIL)
|
||||
&& (entry.team === ALIGNMENT.GOOD || entry.team === ALIGNMENT.EVIL)
|
||||
&& typeof entry.description === 'string'
|
||||
&& entry.description.length > 0
|
||||
&& entry.description.length <= globals.MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH
|
||||
&& entry.description.length <= PRIMITIVES.MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH
|
||||
&& (!entry.custom || typeof entry.custom === 'boolean')
|
||||
&& typeof entry.quantity === 'number'
|
||||
&& entry.quantity >= 0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// noinspection DuplicatedCode
|
||||
const globals = require('../config/globals');
|
||||
const { USER_TYPES } = require('../config/globals');
|
||||
|
||||
class Person {
|
||||
constructor (id, cookie, name, userType, gameRole = null, gameRoleDescription = null, alignment = null, assigned = false) {
|
||||
@@ -12,7 +12,7 @@ class Person {
|
||||
this.gameRoleDescription = gameRoleDescription;
|
||||
this.alignment = alignment;
|
||||
this.assigned = assigned;
|
||||
this.out = userType === globals.USER_TYPES.MODERATOR || userType === globals.USER_TYPES.SPECTATOR;
|
||||
this.out = userType === USER_TYPES.MODERATOR || userType === USER_TYPES.SPECTATOR;
|
||||
this.killed = false;
|
||||
this.revealed = false;
|
||||
this.hasEnteredName = false;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const globals = require('../config/globals');
|
||||
const GameStateCurator = require('./GameStateCurator');
|
||||
const GameCreationRequest = require('../model/GameCreationRequest');
|
||||
const EVENT_IDS = globals.EVENT_IDS;
|
||||
const { EVENT_IDS, STATUS, USER_TYPES, GAME_PROCESS_COMMANDS, REDIS_CHANNELS } = require('../config/globals');
|
||||
|
||||
const Events = [
|
||||
{
|
||||
@@ -12,7 +11,7 @@ const Events = [
|
||||
},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(
|
||||
globals.EVENTS.PLAYER_JOINED,
|
||||
EVENT_IDS.PLAYER_JOINED,
|
||||
GameStateCurator.mapPerson(socketArgs),
|
||||
game.isStartable
|
||||
);
|
||||
@@ -87,7 +86,7 @@ const Events = [
|
||||
},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(
|
||||
globals.EVENT_IDS.ADD_SPECTATOR,
|
||||
EVENT_IDS.ADD_SPECTATOR,
|
||||
GameStateCurator.mapPerson(socketArgs)
|
||||
);
|
||||
}
|
||||
@@ -117,7 +116,7 @@ const Events = [
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
const matchingPerson = vars.gameManager.findPersonByField(game, 'id', socketArgs.personId);
|
||||
if (matchingPerson && vars.gameManager.namespace.sockets.get(matchingPerson.socketId)) {
|
||||
vars.gameManager.namespace.to(matchingPerson.socketId).emit(globals.EVENTS.SYNC_GAME_STATE);
|
||||
vars.gameManager.namespace.to(matchingPerson.socketId).emit(EVENT_IDS.SYNC_GAME_STATE);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -125,7 +124,7 @@ const Events = [
|
||||
id: EVENT_IDS.START_GAME,
|
||||
stateChange: async (game, socketArgs, vars) => {
|
||||
if (game.isStartable) {
|
||||
game.status = globals.STATUS.IN_PROGRESS;
|
||||
game.status = STATUS.IN_PROGRESS;
|
||||
vars.gameManager.deal(game);
|
||||
if (game.hasTimer) {
|
||||
game.timerParams.paused = true;
|
||||
@@ -137,7 +136,7 @@ const Events = [
|
||||
if (vars.ackFn) {
|
||||
vars.ackFn();
|
||||
}
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(globals.EVENT_IDS.START_GAME);
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(EVENT_IDS.START_GAME);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -145,9 +144,9 @@ const Events = [
|
||||
stateChange: async (game, socketArgs, vars) => {
|
||||
const person = game.people.find((person) => person.id === socketArgs.personId);
|
||||
if (person && !person.out) {
|
||||
person.userType = person.userType === globals.USER_TYPES.BOT
|
||||
? globals.USER_TYPES.KILLED_BOT
|
||||
: globals.USER_TYPES.KILLED_PLAYER;
|
||||
person.userType = person.userType === USER_TYPES.BOT
|
||||
? USER_TYPES.KILLED_BOT
|
||||
: USER_TYPES.KILLED_PLAYER;
|
||||
person.out = true;
|
||||
person.killed = true;
|
||||
}
|
||||
@@ -155,7 +154,7 @@ const Events = [
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
const person = game.people.find((person) => person.id === socketArgs.personId);
|
||||
if (person) {
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(globals.EVENT_IDS.KILL_PLAYER, person);
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(EVENT_IDS.KILL_PLAYER, person);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -171,7 +170,7 @@ const Events = [
|
||||
const person = game.people.find((person) => person.id === socketArgs.personId);
|
||||
if (person) {
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(
|
||||
globals.EVENT_IDS.REVEAL_PLAYER,
|
||||
EVENT_IDS.REVEAL_PLAYER,
|
||||
{
|
||||
id: person.id,
|
||||
gameRole: person.gameRole,
|
||||
@@ -184,7 +183,7 @@ const Events = [
|
||||
{
|
||||
id: EVENT_IDS.END_GAME,
|
||||
stateChange: async (game, socketArgs, vars) => {
|
||||
game.status = globals.STATUS.ENDED;
|
||||
game.status = STATUS.ENDED;
|
||||
if (game.hasTimer && vars.timerManager.timerThreads[game.accessCode]) {
|
||||
vars.logger.trace('KILLING TIMER PROCESS FOR ENDED GAME ' + game.accessCode);
|
||||
vars.timerManager.timerThreads[game.accessCode].kill();
|
||||
@@ -195,7 +194,7 @@ const Events = [
|
||||
},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
vars.gameManager.namespace.in(game.accessCode)
|
||||
.emit(globals.EVENT_IDS.END_GAME, GameStateCurator.mapPeopleForModerator(game.people));
|
||||
.emit(EVENT_IDS.END_GAME, GameStateCurator.mapPeopleForModerator(game.people));
|
||||
if (vars.ackFn) {
|
||||
vars.ackFn();
|
||||
}
|
||||
@@ -208,14 +207,14 @@ const Events = [
|
||||
const toTransferTo = vars.gameManager.findPersonByField(game, 'id', socketArgs.personId);
|
||||
if (currentModerator) {
|
||||
if (currentModerator.gameRole) {
|
||||
currentModerator.userType = globals.USER_TYPES.KILLED_PLAYER;
|
||||
currentModerator.userType = USER_TYPES.KILLED_PLAYER;
|
||||
} else {
|
||||
currentModerator.userType = globals.USER_TYPES.SPECTATOR;
|
||||
currentModerator.userType = USER_TYPES.SPECTATOR;
|
||||
}
|
||||
game.previousModeratorId = currentModerator.id;
|
||||
}
|
||||
if (toTransferTo) {
|
||||
toTransferTo.userType = globals.USER_TYPES.MODERATOR;
|
||||
toTransferTo.userType = USER_TYPES.MODERATOR;
|
||||
game.currentModeratorId = toTransferTo.id;
|
||||
}
|
||||
},
|
||||
@@ -223,7 +222,7 @@ const Events = [
|
||||
if (vars.ackFn) {
|
||||
vars.ackFn();
|
||||
}
|
||||
vars.gameManager.namespace.to(game.accessCode).emit(globals.EVENT_IDS.SYNC_GAME_STATE);
|
||||
vars.gameManager.namespace.to(game.accessCode).emit(EVENT_IDS.SYNC_GAME_STATE);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -233,10 +232,10 @@ const Events = [
|
||||
const toTransferTo = vars.gameManager.findPersonByField(game, 'id', socketArgs.personId);
|
||||
if (currentModerator && toTransferTo) {
|
||||
if (currentModerator.id !== toTransferTo.id) {
|
||||
currentModerator.userType = globals.USER_TYPES.PLAYER;
|
||||
currentModerator.userType = USER_TYPES.PLAYER;
|
||||
}
|
||||
|
||||
toTransferTo.userType = globals.USER_TYPES.MODERATOR;
|
||||
toTransferTo.userType = USER_TYPES.MODERATOR;
|
||||
toTransferTo.out = true;
|
||||
toTransferTo.killed = true;
|
||||
game.previousModeratorId = currentModerator.id;
|
||||
@@ -247,14 +246,14 @@ const Events = [
|
||||
const moderator = vars.gameManager.findPersonByField(game, 'id', game.currentModeratorId);
|
||||
const moderatorSocket = vars.gameManager.namespace.sockets.get(moderator?.socketId);
|
||||
if (moderator && moderatorSocket) {
|
||||
vars.gameManager.namespace.to(moderator.socketId).emit(globals.EVENTS.SYNC_GAME_STATE);
|
||||
moderatorSocket.to(game.accessCode).emit(globals.EVENT_IDS.KILL_PLAYER, moderator);
|
||||
vars.gameManager.namespace.to(moderator.socketId).emit(EVENT_IDS.SYNC_GAME_STATE);
|
||||
moderatorSocket.to(game.accessCode).emit(EVENT_IDS.KILL_PLAYER, moderator);
|
||||
} else {
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(globals.EVENT_IDS.KILL_PLAYER, moderator);
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(EVENT_IDS.KILL_PLAYER, moderator);
|
||||
}
|
||||
const previousModerator = vars.gameManager.findPersonByField(game, 'id', game.previousModeratorId);
|
||||
if (previousModerator && previousModerator.id !== moderator.id && vars.gameManager.namespace.sockets.get(previousModerator.socketId)) {
|
||||
vars.gameManager.namespace.to(previousModerator.socketId).emit(globals.EVENTS.SYNC_GAME_STATE);
|
||||
vars.gameManager.namespace.to(previousModerator.socketId).emit(EVENT_IDS.SYNC_GAME_STATE);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -274,7 +273,7 @@ const Events = [
|
||||
if (vars.ackFn) {
|
||||
vars.ackFn();
|
||||
}
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(globals.EVENT_IDS.RESTART_GAME);
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(EVENT_IDS.RESTART_GAME);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -294,7 +293,7 @@ const Events = [
|
||||
const socket = vars.gameManager.namespace.sockets.get(vars.requestingSocketId);
|
||||
if (socket) {
|
||||
vars.gameManager.namespace.to(socket.id).emit(
|
||||
globals.GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
game.timerParams.timeRemaining,
|
||||
game.timerParams.paused
|
||||
);
|
||||
@@ -302,10 +301,10 @@ const Events = [
|
||||
}
|
||||
} else { // we need to consult another container for the timer data
|
||||
await vars.eventManager.publisher?.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
vars.eventManager.createMessageToPublish(
|
||||
game.accessCode,
|
||||
globals.EVENT_IDS.SOURCE_TIMER_EVENT,
|
||||
EVENT_IDS.SOURCE_TIMER_EVENT,
|
||||
vars.instanceId,
|
||||
JSON.stringify({ socketId: vars.requestingSocketId, timerEventSubtype: vars.timerEventSubtype })
|
||||
)
|
||||
@@ -330,7 +329,7 @@ const Events = [
|
||||
});
|
||||
} else {
|
||||
await vars.eventManager.publisher.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
vars.eventManager.createMessageToPublish(
|
||||
game.accessCode,
|
||||
socketArgs.timerEventSubtype,
|
||||
@@ -353,7 +352,7 @@ const Events = [
|
||||
game.timerParams.timeRemaining = 0;
|
||||
},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(globals.GAME_PROCESS_COMMANDS.END_TIMER);
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(GAME_PROCESS_COMMANDS.END_TIMER);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -363,7 +362,7 @@ const Events = [
|
||||
game.timerParams.timeRemaining = socketArgs.timeRemaining;
|
||||
},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(globals.GAME_PROCESS_COMMANDS.PAUSE_TIMER, socketArgs.timeRemaining);
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(GAME_PROCESS_COMMANDS.PAUSE_TIMER, socketArgs.timeRemaining);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -373,7 +372,7 @@ const Events = [
|
||||
game.timerParams.timeRemaining = socketArgs.timeRemaining;
|
||||
},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(globals.GAME_PROCESS_COMMANDS.RESUME_TIMER, socketArgs.timeRemaining);
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(GAME_PROCESS_COMMANDS.RESUME_TIMER, socketArgs.timeRemaining);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -382,7 +381,7 @@ const Events = [
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
const socket = vars.gameManager.namespace.sockets.get(socketArgs.socketId);
|
||||
if (socket) {
|
||||
vars.gameManager.namespace.to(socket.id).emit(globals.GAME_PROCESS_COMMANDS.GET_TIME_REMAINING, socketArgs.timeRemaining, game.timerParams.paused);
|
||||
vars.gameManager.namespace.to(socket.id).emit(GAME_PROCESS_COMMANDS.GET_TIME_REMAINING, socketArgs.timeRemaining, game.timerParams.paused);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const globals = require('../config/globals.js');
|
||||
const { GAME_PROCESS_COMMANDS, PRIMITIVES } = require('../config/globals');
|
||||
const ServerTimer = require('./ServerTimer.js');
|
||||
|
||||
let timer;
|
||||
@@ -7,43 +7,43 @@ let timer;
|
||||
process.on('message', (msg) => {
|
||||
const logger = require('./Logger')(msg.logLevel);
|
||||
switch (msg.command) {
|
||||
case globals.GAME_PROCESS_COMMANDS.START_TIMER:
|
||||
case GAME_PROCESS_COMMANDS.START_TIMER:
|
||||
logger.trace('CHILD PROCESS ' + msg.accessCode + ': START TIMER');
|
||||
timer = new ServerTimer(
|
||||
msg.hours,
|
||||
msg.minutes,
|
||||
globals.CLOCK_TICK_INTERVAL_MILLIS,
|
||||
PRIMITIVES.CLOCK_TICK_INTERVAL_MILLIS,
|
||||
logger
|
||||
);
|
||||
timer.runTimer().then(() => {
|
||||
logger.debug('Timer finished for ' + msg.accessCode);
|
||||
process.send({ command: globals.GAME_PROCESS_COMMANDS.END_TIMER });
|
||||
process.send({ command: GAME_PROCESS_COMMANDS.END_TIMER });
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
break;
|
||||
case globals.GAME_PROCESS_COMMANDS.PAUSE_TIMER:
|
||||
case GAME_PROCESS_COMMANDS.PAUSE_TIMER:
|
||||
timer.stopTimer();
|
||||
logger.trace('CHILD PROCESS ' + msg.accessCode + ': PAUSE TIMER');
|
||||
process.send({ command: globals.GAME_PROCESS_COMMANDS.PAUSE_TIMER, timeRemaining: timer.currentTimeInMillis });
|
||||
process.send({ command: GAME_PROCESS_COMMANDS.PAUSE_TIMER, timeRemaining: timer.currentTimeInMillis });
|
||||
|
||||
break;
|
||||
|
||||
case globals.GAME_PROCESS_COMMANDS.RESUME_TIMER:
|
||||
case GAME_PROCESS_COMMANDS.RESUME_TIMER:
|
||||
timer.resumeTimer().then(() => {
|
||||
logger.debug('Timer finished for ' + msg.accessCode);
|
||||
process.send({ command: globals.GAME_PROCESS_COMMANDS.END_TIMER });
|
||||
process.send({ command: GAME_PROCESS_COMMANDS.END_TIMER });
|
||||
process.exit(0);
|
||||
});
|
||||
logger.trace('CHILD PROCESS ' + msg.accessCode + ': RESUME TIMER');
|
||||
process.send({ command: globals.GAME_PROCESS_COMMANDS.RESUME_TIMER, timeRemaining: timer.currentTimeInMillis });
|
||||
process.send({ command: GAME_PROCESS_COMMANDS.RESUME_TIMER, timeRemaining: timer.currentTimeInMillis });
|
||||
|
||||
break;
|
||||
|
||||
case globals.GAME_PROCESS_COMMANDS.GET_TIME_REMAINING:
|
||||
case GAME_PROCESS_COMMANDS.GET_TIME_REMAINING:
|
||||
logger.trace('CHILD PROCESS ' + msg.accessCode + ': GET TIME REMAINING');
|
||||
process.send({
|
||||
command: globals.GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
command: GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
timeRemaining: timer.currentTimeInMillis,
|
||||
socketId: msg.socketId
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const globals = require('../config/globals');
|
||||
const { USER_TYPES, STATUS } = require('../config/globals');
|
||||
|
||||
/* The purpose of this component is to only return the game state information that is necessary. For example, we only
|
||||
want to return player role information to moderators. This avoids any possibility of a player having access to
|
||||
@@ -12,7 +12,7 @@ const GameStateCurator = {
|
||||
mapPeopleForModerator: (people) => {
|
||||
return people
|
||||
.filter((person) => {
|
||||
return person.assigned === true || (person.userType === globals.USER_TYPES.SPECTATOR || person.userType === globals.USER_TYPES.MODERATOR);
|
||||
return person.assigned === true || (person.userType === USER_TYPES.SPECTATOR || person.userType === USER_TYPES.MODERATOR);
|
||||
})
|
||||
.map((person) => ({
|
||||
name: person.name,
|
||||
@@ -45,7 +45,7 @@ const GameStateCurator = {
|
||||
};
|
||||
|
||||
function getGameStateBasedOnPermissions (game, person) {
|
||||
const client = game.status === globals.STATUS.LOBBY // people won't be able to know their role until past the lobby stage.
|
||||
const client = game.status === STATUS.LOBBY // people won't be able to know their role until past the lobby stage.
|
||||
? { name: person.name, hasEnteredName: person.hasEnteredName, id: person.id, cookie: person.cookie, userType: person.userType }
|
||||
: {
|
||||
name: person.name,
|
||||
@@ -61,7 +61,7 @@ function getGameStateBasedOnPermissions (game, person) {
|
||||
killed: person.killed
|
||||
};
|
||||
switch (person.userType) {
|
||||
case globals.USER_TYPES.MODERATOR:
|
||||
case USER_TYPES.MODERATOR:
|
||||
return {
|
||||
accessCode: game.accessCode,
|
||||
status: game.status,
|
||||
@@ -73,10 +73,10 @@ function getGameStateBasedOnPermissions (game, person) {
|
||||
timerParams: game.timerParams,
|
||||
isStartable: game.isStartable
|
||||
};
|
||||
case globals.USER_TYPES.TEMPORARY_MODERATOR:
|
||||
case globals.USER_TYPES.SPECTATOR:
|
||||
case globals.USER_TYPES.PLAYER:
|
||||
case globals.USER_TYPES.KILLED_PLAYER:
|
||||
case USER_TYPES.TEMPORARY_MODERATOR:
|
||||
case USER_TYPES.SPECTATOR:
|
||||
case USER_TYPES.PLAYER:
|
||||
case USER_TYPES.KILLED_PLAYER:
|
||||
return {
|
||||
accessCode: game.accessCode,
|
||||
status: game.status,
|
||||
@@ -86,7 +86,7 @@ function getGameStateBasedOnPermissions (game, person) {
|
||||
gameSize: game.gameSize,
|
||||
people: game.people
|
||||
.filter((person) => {
|
||||
return person.assigned === true || person.userType === globals.USER_TYPES.SPECTATOR;
|
||||
return person.assigned === true || person.userType === USER_TYPES.SPECTATOR;
|
||||
})
|
||||
.map((filteredPerson) => GameStateCurator.mapPerson(filteredPerson)),
|
||||
timerParams: game.timerParams,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const globals = require('../config/globals');
|
||||
const { LOG_LEVEL } = require('../config/globals');
|
||||
|
||||
module.exports = function (logLevel = globals.LOG_LEVEL.INFO) {
|
||||
module.exports = function (logLevel = LOG_LEVEL.INFO) {
|
||||
return {
|
||||
logLevel: logLevel,
|
||||
info (message = '') {
|
||||
@@ -10,7 +10,7 @@ module.exports = function (logLevel = globals.LOG_LEVEL.INFO) {
|
||||
|
||||
error (message = '') {
|
||||
if (
|
||||
logLevel === globals.LOG_LEVEL.INFO
|
||||
logLevel === LOG_LEVEL.INFO
|
||||
) { return; }
|
||||
const now = new Date();
|
||||
console.error('ERROR ', now.toGMTString(), ': ', message);
|
||||
@@ -18,25 +18,25 @@ module.exports = function (logLevel = globals.LOG_LEVEL.INFO) {
|
||||
|
||||
warn (message = '') {
|
||||
if (
|
||||
logLevel === globals.LOG_LEVEL.INFO
|
||||
|| logLevel === globals.LOG_LEVEL.ERROR
|
||||
logLevel === LOG_LEVEL.INFO
|
||||
|| logLevel === LOG_LEVEL.ERROR
|
||||
) return;
|
||||
const now = new Date();
|
||||
console.error('WARN ', now.toGMTString(), ': ', message);
|
||||
},
|
||||
|
||||
debug (message = '') {
|
||||
if (logLevel === globals.LOG_LEVEL.INFO || logLevel === globals.LOG_LEVEL.ERROR || logLevel === globals.LOG_LEVEL.WARN) return;
|
||||
if (logLevel === LOG_LEVEL.INFO || logLevel === LOG_LEVEL.ERROR || logLevel === LOG_LEVEL.WARN) return;
|
||||
const now = new Date();
|
||||
console.debug('DEBUG ', now.toGMTString(), ': ', message);
|
||||
},
|
||||
|
||||
trace (message = '') {
|
||||
if (
|
||||
logLevel === globals.LOG_LEVEL.INFO
|
||||
|| logLevel === globals.LOG_LEVEL.WARN
|
||||
|| logLevel === globals.LOG_LEVEL.DEBUG
|
||||
|| logLevel === globals.LOG_LEVEL.ERROR
|
||||
logLevel === LOG_LEVEL.INFO
|
||||
|| logLevel === LOG_LEVEL.WARN
|
||||
|| logLevel === LOG_LEVEL.DEBUG
|
||||
|| logLevel === LOG_LEVEL.ERROR
|
||||
) return;
|
||||
|
||||
const now = new Date();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const LOG_LEVEL = require('../config/globals').LOG_LEVEL;
|
||||
const { LOG_LEVEL, ENVIRONMENTS } = require('../config/globals');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const path = require('path');
|
||||
@@ -7,7 +7,6 @@ const crypto = require('crypto');
|
||||
const EventManager = require('./singletons/EventManager.js');
|
||||
const GameManager = require('./singletons/GameManager.js');
|
||||
const TimerManager = require('./singletons/TimerManager.js');
|
||||
const { ENVIRONMENT } = require('../config/globals.js');
|
||||
const rateLimit = require('express-rate-limit').default;
|
||||
|
||||
const ServerBootstrapper = {
|
||||
@@ -17,8 +16,8 @@ const ServerBootstrapper = {
|
||||
timerManager: new TimerManager(logger, instanceId),
|
||||
eventManager: new EventManager(logger, instanceId),
|
||||
gameManager: process.env.NODE_ENV.trim() === 'development'
|
||||
? new GameManager(logger, ENVIRONMENT.LOCAL, instanceId)
|
||||
: new GameManager(logger, ENVIRONMENT.PRODUCTION, instanceId)
|
||||
? new GameManager(logger, ENVIRONMENTS.LOCAL, instanceId)
|
||||
: new GameManager(logger, ENVIRONMENTS.PRODUCTION, instanceId)
|
||||
};
|
||||
},
|
||||
|
||||
@@ -114,7 +113,7 @@ const ServerBootstrapper = {
|
||||
});
|
||||
|
||||
// API endpoints
|
||||
app.use('/api/games', standardRateLimit, require('../api/GamesAPI'));
|
||||
app.use('/api/games', standardRateLimit, require('../api/RoomsAPI'));
|
||||
app.use('/api/admin', (req, res, next) => {
|
||||
if (isAuthorized(req)) {
|
||||
next();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const globals = require('../../config/globals');
|
||||
const { RateLimiterMemory } = require('rate-limiter-flexible');
|
||||
const redis = require('redis');
|
||||
const Events = require('../Events');
|
||||
const { EVENT_IDS, REDIS_CHANNELS, PRIMITIVES, SOCKET_EVENTS, TIMER_EVENTS, SYNCABLE_EVENTS } = require('../../config/globals');
|
||||
|
||||
class EventManager {
|
||||
constructor (logger, instanceId) {
|
||||
@@ -20,7 +20,7 @@ class EventManager {
|
||||
}
|
||||
|
||||
broadcast = (message) => {
|
||||
this.io?.emit(globals.EVENTS.BROADCAST, message);
|
||||
this.io?.emit(EVENT_IDS.BROADCAST, message);
|
||||
};
|
||||
|
||||
createRedisPublisher = async () => {
|
||||
@@ -53,7 +53,7 @@ class EventManager {
|
||||
throw new Error('UNABLE TO CONNECT TO REDIS because: ' + e);
|
||||
}
|
||||
|
||||
await this.subscriber.subscribe(globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM, async (message) => {
|
||||
await this.subscriber.subscribe(REDIS_CHANNELS.ACTIVE_GAME_STREAM, async (message) => {
|
||||
this.logger.debug('MESSAGE: ' + message);
|
||||
let messageComponents, args;
|
||||
try {
|
||||
@@ -64,7 +64,7 @@ class EventManager {
|
||||
}
|
||||
args = JSON.parse(
|
||||
message.slice(
|
||||
message.indexOf(messageComponents[messageComponents.length - 1]) + (globals.INSTANCE_ID_LENGTH + 1)
|
||||
message.indexOf(messageComponents[messageComponents.length - 1]) + (PRIMITIVES.INSTANCE_ID_LENGTH + 1)
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
@@ -133,12 +133,12 @@ class EventManager {
|
||||
};
|
||||
|
||||
registerSocketHandler = (namespace, socket, gameManager) => {
|
||||
socket.on(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, async (eventId, accessCode, args = null, ackFn = null) => {
|
||||
socket.on(SOCKET_EVENTS.IN_GAME_MESSAGE, async (eventId, accessCode, args = null, ackFn = null) => {
|
||||
const game = await gameManager.getActiveGame(accessCode);
|
||||
if (game) {
|
||||
if (globals.TIMER_EVENTS().includes(eventId)) {
|
||||
if (TIMER_EVENTS().includes(eventId)) {
|
||||
await this.handleEventById(
|
||||
globals.EVENT_IDS.TIMER_EVENT,
|
||||
EVENT_IDS.TIMER_EVENT,
|
||||
null,
|
||||
game,
|
||||
socket.id,
|
||||
@@ -160,10 +160,10 @@ class EventManager {
|
||||
handleAndSyncSocketEvent = async (eventId, game, socket, socketArgs, ackFn) => {
|
||||
await this.handleEventById(eventId, null, game, socket?.id, game.accessCode, socketArgs, ackFn, false);
|
||||
/* This server should publish events initiated by a connected socket to Redis for consumption by other instances. */
|
||||
if (globals.SYNCABLE_EVENTS().includes(eventId)) {
|
||||
if (SYNCABLE_EVENTS().includes(eventId)) {
|
||||
await this.gameManager.refreshGame(game);
|
||||
await this.publisher?.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
this.createMessageToPublish(game.accessCode, eventId, this.instanceId, JSON.stringify(socketArgs))
|
||||
);
|
||||
}
|
||||
@@ -194,7 +194,7 @@ class EventManager {
|
||||
timerEventSubtype: timerEventSubtype
|
||||
};
|
||||
if (event) {
|
||||
if (!syncOnly || eventId === globals.EVENT_IDS.RESTART_GAME) {
|
||||
if (!syncOnly || eventId === EVENT_IDS.RESTART_GAME) {
|
||||
await event.stateChange(game, socketArgs, additionalVars);
|
||||
}
|
||||
await event.communicate(game, socketArgs, additionalVars);
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
const globals = require('../../config/globals');
|
||||
const {
|
||||
STATUS,
|
||||
PRIMITIVES,
|
||||
ERROR_MESSAGES,
|
||||
GAME_PROCESS_COMMANDS,
|
||||
USER_TYPES,
|
||||
EVENT_IDS,
|
||||
REDIS_CHANNELS
|
||||
} = require('../../config/globals');
|
||||
const Game = require('../../model/Game');
|
||||
const Person = require('../../model/Person');
|
||||
const GameStateCurator = require('../GameStateCurator');
|
||||
@@ -62,9 +70,9 @@ class GameManager {
|
||||
gameParams.hasDedicatedModerator,
|
||||
gameParams.isTestGame
|
||||
);
|
||||
const newAccessCode = await this.generateAccessCode(globals.ACCESS_CODE_CHAR_POOL);
|
||||
const newAccessCode = await this.generateAccessCode(PRIMITIVES.ACCESS_CODE_CHAR_POOL);
|
||||
if (newAccessCode === null) {
|
||||
return Promise.reject(globals.ERROR_MESSAGE.NO_UNIQUE_ACCESS_CODE);
|
||||
return Promise.reject(ERROR_MESSAGES.NO_UNIQUE_ACCESS_CODE);
|
||||
}
|
||||
const moderator = initializeModerator(req.moderatorName, req.hasDedicatedModerator);
|
||||
moderator.assigned = true;
|
||||
@@ -75,7 +83,7 @@ class GameManager {
|
||||
}
|
||||
const newGame = new Game(
|
||||
newAccessCode,
|
||||
globals.STATUS.LOBBY,
|
||||
STATUS.LOBBY,
|
||||
null,
|
||||
req.deck,
|
||||
req.hasTimer,
|
||||
@@ -88,7 +96,7 @@ class GameManager {
|
||||
);
|
||||
newGame.people = initializePeopleForGame(req.deck, moderator, this.shuffle, req.isTestGame, newGame.gameSize);
|
||||
await this.eventManager.publisher.set(newAccessCode, JSON.stringify(newGame), {
|
||||
EX: globals.STALE_GAME_SECONDS
|
||||
EX: PRIMITIVES.STALE_GAME_SECONDS
|
||||
});
|
||||
return Promise.resolve({ accessCode: newAccessCode, cookie: moderator.cookie, environment: this.environment });
|
||||
}).catch((message) => {
|
||||
@@ -103,7 +111,7 @@ class GameManager {
|
||||
if (thread && !thread.killed) {
|
||||
this.logger.debug('Timer thread found for game ' + game.accessCode);
|
||||
thread.send({
|
||||
command: globals.GAME_PROCESS_COMMANDS.PAUSE_TIMER,
|
||||
command: GAME_PROCESS_COMMANDS.PAUSE_TIMER,
|
||||
accessCode: game.accessCode,
|
||||
logLevel: this.logger.logLevel
|
||||
});
|
||||
@@ -115,7 +123,7 @@ class GameManager {
|
||||
if (thread && !thread.killed) {
|
||||
this.logger.debug('Timer thread found for game ' + game.accessCode);
|
||||
thread.send({
|
||||
command: globals.GAME_PROCESS_COMMANDS.RESUME_TIMER,
|
||||
command: GAME_PROCESS_COMMANDS.RESUME_TIMER,
|
||||
accessCode: game.accessCode,
|
||||
logLevel: this.logger.logLevel
|
||||
});
|
||||
@@ -127,14 +135,14 @@ class GameManager {
|
||||
const thread = this.timerManager.timerThreads[game.accessCode];
|
||||
if (thread && (!thread.killed && thread.exitCode === null)) {
|
||||
thread.send({
|
||||
command: globals.GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
command: GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
accessCode: game.accessCode,
|
||||
socketId: socketId,
|
||||
logLevel: this.logger.logLevel
|
||||
});
|
||||
} else if (thread) {
|
||||
if (game.timerParams && game.timerParams.timeRemaining === 0) {
|
||||
this.namespace.to(socketId).emit(globals.GAME_PROCESS_COMMANDS.GET_TIME_REMAINING, game.timerParams.timeRemaining, game.timerParams.paused);
|
||||
this.namespace.to(socketId).emit(GAME_PROCESS_COMMANDS.GET_TIME_REMAINING, game.timerParams.timeRemaining, game.timerParams.paused);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,9 +162,9 @@ class GameManager {
|
||||
let codeDigits, accessCode;
|
||||
let attempts = 0;
|
||||
while (!accessCode || ((await this.eventManager.publisher.keys('*')).includes(accessCode)
|
||||
&& attempts < globals.ACCESS_CODE_GENERATION_ATTEMPTS)) {
|
||||
&& attempts < PRIMITIVES.ACCESS_CODE_GENERATION_ATTEMPTS)) {
|
||||
codeDigits = [];
|
||||
let iterations = globals.ACCESS_CODE_LENGTH;
|
||||
let iterations = PRIMITIVES.ACCESS_CODE_LENGTH;
|
||||
while (iterations > 0) {
|
||||
iterations --;
|
||||
codeDigits.push(charPool[getRandomInt(charCount)]);
|
||||
@@ -178,7 +186,7 @@ class GameManager {
|
||||
return Promise.reject({ status: 400, reason: 'This name is taken.' });
|
||||
}
|
||||
if (joinAsSpectator
|
||||
&& game.people.filter(person => person.userType === globals.USER_TYPES.SPECTATOR).length === globals.MAX_SPECTATORS
|
||||
&& game.people.filter(person => person.userType === USER_TYPES.SPECTATOR).length === PRIMITIVES.MAX_SPECTATORS
|
||||
) {
|
||||
return Promise.reject({ status: 400, reason: 'There are too many people already spectating.' });
|
||||
} else if (joinAsSpectator || this.isGameStartable(game)) {
|
||||
@@ -196,7 +204,7 @@ class GameManager {
|
||||
createRandomId(),
|
||||
createRandomId(),
|
||||
name,
|
||||
globals.USER_TYPES.PLAYER,
|
||||
USER_TYPES.PLAYER,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
@@ -208,15 +216,15 @@ class GameManager {
|
||||
game.isStartable = this.isGameStartable(game);
|
||||
await this.refreshGame(game);
|
||||
this.namespace.in(game.accessCode).emit(
|
||||
globals.EVENTS.PLAYER_JOINED,
|
||||
EVENT_IDS.PLAYER_JOINED,
|
||||
GameStateCurator.mapPerson(moderator || newPlayer),
|
||||
game.isStartable
|
||||
);
|
||||
await this.eventManager.publisher?.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
this.eventManager.createMessageToPublish(
|
||||
game.accessCode,
|
||||
globals.EVENT_IDS.PLAYER_JOINED,
|
||||
EVENT_IDS.PLAYER_JOINED,
|
||||
this.instanceId,
|
||||
JSON.stringify(moderator || newPlayer)
|
||||
)
|
||||
@@ -237,7 +245,7 @@ class GameManager {
|
||||
return cards;
|
||||
}
|
||||
|
||||
restartGame = async (game, namespace, status = globals.STATUS.IN_PROGRESS) => {
|
||||
restartGame = async (game, namespace) => {
|
||||
// kill any outstanding timer threads
|
||||
const subProcess = this.timerManager.timerThreads[game.accessCode];
|
||||
if (subProcess) {
|
||||
@@ -250,16 +258,16 @@ class GameManager {
|
||||
}
|
||||
|
||||
for (let i = 0; i < game.people.length; i ++) {
|
||||
if (game.people[i].userType === globals.USER_TYPES.KILLED_PLAYER) {
|
||||
game.people[i].userType = globals.USER_TYPES.PLAYER;
|
||||
if (game.people[i].userType === USER_TYPES.KILLED_PLAYER) {
|
||||
game.people[i].userType = USER_TYPES.PLAYER;
|
||||
game.people[i].out = false;
|
||||
}
|
||||
if (game.people[i].userType === globals.USER_TYPES.KILLED_BOT) {
|
||||
game.people[i].userType = globals.USER_TYPES.BOT;
|
||||
if (game.people[i].userType === USER_TYPES.KILLED_BOT) {
|
||||
game.people[i].userType = USER_TYPES.BOT;
|
||||
game.people[i].out = false;
|
||||
}
|
||||
if (game.people[i].gameRole && game.people[i].id === game.currentModeratorId && game.people[i].userType === globals.USER_TYPES.MODERATOR) {
|
||||
game.people[i].userType = globals.USER_TYPES.TEMPORARY_MODERATOR;
|
||||
if (game.people[i].gameRole && game.people[i].id === game.currentModeratorId && game.people[i].userType === USER_TYPES.MODERATOR) {
|
||||
game.people[i].userType = USER_TYPES.TEMPORARY_MODERATOR;
|
||||
game.people[i].out = false;
|
||||
}
|
||||
game.people[i].revealed = false;
|
||||
@@ -270,19 +278,19 @@ class GameManager {
|
||||
game.people[i].customRole = null;
|
||||
}
|
||||
|
||||
game.status = globals.STATUS.LOBBY;
|
||||
game.status = STATUS.LOBBY;
|
||||
|
||||
await this.refreshGame(game);
|
||||
await this.eventManager.publisher?.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
this.eventManager.createMessageToPublish(
|
||||
game.accessCode,
|
||||
globals.EVENT_IDS.RESTART_GAME,
|
||||
EVENT_IDS.RESTART_GAME,
|
||||
this.instanceId,
|
||||
'{}'
|
||||
)
|
||||
);
|
||||
namespace.in(game.accessCode).emit(globals.EVENT_IDS.RESTART_GAME);
|
||||
namespace.in(game.accessCode).emit(EVENT_IDS.RESTART_GAME);
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -305,9 +313,9 @@ class GameManager {
|
||||
deal = (game) => {
|
||||
const cards = this.prepareDeck(game.deck);
|
||||
let i = 0;
|
||||
for (const person of game.people.filter(person => person.userType === globals.USER_TYPES.PLAYER
|
||||
|| person.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
|
||||
|| person.userType === globals.USER_TYPES.BOT)
|
||||
for (const person of game.people.filter(person => person.userType === USER_TYPES.PLAYER
|
||||
|| person.userType === USER_TYPES.TEMPORARY_MODERATOR
|
||||
|| person.userType === USER_TYPES.BOT)
|
||||
) {
|
||||
person.gameRole = cards[i].role;
|
||||
person.customRole = cards[i].custom;
|
||||
@@ -318,9 +326,9 @@ class GameManager {
|
||||
}
|
||||
|
||||
isGameStartable = (game) => {
|
||||
return game.people.filter(person => person.userType === globals.USER_TYPES.PLAYER
|
||||
|| person.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
|
||||
|| person.userType === globals.USER_TYPES.BOT).length === game.gameSize;
|
||||
return game.people.filter(person => person.userType === USER_TYPES.PLAYER
|
||||
|| person.userType === USER_TYPES.TEMPORARY_MODERATOR
|
||||
|| person.userType === USER_TYPES.BOT).length === game.gameSize;
|
||||
}
|
||||
|
||||
findPersonByField = (game, fieldName, value) => {
|
||||
@@ -334,8 +342,8 @@ function getRandomInt (max) {
|
||||
|
||||
function initializeModerator (name, hasDedicatedModerator) {
|
||||
const userType = hasDedicatedModerator
|
||||
? globals.USER_TYPES.MODERATOR
|
||||
: globals.USER_TYPES.TEMPORARY_MODERATOR;
|
||||
? USER_TYPES.MODERATOR
|
||||
: USER_TYPES.TEMPORARY_MODERATOR;
|
||||
return new Person(createRandomId(), createRandomId(), name, userType);
|
||||
}
|
||||
|
||||
@@ -343,7 +351,7 @@ function initializePeopleForGame (uniqueRoles, moderator, shuffle, isTestGame, g
|
||||
const people = [];
|
||||
if (isTestGame) {
|
||||
let j = 0;
|
||||
const number = moderator.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
|
||||
const number = moderator.userType === USER_TYPES.TEMPORARY_MODERATOR
|
||||
? gameSize - 1
|
||||
: gameSize;
|
||||
while (j < number) {
|
||||
@@ -351,7 +359,7 @@ function initializePeopleForGame (uniqueRoles, moderator, shuffle, isTestGame, g
|
||||
createRandomId(),
|
||||
createRandomId(),
|
||||
UsernameGenerator.generate(),
|
||||
globals.USER_TYPES.BOT,
|
||||
USER_TYPES.BOT,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
@@ -369,8 +377,8 @@ function initializePeopleForGame (uniqueRoles, moderator, shuffle, isTestGame, g
|
||||
|
||||
function createRandomId () {
|
||||
let id = '';
|
||||
for (let i = 0; i < globals.INSTANCE_ID_LENGTH; i ++) {
|
||||
id += globals.INSTANCE_ID_CHAR_POOL[Math.floor(Math.random() * globals.INSTANCE_ID_CHAR_POOL.length)];
|
||||
for (let i = 0; i < PRIMITIVES.INSTANCE_ID_LENGTH; i ++) {
|
||||
id += PRIMITIVES.INSTANCE_ID_CHAR_POOL[Math.floor(Math.random() * PRIMITIVES.INSTANCE_ID_CHAR_POOL.length)];
|
||||
}
|
||||
return id;
|
||||
}
|
||||
@@ -385,21 +393,21 @@ async function addSpectator (game, name, logger, namespace, eventManager, instan
|
||||
createRandomId(),
|
||||
createRandomId(),
|
||||
name,
|
||||
globals.USER_TYPES.SPECTATOR
|
||||
USER_TYPES.SPECTATOR
|
||||
);
|
||||
spectator.assigned = true;
|
||||
logger.trace('new spectator: ' + spectator.name);
|
||||
game.people.push(spectator);
|
||||
await refreshGame(game);
|
||||
namespace.in(game.accessCode).emit(
|
||||
globals.EVENT_IDS.ADD_SPECTATOR,
|
||||
EVENT_IDS.ADD_SPECTATOR,
|
||||
GameStateCurator.mapPerson(spectator)
|
||||
);
|
||||
await eventManager.publisher.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
eventManager.createMessageToPublish(
|
||||
game.accessCode,
|
||||
globals.EVENT_IDS.ADD_SPECTATOR,
|
||||
EVENT_IDS.ADD_SPECTATOR,
|
||||
instanceId,
|
||||
JSON.stringify(GameStateCurator.mapPerson(spectator))
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const { fork } = require('child_process');
|
||||
const path = require('path');
|
||||
const globals = require('../../config/globals');
|
||||
const { REDIS_CHANNELS, GAME_PROCESS_COMMANDS } = require('../../config/globals');
|
||||
|
||||
class TimerManager {
|
||||
constructor (logger, instanceId) {
|
||||
@@ -25,7 +25,7 @@ class TimerManager {
|
||||
await eventManager.handleEventById(msg.command, null, game, msg.socketId, game.accessCode, msg, null, false);
|
||||
await gameManager.refreshGame(game);
|
||||
await eventManager.publisher.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
eventManager.createMessageToPublish(game.accessCode, msg.command, this.instanceId, JSON.stringify(msg))
|
||||
);
|
||||
});
|
||||
@@ -34,7 +34,7 @@ class TimerManager {
|
||||
this.logger.debug('Game timer thread ' + gameProcess.pid + ' exiting with code ' + code + ' - game ' + game.accessCode);
|
||||
});
|
||||
gameProcess.send({
|
||||
command: globals.GAME_PROCESS_COMMANDS.START_TIMER,
|
||||
command: GAME_PROCESS_COMMANDS.START_TIMER,
|
||||
accessCode: game.accessCode,
|
||||
logLevel: this.logger.logLevel,
|
||||
hours: game.timerParams.hours,
|
||||
|
||||
@@ -1,52 +1,51 @@
|
||||
import { gameHandler } from '../../client/src/modules/page_handlers/gameHandler.js';
|
||||
import { mockGames } from '../support/MockGames.js';
|
||||
import gameTemplate from '../../client/src/view_templates/GameTemplate.js';
|
||||
import { globals } from '../../client/src/config/globals.js';
|
||||
import {
|
||||
EVENT_IDS,
|
||||
SOCKET_EVENTS,
|
||||
USER_TYPE_ICONS,
|
||||
USER_TYPES
|
||||
} from '../../client/src/config/globals.js';
|
||||
|
||||
describe('game page', () => {
|
||||
const XHRUtility = {
|
||||
xhr (url, method = 'GET', headers, body = null) {
|
||||
switch (url) {
|
||||
case '/api/games/environment':
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({ content: 'production' });
|
||||
});
|
||||
}
|
||||
const mockSocket = {
|
||||
eventHandlers: {},
|
||||
on: function (message, handler) {
|
||||
this.eventHandlers[message] = handler;
|
||||
},
|
||||
once: function (message, handler) {
|
||||
this.eventHandlers[message] = handler;
|
||||
},
|
||||
timeout: (duration) => {
|
||||
return mockSocket;
|
||||
},
|
||||
removeAllListeners: function (...names) {
|
||||
|
||||
},
|
||||
hasListeners: function (listener) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const window = { location: { href: 'host/game/ABCD' }, fetch: () => {} };
|
||||
|
||||
beforeEach(async () => {
|
||||
document.body.innerHTML = '';
|
||||
const response = new Response('production', { status: 200, statusText: 'OK' });
|
||||
spyOn(window, 'fetch').and.resolveTo(response);
|
||||
});
|
||||
describe('lobby game - moderator view', () => {
|
||||
let mockSocket;
|
||||
|
||||
beforeEach(async function () {
|
||||
document.body.innerHTML = '';
|
||||
mockSocket = {
|
||||
eventHandlers: {},
|
||||
on: function (message, handler) {
|
||||
this.eventHandlers[message] = handler;
|
||||
},
|
||||
once: function (message, handler) {
|
||||
this.eventHandlers[message] = handler;
|
||||
},
|
||||
timeout: (duration) => {
|
||||
return mockSocket;
|
||||
},
|
||||
emit: function (eventName, ...args) {
|
||||
switch (args[0]) { // eventName is currently always "inGameMessage" - the first arg after that is the specific message type
|
||||
case globals.EVENT_IDS.FETCH_GAME_STATE:
|
||||
args[args.length - 1](deepCopy(mockGames.gameInLobbyAsModerator)); // copy the game object to prevent leaking of state between specs
|
||||
}
|
||||
},
|
||||
removeAllListeners: function (...names) {
|
||||
|
||||
},
|
||||
hasListeners: function (listener) {
|
||||
return false;
|
||||
beforeEach(async () => {
|
||||
mockSocket.emit = function (eventName, ...args) {
|
||||
switch (args[0]) { // eventName is currently always "inGameMessage" - the first arg after that is the specific message type
|
||||
case EVENT_IDS.FETCH_GAME_STATE:
|
||||
args[args.length - 1](deepCopy(mockGames.gameInLobbyAsModerator)); // copy the game object to prevent leaking of state between specs
|
||||
}
|
||||
};
|
||||
await gameHandler(mockSocket, XHRUtility, { location: { href: 'host/game/ABCD' } }, gameTemplate);
|
||||
spyOn(mockSocket, 'emit').and.callThrough();
|
||||
await gameHandler(mockSocket, window, gameTemplate);
|
||||
mockSocket.eventHandlers.connect();
|
||||
spyOn(mockSocket, 'emit');
|
||||
});
|
||||
|
||||
it('should display the connected client', () => {
|
||||
@@ -59,10 +58,10 @@ describe('game page', () => {
|
||||
});
|
||||
|
||||
it('should display a new player when they join', () => {
|
||||
mockSocket.eventHandlers[globals.EVENT_IDS.PLAYER_JOINED]({
|
||||
mockSocket.eventHandlers[EVENT_IDS.PLAYER_JOINED]({
|
||||
name: 'Jane',
|
||||
id: '123',
|
||||
userType: globals.USER_TYPES.PLAYER,
|
||||
userType: USER_TYPES.PLAYER,
|
||||
out: false,
|
||||
revealed: false
|
||||
}, false);
|
||||
@@ -83,8 +82,8 @@ describe('game page', () => {
|
||||
document.getElementById('save-role-changes-button').click();
|
||||
|
||||
expect(mockSocket.emit).toHaveBeenCalledWith(
|
||||
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
|
||||
globals.EVENT_IDS.UPDATE_GAME_ROLES,
|
||||
SOCKET_EVENTS.IN_GAME_MESSAGE,
|
||||
EVENT_IDS.UPDATE_GAME_ROLES,
|
||||
mockGames.gameInLobbyAsModerator.accessCode,
|
||||
jasmine.any(Object),
|
||||
jasmine.any(Function)
|
||||
@@ -97,42 +96,21 @@ describe('game page', () => {
|
||||
});
|
||||
|
||||
describe('lobby game - player view', () => {
|
||||
let mockSocket;
|
||||
|
||||
beforeEach(async function () {
|
||||
document.body.innerHTML = '';
|
||||
mockSocket = {
|
||||
eventHandlers: {},
|
||||
on: function (message, handler) {
|
||||
this.eventHandlers[message] = handler;
|
||||
},
|
||||
once: function (message, handler) {
|
||||
this.eventHandlers[message] = handler;
|
||||
},
|
||||
timeout: (duration) => {
|
||||
return mockSocket;
|
||||
},
|
||||
emit: function (eventName, ...args) {
|
||||
switch (args[0]) { // eventName is currently always "inGameMessage" - the first arg after that is the specific message type
|
||||
case globals.EVENT_IDS.FETCH_GAME_STATE:
|
||||
args[args.length - 1](deepCopy(mockGames.gameInLobbyAsPlayer)); // copy the game object to prevent leaking of state between specs
|
||||
}
|
||||
},
|
||||
removeAllListeners: function (...names) {
|
||||
|
||||
},
|
||||
hasListeners: function (listener) {
|
||||
return false;
|
||||
beforeEach(async () => {
|
||||
mockSocket.emit = function (eventName, ...args) {
|
||||
switch (args[0]) { // eventName is currently always "inGameMessage" - the first arg after that is the specific message type
|
||||
case EVENT_IDS.FETCH_GAME_STATE:
|
||||
args[args.length - 1](deepCopy(mockGames.gameInLobbyAsPlayer)); // copy the game object to prevent leaking of state between specs
|
||||
}
|
||||
};
|
||||
await gameHandler(mockSocket, XHRUtility, { location: { href: 'host/game/ABCD' } }, gameTemplate);
|
||||
spyOn(mockSocket, 'emit').and.callThrough();
|
||||
await gameHandler(mockSocket, window, gameTemplate);
|
||||
mockSocket.eventHandlers.connect();
|
||||
spyOn(mockSocket, 'emit');
|
||||
});
|
||||
|
||||
it('should display the connected client', () => {
|
||||
expect(document.getElementById('client-name').innerText).toEqual('Lys');
|
||||
expect(document.getElementById('client-user-type').innerText).toEqual('player' + globals.USER_TYPE_ICONS.player);
|
||||
expect(document.getElementById('client-user-type').innerText).toEqual('player' + USER_TYPE_ICONS.player);
|
||||
});
|
||||
|
||||
it('should display the QR Code', () => {
|
||||
@@ -144,18 +122,18 @@ describe('game page', () => {
|
||||
document.getElementById('leave-game-button').click();
|
||||
document.getElementById('confirmation-yes-button').click();
|
||||
expect(mockSocket.emit).toHaveBeenCalledWith(
|
||||
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
|
||||
globals.EVENT_IDS.LEAVE_ROOM,
|
||||
SOCKET_EVENTS.IN_GAME_MESSAGE,
|
||||
EVENT_IDS.LEAVE_ROOM,
|
||||
mockGames.gameInLobbyAsModerator.accessCode,
|
||||
{ personId: mockGames.gameInLobbyAsPlayer.client.id }
|
||||
);
|
||||
});
|
||||
|
||||
it('should display a new player when they join', () => {
|
||||
mockSocket.eventHandlers[globals.EVENT_IDS.PLAYER_JOINED]({
|
||||
mockSocket.eventHandlers[EVENT_IDS.PLAYER_JOINED]({
|
||||
name: 'Jane',
|
||||
id: '123',
|
||||
userType: globals.USER_TYPES.PLAYER,
|
||||
userType: USER_TYPES.PLAYER,
|
||||
out: false,
|
||||
revealed: false
|
||||
}, false);
|
||||
@@ -169,40 +147,20 @@ describe('game page', () => {
|
||||
});
|
||||
|
||||
describe('in-progress game - player view', () => {
|
||||
let mockSocket;
|
||||
|
||||
beforeEach(async () => {
|
||||
document.body.innerHTML = '';
|
||||
mockSocket = {
|
||||
eventHandlers: {},
|
||||
on: function (message, handler) {
|
||||
this.eventHandlers[message] = handler;
|
||||
},
|
||||
timeout: (duration) => {
|
||||
return mockSocket;
|
||||
},
|
||||
emit: function (eventName, ...args) {
|
||||
switch (args[0]) { // eventName is currently always "inGameMessage" - the first arg after that is the specific message type
|
||||
case globals.EVENT_IDS.FETCH_GAME_STATE:
|
||||
args[args.length - 1](deepCopy(mockGames.inProgressGame)); // copy the game object to prevent leaking of state between specs
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
hasListeners: function (listener) {
|
||||
return false;
|
||||
},
|
||||
removeAllListeners: function (...names) {
|
||||
|
||||
},
|
||||
once: function (message, handler) {
|
||||
this.eventHandlers[message] = handler;
|
||||
mockSocket.emit = function (eventName, ...args) {
|
||||
switch (args[0]) { // eventName is currently always "inGameMessage" - the first arg after that is the specific message type
|
||||
case EVENT_IDS.FETCH_GAME_STATE:
|
||||
args[args.length - 1](deepCopy(mockGames.inProgressGame)); // copy the game object to prevent leaking of state between specs
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
await gameHandler(mockSocket, XHRUtility, { location: { href: 'host/game/ABCD' } }, gameTemplate);
|
||||
spyOn(mockSocket, 'emit').and.callThrough();
|
||||
await gameHandler(mockSocket, window, gameTemplate);
|
||||
mockSocket.eventHandlers.connect();
|
||||
mockSocket.eventHandlers.getTimeRemaining(120000, true);
|
||||
await mockSocket.eventHandlers.getTimeRemaining(120000, true);
|
||||
});
|
||||
|
||||
it('should display the game role of the client', () => {
|
||||
@@ -246,44 +204,24 @@ describe('game page', () => {
|
||||
});
|
||||
|
||||
describe('in-progress game - moderator view', () => {
|
||||
let mockSocket;
|
||||
|
||||
beforeEach(async () => {
|
||||
document.body.innerHTML = '';
|
||||
mockSocket = {
|
||||
eventHandlers: {},
|
||||
on: function (message, handler) {
|
||||
this.eventHandlers[message] = handler;
|
||||
},
|
||||
timeout: (duration) => {
|
||||
return mockSocket;
|
||||
},
|
||||
once: function (message, handler) {
|
||||
this.eventHandlers[message] = handler;
|
||||
},
|
||||
emit: function (eventName, ...args) {
|
||||
switch (args[0]) { // eventName is currently always "inGameMessage" - the first arg after that is the specific message type
|
||||
case globals.EVENT_IDS.FETCH_GAME_STATE:
|
||||
args[args.length - 1](deepCopy(mockGames.moderatorGame)); // copy the game object to prevent leaking of state between specs
|
||||
break;
|
||||
case globals.EVENT_IDS.END_GAME:
|
||||
args[args.length - 1]();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
hasListeners: function (listener) {
|
||||
return false;
|
||||
},
|
||||
removeAllListeners: function (...names) {
|
||||
|
||||
mockSocket.emit = function (eventName, ...args) {
|
||||
switch (args[0]) { // eventName is currently always "inGameMessage" - the first arg after that is the specific message type
|
||||
case EVENT_IDS.FETCH_GAME_STATE:
|
||||
args[args.length - 1](deepCopy(mockGames.moderatorGame)); // copy the game object to prevent leaking of state between specs
|
||||
break;
|
||||
case EVENT_IDS.END_GAME:
|
||||
args[args.length - 1]();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
await gameHandler(mockSocket, XHRUtility, { location: { href: 'host/game/ABCD' } }, gameTemplate);
|
||||
spyOn(mockSocket, 'emit').and.callThrough();
|
||||
await gameHandler(mockSocket, window, gameTemplate);
|
||||
mockSocket.eventHandlers.connect();
|
||||
mockSocket.eventHandlers.getTimeRemaining(120000, true);
|
||||
spyOn(mockSocket, 'emit');
|
||||
await mockSocket.eventHandlers.getTimeRemaining(120000, true);
|
||||
});
|
||||
|
||||
it('should display the button to play/pause the timer', () => {
|
||||
@@ -317,14 +255,14 @@ describe('game page', () => {
|
||||
.querySelector('.kill-player-button').click();
|
||||
document.getElementById('confirmation-yes-button').click();
|
||||
expect(mockSocket.emit).toHaveBeenCalledWith(
|
||||
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
|
||||
globals.EVENT_IDS.KILL_PLAYER,
|
||||
SOCKET_EVENTS.IN_GAME_MESSAGE,
|
||||
EVENT_IDS.KILL_PLAYER,
|
||||
mockGames.moderatorGame.accessCode,
|
||||
{ personId: 'pTtVXDJaxtXcrlbG8B43Wom67snoeO24RNEkO6eB2BaIftTdvpnfe1QR65DVj9A6I3VOoKZkYQW' }
|
||||
);
|
||||
mockSocket.eventHandlers.killPlayer({
|
||||
id: 'pTtVXDJaxtXcrlbG8B43Wom67snoeO24RNEkO6eB2BaIftTdvpnfe1QR65DVj9A6I3VOoKZkYQW',
|
||||
userType: globals.USER_TYPES.KILLED_PLAYER,
|
||||
userType: USER_TYPES.KILLED_PLAYER,
|
||||
out: true,
|
||||
killed: true,
|
||||
revealed: false,
|
||||
@@ -339,8 +277,8 @@ describe('game page', () => {
|
||||
.querySelector('.reveal-role-button').click();
|
||||
document.getElementById('confirmation-yes-button').click();
|
||||
expect(mockSocket.emit).toHaveBeenCalledWith(
|
||||
globals.SOCKET_EVENTS.IN_GAME_MESSAGE,
|
||||
globals.EVENT_IDS.REVEAL_PLAYER,
|
||||
SOCKET_EVENTS.IN_GAME_MESSAGE,
|
||||
EVENT_IDS.REVEAL_PLAYER,
|
||||
mockGames.moderatorGame.accessCode,
|
||||
{ personId: 'pTtVXDJaxtXcrlbG8B43Wom67snoeO24RNEkO6eB2BaIftTdvpnfe1QR65DVj9A6I3VOoKZkYQW' }
|
||||
);
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// TODO: clean up these deep relative paths? jsconfig.json is not working...
|
||||
const Game = require('../../../../server/model/Game');
|
||||
const globals = require('../../../../server/config/globals');
|
||||
const EVENT_IDS = globals.EVENT_IDS;
|
||||
const USER_TYPES = globals.USER_TYPES;
|
||||
const STATUS = globals.STATUS;
|
||||
const { ENVIRONMENTS, EVENT_IDS, USER_TYPES, STATUS } = require('../../../../server/config/globals.js');
|
||||
const GameManager = require('../../../../server/modules/singletons/GameManager.js');
|
||||
const TimerManager = require('../../../../server/modules/singletons/TimerManager.js');
|
||||
const EventManager = require('../../../../server/modules/singletons/EventManager.js');
|
||||
@@ -21,7 +18,7 @@ describe('Events', () => {
|
||||
const toObj = { emit: () => {} };
|
||||
namespace = { in: () => { return inObj; }, to: () => { return toObj; }, sockets: new Map() };
|
||||
socket = { id: '123', emit: () => {}, to: () => { return { emit: () => {} }; } };
|
||||
gameManager = GameManager.instance ? GameManager.instance : new GameManager(logger, globals.ENVIRONMENT.PRODUCTION, 'test');
|
||||
gameManager = GameManager.instance ? GameManager.instance : new GameManager(logger, ENVIRONMENTS.PRODUCTION, 'test');
|
||||
timerManager = TimerManager.instance ? TimerManager.instance : new TimerManager(logger, 'test');
|
||||
eventManager = EventManager.instance ? EventManager.instance : new EventManager(logger, 'test');
|
||||
gameManager.setGameSocketNamespace(namespace);
|
||||
@@ -83,7 +80,7 @@ describe('Events', () => {
|
||||
.communicate(game, { id: 'd', assigned: true, userType: USER_TYPES.PLAYER }, { gameManager: gameManager });
|
||||
expect(namespace.in).toHaveBeenCalledWith(game.accessCode);
|
||||
expect(namespace.in().emit).toHaveBeenCalledWith(
|
||||
globals.EVENTS.PLAYER_JOINED,
|
||||
EVENT_IDS.PLAYER_JOINED,
|
||||
GameStateCurator.mapPerson({ id: 'd', assigned: true, userType: USER_TYPES.PLAYER }),
|
||||
game.isStartable
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user