join purposefully as spectator, various improvements

This commit is contained in:
AlecM33
2022-12-29 15:38:40 -05:00
parent 63b1157c7d
commit e0dffe17b6
22 changed files with 221 additions and 120 deletions

View File

@@ -56,7 +56,7 @@ export const globals = {
USER_TYPES: {
MODERATOR: 'moderator',
PLAYER: 'player',
TEMPORARY_MODERATOR: 'player / temp mod',
TEMPORARY_MODERATOR: 'temp mod',
KILLED_PLAYER: 'killed',
SPECTATOR: 'spectator'
},
@@ -67,7 +67,7 @@ export const globals = {
USER_TYPE_ICONS: {
player: ' \uD83C\uDFAE',
moderator: ' \uD83D\uDC51',
'player / temp mod': ' \uD83C\uDFAE\uD83D\uDC51',
'temp mod': ' \uD83C\uDFAE\uD83D\uDC51',
spectator: ' \uD83D\uDC7B',
killed: '\uD83D\uDC80'
}

View File

@@ -1,6 +1,6 @@
import { toast } from './Toast.js';
export const Confirmation = (message, onYes = null) => {
export const Confirmation = (message, onYes = null, innerHTML = false) => {
document.querySelector('#confirmation')?.remove();
document.querySelector('#confirmation-background')?.remove();
@@ -17,7 +17,11 @@ export const Confirmation = (message, onYes = null) => {
<button id="confirmation-yes-button" class="app-button">OK</button>
</div>`;
confirmation.querySelector('#confirmation-message').innerText = message;
if (innerHTML) {
confirmation.querySelector('#confirmation-message').innerHTML = message;
} else {
confirmation.querySelector('#confirmation-message').innerText = message;
}
let background = document.createElement('div');
background.setAttribute('id', 'confirmation-background');

View File

@@ -69,6 +69,7 @@ export const HTMLFragments = {
</div>
<div id='game-people-container'>
<label id='players-alive-label'></label>
<div id="spectator-count"></div>
<div id='game-player-list'></div>
</div>`,
SPECTATOR_GAME_VIEW:
@@ -83,6 +84,7 @@ export const HTMLFragments = {
</div>
<div id='game-people-container'>
<label id='players-alive-label'></label>
<div id="spectator-count"></div>
<div id='game-player-list'></div>
</div>`,
TRANSFER_MOD_MODAL:
@@ -147,6 +149,7 @@ export const HTMLFragments = {
</div>
<div id='game-people-container'>
<label id='players-alive-label'></label>
<div id="spectator-count"></div>
<div id='game-player-list'></div>
</div>
</div>`,

View File

@@ -44,8 +44,7 @@ function getNavbarLinks (page = null, device) {
'<a class="' + linkClass + '" href="/create">Create</a>' +
'<a class="' + linkClass + '" href="/how-to-use">How to Use</a>' +
'<a class="' + linkClass + ' "href="mailto:play.werewolf.contact@gmail.com?Subject=Werewolf App" target="_top">Feedback</a>' +
'<a class="' + linkClass + ' "href="https://github.com/alecm33/Werewolf" target="_top">Github</a>' +
'<a class="' + linkClass + '" href="https://www.buymeacoffee.com/alecm33">Support the App</a>';
'<a class="' + linkClass + ' "href="https://github.com/alecm33/Werewolf" target="_top">Github</a>';
}
function attachHamburgerListener () {

View File

@@ -153,9 +153,12 @@ export class GameCreationStepManager {
}
}).catch((e) => {
restoreButton();
toast(e.content, 'error', true, true, 'medium');
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');
}
});
}

View File

@@ -56,6 +56,19 @@ export class InProgress {
document.querySelector('#timer-container-moderator')?.remove();
document.querySelector('label[for="game-timer"]')?.remove();
}
const spectatorCount = this.container.querySelector('#spectator-count');
if (spectatorCount) {
spectatorCount?.addEventListener('click', () => {
Confirmation(SharedStateUtil.buildSpectatorList(this.stateBucket.currentGameState.spectators), null, true);
});
SharedStateUtil.setNumberOfSpectators(
this.stateBucket.currentGameState.spectators.length,
spectatorCount
);
}
}
renderPlayerView (isKilled = false) {
@@ -142,6 +155,7 @@ export class InProgress {
const killedPerson = this.stateBucket.currentGameState.people.find((person) => person.id === id);
if (killedPerson) {
killedPerson.out = true;
killedPerson.userType = globals.USER_TYPES.KILLED_PLAYER;
if (this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR) {
toast(killedPerson.name + ' killed.', 'success', true, true, 'medium');
this.renderPlayersWithRoleAndAlignmentInfo(this.stateBucket.currentGameState.status === globals.STATUS.ENDED);
@@ -459,7 +473,9 @@ function renderPotentialMods (gameState, group, transferModHandlers, socket) {
container.classList.add('potential-moderator');
container.setAttribute('tabindex', '0');
container.dataset.pointer = member.id;
container.innerText = member.name;
container.innerHTML =
'<div class=\'potential-mod-name\'>' + member.name + '</div>' +
'<div>' + member.userType + ' ' + globals.USER_TYPE_ICONS[member.userType] + ' </div>';
transferModHandlers[member.id] = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
ModalManager.dispelModal('transfer-mod-modal', 'transfer-mod-modal-background');

View File

@@ -3,6 +3,7 @@ import { toast } from '../../front_end_components/Toast.js';
import { globals } from '../../../config/globals.js';
import { HTMLFragments } from '../../front_end_components/HTMLFragments.js';
import { Confirmation } from '../../front_end_components/Confirmation.js';
import { SharedStateUtil } from './shared/SharedStateUtil.js';
export class Lobby {
constructor (containerId, stateBucket, socket) {
@@ -46,10 +47,14 @@ export class Lobby {
const playerCount = this.container.querySelector('#game-player-count');
playerCount.innerText = this.stateBucket.currentGameState.gameSize + ' Players';
setNumberOfSpectators(
this.container.querySelector('#spectator-count').addEventListener('click', () => {
Confirmation(SharedStateUtil.buildSpectatorList(this.stateBucket.currentGameState.spectators), null, true);
});
SharedStateUtil.setNumberOfSpectators(
this.stateBucket.currentGameState.spectators.length,
this.container.querySelector('#spectator-count')
)
);
const gameCode = this.container.querySelector('#game-code');
gameCode.innerHTML = 'Or enter this code on the homepage: <span>' +
@@ -96,7 +101,7 @@ export class Lobby {
this.socket.on(globals.EVENT_IDS.NEW_SPECTATOR, (spectator) => {
this.stateBucket.currentGameState.spectators.push(spectator);
setNumberOfSpectators(
SharedStateUtil.setNumberOfSpectators(
this.stateBucket.currentGameState.spectators.length,
document.getElementById('spectator-count')
);
@@ -136,12 +141,6 @@ export class Lobby {
}
}
function setNumberOfSpectators(number, el) {
el.innerText = '+ ' + (number === 1
? number + ' Spectator'
: number + ' Spectators');
}
function enableOrDisableStartButton (gameState, buttonContainer, handler) {
if (gameState.isFull) {
buttonContainer.querySelector('#start-game-button').addEventListener('click', handler);
@@ -189,6 +188,7 @@ function getTimeString (gameState) {
function renderLobbyPerson (name, userType) {
const el = document.createElement('div');
const personNameEl = document.createElement('div');
personNameEl.classList.add('lobby-player-name');
const personTypeEl = document.createElement('div');
personNameEl.innerText = name;
personTypeEl.innerText = userType + globals.USER_TYPE_ICONS[userType];

View File

@@ -130,6 +130,27 @@ export const SharedStateUtil = {
} else {
window.location = '/not-found?reason=' + encodeURIComponent('invalid-access-code');
}
},
buildSpectatorList (spectators) {
if (spectators.length === 0) {
return '<div>Nobody currently spectating.</div>';
}
let html = '';
for (const spectator of spectators) {
html += `<div class='spectator'>
<div class='spectator-name'>` + spectator.name + '</div>' +
'<div>' + 'spectator' + globals.USER_TYPE_ICONS.spectator + `</div>
</div>`;
}
return html;
},
setNumberOfSpectators: (number, el) => {
el.innerText = '+ ' + (number === 1
? number + ' Spectator'
: number + ' Spectators');
}
};

View File

@@ -27,45 +27,63 @@ const joinHandler = (e) => {
e.preventDefault();
const name = document.getElementById('player-new-name').value;
if (validateName(name)) {
document.getElementById('join-game-form').onsubmit = null;
document.getElementById('submit-new-name').classList.add('submitted');
document.getElementById('submit-new-name').setAttribute('value', 'Joining...');
XHRUtility.xhr(
'/api/games/' + accessCode + '/players',
'PATCH',
null,
JSON.stringify({
playerName: name,
accessCode: accessCode,
sessionCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.LOCAL),
localCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.PRODUCTION)
})
)
sendJoinRequest(e, name, accessCode)
.then((res) => {
const json = JSON.parse(res.content);
UserUtility.setAnonymousUserId(json.cookie, json.environment);
window.location = '/game/' + accessCode;
}).catch((res) => {
document.getElementById('join-game-form').onsubmit = joinHandler;
document.getElementById('submit-new-name').classList.remove('submitted');
document.getElementById('submit-new-name').setAttribute('value', 'Join Game');
if (res.status === 404) {
toast('This game was not found.', 'error', true, true, 'long');
} else if (res.status === 400) {
toast('This name is already taken.', 'error', true, true, 'long');
} else if (res.status >= 500) {
toast(
'The server is experiencing problems. Please try again later',
'error',
true
);
}
handleJoinError(e, res, joinHandler);
});
} else {
toast('Name must be between 1 and 30 characters.', 'error', true, true, 'long');
}
};
function sendJoinRequest (e, name, accessCode) {
document.getElementById('join-game-form').onsubmit = null;
if (e.submitter.getAttribute('id') === 'submit-join-as-player') {
document.getElementById('submit-join-as-player').classList.add('submitted');
document.getElementById('submit-join-as-player').setAttribute('value', '...');
} else {
document.getElementById('submit-join-as-spectator').classList.add('submitted');
document.getElementById('submit-join-as-spectator').setAttribute('value', '...');
}
return XHRUtility.xhr(
'/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: e.submitter.getAttribute('id') === 'submit-join-as-spectator'
})
);
}
function handleJoinError (e, res, joinHandler) {
document.getElementById('join-game-form').onsubmit = joinHandler;
e.submitter.classList.remove('submitted');
if (e.submitter.getAttribute('id') === 'submit-join-as-player') {
e.submitter.setAttribute('value', 'Join');
} else {
e.submitter.setAttribute('value', 'Spectate');
}
if (res.status === 404) {
toast('This game was not found.', 'error', true, true, 'long');
} else if (res.status === 400) {
toast(res.content, 'error', true, true, 'long');
} else if (res.status >= 500) {
toast(
'The server is experiencing problems. Please try again later',
'error',
true
);
}
}
function validateName (name) {
return typeof name === 'string' && name.length > 0 && name.length <= 30;
}

View File

@@ -572,7 +572,7 @@ input {
}
.good, .compact-card.good .card-role {
color: #4b6bfa;
color: #5f7cfb;
}
.good-players, #deck-good {
@@ -786,7 +786,7 @@ input {
display:inline-flex !important;
align-items: center !important;
color:#ffffff !important;
background-color:#333243 !important;
background-color:#2d2c3a !important;
border-radius: 5px !important;
border: 1px solid transparent !important;
padding: 7px 15px 7px 10px !important;

View File

@@ -2,13 +2,12 @@
border-radius: 2px;
text-align: center;
position: fixed;
border: 2px solid #333243;
width: 85%;
z-index: 100001;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #191920;
background-color: #292834;
align-items: center;
justify-content: center;
max-width: 25em;
@@ -32,6 +31,12 @@
font-size: 20px;
color: #e7e7e7;
margin: 1em 0 2em 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
max-height: 20em;
overflow-y: auto;
}
.confirmation-buttons button, .confirmation-buttons-centered button {

View File

@@ -106,7 +106,7 @@
display: flex;
justify-content: space-between;
background-color: #0f0f10;
border: 2px solid #333243;
border: 2px solid #2d2c3a;
padding: 5px;
border-radius: 3px;
font-size: 16px;
@@ -159,7 +159,7 @@
background-color: #191920;
padding: 10px;
border-radius: 3px;
border: 2px solid #333243;
border: 2px solid #2d2c3a;
position: relative;
}
@@ -173,7 +173,7 @@
#deck-count {
font-size: 30px;
background-color: #333243;
background-color: #2d2c3a;
width: fit-content;
padding: 0 5px;
border-radius: 3px;
@@ -236,7 +236,7 @@
z-index: 25;
top: 38px;
right: 29px;
background-color: #333243;
background-color: #2d2c3a;
border-radius: 3px;
box-shadow: -3px -3px 6px rgb(0 0 0 / 60%);
}
@@ -245,7 +245,7 @@
display: flex;
width: 100%;
padding: 10px;
background-color: #333243;
background-color: #2d2c3a;
text-align: center;
justify-content: center;
align-items: center;
@@ -362,7 +362,7 @@ select {
display: flex;
flex-wrap: wrap;
background-color: #191920;
border: 2px solid #333243;
border: 2px solid #2d2c3a;
border-radius: 3px;
}
@@ -593,7 +593,7 @@ input[type="number"] {
max-width: 20em;
margin: 0.5em;
cursor: pointer;
border: 2px solid #333243;
border: 2px solid #2d2c3a;
border-radius: 3px;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.6);
}
@@ -613,7 +613,7 @@ input[type="number"] {
.review-option {
background-color: #191920;
border: 2px solid #333243;
border: 2px solid #2d2c3a;
color: #e7e7e7;
padding: 10px;
font-size: 18px;

View File

@@ -1,18 +1,20 @@
.lobby-player, #moderator {
.lobby-player, #moderator, .spectator, .potential-moderator {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
background-color: black;
background-color: #171522;
color: #e7e7e7;
padding: 5px;
border-radius: 3px;
font-size: 17px;
width: fit-content;
min-width: 15em;
width: 17em;
border: 2px solid transparent;
margin: 0.25em 0;
box-shadow: 2px 2px 5px rgb(0 0 0 / 40%);
margin: 0 auto 0.25em auto;
}
.potential-moderator {
margin: 0.5em auto;
}
#lobby-players {
@@ -29,13 +31,17 @@
#spectator-count {
color: #b1afcd;
text-decoration: underline;
font-size: 17px;
margin: 5px 0;
cursor: pointer;
}
.lobby-player-client {
border: 2px solid #21ba45;
}
.lobby-player div:nth-child(2) {
.lobby-player div:nth-child(2), .spectator div:nth-child(2), .potential-moderator div:nth-child(2) {
color: #21ba45;
}
@@ -122,7 +128,7 @@ h1 {
}
#end-of-game-header h2 {
border: 1px solid #333243;
border: 1px solid #2d2c3a;
border-radius: 5px;
background-color: #1a1726;
padding: 7px;
@@ -141,19 +147,6 @@ h1 {
padding: 10px;
}
.potential-moderator {
display: flex;
color: #d7d7d7;
background-color: black;
border: 2px solid transparent;
align-items: center;
padding: 10px;
border-radius: 3px;
justify-content: space-between;
margin: 0.5em 0;
position: relative;
}
.potential-moderator:hover {
border: 2px solid #d7d7d7;
cursor: pointer;
@@ -170,7 +163,7 @@ h1 {
padding: 7px;
border-radius: 3px;
background-color: #121314;
border: 2px solid #333243;
border: 2px solid #2d2c3a;
color: #e7e7e7;
align-items: center;
display: flex;
@@ -243,7 +236,7 @@ h1 {
font-size: 20px;
font-family: signika-negative, sans-serif;
margin: 0.5em 0;
background-color: black;
background-color: #171522;
}
#role-info-modal h2 {
@@ -423,7 +416,7 @@ h1 {
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
background-color: #333243;
background-color: #2d2c3a;
border-radius: 3px;
min-width: 15em;
}
@@ -431,7 +424,7 @@ h1 {
#client-name {
color: #e7e7e7;
font-family: 'signika-negative', sans-serif;
font-size: 30px;
font-size: 25px;
margin: 0.25em 2em 0.25em 0;
}
@@ -439,7 +432,7 @@ h1 {
color: #21ba45;
font-family: 'signika-negative', sans-serif;
font-size: 25px;
background-color: black;
background-color: #171522;
border-radius: 3px;
display: block;
padding: 0 5px;
@@ -608,7 +601,7 @@ label[for='moderator'] {
margin-bottom: 1em;
padding: 0.5em;
border-radius: 3px;
background-color: #333243;
background-color: #2d2c3a;
}
canvas {
@@ -620,13 +613,12 @@ canvas {
border-left: 3px solid #21ba45;
display: flex;
color: #d7d7d7;
background-color: black;
background-color: #171522;
align-items: center;
padding: 0 5px;
justify-content: space-between;
margin: 0.25em 0;
position: relative;
box-shadow: 2px 3px 6px rgb(0 0 0 / 50%);
border-radius: 3px;
}
@@ -634,18 +626,20 @@ canvas {
justify-content: flex-end;
}
.game-player-name {
.game-player-name, .lobby-player-name, .spectator-name, .potential-mod-name {
position: relative;
min-width: 6em;
max-width: 10em;
overflow: hidden;
white-space: nowrap;
font-weight: bold;
font-size: 18px;
font-size: 16px;
text-overflow: ellipsis;
text-align: left;
}
.kill-player-button, .reveal-role-button {
background-color: #333243;
background-color: #434156;
font-family: 'signika-negative', sans-serif !important;
border-radius: 3px;
color: #e7e7e7;
@@ -750,7 +744,7 @@ canvas {
}
#game-parameters {
background-color: #333243;
background-color: #2d2c3a;
border-radius: 3px;
padding: 5px 20px;
}
@@ -765,12 +759,11 @@ canvas {
#game-player-list > div {
padding: 2px 10px;
border-radius: 3px;
margin-bottom: 0.5em;
margin-bottom: 0.25em;
}
#players-alive-label {
display: block;
margin-bottom: 10px;
font-size: 25px;
}
@@ -795,10 +788,10 @@ canvas {
}
#lobby-people-container , #game-people-container {
background-color: #333243;
padding: 10px;
border-radius: 3px;
min-height: 25em;
background-color: #292834;
max-width: 35em;
min-width: 17em;
margin-top: 1em;

View File

@@ -6,6 +6,25 @@
z-index: 1 !important;
}
#join-game-form .modal-button-container {
justify-content: flex-end;
margin-top: 2em;
}
#join-game-form .modal-button-container input {
width: 5em;
margin: 0 10px;
}
#join-game-form .modal-button-container #submit-join-as-spectator {
background-color: #045EA6;
}
#join-game-form .modal-button-container #submit-join-as-spectator:hover {
background-color: #0078D773;
border: 2px solid #045EA6;
}
#player-new-name {
max-width: 17em;
}

View File

@@ -7,7 +7,7 @@
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #191920;
background-color: #292834;
align-items: center;
justify-content: center;
max-width: 25em;
@@ -15,7 +15,6 @@
flex-direction: column;
padding: 1em;
display: none;
border: 2px solid #333243;
}
.modal-background {

View File

@@ -43,7 +43,8 @@
<input id="player-new-name" autocomplete="given-name" type="text"/>
</div>
<div class="modal-button-container">
<input type="submit" id="submit-new-name" value="Join Game"/>
<input type="submit" id="submit-join-as-spectator" value="Spectate"/>
<input type="submit" id="submit-join-as-player" value="Join"/>
</div>
</form>
</div>

View File

@@ -4,7 +4,7 @@ const express = require('express');
const app = express();
const ServerBootstrapper = require('./server/modules/ServerBootstrapper');
app.use(express.json());
app.use(express.json({ limit: '10kb' }));
const args = ServerBootstrapper.processCLIArgs();

View File

@@ -74,16 +74,17 @@ router.patch('/:code/players', function (req, res) {
|| !validateName(req.body.playerName)
|| !validateCookie(req.body.localCookie)
|| !validateCookie(req.body.sessionCookie)
|| !validateSpectatorFlag(req.body.joinAsSpectator)
) {
res.status(400).send();
} else {
const game = gameManager.activeGameRunner.activeGames.get(req.body.accessCode);
if (game) {
const inUseCookie = gameManager.environment === globals.ENVIRONMENT.PRODUCTION ? req.body.localCookie : req.body.sessionCookie;
gameManager.joinGame(game, req.body.playerName, inUseCookie).then((data) => {
gameManager.joinGame(game, req.body.playerName, inUseCookie, req.body.joinAsSpectator).then((data) => {
res.status(200).send({ cookie: data, environment: gameManager.environment });
}).catch((code) => {
res.status(code).send();
}).catch((data) => {
res.status(data.status).send(data.reason);
});
} else {
res.status(404).send();
@@ -130,4 +131,8 @@ function validateAccessCode (accessCode) {
return /^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === globals.ACCESS_CODE_LENGTH;
}
function validateSpectatorFlag (spectatorFlag) {
return typeof spectatorFlag === 'boolean';
}
module.exports = router;

View File

@@ -49,7 +49,7 @@ const globals = {
USER_TYPES: {
MODERATOR: 'moderator',
PLAYER: 'player',
TEMPORARY_MODERATOR: 'player / temp mod',
TEMPORARY_MODERATOR: 'temp mod',
KILLED_PLAYER: 'killed',
SPECTATOR: 'spectator'
},
@@ -84,7 +84,8 @@ const globals = {
RESUME_TIMER: 'resumeTimer',
GET_TIME_REMAINING: 'getTimeRemaining'
},
MOCK_AUTH: 'mock_auth'
MOCK_AUTH: 'mock_auth',
MAX_SPECTATORS: 25
};
module.exports = globals;

View File

@@ -199,6 +199,7 @@ class GameManager {
if (game.spectators.includes(person)) {
game.spectators.splice(game.spectators.indexOf(person), 1);
}
namespace.in(game.accessCode).emit(globals.EVENTS.NEW_SPECTATOR);
}
person.userType = globals.USER_TYPES.MODERATOR;
game.moderator = person;
@@ -224,13 +225,18 @@ class GameManager {
}
};
joinGame = (game, name, cookie) => {
joinGame = (game, name, cookie, joinAsSpectator) => {
const matchingPerson = findPersonByField(game, 'cookie', cookie);
if (matchingPerson) {
return Promise.resolve(matchingPerson.cookie);
}
if (isNameTaken(game, name)) {
return Promise.reject(400);
return Promise.reject({ status: 400, reason: 'This name is taken.' });
}
if (joinAsSpectator && game.spectators.length === globals.MAX_SPECTATORS) {
return Promise.reject({ status: 400, reason: 'There are too many people already spectating.' });
} else if (joinAsSpectator) {
return addSpectator(game, name, this.logger, this.namespace);
}
const unassignedPerson = game.moderator.assigned === false
? game.moderator
@@ -246,20 +252,11 @@ class GameManager {
game.isFull
);
return Promise.resolve(unassignedPerson.cookie);
} else { // if the game is full, make them a spectator.
const spectator = new Person(
createRandomId(),
createRandomId(),
name,
globals.USER_TYPES.SPECTATOR
);
this.logger.trace('new spectator: ' + spectator.name);
game.spectators.push(spectator);
this.namespace.in(game.accessCode).emit(
globals.EVENTS.NEW_SPECTATOR,
GameStateCurator.mapPerson(spectator)
);
return Promise.resolve(spectator.cookie);
} else {
if (game.spectators.length === globals.MAX_SPECTATORS) {
return Promise.reject({ status: 400, reason: 'This game has reached the maximum number of players and spectators.' });
}
return addSpectator(game, name, this.logger, this.namespace);
}
};
@@ -496,4 +493,20 @@ function getGameSize (cards) {
return quantity;
}
function addSpectator (game, name, logger, namespace) {
const spectator = new Person(
createRandomId(),
createRandomId(),
name,
globals.USER_TYPES.SPECTATOR
);
logger.trace('new spectator: ' + spectator.name);
game.spectators.push(spectator);
namespace.in(game.accessCode).emit(
globals.EVENTS.NEW_SPECTATOR,
GameStateCurator.mapPerson(spectator)
);
return Promise.resolve(spectator.cookie);
}
module.exports = GameManager;

View File

@@ -199,7 +199,7 @@ describe('game page', () => {
it('should display the mod transfer modal, with the single spectator available for selection', () => {
document.getElementById('mod-transfer-button').click();
expect(document.querySelector('div[data-pointer="MGGVR8KQ7V7HGN3QBLJ5339ZL"].potential-moderator')
.innerText).toEqual('Matt');
.innerText).toContain('Matt');
document.getElementById('close-mod-transfer-modal-button').click();
});

View File

@@ -163,7 +163,8 @@ export const mockGames = {
paused: true,
timeRemaining: 120000
},
isFull: true
isFull: true,
spectators: []
},
moderatorGame:
{