mirror of
https://github.com/AlecM33/Werewolf.git
synced 2025-12-26 15:57:50 +01:00
progress on new lobby
This commit is contained in:
@@ -3,6 +3,7 @@ import { cancelCurrentToast, toast } from "./Toast.js";
|
||||
import { customCards } from "../config/customCards.js";
|
||||
import { ModalManager } from "./ModalManager.js";
|
||||
import {XHRUtility} from "./XHRUtility.js";
|
||||
import {globals} from "../config/globals.js";
|
||||
|
||||
export class GameCreationStepManager {
|
||||
constructor(deckManager) {
|
||||
@@ -401,8 +402,9 @@ function loadCustomRoles(deckManager) {
|
||||
|
||||
function constructCompactDeckBuilderElement(card, deckManager) {
|
||||
const cardContainer = document.createElement("div");
|
||||
let alignmentClass = card.team === globals.ALIGNMENT.GOOD ? globals.ALIGNMENT.GOOD : globals.ALIGNMENT.EVIL
|
||||
|
||||
cardContainer.setAttribute("class", "compact-card");
|
||||
cardContainer.setAttribute("class", "compact-card " + alignmentClass);
|
||||
|
||||
cardContainer.setAttribute("id", "card-" + card.role.replaceAll(' ', '-'));
|
||||
|
||||
@@ -419,6 +421,7 @@ function constructCompactDeckBuilderElement(card, deckManager) {
|
||||
"</div>";
|
||||
|
||||
cardContainer.querySelector('.card-role').innerText = card.role;
|
||||
cardContainer.title = card.role;
|
||||
cardContainer.querySelector('.card-quantity').innerText = card.quantity;
|
||||
|
||||
if (card.quantity > 0) {
|
||||
@@ -477,6 +480,8 @@ function updateCustomRoleOptionsList(deckManager, selectEl) {
|
||||
function addOptionsToList(options, selectEl) {
|
||||
for (let i = 0; i < options.length; i ++) {
|
||||
let optionEl = document.createElement("option");
|
||||
let alignmentClass = customCards[i].team === globals.ALIGNMENT.GOOD ? globals.ALIGNMENT.GOOD : globals.ALIGNMENT.EVIL
|
||||
optionEl.classList.add(alignmentClass);
|
||||
optionEl.setAttribute("value", customCards[i].role);
|
||||
optionEl.innerText = customCards[i].role;
|
||||
selectEl.appendChild(optionEl);
|
||||
|
||||
75
client/modules/GameStateRenderer.js
Normal file
75
client/modules/GameStateRenderer.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import {globals} from "../config/globals.js";
|
||||
|
||||
export class GameStateRenderer {
|
||||
constructor(gameState) {
|
||||
this.gameState = gameState;
|
||||
}
|
||||
|
||||
renderLobbyPlayers() {
|
||||
document.querySelectorAll('.lobby-player').forEach((el) => el.remove())
|
||||
let lobbyPlayersContainer = document.getElementById("lobby-players");
|
||||
if (this.gameState.userType !== globals.USER_TYPES.MODERATOR) {
|
||||
renderClient(this.gameState.client, lobbyPlayersContainer);
|
||||
}
|
||||
for (let person of this.gameState.people) {
|
||||
let personEl = document.createElement("div");
|
||||
personEl.innerText = person.name;
|
||||
personEl.classList.add('lobby-player');
|
||||
lobbyPlayersContainer.appendChild(personEl);
|
||||
}
|
||||
let playerCount;
|
||||
if (this.gameState.userType === globals.USER_TYPES.MODERATOR) {
|
||||
playerCount = this.gameState.people.length;
|
||||
} else {
|
||||
playerCount = 1 + this.gameState.people.length;
|
||||
}
|
||||
document.querySelector("label[for='lobby-players']").innerText =
|
||||
"Players ( " + playerCount + " / " + getGameSize(this.gameState.deck) + " )";
|
||||
}
|
||||
|
||||
renderLobbyHeader() {
|
||||
let title = document.createElement("h1");
|
||||
title.innerText = "Lobby";
|
||||
document.body.prepend(title);
|
||||
let gameLinkContainer = document.getElementById("game-link");
|
||||
gameLinkContainer.innerText = window.location;
|
||||
let copyImg = document.createElement("img");
|
||||
copyImg.setAttribute("src", "../images/copy.svg");
|
||||
gameLinkContainer.appendChild(copyImg);
|
||||
|
||||
let moderatorContainer = document.getElementById("moderator");
|
||||
let text, modClass;
|
||||
if (this.gameState.userType === globals.USER_TYPES.MODERATOR || this.gameState.userType === globals.USER_TYPES.TEMPORARY_MODERATOR) {
|
||||
moderatorContainer.innerText = this.gameState.moderator.name + " (you)";
|
||||
moderatorContainer.classList.add('moderator-client');
|
||||
} else {
|
||||
moderatorContainer.innerText = this.gameState.moderator.name;
|
||||
}
|
||||
}
|
||||
|
||||
renderLobbyFooter() {
|
||||
let gameDeckContainer = document.getElementById("game-deck");
|
||||
for (let card of this.gameState.deck) {
|
||||
let cardEl = document.createElement("div");
|
||||
cardEl.innerText = card.quantity + 'x ' + card.role;
|
||||
cardEl.classList.add('lobby-card')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderClient(client, container) {
|
||||
let clientEl = document.createElement("div");
|
||||
clientEl.innerText = client.name + ' (you)';
|
||||
clientEl.classList.add('lobby-player');
|
||||
clientEl.classList.add('lobby-player-client');
|
||||
container.prepend(clientEl);
|
||||
}
|
||||
|
||||
function getGameSize(cards) {
|
||||
let quantity = 0;
|
||||
for (let card of cards) {
|
||||
quantity += card.quantity;
|
||||
}
|
||||
|
||||
return quantity;
|
||||
}
|
||||
24
client/modules/Templates.js
Normal file
24
client/modules/Templates.js
Normal file
@@ -0,0 +1,24 @@
|
||||
export const templates = {
|
||||
LOBBY:
|
||||
"<div id='lobby-header'>" +
|
||||
"<div>" +
|
||||
"<label for='game-link'>Share Link</label>" +
|
||||
"<div id='game-link'></div>" +
|
||||
"</div>" +
|
||||
"<div id='game-time'></div>" +
|
||||
"<div id='game-player-count'></div>" +
|
||||
"<div>" +
|
||||
"<label for='moderator'>Moderator</label>" +
|
||||
"<div id='moderator'></div>" +
|
||||
"</div>" +
|
||||
"</div>" +
|
||||
"<div>" +
|
||||
"<div>" +
|
||||
"<label for='lobby-players'>Players</label>" +
|
||||
"<div id='lobby-players'></div>" +
|
||||
"</div>" +
|
||||
"<div id='lobby-footer'>" +
|
||||
"<div id='game-deck'></div>" +
|
||||
"</div>" +
|
||||
"</div>"
|
||||
}
|
||||
86
client/modules/Timer.js
Normal file
86
client/modules/Timer.js
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
A timer using setTimeout that compensates for drift. Drift can happen for several reasons:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#reasons_for_delays
|
||||
|
||||
This means the timer may very well be late in executing the next call (but never early).
|
||||
This timer is accurate to within a few ms for any amount of time provided. It's meant to be utilized as a Web Worker.
|
||||
See: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
|
||||
*/
|
||||
|
||||
const messageParameters = {
|
||||
STOP: 'stop',
|
||||
TOTAL_TIME: 'totalTime',
|
||||
TICK_INTERVAL: 'tickInterval'
|
||||
};
|
||||
|
||||
onmessage = function (e) {
|
||||
if (typeof e.data === 'object'
|
||||
&& e.data.hasOwnProperty(messageParameters.TOTAL_TIME)
|
||||
&& e.data.hasOwnProperty(messageParameters.TICK_INTERVAL)
|
||||
) {
|
||||
const timer = new Singleton(e.data.totalTime, e.data.tickInterval);
|
||||
timer.startTimer();
|
||||
}
|
||||
};
|
||||
|
||||
function stepFn (expected, interval, start, totalTime) {
|
||||
const now = Date.now();
|
||||
if (now - start >= totalTime) {
|
||||
return;
|
||||
}
|
||||
const delta = now - expected;
|
||||
expected += interval;
|
||||
postMessage({ timeRemaining: (totalTime - (expected - start)) / 1000 });
|
||||
Singleton.setNewTimeoutReference(setTimeout(() => {
|
||||
stepFn(expected, interval, start, totalTime);
|
||||
}, Math.max(0, interval - delta)
|
||||
)); // take into account drift - also retain a reference to this clock tick so it can be cleared later
|
||||
}
|
||||
|
||||
class Timer {
|
||||
constructor (totalTime, tickInterval) {
|
||||
this.timeoutId = undefined;
|
||||
this.totalTime = totalTime;
|
||||
this.tickInterval = tickInterval;
|
||||
}
|
||||
|
||||
startTimer () {
|
||||
if (!isNaN(this.totalTime) && !isNaN(this.tickInterval)) {
|
||||
const interval = this.tickInterval;
|
||||
const totalTime = this.totalTime;
|
||||
const start = Date.now();
|
||||
const expected = Date.now() + this.tickInterval;
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId);
|
||||
}
|
||||
this.timeoutId = setTimeout(() => {
|
||||
stepFn(expected, interval, start, totalTime);
|
||||
}, this.tickInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Singleton {
|
||||
constructor (totalTime, tickInterval) {
|
||||
if (!Singleton.instance) {
|
||||
Singleton.instance = new Timer(totalTime, tickInterval);
|
||||
} else {
|
||||
// This allows the same timer to be configured to run for different intervals / at a different granularity.
|
||||
Singleton.setNewTimerParameters(totalTime, tickInterval);
|
||||
}
|
||||
return Singleton.instance;
|
||||
}
|
||||
|
||||
static setNewTimerParameters (totalTime, tickInterval) {
|
||||
if (Singleton.instance) {
|
||||
Singleton.instance.totalTime = totalTime;
|
||||
Singleton.instance.tickInterval = tickInterval;
|
||||
}
|
||||
}
|
||||
|
||||
static setNewTimeoutReference (timeoutId) {
|
||||
if (Singleton.instance) {
|
||||
Singleton.instance.timeoutId = timeoutId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ export const toast = (message, type, positionAtTop = true) => {
|
||||
function buildAndInsertMessageElement (message, type, positionAtTop) {
|
||||
cancelCurrentToast();
|
||||
let backgroundColor;
|
||||
const position = positionAtTop ? 'top:4rem;' : 'bottom: 15px;';
|
||||
const position = positionAtTop ? 'top:4rem;' : 'bottom: 35px;';
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
backgroundColor = '#fff5b1';
|
||||
|
||||
@@ -2,24 +2,41 @@ import { globals } from '../config/globals.js';
|
||||
|
||||
export const UserUtility = {
|
||||
|
||||
createNewAnonymousUserId (force = true) {
|
||||
let newId;
|
||||
const currentId = sessionStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
|
||||
createNewAnonymousUserId (force = true, environment) {
|
||||
let newId, currentId;
|
||||
if (environment === globals.ENVIRONMENT.LOCAL) {
|
||||
currentId = sessionStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
|
||||
} else {
|
||||
currentId = localStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
|
||||
}
|
||||
if (currentId !== null && !force) {
|
||||
newId = currentId;
|
||||
} else {
|
||||
newId = createRandomUserId();
|
||||
sessionStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, newId);
|
||||
if (environment === globals.ENVIRONMENT.LOCAL) {
|
||||
sessionStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, newId);
|
||||
} else {
|
||||
localStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, newId);
|
||||
}
|
||||
}
|
||||
return newId;
|
||||
},
|
||||
|
||||
setAnonymousUserId (id) {
|
||||
sessionStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, id);
|
||||
setAnonymousUserId (id, environment) {
|
||||
if (environment === globals.ENVIRONMENT.LOCAL) { // use sessionStorage for cookie during local development to aid in testing.
|
||||
sessionStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, id);
|
||||
} else {
|
||||
localStorage.setItem(globals.PLAYER_ID_COOKIE_KEY, id);
|
||||
}
|
||||
},
|
||||
|
||||
validateAnonUserSignature () {
|
||||
const userSig = sessionStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
|
||||
validateAnonUserSignature (environment) {
|
||||
let userSig;
|
||||
if (environment === globals.ENVIRONMENT.LOCAL) { // use sessionStorage for cookie during local development to aid in testing.
|
||||
userSig = sessionStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
|
||||
} else {
|
||||
userSig = localStorage.getItem(globals.PLAYER_ID_COOKIE_KEY);
|
||||
}
|
||||
return (
|
||||
userSig
|
||||
&& typeof userSig === 'string'
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2109,7 +2109,7 @@
|
||||
module.error(error.noStorage);
|
||||
return;
|
||||
}
|
||||
name = sessionStorage.getItem(value);
|
||||
name = localStorage.getItem(value);
|
||||
return (name !== undefined)
|
||||
? name
|
||||
: false
|
||||
@@ -2153,7 +2153,7 @@
|
||||
return;
|
||||
}
|
||||
module.verbose('Saving remote data to session storage', value, name);
|
||||
sessionStorage.setItem(value, name);
|
||||
localStorage.setItem(value, name);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3714,7 +3714,7 @@
|
||||
minCharacters : 0, // Minimum characters required to trigger API call
|
||||
|
||||
filterRemoteData : false, // Whether API results should be filtered after being returned for query term
|
||||
saveRemoteData : true, // Whether remote name/value pairs should be stored in sessionStorage to allow remote data to be restored on page refresh
|
||||
saveRemoteData : true, // Whether remote name/value pairs should be stored in localStorage to allow remote data to be restored on page refresh
|
||||
|
||||
throttle : 200, // How long to wait after last user input to search remotely
|
||||
|
||||
|
||||
Reference in New Issue
Block a user