mirror of
https://github.com/AlecM33/Werewolf.git
synced 2025-12-26 15:57:50 +01:00
Merge pull request #107 from AlecM33/restart-game
Restart game functionality
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
export const globals = {
|
export const globals = {
|
||||||
USER_SIGNATURE_LENGTH: 25,
|
USER_SIGNATURE_LENGTH: 25,
|
||||||
CLOCK_TICK_INTERVAL_MILLIS: 10,
|
CLOCK_TICK_INTERVAL_MILLIS: 100,
|
||||||
MAX_CUSTOM_ROLE_NAME_LENGTH: 30,
|
MAX_CUSTOM_ROLE_NAME_LENGTH: 30,
|
||||||
MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH: 500,
|
MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH: 500,
|
||||||
TOAST_DURATION_DEFAULT: 6,
|
TOAST_DURATION_DEFAULT: 6,
|
||||||
|
|||||||
@@ -107,13 +107,23 @@ export class GameCreationStepManager {
|
|||||||
5: {
|
5: {
|
||||||
title: 'Review and submit:',
|
title: 'Review and submit:',
|
||||||
backHandler: this.defaultBackHandler,
|
backHandler: this.defaultBackHandler,
|
||||||
forwardHandler: (deck, hasTimer, hasDedicatedModerator, moderatorName, timerParams) => {
|
forwardHandler: () => {
|
||||||
|
const button = document.getElementById('create-game');
|
||||||
|
button.removeEventListener('click', this.steps['5'].forwardHandler);
|
||||||
|
button.classList.add('submitted');
|
||||||
|
button.innerText = 'Creating';
|
||||||
XHRUtility.xhr(
|
XHRUtility.xhr(
|
||||||
'/api/games/create',
|
'/api/games/create',
|
||||||
'POST',
|
'POST',
|
||||||
null,
|
null,
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
new Game(deck, hasTimer, hasDedicatedModerator, moderatorName, timerParams)
|
new Game(
|
||||||
|
this.currentGame.deck.filter((card) => card.quantity > 0),
|
||||||
|
this.currentGame.hasTimer,
|
||||||
|
this.currentGame.hasDedicatedModerator,
|
||||||
|
this.currentGame.moderatorName,
|
||||||
|
this.currentGame.timerParams
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@@ -128,9 +138,10 @@ export class GameCreationStepManager {
|
|||||||
}
|
}
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
const button = document.getElementById('create-game');
|
const button = document.getElementById('create-game');
|
||||||
button.innerText = 'Create Game';
|
button.innerText = 'Create';
|
||||||
button.classList.remove('submitted');
|
button.classList.remove('submitted');
|
||||||
button.addEventListener('click', this.steps['4'].forwardHandler);
|
button.addEventListener('click', this.steps['5'].forwardHandler);
|
||||||
|
toast(e.content, 'error', true, true, 'medium');
|
||||||
if (e.status === 429) {
|
if (e.status === 429) {
|
||||||
toast('You\'ve sent this request too many times.', 'error', true, true, 'medium');
|
toast('You\'ve sent this request too many times.', 'error', true, true, 'medium');
|
||||||
}
|
}
|
||||||
@@ -449,18 +460,7 @@ function showButtons (back, forward, forwardHandler, backHandler, builtGame = nu
|
|||||||
createButton.innerText = 'Create';
|
createButton.innerText = 'Create';
|
||||||
createButton.setAttribute('id', 'create-game');
|
createButton.setAttribute('id', 'create-game');
|
||||||
createButton.classList.add('app-button');
|
createButton.classList.add('app-button');
|
||||||
createButton.addEventListener('click', () => {
|
createButton.addEventListener('click', forwardHandler);
|
||||||
createButton.removeEventListener('click', forwardHandler);
|
|
||||||
createButton.classList.add('submitted');
|
|
||||||
createButton.innerText = 'Creating...';
|
|
||||||
forwardHandler(
|
|
||||||
builtGame.deck.filter((card) => card.quantity > 0),
|
|
||||||
builtGame.hasTimer,
|
|
||||||
builtGame.hasDedicatedModerator,
|
|
||||||
builtGame.moderatorName,
|
|
||||||
builtGame.timerParams
|
|
||||||
);
|
|
||||||
});
|
|
||||||
document.getElementById('tracker-container').appendChild(createButton);
|
document.getElementById('tracker-container').appendChild(createButton);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { globals } from '../config/globals.js';
|
|||||||
import { toast } from './Toast.js';
|
import { toast } from './Toast.js';
|
||||||
import { HTMLFragments } from './HTMLFragments.js';
|
import { HTMLFragments } from './HTMLFragments.js';
|
||||||
import { ModalManager } from './ModalManager.js';
|
import { ModalManager } from './ModalManager.js';
|
||||||
|
import { XHRUtility } from './XHRUtility.js';
|
||||||
|
import { UserUtility } from './UserUtility.js';
|
||||||
|
|
||||||
export class GameStateRenderer {
|
export class GameStateRenderer {
|
||||||
constructor (stateBucket, socket) {
|
constructor (stateBucket, socket) {
|
||||||
@@ -10,12 +12,40 @@ export class GameStateRenderer {
|
|||||||
this.killPlayerHandlers = {};
|
this.killPlayerHandlers = {};
|
||||||
this.revealRoleHandlers = {};
|
this.revealRoleHandlers = {};
|
||||||
this.transferModHandlers = {};
|
this.transferModHandlers = {};
|
||||||
this.startGameHandler = (e) => {
|
this.startGameHandler = (e) => { // TODO: prevent multiple emissions of this event (recommend converting to XHR)
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (confirm('Start the game and deal roles?')) {
|
if (confirm('Start the game and deal roles?')) {
|
||||||
socket.emit(globals.COMMANDS.START_GAME, this.stateBucket.currentGameState.accessCode);
|
socket.emit(globals.COMMANDS.START_GAME, this.stateBucket.currentGameState.accessCode);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
this.restartGameHandler = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const button = document.getElementById('restart-game');
|
||||||
|
button.removeEventListener('click', this.restartGameHandler);
|
||||||
|
button.classList.add('submitted');
|
||||||
|
button.innerText = 'Restarting...';
|
||||||
|
XHRUtility.xhr(
|
||||||
|
'/api/games/' + this.stateBucket.currentGameState.accessCode + '/restart',
|
||||||
|
'PATCH',
|
||||||
|
null,
|
||||||
|
JSON.stringify({
|
||||||
|
playerName: this.stateBucket.currentGameState.client.name,
|
||||||
|
accessCode: this.stateBucket.currentGameState.accessCode,
|
||||||
|
sessionCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.LOCAL),
|
||||||
|
localCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.PRODUCTION)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
toast('Game restarted!', 'success', true, true, 'medium');
|
||||||
|
})
|
||||||
|
.catch((res) => {
|
||||||
|
const button = document.getElementById('restart-game');
|
||||||
|
button.innerText = 'Run it back 🔄';
|
||||||
|
button.classList.remove('submitted');
|
||||||
|
button.addEventListener('click', this.restartGameHandler);
|
||||||
|
toast(res.content, 'error', true, true, 'medium');
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLobbyPlayers () {
|
renderLobbyPlayers () {
|
||||||
@@ -256,7 +286,17 @@ export class GameStateRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderEndOfGame () {
|
renderEndOfGame (gameState) {
|
||||||
|
if (
|
||||||
|
gameState.client.userType === globals.USER_TYPES.MODERATOR
|
||||||
|
|| gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
|
||||||
|
) {
|
||||||
|
const restartGameContainer = document.createElement('div');
|
||||||
|
restartGameContainer.innerHTML = HTMLFragments.RESTART_GAME_BUTTON;
|
||||||
|
const button = restartGameContainer.querySelector('#restart-game');
|
||||||
|
button.addEventListener('click', this.restartGameHandler);
|
||||||
|
document.getElementById('end-of-game-buttons').appendChild(restartGameContainer);
|
||||||
|
}
|
||||||
this.renderPlayersWithNoRoleInformationUnlessRevealed();
|
this.renderPlayersWithNoRoleInformationUnlessRevealed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,6 +313,10 @@ function renderPotentialMods (gameState, group, transferModHandlers, socket) {
|
|||||||
transferModHandlers[member.id] = (e) => {
|
transferModHandlers[member.id] = (e) => {
|
||||||
if (e.type === 'click' || e.code === 'Enter') {
|
if (e.type === 'click' || e.code === 'Enter') {
|
||||||
if (confirm('Transfer moderator powers to ' + member.name + '?')) {
|
if (confirm('Transfer moderator powers to ' + member.name + '?')) {
|
||||||
|
const transferPrompt = document.getElementById('transfer-mod-prompt');
|
||||||
|
if (transferPrompt !== null) {
|
||||||
|
transferPrompt.innerHTML = '';
|
||||||
|
}
|
||||||
socket.emit(globals.COMMANDS.TRANSFER_MODERATOR, gameState.accessCode, member.id);
|
socket.emit(globals.COMMANDS.TRANSFER_MODERATOR, gameState.accessCode, member.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ export class GameTimerManager {
|
|||||||
const playBtn = document.createElement('img');
|
const playBtn = document.createElement('img');
|
||||||
playBtn.setAttribute('src', '../images/play-button.svg');
|
playBtn.setAttribute('src', '../images/play-button.svg');
|
||||||
playBtn.addEventListener('click', this.playListener);
|
playBtn.addEventListener('click', this.playListener);
|
||||||
|
document.querySelector('#play-pause-placeholder')?.remove();
|
||||||
document.getElementById('play-pause').appendChild(playBtn);
|
document.getElementById('play-pause').appendChild(playBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +149,7 @@ export class GameTimerManager {
|
|||||||
const pauseBtn = document.createElement('img');
|
const pauseBtn = document.createElement('img');
|
||||||
pauseBtn.setAttribute('src', '../images/pause-button.svg');
|
pauseBtn.setAttribute('src', '../images/pause-button.svg');
|
||||||
pauseBtn.addEventListener('click', this.pauseListener);
|
pauseBtn.addEventListener('click', this.pauseListener);
|
||||||
|
document.querySelector('#play-pause-placeholder')?.remove();
|
||||||
document.getElementById('play-pause').appendChild(pauseBtn);
|
document.getElementById('play-pause').appendChild(pauseBtn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export const HTMLFragments = {
|
|||||||
<label id='players-alive-label'></label>
|
<label id='players-alive-label'></label>
|
||||||
<div id='game-player-list'></div>
|
<div id='game-player-list'></div>
|
||||||
</div>`,
|
</div>`,
|
||||||
MODERATOR_GAME_VIEW:
|
TRANSFER_MOD_MODAL:
|
||||||
`<div id='transfer-mod-modal-background' class='modal-background'></div>
|
`<div id='transfer-mod-modal-background' class='modal-background'></div>
|
||||||
<div tabindex='-1' id='transfer-mod-modal' class='modal'>
|
<div tabindex='-1' id='transfer-mod-modal' class='modal'>
|
||||||
<h3>Transfer Mod Powers 👑</h3>
|
<h3>Transfer Mod Powers 👑</h3>
|
||||||
@@ -88,14 +88,17 @@ export const HTMLFragments = {
|
|||||||
<div class='modal-button-container'>
|
<div class='modal-button-container'>
|
||||||
<button id='close-mod-transfer-modal-button' class='app-button cancel'>Cancel</button>
|
<button id='close-mod-transfer-modal-button' class='app-button cancel'>Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>`,
|
||||||
<div id='game-header'>
|
MODERATOR_GAME_VIEW:
|
||||||
<div class='timer-container-moderator'>
|
`<div id='game-header'>
|
||||||
|
<div id='timer-container-moderator'>
|
||||||
<div>
|
<div>
|
||||||
<label for='game-timer'>Time Remaining</label>
|
<label for='game-timer'>Time Remaining</label>
|
||||||
<div id='game-timer'></div>
|
<div id='game-timer'></div>
|
||||||
</div>
|
</div>
|
||||||
<div id='play-pause'> </div>
|
<div id='play-pause'>
|
||||||
|
<div id="play-pause-placeholder"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button id='mod-transfer-button' class='moderator-player-button make-mod-button'>Transfer Mod Powers \uD83D\uDD00</button>
|
<button id='mod-transfer-button' class='moderator-player-button make-mod-button'>Transfer Mod Powers \uD83D\uDD00</button>
|
||||||
<div>
|
<div>
|
||||||
@@ -116,19 +119,8 @@ export const HTMLFragments = {
|
|||||||
</div>
|
</div>
|
||||||
</div>`,
|
</div>`,
|
||||||
TEMP_MOD_GAME_VIEW:
|
TEMP_MOD_GAME_VIEW:
|
||||||
`<div id='transfer-mod-modal-background' class='modal-background'></div>
|
`<div id='game-header'>
|
||||||
<div id='transfer-mod-modal' class='modal'>
|
<div id='timer-container-moderator'>
|
||||||
<form id='transfer-mod-form'>
|
|
||||||
<div id='transfer-mod-form-content'>
|
|
||||||
<h3>Transfer Mod Powers 👑</h3>
|
|
||||||
</div>
|
|
||||||
<div class='modal-button-container'>
|
|
||||||
<button id='close-modal-button' class='cancel app-button'>Cancel</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div id='game-header'>
|
|
||||||
<div class='timer-container-moderator'>
|
|
||||||
<div>
|
<div>
|
||||||
<label for='game-timer'>Time Remaining</label>
|
<label for='game-timer'>Time Remaining</label>
|
||||||
<div id='game-timer'></div>
|
<div id='game-timer'></div>
|
||||||
@@ -216,21 +208,27 @@ export const HTMLFragments = {
|
|||||||
</div>
|
</div>
|
||||||
</div>`,
|
</div>`,
|
||||||
END_OF_GAME_VIEW:
|
END_OF_GAME_VIEW:
|
||||||
`<h2>The moderator has ended the game. Roles are revealed.</h2>
|
`<div id='end-of-game-header'>
|
||||||
<div id='end-of-game-header'>
|
<h2>🏁 The moderator has ended the game. Roles are revealed.</h2>
|
||||||
<div>
|
<div id="end-of-game-buttons">
|
||||||
<button id='role-info-button' class='app-button'>View Role Info <img src='/images/info.svg'/></button>
|
<div>
|
||||||
</div>
|
<button id='role-info-button' class='app-button'>View Role Info <img src='/images/info.svg'/></button>
|
||||||
<div>
|
</div>
|
||||||
<a href='/'>
|
<div>
|
||||||
<button class='app-button'>Go Home \uD83C\uDFE0</button>
|
<a href='/'>
|
||||||
</a>
|
<button class='app-button'>Go Home \uD83C\uDFE0</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id='game-people-container'>
|
<div id='game-people-container'>
|
||||||
<label id='players-alive-label'></label>
|
<label id='players-alive-label'></label>
|
||||||
<div id='game-player-list'></div>
|
<div id='game-player-list'></div>
|
||||||
</div>`,
|
</div>`,
|
||||||
|
RESTART_GAME_BUTTON:
|
||||||
|
`<div>
|
||||||
|
<button id='restart-game' class='app-button'>Run it back 🔄</button>
|
||||||
|
</div>`,
|
||||||
CREATE_GAME_DECK:
|
CREATE_GAME_DECK:
|
||||||
`<div id='deck-container'>
|
`<div id='deck-container'>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ function syncWithGame (stateBucket, gameTimerManager, gameStateRenderer, timerWo
|
|||||||
document.querySelector('.spinner-background')?.remove();
|
document.querySelector('.spinner-background')?.remove();
|
||||||
document.getElementById('game-content').innerHTML = HTMLFragments.INITIAL_GAME_DOM;
|
document.getElementById('game-content').innerHTML = HTMLFragments.INITIAL_GAME_DOM;
|
||||||
toast('You are connected.', 'success', true, true, 'short');
|
toast('You are connected.', 'success', true, true, 'short');
|
||||||
processGameState(stateBucket.currentGameState, cookie, socket, gameStateRenderer, gameTimerManager, timerWorker);
|
processGameState(stateBucket.currentGameState, cookie, socket, gameStateRenderer, gameTimerManager, timerWorker, true, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -67,7 +67,28 @@ function syncWithGame (stateBucket, gameTimerManager, gameStateRenderer, timerWo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processGameState (currentGameState, userId, socket, gameStateRenderer, gameTimerManager, timerWorker, refreshPrompt = true) {
|
function processGameState (
|
||||||
|
currentGameState,
|
||||||
|
userId,
|
||||||
|
socket,
|
||||||
|
gameStateRenderer,
|
||||||
|
gameTimerManager,
|
||||||
|
timerWorker,
|
||||||
|
refreshPrompt = true,
|
||||||
|
animateContainer = false
|
||||||
|
) {
|
||||||
|
const containerAnimation = document.getElementById('game-state-container').animate(
|
||||||
|
[
|
||||||
|
{ opacity: '0', transform: 'translateY(10px)' },
|
||||||
|
{ opacity: '1', transform: 'translateY(0px)' }
|
||||||
|
], {
|
||||||
|
duration: 500,
|
||||||
|
easing: 'ease-in-out',
|
||||||
|
fill: 'both'
|
||||||
|
});
|
||||||
|
if (animateContainer) {
|
||||||
|
containerAnimation.play();
|
||||||
|
}
|
||||||
displayClientInfo(currentGameState.client.name, currentGameState.client.userType);
|
displayClientInfo(currentGameState.client.name, currentGameState.client.userType);
|
||||||
if (refreshPrompt) {
|
if (refreshPrompt) {
|
||||||
removeStartGameFunctionalityIfPresent(gameStateRenderer);
|
removeStartGameFunctionalityIfPresent(gameStateRenderer);
|
||||||
@@ -102,10 +123,12 @@ function processGameState (currentGameState, userId, socket, gameStateRenderer,
|
|||||||
gameStateRenderer.renderPlayerView(true);
|
gameStateRenderer.renderPlayerView(true);
|
||||||
break;
|
break;
|
||||||
case globals.USER_TYPES.MODERATOR:
|
case globals.USER_TYPES.MODERATOR:
|
||||||
|
document.getElementById('transfer-mod-prompt').innerHTML = HTMLFragments.TRANSFER_MOD_MODAL;
|
||||||
document.getElementById('game-state-container').innerHTML = HTMLFragments.MODERATOR_GAME_VIEW;
|
document.getElementById('game-state-container').innerHTML = HTMLFragments.MODERATOR_GAME_VIEW;
|
||||||
gameStateRenderer.renderModeratorView();
|
gameStateRenderer.renderModeratorView();
|
||||||
break;
|
break;
|
||||||
case globals.USER_TYPES.TEMPORARY_MODERATOR:
|
case globals.USER_TYPES.TEMPORARY_MODERATOR:
|
||||||
|
document.getElementById('transfer-mod-prompt').innerHTML = HTMLFragments.TRANSFER_MOD_MODAL;
|
||||||
document.getElementById('game-state-container').innerHTML = HTMLFragments.TEMP_MOD_GAME_VIEW;
|
document.getElementById('game-state-container').innerHTML = HTMLFragments.TEMP_MOD_GAME_VIEW;
|
||||||
gameStateRenderer.renderTempModView();
|
gameStateRenderer.renderTempModView();
|
||||||
break;
|
break;
|
||||||
@@ -120,14 +143,14 @@ function processGameState (currentGameState, userId, socket, gameStateRenderer,
|
|||||||
socket.emit(globals.COMMANDS.GET_TIME_REMAINING, currentGameState.accessCode);
|
socket.emit(globals.COMMANDS.GET_TIME_REMAINING, currentGameState.accessCode);
|
||||||
} else {
|
} else {
|
||||||
document.querySelector('#game-timer')?.remove();
|
document.querySelector('#game-timer')?.remove();
|
||||||
|
document.querySelector('#timer-container-moderator')?.remove();
|
||||||
document.querySelector('label[for="game-timer"]')?.remove();
|
document.querySelector('label[for="game-timer"]')?.remove();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case globals.STATUS.ENDED: {
|
case globals.STATUS.ENDED: {
|
||||||
const container = document.getElementById('game-state-container');
|
const container = document.getElementById('game-state-container');
|
||||||
container.innerHTML = HTMLFragments.END_OF_GAME_VIEW;
|
container.innerHTML = HTMLFragments.END_OF_GAME_VIEW;
|
||||||
container.classList.add('vertical-flex');
|
gameStateRenderer.renderEndOfGame(currentGameState);
|
||||||
gameStateRenderer.renderEndOfGame();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -190,7 +213,9 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW
|
|||||||
socket,
|
socket,
|
||||||
gameStateRenderer,
|
gameStateRenderer,
|
||||||
gameTimerManager,
|
gameTimerManager,
|
||||||
timerWorker
|
timerWorker,
|
||||||
|
true,
|
||||||
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -209,7 +234,9 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW
|
|||||||
socket,
|
socket,
|
||||||
gameStateRenderer,
|
gameStateRenderer,
|
||||||
gameTimerManager,
|
gameTimerManager,
|
||||||
timerWorker
|
timerWorker,
|
||||||
|
true,
|
||||||
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -280,6 +307,7 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW
|
|||||||
gameStateRenderer,
|
gameStateRenderer,
|
||||||
gameTimerManager,
|
gameTimerManager,
|
||||||
timerWorker,
|
timerWorker,
|
||||||
|
false,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -293,7 +321,9 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW
|
|||||||
socket,
|
socket,
|
||||||
gameStateRenderer,
|
gameStateRenderer,
|
||||||
gameTimerManager,
|
gameTimerManager,
|
||||||
timerWorker
|
timerWorker,
|
||||||
|
true,
|
||||||
|
true
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -357,7 +387,7 @@ function activateRoleInfoButton (deck) {
|
|||||||
});
|
});
|
||||||
document.getElementById('role-info-button').addEventListener('click', (e) => {
|
document.getElementById('role-info-button').addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
document.getElementById('prompt').innerHTML = HTMLFragments.ROLE_INFO_MODAL;
|
document.getElementById('role-info-prompt').innerHTML = HTMLFragments.ROLE_INFO_MODAL;
|
||||||
const modalContent = document.getElementById('game-role-info-container');
|
const modalContent = document.getElementById('game-role-info-container');
|
||||||
for (const card of deck) {
|
for (const card of deck) {
|
||||||
const roleDiv = document.createElement('div');
|
const roleDiv = document.createElement('div');
|
||||||
|
|||||||
@@ -4,45 +4,50 @@ import { injectNavbar } from '../modules/Navbar.js';
|
|||||||
|
|
||||||
const home = () => {
|
const home = () => {
|
||||||
injectNavbar();
|
injectNavbar();
|
||||||
document.getElementById('join-form').onsubmit = (e) => {
|
document.getElementById('join-form').addEventListener('submit', attemptToJoinGame);
|
||||||
e.preventDefault();
|
|
||||||
const userCode = document.getElementById('room-code').value;
|
|
||||||
if (roomCodeIsValid(userCode)) {
|
|
||||||
attemptToJoinGame(userCode);
|
|
||||||
} else {
|
|
||||||
toast('Invalid code. Codes are 4 numbers or letters.', 'error', true, true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function roomCodeIsValid (code) {
|
function roomCodeIsValid (code) {
|
||||||
return typeof code === 'string' && /^[A-Z0-9]{4}$/.test(code.toUpperCase().trim());
|
return typeof code === 'string' && /^[A-Z0-9]{4}$/.test(code.toUpperCase().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
function attemptToJoinGame (code) {
|
function attemptToJoinGame (event) {
|
||||||
XHRUtility.xhr(
|
event.preventDefault();
|
||||||
'/api/games/' + code.toUpperCase().trim() + '/availability',
|
const userCode = document.getElementById('room-code').value;
|
||||||
'GET',
|
if (roomCodeIsValid(userCode)) {
|
||||||
null,
|
const form = document.getElementById('join-form');
|
||||||
null
|
form.removeEventListener('submit', attemptToJoinGame);
|
||||||
)
|
form.classList.add('submitted');
|
||||||
.then((res) => {
|
document.getElementById('join-button')?.setAttribute('value', 'Joining...');
|
||||||
if (res.status === 200) {
|
XHRUtility.xhr(
|
||||||
const json = JSON.parse(res.content);
|
'/api/games/' + userCode.toUpperCase().trim() + '/availability',
|
||||||
window.location = window.location.protocol + '//' + window.location.host +
|
'GET',
|
||||||
'/join/' + encodeURIComponent(json.accessCode) +
|
null,
|
||||||
'?playerCount=' + encodeURIComponent(json.playerCount) +
|
null
|
||||||
'&timer=' + encodeURIComponent(getTimeString(json.timerParams));
|
)
|
||||||
}
|
.then((res) => {
|
||||||
}).catch((res) => {
|
if (res.status === 200) {
|
||||||
if (res.status === 404) {
|
const json = JSON.parse(res.content);
|
||||||
toast('Game not found', 'error', true);
|
window.location = window.location.protocol + '//' + window.location.host +
|
||||||
} else if (res.status === 400) {
|
'/join/' + encodeURIComponent(json.accessCode) +
|
||||||
toast(res.content, 'error', true);
|
'?playerCount=' + encodeURIComponent(json.playerCount) +
|
||||||
} else {
|
'&timer=' + encodeURIComponent(getTimeString(json.timerParams));
|
||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTimeString (timerParams) {
|
function getTimeString (timerParams) {
|
||||||
|
|||||||
@@ -607,7 +607,7 @@ input {
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100% + 100px);
|
height: calc(100% + 100px);
|
||||||
background-color: rgba(0, 0, 0, 0.75);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@
|
|||||||
#deck-status-container {
|
#deck-status-container {
|
||||||
width: 20em;
|
width: 20em;
|
||||||
max-width: 95%;
|
max-width: 95%;
|
||||||
height: 13em;
|
height: 10em;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@@ -356,7 +356,7 @@ input[type="number"] {
|
|||||||
#deck-select {
|
#deck-select {
|
||||||
margin: 0.5em 1em 1.5em 0;
|
margin: 0.5em 1em 1.5em 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
height: 15em;
|
height: 12em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.deck-select-role {
|
.deck-select-role {
|
||||||
|
|||||||
@@ -47,14 +47,28 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
width: 95%;
|
width: 95%;
|
||||||
margin: 1em auto 100px auto;
|
margin: 1em auto 100px auto;
|
||||||
|
animation: fade-in-slide-up 2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
#game-state-container h2 {
|
#game-state-container h2 {
|
||||||
margin: 1em 0;
|
margin: 0.5em 0;
|
||||||
text-align: center;
|
|
||||||
max-width: 17em;
|
max-width: 17em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#restart-game {
|
||||||
|
background-color: #0078D7;
|
||||||
|
min-width: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#play-pause-placeholder {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#restart-game:hover {
|
||||||
|
border-color: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
#lobby-header {
|
#lobby-header {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
max-width: 95%;
|
max-width: 95%;
|
||||||
@@ -84,6 +98,7 @@ h1 {
|
|||||||
|
|
||||||
#end-of-game-header {
|
#end-of-game-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -91,6 +106,7 @@ h1 {
|
|||||||
|
|
||||||
#end-of-game-header button {
|
#end-of-game-header button {
|
||||||
margin: 0.5em;
|
margin: 0.5em;
|
||||||
|
min-width: 10em;
|
||||||
}
|
}
|
||||||
.potential-moderator {
|
.potential-moderator {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -494,12 +510,19 @@ label[for='moderator'] {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timer-container-moderator {
|
#game-header button {
|
||||||
|
min-width: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#timer-container-moderator {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
|
padding: 0.5em;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: #333243;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-player {
|
.game-player {
|
||||||
@@ -517,7 +540,7 @@ label[for='moderator'] {
|
|||||||
|
|
||||||
.game-player-name {
|
.game-player-name {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 10em;
|
min-width: 6em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
<link rel="preload" href="/webfonts/SignikaNegative-Light.woff2" as="font" type="font/woff2" crossorigin>
|
<link rel="preload" href="/webfonts/SignikaNegative-Light.woff2" as="font" type="font/woff2" crossorigin>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="prompt"></div>
|
<div id="role-info-prompt"></div>
|
||||||
|
<div id="transfer-mod-prompt"></div>
|
||||||
<div class="spinner-background"></div>
|
<div class="spinner-background"></div>
|
||||||
<div class="spinner-container">
|
<div class="spinner-container">
|
||||||
<div class="lds-spinner">
|
<div class="lds-spinner">
|
||||||
|
|||||||
@@ -89,6 +89,29 @@ router.patch('/:code/players', function (req, res) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.patch('/:code/restart', function (req, res) {
|
||||||
|
if (
|
||||||
|
req.body === null
|
||||||
|
|| !validateAccessCode(req.body.accessCode)
|
||||||
|
|| !validateName(req.body.playerName)
|
||||||
|
|| !validateCookie(req.body.localCookie)
|
||||||
|
|| !validateCookie(req.body.sessionCookie)
|
||||||
|
) {
|
||||||
|
res.status(400).send();
|
||||||
|
} else {
|
||||||
|
const game = gameManager.activeGameRunner.activeGames[req.body.accessCode];
|
||||||
|
if (game) {
|
||||||
|
gameManager.restartGame(game, gameManager.namespace).then((data) => {
|
||||||
|
res.status(200).send();
|
||||||
|
}).catch((code) => {
|
||||||
|
res.status(code).send();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(404).send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
router.get('/environment', function (req, res) {
|
router.get('/environment', function (req, res) {
|
||||||
res.status(200).send(gameManager.environment);
|
res.status(200).send(gameManager.environment);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ const globals = {
|
|||||||
ACCESS_CODE_CHAR_POOL: 'BCDFGHJKLMNPQRSTVWXYZ23456789',
|
ACCESS_CODE_CHAR_POOL: 'BCDFGHJKLMNPQRSTVWXYZ23456789',
|
||||||
ACCESS_CODE_LENGTH: 4,
|
ACCESS_CODE_LENGTH: 4,
|
||||||
ACCESS_CODE_GENERATION_ATTEMPTS: 50,
|
ACCESS_CODE_GENERATION_ATTEMPTS: 50,
|
||||||
CLOCK_TICK_INTERVAL_MILLIS: 10,
|
CLOCK_TICK_INTERVAL_MILLIS: 100,
|
||||||
STALE_GAME_HOURS: 12,
|
STALE_GAME_HOURS: 12,
|
||||||
CLIENT_COMMANDS: {
|
CLIENT_COMMANDS: {
|
||||||
FETCH_GAME_STATE: 'fetchGameState',
|
FETCH_GAME_STATE: 'fetchGameState',
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
class Game {
|
class Game {
|
||||||
constructor (accessCode, status, people, deck, hasTimer, moderator, timerParams = null) {
|
constructor (
|
||||||
|
accessCode,
|
||||||
|
status,
|
||||||
|
people,
|
||||||
|
deck,
|
||||||
|
hasTimer,
|
||||||
|
moderator,
|
||||||
|
hasDedicatedModerator,
|
||||||
|
originalModeratorId,
|
||||||
|
timerParams = null
|
||||||
|
) {
|
||||||
this.accessCode = accessCode;
|
this.accessCode = accessCode;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.moderator = moderator;
|
this.moderator = moderator;
|
||||||
this.people = people;
|
this.people = people;
|
||||||
this.deck = deck;
|
this.deck = deck;
|
||||||
this.hasTimer = hasTimer;
|
this.hasTimer = hasTimer;
|
||||||
|
this.hasDedicatedModerator = hasDedicatedModerator;
|
||||||
|
this.originalModeratorId = originalModeratorId;
|
||||||
this.timerParams = timerParams;
|
this.timerParams = timerParams;
|
||||||
this.isFull = false;
|
this.isFull = false;
|
||||||
this.timeRemaining = null;
|
this.timeRemaining = null;
|
||||||
|
|||||||
@@ -183,10 +183,12 @@ class GameManager {
|
|||||||
this.activeGameRunner.activeGames[newAccessCode] = new Game(
|
this.activeGameRunner.activeGames[newAccessCode] = new Game(
|
||||||
newAccessCode,
|
newAccessCode,
|
||||||
globals.STATUS.LOBBY,
|
globals.STATUS.LOBBY,
|
||||||
initializePeopleForGame(gameParams.deck, moderator),
|
initializePeopleForGame(gameParams.deck, moderator, this.shuffle),
|
||||||
gameParams.deck,
|
gameParams.deck,
|
||||||
gameParams.hasTimer,
|
gameParams.hasTimer,
|
||||||
moderator,
|
moderator,
|
||||||
|
gameParams.hasDedicatedModerator,
|
||||||
|
moderator.id,
|
||||||
gameParams.timerParams
|
gameParams.timerParams
|
||||||
);
|
);
|
||||||
this.activeGameRunner.activeGames[newAccessCode].createTime = new Date().toJSON();
|
this.activeGameRunner.activeGames[newAccessCode].createTime = new Date().toJSON();
|
||||||
@@ -308,6 +310,67 @@ class GameManager {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
restartGame = async (game, namespace) => {
|
||||||
|
// kill any outstanding timer threads
|
||||||
|
if (this.activeGameRunner.timerThreads[game.accessCode]) {
|
||||||
|
this.logger.info('KILLING STALE TIMER PROCESS FOR ' + game.accessCode);
|
||||||
|
this.activeGameRunner.timerThreads[game.accessCode].kill();
|
||||||
|
delete this.activeGameRunner.timerThreads[game.accessCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-shuffle the deck
|
||||||
|
const cards = [];
|
||||||
|
for (const card of game.deck) {
|
||||||
|
for (let i = 0; i < card.quantity; i ++) {
|
||||||
|
cards.push(card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.shuffle(cards);
|
||||||
|
|
||||||
|
// make sure no players are marked as out or revealed, and give them new cards.
|
||||||
|
for (let i = 0; i < game.people.length; i ++) {
|
||||||
|
if (game.people[i].out) {
|
||||||
|
game.people[i].out = false;
|
||||||
|
}
|
||||||
|
if (game.people[i].userType === globals.USER_TYPES.KILLED_PLAYER) {
|
||||||
|
game.people[i].userType = globals.USER_TYPES.PLAYER;
|
||||||
|
}
|
||||||
|
game.people[i].revealed = false;
|
||||||
|
game.people[i].gameRole = cards[i].role;
|
||||||
|
game.people[i].gameRoleDescription = cards[i].description;
|
||||||
|
game.people[i].alignment = cards[i].team;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the game was originally set up with a TEMP mod and the game has gone far enough to establish
|
||||||
|
a DEDICATED mod, make the current mod a TEMP mod for the restart. */
|
||||||
|
if (!game.hasDedicatedModerator && game.moderator.userType === globals.USER_TYPES.MODERATOR) {
|
||||||
|
game.moderator.userType = globals.USER_TYPES.TEMPORARY_MODERATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the game was originally set up with a DEDICATED moderator and the current mod is DIFFERENT from that mod
|
||||||
|
(i.e. they transferred their powers at some point), check if the current mod was once a player (i.e. they have
|
||||||
|
a game role). If they were once a player, make them a temp mod for the restart. Otherwise, they were a
|
||||||
|
spectator, and we want to leave them as a dedicated moderator.
|
||||||
|
*/
|
||||||
|
if (game.hasDedicatedModerator && game.moderator.id !== game.originalModeratorId) {
|
||||||
|
if (game.moderator.gameRole) {
|
||||||
|
game.moderator.userType = globals.USER_TYPES.TEMPORARY_MODERATOR;
|
||||||
|
} else {
|
||||||
|
game.moderator.userType = globals.USER_TYPES.MODERATOR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start the new game
|
||||||
|
game.status = globals.STATUS.IN_PROGRESS;
|
||||||
|
if (game.hasTimer) {
|
||||||
|
game.timerParams.paused = true;
|
||||||
|
this.activeGameRunner.runGame(game, namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace.in(game.accessCode).emit(globals.CLIENT_COMMANDS.START_GAME);
|
||||||
|
};
|
||||||
|
|
||||||
handleRequestForGameState = async (namespace, logger, gameRunner, accessCode, personCookie, ackFn, clientSocket) => {
|
handleRequestForGameState = async (namespace, logger, gameRunner, accessCode, personCookie, ackFn, clientSocket) => {
|
||||||
const game = gameRunner.activeGames[accessCode];
|
const game = gameRunner.activeGames[accessCode];
|
||||||
if (game) {
|
if (game) {
|
||||||
@@ -355,6 +418,23 @@ class GameManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
-- To shuffle an array a of n elements (indices 0..n-1):
|
||||||
|
for i from n−1 downto 1 do
|
||||||
|
j ← random integer such that 0 ≤ j ≤ i
|
||||||
|
exchange a[j] and a[i]
|
||||||
|
*/
|
||||||
|
shuffle = (array) => {
|
||||||
|
for (let i = array.length - 1; i > 0; i --) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
const temp = array[j];
|
||||||
|
array[j] = array[i];
|
||||||
|
array[i] = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRandomInt (max) {
|
function getRandomInt (max) {
|
||||||
@@ -365,12 +445,12 @@ function initializeModerator (name, hasDedicatedModerator) {
|
|||||||
const userType = hasDedicatedModerator
|
const userType = hasDedicatedModerator
|
||||||
? globals.USER_TYPES.MODERATOR
|
? globals.USER_TYPES.MODERATOR
|
||||||
: globals.USER_TYPES.TEMPORARY_MODERATOR;
|
: globals.USER_TYPES.TEMPORARY_MODERATOR;
|
||||||
return new Person(createRandomId(), createRandomId(), name, userType); ;
|
return new Person(createRandomId(), createRandomId(), name, userType);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializePeopleForGame (uniqueCards, moderator) {
|
function initializePeopleForGame (uniqueCards, moderator, shuffle) {
|
||||||
const people = [];
|
const people = [];
|
||||||
let cards = []; // this will contain copies of each card equal to the quantity.
|
const cards = [];
|
||||||
let numberOfRoles = 0;
|
let numberOfRoles = 0;
|
||||||
for (const card of uniqueCards) {
|
for (const card of uniqueCards) {
|
||||||
for (let i = 0; i < card.quantity; i ++) {
|
for (let i = 0; i < card.quantity; i ++) {
|
||||||
@@ -379,7 +459,7 @@ function initializePeopleForGame (uniqueCards, moderator) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cards = shuffle(cards); // The deck should probably be shuffled, ey?.
|
shuffle(cards); // this shuffles in-place.
|
||||||
|
|
||||||
let j = 0;
|
let j = 0;
|
||||||
if (moderator.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { // temporary moderators should be dealt in.
|
if (moderator.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) { // temporary moderators should be dealt in.
|
||||||
@@ -410,23 +490,6 @@ function initializePeopleForGame (uniqueCards, moderator) {
|
|||||||
return people;
|
return people;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
-- To shuffle an array a of n elements (indices 0..n-1):
|
|
||||||
for i from n−1 downto 1 do
|
|
||||||
j ← random integer such that 0 ≤ j ≤ i
|
|
||||||
exchange a[j] and a[i]
|
|
||||||
*/
|
|
||||||
function shuffle(array) {
|
|
||||||
for (let i = array.length - 1; i > 0; i --) {
|
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
|
||||||
const temp = array[j];
|
|
||||||
array[j] = array[i];
|
|
||||||
array[i] = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createRandomId () {
|
function createRandomId () {
|
||||||
let id = '';
|
let id = '';
|
||||||
for (let i = 0; i < globals.USER_SIGNATURE_LENGTH; i ++) {
|
for (let i = 0; i < globals.USER_SIGNATURE_LENGTH; i ++) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
const Game = require('../../../../server/model/Game');
|
const Game = require('../../../../server/model/Game');
|
||||||
const globals = require('../../../../server/config/globals');
|
const globals = require('../../../../server/config/globals');
|
||||||
const USER_TYPES = globals.USER_TYPES;
|
const USER_TYPES = globals.USER_TYPES;
|
||||||
|
const STATUS = globals.STATUS;
|
||||||
const Person = require('../../../../server/model/Person');
|
const Person = require('../../../../server/model/Person');
|
||||||
const GameManager = require('../../../../server/modules/GameManager.js');
|
const GameManager = require('../../../../server/modules/GameManager.js');
|
||||||
const GameStateCurator = require('../../../../server/modules/GameStateCurator');
|
const GameStateCurator = require('../../../../server/modules/GameStateCurator');
|
||||||
@@ -280,4 +281,146 @@ describe('GameManager', () => {
|
|||||||
expect(accessCode).toEqual('BBBB');
|
expect(accessCode).toEqual('BBBB');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#restartGame', () => {
|
||||||
|
let person1,
|
||||||
|
person2,
|
||||||
|
person3,
|
||||||
|
shuffleSpy,
|
||||||
|
game,
|
||||||
|
moderator;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
person1 = new Person('1', '123', 'Placeholder1', USER_TYPES.KILLED_PLAYER);
|
||||||
|
person2 = new Person('2', '456', 'Placeholder2', USER_TYPES.PLAYER);
|
||||||
|
person3 = new Person('3', '789', 'Placeholder3', USER_TYPES.PLAYER);
|
||||||
|
moderator = new Person('4', '000', 'Jack', USER_TYPES.MODERATOR);
|
||||||
|
person1.out = true;
|
||||||
|
person2.revealed = true;
|
||||||
|
moderator.assigned = true;
|
||||||
|
shuffleSpy = spyOn(gameManager, 'shuffle').and.stub();
|
||||||
|
game = new Game(
|
||||||
|
'test',
|
||||||
|
STATUS.ENDED,
|
||||||
|
[person1, person2, person3],
|
||||||
|
[
|
||||||
|
{ role: 'Villager', description: 'test', team: 'good', quantity: 1 },
|
||||||
|
{ role: 'Seer', description: 'test', team: 'good', quantity: 1 },
|
||||||
|
{ role: 'Werewolf', description: 'test', team: 'evil', quantity: 1 }
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
moderator,
|
||||||
|
true,
|
||||||
|
'4',
|
||||||
|
null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset all relevant game parameters', async () => {
|
||||||
|
const emitSpy = spyOn(namespace.in(), 'emit');
|
||||||
|
|
||||||
|
await gameManager.restartGame(game, namespace);
|
||||||
|
|
||||||
|
expect(game.status).toEqual(STATUS.IN_PROGRESS);
|
||||||
|
expect(game.moderator.id).toEqual('4');
|
||||||
|
expect(game.moderator.userType).toEqual(USER_TYPES.MODERATOR);
|
||||||
|
expect(person1.out).toEqual(false);
|
||||||
|
expect(person2.revealed).toEqual(false);
|
||||||
|
for (const person of game.people) {
|
||||||
|
expect(person.gameRole).toBeDefined();
|
||||||
|
}
|
||||||
|
expect(shuffleSpy).toHaveBeenCalled();
|
||||||
|
expect(emitSpy).toHaveBeenCalledWith(globals.CLIENT_COMMANDS.START_GAME);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset all relevant game parameters, including when the game has a timer', async () => {
|
||||||
|
game.timerParams = { hours: 2, minutes: 2, paused: false };
|
||||||
|
game.hasTimer = true;
|
||||||
|
gameManager.activeGameRunner.timerThreads = { test: { kill: () => {} } };
|
||||||
|
|
||||||
|
const threadKillSpy = spyOn(gameManager.activeGameRunner.timerThreads.test, 'kill');
|
||||||
|
const runGameSpy = spyOn(gameManager.activeGameRunner, 'runGame').and.stub();
|
||||||
|
const emitSpy = spyOn(namespace.in(), 'emit');
|
||||||
|
|
||||||
|
await gameManager.restartGame(game, namespace);
|
||||||
|
|
||||||
|
expect(game.status).toEqual(STATUS.IN_PROGRESS);
|
||||||
|
expect(game.timerParams.paused).toBeTrue();
|
||||||
|
expect(game.moderator.id).toEqual('4');
|
||||||
|
expect(game.moderator.userType).toEqual(USER_TYPES.MODERATOR);
|
||||||
|
expect(person1.out).toEqual(false);
|
||||||
|
expect(person2.revealed).toEqual(false);
|
||||||
|
for (const person of game.people) {
|
||||||
|
expect(person.gameRole).toBeDefined();
|
||||||
|
}
|
||||||
|
expect(threadKillSpy).toHaveBeenCalled();
|
||||||
|
expect(runGameSpy).toHaveBeenCalled();
|
||||||
|
expect(Object.keys(gameManager.activeGameRunner.timerThreads).length).toEqual(0);
|
||||||
|
expect(shuffleSpy).toHaveBeenCalled();
|
||||||
|
expect(emitSpy).toHaveBeenCalledWith(globals.CLIENT_COMMANDS.START_GAME);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset all relevant game parameters and preserve temporary moderator', async () => {
|
||||||
|
const emitSpy = spyOn(namespace.in(), 'emit');
|
||||||
|
game.moderator = game.people[0];
|
||||||
|
game.moderator.userType = USER_TYPES.TEMPORARY_MODERATOR;
|
||||||
|
game.hasDedicatedModerator = false;
|
||||||
|
|
||||||
|
await gameManager.restartGame(game, namespace);
|
||||||
|
|
||||||
|
expect(game.status).toEqual(STATUS.IN_PROGRESS);
|
||||||
|
expect(game.moderator.id).toEqual('1');
|
||||||
|
expect(game.moderator.userType).toEqual(USER_TYPES.TEMPORARY_MODERATOR);
|
||||||
|
expect(game.moderator.gameRole).toBeDefined();
|
||||||
|
expect(person1.out).toEqual(false);
|
||||||
|
expect(person2.revealed).toEqual(false);
|
||||||
|
for (const person of game.people) {
|
||||||
|
expect(person.gameRole).toBeDefined();
|
||||||
|
}
|
||||||
|
expect(shuffleSpy).toHaveBeenCalled();
|
||||||
|
expect(emitSpy).toHaveBeenCalledWith(globals.CLIENT_COMMANDS.START_GAME);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset all relevant game parameters and restore a temporary moderator from a dedicated moderator', async () => {
|
||||||
|
const emitSpy = spyOn(namespace.in(), 'emit');
|
||||||
|
game.moderator = game.people[0];
|
||||||
|
game.moderator.userType = USER_TYPES.MODERATOR;
|
||||||
|
game.hasDedicatedModerator = false;
|
||||||
|
|
||||||
|
await gameManager.restartGame(game, namespace);
|
||||||
|
|
||||||
|
expect(game.status).toEqual(STATUS.IN_PROGRESS);
|
||||||
|
expect(game.moderator.id).toEqual('1');
|
||||||
|
expect(game.moderator.userType).toEqual(USER_TYPES.TEMPORARY_MODERATOR);
|
||||||
|
expect(game.moderator.gameRole).toBeDefined();
|
||||||
|
expect(person1.out).toEqual(false);
|
||||||
|
expect(person2.revealed).toEqual(false);
|
||||||
|
for (const person of game.people) {
|
||||||
|
expect(person.gameRole).toBeDefined();
|
||||||
|
}
|
||||||
|
expect(shuffleSpy).toHaveBeenCalled();
|
||||||
|
expect(emitSpy).toHaveBeenCalledWith(globals.CLIENT_COMMANDS.START_GAME);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset all relevant game parameters and create a temporary mod if a dedicated mod transferred to a killed player', async () => {
|
||||||
|
const emitSpy = spyOn(namespace.in(), 'emit');
|
||||||
|
game.moderator = game.people[0];
|
||||||
|
game.moderator.userType = USER_TYPES.MODERATOR;
|
||||||
|
game.hasDedicatedModerator = true;
|
||||||
|
|
||||||
|
await gameManager.restartGame(game, namespace);
|
||||||
|
|
||||||
|
expect(game.status).toEqual(STATUS.IN_PROGRESS);
|
||||||
|
expect(game.moderator.id).toEqual('1');
|
||||||
|
expect(game.moderator.userType).toEqual(USER_TYPES.TEMPORARY_MODERATOR);
|
||||||
|
expect(game.moderator.gameRole).toBeDefined();
|
||||||
|
expect(person1.out).toEqual(false);
|
||||||
|
expect(person2.revealed).toEqual(false);
|
||||||
|
for (const person of game.people) {
|
||||||
|
expect(person.gameRole).toBeDefined();
|
||||||
|
}
|
||||||
|
expect(shuffleSpy).toHaveBeenCalled();
|
||||||
|
expect(emitSpy).toHaveBeenCalledWith(globals.CLIENT_COMMANDS.START_GAME);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user