re-name static directory, move stylesheet to its own folder

This commit is contained in:
Alec Maier
2020-05-03 06:02:06 -04:00
parent de0e365ed5
commit ecfa19c428
16 changed files with 12 additions and 12 deletions

57
javascript/cards.js Normal file
View File

@@ -0,0 +1,57 @@
export let cards = [
{
role: "Villager",
team: "good",
description: "During the day, find the wolves and kill them.",
isTypeOfWerewolf: false
},
{
role: "Werewolf",
team: "evil",
description: "During the night, choose a villager to kill. Don't get killed.",
isTypeOfWerewolf: true
},
{
role: "Dream Wolf",
team: "evil",
description: "If a Werewolf dies, you become a Werewolf. You do not wake up with the Werewolves until this happens. You count for parity only after converting to a wolf.",
isTypeOfWerewolf: false
},
{
role: "Minion",
team: "evil",
description: "You are an evil villager - you know who the wolves are, and you want them to win.",
isTypeOfWerewolf: false
},
{
role: "Seer",
team: "good",
description: "During each night, choose one person. The moderator will tell you whether that player is a wolf.",
isTypeOfWerewolf: false
},
{
role: "Shadow",
team: "evil",
description: "If the Seer checks you, the Seer dies that night instead of whoever the wolves chose to kill. Reveal" +
" yourself to the moderator.",
isTypeOfWerewolf: false
},
{
role: "Hunter",
team: "good",
description: "If you are alive with a wolf at the end of the game, you best the wolf, and the village wins.",
isTypeOfWerewolf: false
},
{
role: "Sorcerer",
team: "good",
description: "Once a game, change who the wolves are going to kill to someone else, including yourself.",
isTypeOfWerewolf: false
},
{
role: "Mason",
team: "good",
description: "Masons know who other Masons are. Wake them up to see each other on the first night.",
isTypeOfWerewolf: false
}
];

407
javascript/game.js Normal file
View File

@@ -0,0 +1,407 @@
import {utility} from './util.js'
const socket = io();
const standardRoles = ["Villager", "Werewolf", "Seer", "Shadow", "Hunter", "Mason", "Minion", "Sorcerer", "Dream Wolf"];
let clock;
let currentGame = null;
let lastGameState = null;
let cardFlippedOver = false;
let cardRendered = false;
let lastKilled = null;
// respond to the game state received from the server
socket.on('state', function(game) {
currentGame = game;
if(detectChanges(game)) {
buildGameBasedOnState(game);
}
});
function buildGameBasedOnState(game) {
switch(game.status) {
case "lobby":
renderLobby();
break;
case "started":
renderGame();
break;
case "ended":
renderEndSplash();
break;
default:
break;
}
}
function detectChanges(game) {
if (lastGameState === null ||
lastGameState.status !== game.status ||
lastGameState.paused !== game.paused ||
lastGameState.lastKilled !== game.lastKilled ||
lastGameState.startTime !== game.startTime ||
lastGameState.players.length !== game.players.length) {
lastGameState = game;
return true;
}
return false;
}
function hideAfterExit(e) {
e.target.style.display = 'none';
e.target.classList.remove(e.target.exitClass);
}
function triggerExitAnimation(e) {
e.target.classList.remove(e.target.entranceClass);
e.target.classList.remove(e.target.exitClass);
e.target.offsetWidth;
e.target.classList.add(e.target.exitClass);
window.setTimeout(()=>{
e.target.addEventListener('animationend', hideAfterExit, true);
},0);
}
function triggerEntranceAnimation(selector, entranceClass, exitClass, image) {
let transitionEl = document.querySelector(selector);
transitionEl.style.display = 'flex';
transitionEl.addEventListener('animationend', triggerExitAnimation, true);
transitionEl.classList.remove(entranceClass);
transitionEl.entranceClass = entranceClass;
transitionEl.exitClass = exitClass;
transitionEl.offsetWidth;
if (image && standardRoles.includes(currentGame.killedRole)) {
transitionEl.classList.remove("killed-role-custom");
transitionEl.setAttribute("src", "../assets/images/roles/" + currentGame.killedRole.replace(/\s/g, '') + ".png");
} else {
if (image) {
transitionEl.setAttribute("src", "../assets/images/custom.svg");
transitionEl.setAttribute("class", "killed-role-custom");
}
}
transitionEl.classList.add(entranceClass);
}
function playKilledAnimation() {
triggerEntranceAnimation('#overlay', 'animate-overlay-in', 'animate-overlay-out', false);
triggerEntranceAnimation('#killed-role', 'animate-role-in', 'animate-role-out', true);
triggerEntranceAnimation('#killed-name', 'animate-name-in', 'animate-name-out', false);
}
function launchGame() {
randomlyDealCardsToPlayers();
utility.shuffle(currentGame.players); // put the players in a random order
socket.emit('startGame', { players: currentGame.players , code: currentGame.accessCode});
}
function randomlyDealCardsToPlayers() {
for (let player of currentGame.players) {
player.card = drawRandomCard();
}
}
function drawRandomCard() {
return currentGame.deck.splice(utility.getRandomInt(currentGame.deck.length) - 1, 1)[0];
}
function getLiveCount() {
let liveCount = 0;
for (let player of currentGame.players) {
if (!player.dead) {
liveCount ++;
}
}
return liveCount;
}
function renderEndSplash() {
clearInterval(clock);
document.getElementById("game-container").remove();
document.querySelector("#message-box").style.display = 'none';
currentGame.winningTeam === "village"
? document.getElementById("end-container").innerHTML ="<div class='winner-header'><p class='winner-village'>Village</p> wins!</div>"
: document.getElementById("end-container").innerHTML ="<div class='winner-header'><p class='winner-wolf'>Wolves</p>win!</div>";
const rosterContainer = document.createElement("div");
rosterContainer.setAttribute("id", "roster");
document.getElementById("end-container").innerHTML += "<div class='roster-header'>Here's what everyone was:</div>";
let rosterContent = "";
for (const player of currentGame.players) {
rosterContent += "<div class='roster-list-item'>";
rosterContent += standardRoles.includes(player.card.role)
? "<img alt='' src='/assets/images/roles-small/" + player.card.role.replace(/\s/g, '') + ".png' />"
: "<img alt='' class='card-image-custom' src='/assets/images/custom.svg' />";
rosterContent += player.name + ": " + player.card.role + "</div>"
}
rosterContainer.innerHTML = rosterContent;
document.getElementById("end-container").appendChild(rosterContainer);
document.getElementById("end-container").innerHTML += "<a href='/'><button class='app-btn'>Home</button></a>";
}
function renderGame() {
// remove lobby components if present
if (document.getElementById("lobby-container") !== null && document.getElementById("launch") !== null) {
document.getElementById("lobby-container").remove();
document.getElementById("launch").remove();
}
document.querySelector("#message-box").style.display = 'block';
if (currentGame.killedRole && currentGame.lastKilled !== lastKilled) { // a new player has been killed
lastKilled = currentGame.lastKilled;
document.getElementById("killed-name").innerText = currentGame.killedPlayer + " was a " + currentGame.killedRole + "!";
playKilledAnimation();
document.getElementById("message-box").innerText = currentGame.message;
}
const player = currentGame.players.find((player) => player.id === sessionStorage.getItem("id"));
// render the header
document.getElementById("game-container").setAttribute("class", "game-container");
const gameHeader = document.createElement("div");
gameHeader.setAttribute("id", "game-header");
gameHeader.innerHTML =
"<div id='players-remaining'>" + getLiveCount() + "/" + currentGame.size + " alive</div>" +
"<div id='clock'></div>" +
"<div id='pause-container'></div>";
if (document.getElementById("game-header")) {
document.getElementById("card-container").removeChild(document.getElementById("game-header"));
}
document.getElementById("card-container").prepend(gameHeader);
// render the card if it hasn't been yet
if (!cardRendered) {
renderPlayerCard(player);
cardRendered = true;
}
// build the clock
if (currentGame.time) {
updateClock();
document.getElementById("pause-container").innerHTML = currentGame.paused ?
"<img alt='pause' src='../assets/images/play-button.svg' id='play-pause'/>"
: "<img alt='pause' src='../assets/images/pause-button.svg' id='play-pause'/>";
document.getElementById("play-pause").addEventListener("click", pauseOrResumeGame)
}
// add the "I'm dead" button
let killedBtn = document.createElement("button");
killedBtn.setAttribute("id", "dead-btn");
if (player.dead) {
killedBtn.setAttribute("class", "app-btn killed-btn disabled");
killedBtn.innerText = "Killed"
} else {
killedBtn.setAttribute("class", "app-btn killed-btn");
killedBtn.innerText = "I'm dead";
}
if (document.getElementById("dead-btn")) {
document.getElementById("card-container").removeChild(document.getElementById("dead-btn"));
}
document.getElementById("card-container").appendChild(killedBtn);
document.getElementById("dead-btn").addEventListener("click", killPlayer);
// add the list of dead/alive players
renderDeadAndAliveInformation();
}
function renderDeadAndAliveInformation() {
let infoContainer = document.getElementById("info-container");
let alivePlayers = currentGame.players.filter((player) => !player.dead).sort((a, b) =>
{
return a.card.role > b.card.role ? 1 : -1;
});
let deadPlayers = currentGame.players.filter((player) => player.dead);
deadPlayers.sort((a, b) => { // sort players by the time they died
return new Date(a.deadAt) > new Date(b.deadAt) ? -1 : 1;
});
let killedContainer = document.createElement("div");
killedContainer.setAttribute("id", "killed-container");
let killedHeader = document.createElement("h2");
killedHeader.innerText = "Killed Players";
killedContainer.appendChild(killedHeader);
deadPlayers.forEach((player) => {
let deadPlayerClass = player.card.team === "good" ? "dead-player-village" : "dead-player-evil";
if (player.card.isTypeOfWerewolf) {
deadPlayerClass += " dead-player-wolf";
}
const killedPlayer = document.createElement("div");
killedPlayer.setAttribute("class", "killed-player " + deadPlayerClass);
killedPlayer.innerText = player.name + ": " + player.card.role;
killedContainer.appendChild(killedPlayer);
});
let aliveContainer = document.createElement("div");
aliveContainer.setAttribute("id", "alive-container");
let aliveHeader = document.createElement("h2");
aliveContainer.appendChild(aliveHeader);
aliveHeader.innerText = "Roles Still Alive";
alivePlayers.forEach((player) => {
let alivePlayerClass = player.card.team === "good" ? "alive-player-village" : "alive-player-evil";
if (player.card.isTypeOfWerewolf) {
alivePlayerClass += " alive-player-wolf";
}
const alivePlayer = document.createElement("div");
alivePlayer.setAttribute("class", "alive-player " + alivePlayerClass);
alivePlayer.innerHTML = "<p>" + player.card.role + "</p><img src='../assets/images/info.svg'/>";
//Add hidden description span - RTM 4/18/2020
let playerCardInfo=document.createElement("span");
playerCardInfo.setAttribute("class","tooltiptext");
playerCardInfo.innerText=player.card.description;
alivePlayer.appendChild(playerCardInfo);
aliveContainer.appendChild(alivePlayer);
});
if (infoContainer === null) {
infoContainer = document.createElement("div");
infoContainer.setAttribute("id", "info-container");
infoContainer.appendChild(killedContainer);
infoContainer.appendChild(aliveContainer);
document.getElementById("game-container").appendChild(infoContainer);
} else {
document.getElementById("killed-container").remove();
document.getElementById("alive-container").remove();
document.getElementById("info-container").append(killedContainer);
document.getElementById("info-container").append(aliveContainer);
}
}
function renderPlayerCard(player) {
const card = player.card;
const cardArt = standardRoles.includes(card.role) ?
"<img alt='" + card.role + "' src='../assets/images/roles/" + card.role.replace(/\s/g, '') + ".png' />"
: "<div class='placeholder'>Custom Role</div>";
const cardClass = player.card.team === "good" ? "game-card-inner village" : "game-card-inner wolf";
const playerCard = document.createElement("div");
playerCard.setAttribute("id", "game-card");
playerCard.setAttribute("class", getFlipState());
playerCard.innerHTML =
"<div class='" + cardClass + "'>" +
"<div class='game-card-front'>" +
"<h2>" + card.role + "</h2>" +
cardArt +
"<div>" +
"<p>" + card.description + "</p>" +
"<p id='flip-instruction'>Click to flip</p>" +
"</div>" +
"</div>" +
"<div class='game-card-back'></div>" +
"</div>";
document.getElementById("card-container").appendChild(playerCard);
document.getElementById("game-card").addEventListener("click", flipCard);
}
function pauseOrResumeGame() {
if (currentGame.paused) {
socket.emit('resumeGame', currentGame.accessCode);
} else {
socket.emit('pauseGame', currentGame.accessCode);
}
}
function getFlipState() {
return cardFlippedOver ? "flip-down" : "flip-up";
}
function flipCard() {
cardFlippedOver
? flipUp()
: flipDown();
cardFlippedOver = !cardFlippedOver;
}
function flipUp(){
const card = document.getElementById("game-card");
card.classList.add("flip-up");
card.classList.remove("flip-down");
}
function flipDown(){
const card = document.getElementById("game-card");
card.classList.add("flip-down");
card.classList.remove("flip-up");
}
function displayTime() {
const start = currentGame.paused ? new Date(currentGame.pauseTime) : new Date();
const end = new Date(currentGame.endTime);
const delta = end - start;
let seconds = Math.floor((delta / 1000) % 60);
let minutes = Math.floor((delta / 1000 / 60) % 60);
let hours = Math.floor((delta / (1000 * 60 * 60)) % 24);
seconds = seconds < 10 ? "0" + seconds : seconds;
minutes = minutes < 10 ? "0" + minutes : minutes;
document.getElementById("clock").innerText = hours > 0
? hours + ":" + minutes + ":" + seconds
: minutes + ":" + seconds;
}
function updateClock() {
clearInterval(clock);
if (document.getElementById("clock") !== null) {
displayTime();
clock = setInterval(function() {
displayTime();
}, 1000);
}
}
function killPlayer() {
if(confirm("Are you sure you are dead?")) {
socket.emit("killPlayer", currentGame.players.find((player) => player.id === sessionStorage.getItem("id")).id, currentGame.accessCode);
}
}
function renderLobby() {
document.querySelector("#message-box").style.display = 'none';
// Render lobby header
if (document.getElementsByClassName("lobby-player").length === 0) {
let header = document.createElement("h2");
header.setAttribute("class", "app-header-secondary");
header.innerText = "Lobby";
document.getElementById("lobby-container").appendChild(header);
let subHeader = document.createElement("div");
subHeader.setAttribute("id", "lobby-subheader");
subHeader.innerHTML = "<div>" +
"<span id='join-count'>" + currentGame.players.length + "</span>" +
"<span id='deck-size'>/" + currentGame.size + " Players</span>" +
"</div>" +
"<br>" +
"<div id='game-code'>Access Code: " + currentGame.accessCode + "</div>";
document.getElementById("lobby-container").appendChild(subHeader);
}
// Render all players that are new
let i = 1;
for (let player of currentGame.players) {
if(!document.getElementById("player-" + i)) {
const playerContainer = document.createElement("div");
player.id === sessionStorage.getItem("id") ?
playerContainer.setAttribute("class", "lobby-player highlighted")
: playerContainer.setAttribute("class", "lobby-player");
playerContainer.setAttribute("id", "player-" + i);
playerContainer.innerHTML = "<p>" + player.name + "</p>";
document.getElementById("lobby-container").appendChild(playerContainer);
document.getElementById("join-count").innerText = currentGame.players.length.toString();
}
i ++;
}
// display the launch button if the player is the host
if (sessionStorage.getItem("host")) {
if (currentGame.players.length === currentGame.size) {
document.getElementById("launch").innerHTML = "<button class='app-btn'>Start Game</button>";
document.getElementById("launch").addEventListener("click", launchGame);
} else {
document.getElementById("launch").innerHTML = "<button class='app-btn disabled'>Start Game</button>";
}
} else {
document.getElementById("launch").innerHTML = "<p>The host will start the game.</p>"
}
}
// request game state from server periodically
setInterval(function () {
socket.emit('requestState', {code: sessionStorage.getItem("code")});
}, 200);

45
javascript/join.js Normal file
View File

@@ -0,0 +1,45 @@
const socket = io();
import { utility } from './util.js'
// respond to the game state received from the server
socket.on('joinError', function(message) {
document.getElementById("join-btn").classList.remove('disabled');
document.getElementById("code").classList.add("error");
document.getElementById("join-error").innerText = message;
});
// respond to the game state received from the server
socket.on('success', function() {
document.getElementById("join-btn").classList.remove('disabled');
if (document.getElementById("code").classList.contains("error")) {
document.getElementById("code").classList.remove("error");
document.getElementById("join-error").innerText = "";
}
// If a player was a host of a previous game, don't make them the host of this one
if (sessionStorage.getItem("host")) {
sessionStorage.removeItem("host");
}
window.location.replace('/' + document.getElementById("code").value.toString().trim().toLowerCase());
});
document.getElementById("join-btn").addEventListener("click", function() {
document.getElementById("join-btn").classList.add('disabled');
if (document.getElementById("name").value.length > 0) {
const code = document.getElementById("code").value.toString().trim().toLowerCase();
if (document.getElementById("name").classList.contains("error")) {
document.getElementById("name").classList.remove("error");
document.getElementById("name-error").innerText = "";
}
sessionStorage.setItem("code", code);
let playerId = utility.generateID();
sessionStorage.setItem("id", playerId);
const playerInfo = {name: document.getElementById("name").value, id: playerId, code: code};
socket.emit('joinGame', playerInfo);
} else {
document.getElementById("join-btn").classList.remove('disabled');
document.getElementById("name").classList.add("error");
document.getElementById("name-error").innerText = "Name is required.";
}
});

View File

@@ -0,0 +1,133 @@
const finishedArtArray = ["Villager", "Werewolf", "Seer", "Shadow", "Hunter", "Mason", "Minion", "Sorcerer", "Dream Wolf"];
export class CardManager {
constructor() {}
static createCard(card) {
return new Card(card.role, card.team, card.description, card.quantity=0, card.isTypeOfWerewolf, card.custom, card.saved);
}
// builds element for the informational role modal on the setup page
static constructModalRoleElement(card) {
const modalRole = document.createElement("div");
modalRole.setAttribute("class", "modal-role");
const roleClass = card.team === "good" ? "role-village" : "role-wolf";
let roleImage;
if (card.custom === true) {
roleImage = "<img alt='No art' class='card-image-custom' src='/assets/images/custom.svg' />";
} else {
roleImage = finishedArtArray.includes(card.role) ?
"<img alt='No art' src='/assets/images/roles-small/" + card.role.replace(/\s/g, '') + ".png' />"
: "<span>Art soon.</span>";
}
modalRole.innerHTML =
"<div>" +
roleImage +
"<div>" +
"<h2 class='" + roleClass + "'>" + card.role + "</h2>" +
"<p>" + card.team + "</p>" +
"</div>" +
"</div>" +
"<p>" + card.description + "</p>";
return modalRole;
}
static constructDeckBuilderElement(card, index) {
const cardContainer = document.createElement("div");
const quantityClass = card.team === "good" ? "card-quantity quantity-village" : "card-quantity quantity-wolf";
let cardClass = card.isTypeOfWerewolf ? "card card-werewolf" : "card";
cardContainer.setAttribute("class", cardClass);
if (card.team === "good") {
cardContainer.setAttribute("id", "card-" + index);
} else {
cardContainer.setAttribute("id", "card-" + index);
}
cardContainer.innerHTML =
"<div class='card-top'>" +
"<div class='card-header'>" +
"<div>" +
"<p class='card-role'>" + card.role + "</p>" +
"<div class='" + quantityClass + "'>" + card.quantity + "</div>" +
"</div>" +
"<p>+</p>" +
"</div>" +
"</div>";
cardContainer.innerHTML = card.custom
? cardContainer.innerHTML += "<img class='card-image card-image-custom' src='/assets/images/custom.svg' alt='" + card.role + "'/>"
: cardContainer.innerHTML +="<img class='card-image' src='/assets/images/roles-small/" + card.role.replace(/\s/g, '') + ".png' alt='" + card.role + "'/>";
cardContainer.innerHTML +=
"<div class='card-bottom'>" +
"<p>-</p>" +
"</div>";
return cardContainer;
}
static constructCompactDeckBuilderElement(card, index) {
const cardContainer = document.createElement("div");
const quantityClass = card.team === "good" ? "card-quantity quantity-village" : "card-quantity quantity-wolf";
let cardClass = card.isTypeOfWerewolf ? "compact-card card-werewolf" : "compact-card";
cardContainer.setAttribute("class", cardClass);
if (card.team === "good") {
cardContainer.setAttribute("id", "card-" + index);
} else {
cardContainer.setAttribute("id", "card-" + index);
}
cardContainer.innerHTML =
"<div class='compact-card-left'>" +
"<p>-</p>" +
"</div>" +
"<div class='compact-card-header'>" +
"<p class='card-role'>" + card.role + "</p>" +
"<div class='" + quantityClass + "'>" + card.quantity + "</div>" +
"</div>" +
"<div class='compact-card-right'>" +
"<p>+</p>" +
"</div>";
return cardContainer;
}
static constructCustomCardIndicator(isCondensed, team) {
let customCard = document.createElement("div");
if (isCondensed) {
customCard.classList.add("compact-card", "custom-card");
} else {
customCard.classList.add("card", "custom-card");
}
if (team === "good") {
customCard.setAttribute("id", "custom-good");
} else {
customCard.setAttribute("id", "custom-evil");
}
let cardHeader = document.createElement("h1");
cardHeader.innerText = "Add Custom Role";
let cardBody = document.createElement("div");
cardBody.innerText = "+";
customCard.appendChild(cardHeader);
customCard.appendChild(cardBody);
return customCard;
}
}
class Card {
constructor(role, team, description, quantity, isTypeOfWerewolf, custom, saved) {
this.id = null;
this.role = role;
this.isTypeOfWerewolf = isTypeOfWerewolf;
this.team = team;
this.description = description;
this.quantity = quantity;
this.custom = custom;
this.saved = saved;
}
}

View File

@@ -0,0 +1,20 @@
module.exports = function(debugMode = false){
return {
log(message = "") {
const now = new Date();
console.log('LOG ', now.toGMTString(), ': ', message);
},
debug(message = "") {
if (!debugMode) return;
const now = new Date();
console.debug('DEBUG ', now.toGMTString(), ': ', message);
},
error(message = "") {
if (!debugMode) return;
const now = new Date();
console.error('ERROR ', now.toGMTString(), ': ', message);
}
};
};

453
javascript/setup.js Normal file
View File

@@ -0,0 +1,453 @@
import {cards} from './cards.js'
import {utility} from './util.js'
import {CardManager} from './modules/card-manager.js'
const socket = io();
class Game {
constructor(accessCode, size, deck, time, hasDreamWolf) {
this.accessCode = accessCode;
this.size = size;
this.deck = deck;
this.time = time;
this.players = [];
this.status = "lobby";
this.hasDreamWolf = hasDreamWolf;
this.endTime = null;
}
}
const fullDeck = [];
let gameSize = 0;
let atLeastOnePlayer = false;
// register event listeners on buttons
document.getElementById("reset-btn").addEventListener("click", resetCardQuantities);
document.getElementById("create-btn").addEventListener("click", createGame);
document.getElementById("role-view-changer-gallery").addEventListener("click", function() { toggleViewChanger(false) });
document.getElementById("role-view-changer-list").addEventListener("click", function() { toggleViewChanger(true) });
document.getElementById("role-btn").addEventListener("click", function() { displayModal("role-modal", undefined) });
document.getElementById("edit-role-btn").addEventListener("click", function() { displayModal("edit-custom-roles-modal", undefined) });
document.getElementById("custom-role-form").addEventListener("submit", function(e) {
addCustomCardToRoles(e);
});
Array.from(document.getElementsByClassName("close")).forEach(function(element) {
element.addEventListener('click', closeModal);
});
// render all of the available cards to the user
window.onload = function() {
readInUserCustomRoles();
renderAvailableCards(false);
};
function renderAvailableCards(isCondensed) {
cards.sort(function(a, b) {
return a.role.toUpperCase().localeCompare(b.role);
});
document.getElementById("card-select-good").innerHTML = "";
document.getElementById("card-select-evil").innerHTML = "";
document.getElementById("roles").innerHTML = "";
document.getElementById("custom-roles").innerHTML = "";
for (let i = 0; i < cards.length; i ++) {
cards[i].team === "good"
? renderGoodRole(cards[i], i, isCondensed)
: renderEvilRole(cards[i], i, isCondensed);
}
if (document.getElementById("custom-roles").getElementsByClassName("custom-role-edit").length === 0) {
document.getElementById("custom-roles").innerHTML = "<h2>You haven't added any custom cards.</h2>";
}
let customCardGood = CardManager.constructCustomCardIndicator(isCondensed, "good");
let customCardEvil = CardManager.constructCustomCardIndicator(isCondensed, "evil");
document.getElementById("card-select-good").appendChild(customCardGood);
document.getElementById("card-select-evil").appendChild(customCardEvil);
customCardGood.addEventListener("click", function() {
displayModal("custom-card-modal", "Good");
});
customCardEvil.addEventListener("click", function() {
displayModal("custom-card-modal", "Evil");
});
}
function renderGoodRole(cardInfo, i, isCondensed) {
const card = CardManager.createCard(cardInfo);
if (card.custom) {
renderCustomRoleInModal(card, i);
}
fullDeck.push(card);
document.getElementById("roles").appendChild(CardManager.constructModalRoleElement(card));
if (isCondensed) {
document.getElementById("card-select-good").appendChild(CardManager.constructCompactDeckBuilderElement(card, i));
let cardLeft = document.getElementById("card-" + i).getElementsByClassName("compact-card-left")[0];
let cardQuantity = document.getElementById("card-" + i).getElementsByClassName("card-quantity")[0];
let cardRight = document.getElementById("card-" + i).getElementsByClassName("compact-card-right")[0];
cardRight.addEventListener("click", function() { incrementCardQuantity(cardRight) }, true);
cardLeft.addEventListener("click", function() { decrementCardQuantity(cardLeft) }, true);
cardRight.card = card;
cardRight.quantityEl = cardQuantity;
cardLeft.card = card;
cardLeft.quantityEl = cardQuantity;
} else {
document.getElementById("card-select-good").appendChild(CardManager.constructDeckBuilderElement(card, i));
// Add event listeners to the top and bottom halves of the card to change the quantity.
let cardTop = document.getElementById("card-" + i).getElementsByClassName("card-top")[0];
let cardQuantity = document.getElementById("card-" + i).getElementsByClassName("card-quantity")[0];
let cardBottom = document.getElementById("card-" + i).getElementsByClassName("card-bottom")[0];
cardTop.addEventListener("click", function() { incrementCardQuantity(cardTop) }, false);
cardBottom.addEventListener("click", function() { decrementCardQuantity(cardBottom) }, false);
cardTop.card = card;
cardTop.quantityEl = cardQuantity;
cardBottom.card = card;
cardBottom.quantityEl = cardQuantity;
}
}
function renderEvilRole(cardInfo, i, isCondensed) {
const card = CardManager.createCard(cardInfo);
if (card.custom) {
renderCustomRoleInModal(card, i);
}
fullDeck.push(card);
document.getElementById("roles").appendChild(CardManager.constructModalRoleElement(card));
if (isCondensed) {
document.getElementById("card-select-evil").appendChild(CardManager.constructCompactDeckBuilderElement(card, i));
let cardLeft = document.getElementById("card-" + i).getElementsByClassName("compact-card-left")[0];
let cardQuantity = document.getElementById("card-" + i).getElementsByClassName("card-quantity")[0];
let cardRight = document.getElementById("card-" + i).getElementsByClassName("compact-card-right")[0];
cardRight.addEventListener("click", function() { incrementCardQuantity(cardRight) }, false);
cardLeft.addEventListener("click", function() { decrementCardQuantity(cardLeft) }, false);
cardRight.card = card;
cardRight.quantityEl = cardQuantity;
cardLeft.card = card;
cardLeft.quantityEl = cardQuantity;
} else {
document.getElementById("card-select-evil").appendChild(CardManager.constructDeckBuilderElement(card, i));
// Add event listeners to the top and bottom halves of the card to change the quantity.
let cardTop = document.getElementById("card-" + i).getElementsByClassName("card-top")[0];
let cardQuantity = document.getElementById("card-" + i).getElementsByClassName("card-quantity")[0];
let cardBottom = document.getElementById("card-" + i).getElementsByClassName("card-bottom")[0];
cardTop.addEventListener("click", function() { incrementCardQuantity(cardTop) }, false);
cardBottom.addEventListener("click", function() { decrementCardQuantity(cardBottom) }, false);
cardTop.card = card;
cardTop.quantityEl = cardQuantity;
cardBottom.card = card;
cardBottom.quantityEl = cardQuantity;
}
}
function addCustomCardToRoles(e) {
e.preventDefault();
if (!cards.find((card) => card.role === document.getElementById("custom-role-name").value)) {
let newCard = {
role: document.getElementById("custom-role-name").value,
team: document.getElementById("custom-role-team").value,
description: document.getElementById("custom-role-desc").value,
isTypeOfWerewolf: document.getElementById("custom-role-wolf").checked,
custom: true,
saved: document.getElementById("custom-role-remember").checked
};
cards.push(newCard);
renderAvailableCards(document.getElementById("role-view-changer-list").classList.contains("selected"));
if (newCard.saved === true) {
let existingRoles = localStorage.getItem("play-werewolf-custom-roles");
if (existingRoles !== null) {
let rolesArray;
try {
rolesArray = JSON.parse(existingRoles);
} catch (e) {
console.error(e.message);
}
if (rolesArray) {
rolesArray.push(newCard);
}
localStorage.setItem("play-werewolf-custom-roles", JSON.stringify(rolesArray));
} else {
localStorage.setItem("play-werewolf-custom-roles", JSON.stringify(new Array(newCard)));
}
}
updateCustomRoleModal();
closeModal();
document.getElementById("custom-role-form").reset();
} else {
alert("A custom or standard card already exists with that name!")
}
}
function updateCustomRoleModal() {
document.getElementById("custom-roles").innerHTML = "";
for (let i = 0; i < cards.length; i++){
if (cards[i].custom) {
renderCustomRoleInModal(cards[i], i);
}
}
}
function readInUserCustomRoles() {
let expectedKeys = ["role", "description", "team", "isTypeOfWerewolf", "custom", "saved"];
let userCustomRoles = utility.validateCustomRolesJsonObject("play-werewolf-custom-roles", expectedKeys);
if (userCustomRoles) {
for (let i = 0; i < userCustomRoles.length; i++) {
cards.push(userCustomRoles[i]);
}
}
}
function renderCustomRoleInModal(card, index) {
let roleElement = document.createElement("div");
let editRemoveContainer = document.createElement("div");
let roleLabel = document.createElement("div");
let roleName = document.createElement("p");
let remove = document.createElement("img");
let edit = document.createElement("img");
let editForm = buildRoleEditForm(index);
roleName.innerText = card.role;
remove.setAttribute("src", "../assets/images/delete.svg");
remove.setAttribute("title", "Delete");
remove.addEventListener("click", function() { removeCustomRole(card.role) });
edit.setAttribute("src", "../assets/images/pencil_green.svg");
edit.setAttribute("title", "Edit");
edit.addEventListener("click", function(e) { toggleEditForm(e, index) });
roleElement.setAttribute("class", "custom-role-edit");
editRemoveContainer.appendChild(remove);
editRemoveContainer.appendChild(edit);
roleLabel.appendChild(roleName);
roleLabel.appendChild(editRemoveContainer);
roleElement.appendChild(roleLabel);
roleElement.appendChild(editForm);
document.getElementById("custom-roles").appendChild(roleElement);
document.getElementById("edit-form-" + index).addEventListener("submit", function(e) {
updateCustomRole(e, index);
});
}
function toggleEditForm(event, index) {
event.preventDefault();
let displayRule = document.getElementById("edit-form-" + index).style.display;
document.getElementById("edit-form-" + index).style.display = displayRule === "none" ? "block" : "none";
if (document.getElementById("edit-form-" + index).style.display === "block") {
populateEditRoleForm(cards[index], index);
}
}
function toggleViewChanger(isCondensed) {
if (isCondensed) {
document.getElementById("role-view-changer-gallery").classList.remove("selected");
document.getElementById("role-view-changer-list").classList.add("selected");
} else {
document.getElementById("role-view-changer-gallery").classList.add("selected");
document.getElementById("role-view-changer-list").classList.remove("selected");
}
renderAvailableCards(isCondensed);
}
function buildRoleEditForm(index) {
let infoForm = document.createElement("div");
infoForm.style.display = "none";
infoForm.setAttribute("id", "edit-form-" + index);
infoForm.innerHTML =
"<form class=\"edit-role-form\" id=\"edit-role-form-" + index + "\">" +
"<label for=\"edit-role-desc-" + index + "\">Description</label>" +
"<textarea rows=\"3\" id=\"edit-role-desc-" + index + "\" required></textarea>" +
"<label for=\"edit-role-team-" + index + "\">Team</label>" +
"<select id=\"edit-role-team-" + index + "\">" +
"<option value=\"good\">Good</option>" +
"<option value=\"evil\">Evil</option>" +
"</select>" +
"<div class=\"checkbox\">" +
"<input type=\"checkbox\" id=\"edit-role-wolf-" + index + "\">" +
"<label for=\"edit-role-wolf-" + index + "\">Werewolf role (counts for parity)</label>" +
"</div>" +
"<div class=\"checkbox\">" +
"<input type=\"checkbox\" id=\"edit-role-remember-" + index + "\">" +
"<label for=\"edit-role-remember-" + index + "\">Remember this role for later (uses cookies)</label>" +
"</div>" +
"<br><br>" +
"<input type=\"submit\" class=\"app-btn\" value=\"Update\">" +
"</form>";
return infoForm;
}
function populateEditRoleForm(card, index) {
document.getElementById("edit-role-desc-" + index).value = card.description;
document.getElementById("edit-role-team-" + index).value = card.team;
document.getElementById("edit-role-wolf-" + index).checked = card.isTypeOfWerewolf;
document.getElementById("edit-role-remember-" + index).checked = card.saved;
}
function removeCustomRole(name) {
if (confirm("Delete this role?")) {
let matchingCards = cards.filter((card) => card.role === name);
matchingCards.forEach((card) => {
cards.splice(cards.indexOf(card), 1);
});
let expectedKeys = ["role", "description", "team", "isTypeOfWerewolf", "custom", "saved"];
let userCustomRoles = utility.validateCustomRolesJsonObject("play-werewolf-custom-roles", expectedKeys);
if (userCustomRoles) {
userCustomRoles = userCustomRoles.filter((card) => card.role !== name);
localStorage.setItem("play-werewolf-custom-roles", JSON.stringify(userCustomRoles));
}
updateCustomRoleModal();
renderAvailableCards(document.getElementById("role-view-changer-list").classList.contains("selected"));
}
}
function updateCustomRole(event, index) {
event.preventDefault();
if (index >= 0 && index < cards.length) {
let cardToUpdate = cards[index];
cardToUpdate.team = document.getElementById("edit-role-team-" + index).value;
cardToUpdate.description = document.getElementById("edit-role-desc-" + index).value;
cardToUpdate.isTypeOfWerewolf = document.getElementById("edit-role-wolf-" + index).checked;
cardToUpdate.saved = document.getElementById("edit-role-remember-" + index).checked;
removeOrAddSavedRoleIfNeeded(cardToUpdate);
toggleEditForm(event, index);
renderAvailableCards(document.getElementById("role-view-changer-list").classList.contains("selected"));
}
}
function removeOrAddSavedRoleIfNeeded(card) {
let expectedKeys = ["role", "description", "team", "isTypeOfWerewolf", "custom", "saved"];
let userCustomRoles = utility.validateCustomRolesJsonObject("play-werewolf-custom-roles", expectedKeys);
if (userCustomRoles) {
if (card.saved) {
let roleToUpdate = userCustomRoles.find((savedCard) => savedCard.role === card.role);
if (roleToUpdate) {
userCustomRoles[userCustomRoles.indexOf(roleToUpdate)] = card;
} else {
userCustomRoles.push(card);
}
localStorage.setItem("play-werewolf-custom-roles", JSON.stringify(userCustomRoles));
} else {
let roleToRemove = userCustomRoles.find((savedCard) => savedCard.role === card.role);
if (roleToRemove) {
userCustomRoles.splice(userCustomRoles.indexOf(roleToRemove), 1);
localStorage.setItem("play-werewolf-custom-roles", JSON.stringify(userCustomRoles));
}
}
}
}
function incrementCardQuantity(e) {
if(e.card.quantity < 25) {
e.card.quantity += 1;
}
e.quantityEl.innerHTML = e.card.quantity;
updateGameSize();
}
function decrementCardQuantity(e) {
if(e.card.quantity > 0) {
e.card.quantity -= 1;
}
e.quantityEl.innerHTML = e.card.quantity;
updateGameSize();
}
function updateGameSize() {
gameSize = 0;
for (let card of fullDeck) {
gameSize += card.quantity;
}
document.getElementById("game-size").innerText = gameSize + " Players";
atLeastOnePlayer = gameSize > 0;
return gameSize;
}
function resetCardQuantities() {
for (let card of fullDeck) {
card.quantity = 0;
}
updateGameSize();
Array.prototype.filter.call(document.getElementsByClassName("card-quantity"), function(quantities){
return quantities.innerHTML = 0;
});
}
function displayModal(modalId, teamForCustomRole) {
if (teamForCustomRole === "Good") {
document.getElementById("option-evil").removeAttribute("selected");
document.getElementById("option-good").setAttribute("selected", "selected");
}
if (teamForCustomRole === "Evil") {
document.getElementById("option-good").removeAttribute("selected");
document.getElementById("option-evil").setAttribute("selected", "selected");
}
document.getElementById(modalId).classList.remove("hidden");
document.getElementById("app-content").classList.add("hidden");
}
function closeModal() {
document.getElementById("role-modal").classList.add("hidden");
document.getElementById("custom-card-modal").classList.add("hidden");
document.getElementById("edit-custom-roles-modal").classList.add("hidden");
document.getElementById("app-content").classList.remove("hidden");
}
function buildDeckFromQuantities() {
let playerDeck = [];
for (const card of fullDeck) {
for (let i = 0; i < card.quantity; i++) {
card.id = utility.generateID();
playerDeck.push(card);
}
}
return playerDeck;
}
function createGame() {
if (document.getElementById("name").value.length > 0 && atLeastOnePlayer) {
// generate 6 digit access code
let code = "";
let charPool = "abcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 6; i++) {
code += charPool[utility.getRandomInt(36)]
}
// generate unique player Id for session
let id = utility.generateID();
sessionStorage.setItem("id", id);
// player who creates the game is the host
sessionStorage.setItem("host", true);
// send a new game to the server, and then join it
const playerInfo = {name: document.getElementById("name").value, code: code, id: id};
let gameDeck = buildDeckFromQuantities();
const game = new Game(
code,
gameSize,
gameDeck,
Math.ceil(document.getElementById("time").value),
gameDeck.find((card) => card.role === "Dream Wolf") !== undefined
);
socket.emit('newGame', game, function() {
socket.emit('joinGame', playerInfo);
sessionStorage.setItem('code', code);
window.location.replace('/' + code);
});
} else {
document.getElementById("some-error").innerText = "There are problems with your above setup.";
if (!atLeastOnePlayer) {
document.getElementById("game-size").classList.add("error");
} else {
document.getElementById("game-size").classList.remove("error");
}
document.getElementById("name").classList.add("error");
document.getElementById("name-error").innerText = "Name is required.";
}
}

50
javascript/util.js Normal file
View File

@@ -0,0 +1,50 @@
export const utility =
{
generateID() {
let code = "";
let charPool = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
for (let i = 0; i < 10; i++) {
code += charPool[this.getRandomInt(61)]
}
return code;
},
getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
},
shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
},
validateCustomRolesJsonObject(name, expectedKeys) {
let value = localStorage.getItem(name);
if (value !== null) {
let valueJson;
try {
valueJson = JSON.parse(value);
} catch(e) {
console.error(e.message);
localStorage.removeItem(name);
return false;
}
if (valueJson && Array.isArray(valueJson)) { // some defensive programming - check if it's an array, and that the object has the expected structure
for (let i = 0; i < valueJson.length; i++){
if (expectedKeys.some((key) => !Object.keys(valueJson[i]).includes(key))) {
console.error("tried to read invalid object: " + valueJson[i] + " with expected keys: " + expectedKeys);
valueJson.splice(i, 1);
localStorage.setItem(name, JSON.stringify(valueJson));
}
}
return valueJson;
} else { // object has been messed with. remove it.
localStorage.removeItem(name);
return false;
}
}
}
};