mirror of
https://github.com/AlecM33/Werewolf.git
synced 2025-12-26 15:57:50 +01:00
restart functionality
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
export const globals = {
|
||||
USER_SIGNATURE_LENGTH: 25,
|
||||
CLOCK_TICK_INTERVAL_MILLIS: 10,
|
||||
CLOCK_TICK_INTERVAL_MILLIS: 100,
|
||||
MAX_CUSTOM_ROLE_NAME_LENGTH: 30,
|
||||
MAX_CUSTOM_ROLE_DESCRIPTION_LENGTH: 500,
|
||||
TOAST_DURATION_DEFAULT: 6,
|
||||
|
||||
@@ -107,13 +107,23 @@ export class GameCreationStepManager {
|
||||
5: {
|
||||
title: 'Review and submit:',
|
||||
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(
|
||||
'/api/games/create',
|
||||
'POST',
|
||||
null,
|
||||
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) => {
|
||||
@@ -128,9 +138,10 @@ export class GameCreationStepManager {
|
||||
}
|
||||
}).catch((e) => {
|
||||
const button = document.getElementById('create-game');
|
||||
button.innerText = 'Create Game';
|
||||
button.innerText = 'Create';
|
||||
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) {
|
||||
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.setAttribute('id', 'create-game');
|
||||
createButton.classList.add('app-button');
|
||||
createButton.addEventListener('click', () => {
|
||||
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
|
||||
);
|
||||
});
|
||||
createButton.addEventListener('click', forwardHandler);
|
||||
document.getElementById('tracker-container').appendChild(createButton);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { globals } from '../config/globals.js';
|
||||
import { toast } from './Toast.js';
|
||||
import { HTMLFragments } from './HTMLFragments.js';
|
||||
import { ModalManager } from './ModalManager.js';
|
||||
import {XHRUtility} from "./XHRUtility";
|
||||
import {UserUtility} from "./UserUtility";
|
||||
import { XHRUtility } from './XHRUtility.js';
|
||||
import { UserUtility } from './UserUtility.js';
|
||||
|
||||
export class GameStateRenderer {
|
||||
constructor (stateBucket, socket) {
|
||||
@@ -12,12 +12,40 @@ export class GameStateRenderer {
|
||||
this.killPlayerHandlers = {};
|
||||
this.revealRoleHandlers = {};
|
||||
this.transferModHandlers = {};
|
||||
this.startGameHandler = (e) => {
|
||||
this.startGameHandler = (e) => { // TODO: prevent multiple emissions of this event (recommend converting to XHR)
|
||||
e.preventDefault();
|
||||
if (confirm('Start the game and deal roles?')) {
|
||||
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 () {
|
||||
@@ -263,27 +291,11 @@ export class GameStateRenderer {
|
||||
gameState.client.userType === globals.USER_TYPES.MODERATOR
|
||||
|| gameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
|
||||
) {
|
||||
let div = document.createElement('div');
|
||||
div.innerHTML = HTMLFragments.RESTART_GAME_BUTTON;
|
||||
div.querySelector('#restart-game').addEventListener('click', () => {
|
||||
XHRUtility.xhr(
|
||||
'/api/games/' + gameState.accessCode + '/restart',
|
||||
'PATCH',
|
||||
null,
|
||||
JSON.stringify({
|
||||
playerName: gameState.client.name,
|
||||
accessCode: gameState.accessCode,
|
||||
sessionCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.LOCAL),
|
||||
localCookie: UserUtility.validateAnonUserSignature(globals.ENVIRONMENT.PRODUCTION)
|
||||
})
|
||||
)
|
||||
.then((res) => {
|
||||
|
||||
}).catch((res) => {
|
||||
|
||||
});
|
||||
})
|
||||
document.getElementById('end-of-game-buttons').appendChild(div);
|
||||
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();
|
||||
}
|
||||
@@ -301,6 +313,10 @@ function renderPotentialMods (gameState, group, transferModHandlers, socket) {
|
||||
transferModHandlers[member.id] = (e) => {
|
||||
if (e.type === 'click' || e.code === 'Enter') {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ export class GameTimerManager {
|
||||
const pauseBtn = document.createElement('img');
|
||||
pauseBtn.setAttribute('src', '../images/pause-button.svg');
|
||||
pauseBtn.addEventListener('click', this.pauseListener);
|
||||
document.querySelector('#play-pause-placeholder')?.remove();
|
||||
document.getElementById('play-pause').appendChild(pauseBtn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ export const HTMLFragments = {
|
||||
<label id='players-alive-label'></label>
|
||||
<div id='game-player-list'></div>
|
||||
</div>`,
|
||||
MODERATOR_GAME_VIEW:
|
||||
TRANSFER_MOD_MODAL:
|
||||
`<div id='transfer-mod-modal-background' class='modal-background'></div>
|
||||
<div tabindex='-1' id='transfer-mod-modal' class='modal'>
|
||||
<h3>Transfer Mod Powers 👑</h3>
|
||||
@@ -88,9 +88,10 @@ export const HTMLFragments = {
|
||||
<div class='modal-button-container'>
|
||||
<button id='close-mod-transfer-modal-button' class='app-button cancel'>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id='game-header'>
|
||||
<div class='timer-container-moderator'>
|
||||
</div>`,
|
||||
MODERATOR_GAME_VIEW:
|
||||
`<div id='game-header'>
|
||||
<div id='timer-container-moderator'>
|
||||
<div>
|
||||
<label for='game-timer'>Time Remaining</label>
|
||||
<div id='game-timer'></div>
|
||||
@@ -118,19 +119,8 @@ export const HTMLFragments = {
|
||||
</div>
|
||||
</div>`,
|
||||
TEMP_MOD_GAME_VIEW:
|
||||
`<div id='transfer-mod-modal-background' class='modal-background'></div>
|
||||
<div id='transfer-mod-modal' class='modal'>
|
||||
<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 id='game-header'>
|
||||
<div id='timer-container-moderator'>
|
||||
<div>
|
||||
<label for='game-timer'>Time Remaining</label>
|
||||
<div id='game-timer'></div>
|
||||
@@ -219,7 +209,7 @@ export const HTMLFragments = {
|
||||
</div>`,
|
||||
END_OF_GAME_VIEW:
|
||||
`<div id='end-of-game-header'>
|
||||
<h2>The moderator has ended the game. Roles are revealed.</h2>
|
||||
<h2>🏁 The moderator has ended the game. Roles are revealed.</h2>
|
||||
<div id="end-of-game-buttons">
|
||||
<div>
|
||||
<button id='role-info-button' class='app-button'>View Role Info <img src='/images/info.svg'/></button>
|
||||
|
||||
@@ -75,7 +75,7 @@ function processGameState (
|
||||
gameTimerManager,
|
||||
timerWorker,
|
||||
refreshPrompt = true,
|
||||
animateContainer= false
|
||||
animateContainer = false
|
||||
) {
|
||||
const containerAnimation = document.getElementById('game-state-container').animate(
|
||||
[
|
||||
@@ -123,10 +123,12 @@ function processGameState (
|
||||
gameStateRenderer.renderPlayerView(true);
|
||||
break;
|
||||
case globals.USER_TYPES.MODERATOR:
|
||||
document.getElementById('transfer-mod-prompt').innerHTML = HTMLFragments.TRANSFER_MOD_MODAL;
|
||||
document.getElementById('game-state-container').innerHTML = HTMLFragments.MODERATOR_GAME_VIEW;
|
||||
gameStateRenderer.renderModeratorView();
|
||||
break;
|
||||
case globals.USER_TYPES.TEMPORARY_MODERATOR:
|
||||
document.getElementById('transfer-mod-prompt').innerHTML = HTMLFragments.TRANSFER_MOD_MODAL;
|
||||
document.getElementById('game-state-container').innerHTML = HTMLFragments.TEMP_MOD_GAME_VIEW;
|
||||
gameStateRenderer.renderTempModView();
|
||||
break;
|
||||
@@ -141,6 +143,7 @@ function processGameState (
|
||||
socket.emit(globals.COMMANDS.GET_TIME_REMAINING, currentGameState.accessCode);
|
||||
} else {
|
||||
document.querySelector('#game-timer')?.remove();
|
||||
document.querySelector('#timer-container-moderator')?.remove();
|
||||
document.querySelector('label[for="game-timer"]')?.remove();
|
||||
}
|
||||
break;
|
||||
@@ -384,7 +387,7 @@ function activateRoleInfoButton (deck) {
|
||||
});
|
||||
document.getElementById('role-info-button').addEventListener('click', (e) => {
|
||||
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');
|
||||
for (const card of deck) {
|
||||
const roleDiv = document.createElement('div');
|
||||
|
||||
@@ -4,45 +4,50 @@ import { injectNavbar } from '../modules/Navbar.js';
|
||||
|
||||
const home = () => {
|
||||
injectNavbar();
|
||||
document.getElementById('join-form').onsubmit = (e) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
document.getElementById('join-form').addEventListener('submit', attemptToJoinGame);
|
||||
};
|
||||
|
||||
function roomCodeIsValid (code) {
|
||||
return typeof code === 'string' && /^[A-Z0-9]{4}$/.test(code.toUpperCase().trim());
|
||||
}
|
||||
|
||||
function attemptToJoinGame (code) {
|
||||
XHRUtility.xhr(
|
||||
'/api/games/' + code.toUpperCase().trim() + '/availability',
|
||||
'GET',
|
||||
null,
|
||||
null
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
const json = JSON.parse(res.content);
|
||||
window.location = window.location.protocol + '//' + window.location.host +
|
||||
'/join/' + encodeURIComponent(json.accessCode) +
|
||||
'?playerCount=' + encodeURIComponent(json.playerCount) +
|
||||
'&timer=' + encodeURIComponent(getTimeString(json.timerParams));
|
||||
}
|
||||
}).catch((res) => {
|
||||
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);
|
||||
}
|
||||
});
|
||||
function attemptToJoinGame (event) {
|
||||
event.preventDefault();
|
||||
const userCode = document.getElementById('room-code').value;
|
||||
if (roomCodeIsValid(userCode)) {
|
||||
const form = document.getElementById('join-form');
|
||||
form.removeEventListener('submit', attemptToJoinGame);
|
||||
form.classList.add('submitted');
|
||||
document.getElementById('join-button')?.setAttribute('value', 'Joining...');
|
||||
XHRUtility.xhr(
|
||||
'/api/games/' + userCode.toUpperCase().trim() + '/availability',
|
||||
'GET',
|
||||
null,
|
||||
null
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
const json = JSON.parse(res.content);
|
||||
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);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
toast('Invalid code. Codes are 4 numbers or letters.', 'error', true, true);
|
||||
}
|
||||
}
|
||||
|
||||
function getTimeString (timerParams) {
|
||||
|
||||
@@ -607,7 +607,7 @@ input {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100% + 100px);
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
#deck-status-container {
|
||||
width: 20em;
|
||||
max-width: 95%;
|
||||
height: 13em;
|
||||
height: 10em;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
}
|
||||
@@ -356,7 +356,7 @@ input[type="number"] {
|
||||
#deck-select {
|
||||
margin: 0.5em 1em 1.5em 0;
|
||||
overflow-y: auto;
|
||||
height: 15em;
|
||||
height: 12em;
|
||||
}
|
||||
|
||||
.deck-select-role {
|
||||
|
||||
@@ -510,12 +510,19 @@ label[for='moderator'] {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.timer-container-moderator {
|
||||
#game-header button {
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
#timer-container-moderator {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 1em;
|
||||
padding: 0.5em;
|
||||
border-radius: 3px;
|
||||
background-color: #333243;
|
||||
}
|
||||
|
||||
.game-player {
|
||||
@@ -533,7 +540,7 @@ label[for='moderator'] {
|
||||
|
||||
.game-player-name {
|
||||
position: relative;
|
||||
width: 10em;
|
||||
min-width: 6em;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
<link rel="preload" href="/webfonts/SignikaNegative-Light.woff2" as="font" type="font/woff2" crossorigin>
|
||||
</head>
|
||||
<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-container">
|
||||
<div class="lds-spinner">
|
||||
|
||||
Reference in New Issue
Block a user