first draft editing of custom roles

This commit is contained in:
Alec Maier
2020-04-25 00:36:15 -04:00
parent 28ad7b5575
commit 4109b6c72e
7 changed files with 323 additions and 161 deletions

View File

@@ -2,7 +2,7 @@
<!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ -->
<g>
<title>background</title>
<rect fill="none" id="canvas_background" height="211" width="211" y="-1" x="-1"/>
<rect fill="none" id="canvas_background" height="172" width="172" y="-1" x="-1"/>
<g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid">
<rect fill="url(#gridpattern)" stroke-width="0" y="0" x="0" height="100%" width="100%"/>
</g>

Before

Width:  |  Height:  |  Size: 840 B

After

Width:  |  Height:  |  Size: 840 B

View File

@@ -1,14 +1,14 @@
<svg width="132" height="192" xmlns="http://www.w3.org/2000/svg">
<svg width="172" height="172" xmlns="http://www.w3.org/2000/svg">
<!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ -->
<g>
<title>background</title>
<rect fill="none" id="canvas_background" height="194" width="134" y="-1" x="-1"/>
<rect fill="none" id="canvas_background" height="174" width="174" y="-1" x="-1"/>
<g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid">
<rect fill="url(#gridpattern)" stroke-width="0" y="0" x="0" height="100%" width="100%"/>
</g>
</g>
<g>
<title>Layer 1</title>
<path id="svg_2" d="m124.961908,17.099698l-20.881294,-13.124489c-5.268417,-3.308101 -12.210471,-1.715912 -15.530119,3.553603l-8.230499,13.1l39.946799,25.089697l8.23699,-13.093919c3.307049,-5.275788 1.728716,-12.224161 -3.541878,-15.524891l0,0zm-115.727379,116.707156l39.948976,25.089439l65.110209,-103.65022l-39.967994,-25.095989l-65.091198,103.656764l0.000007,0.000007zm-6.102198,31.877066l-0.88233,23.566079l20.849261,-11.027851l19.374887,-10.229887l-38.540057,-24.219893l-0.801755,21.911559l0,0l-0.000007,-0.000007z" stroke-width="4.5" stroke="none" fill="green"/>
<path id="svg_1" d="m139.382578,14.249727l-18.982996,-11.931355c-4.78947,-3.007365 -11.100429,-1.559921 -14.118291,3.230549l-7.482272,11.909092l36.315275,22.808817l7.488173,-11.903564c3.006409,-4.796172 1.57156,-11.112875 -3.219889,-14.113539l0,0zm-105.206717,106.097424l36.317254,22.808582l59.191104,-94.22748l-36.334543,-22.814538l-59.173821,94.233429l0.000006,0.000006zm-5.547453,28.979153l-0.802118,21.42371l18.953876,-10.02532l17.613535,-9.299898l-35.036418,-22.018086l-0.728868,19.9196l0,0l-0.000006,-0.000006z" stroke-width="1.5" stroke="none" fill="green"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,94 @@
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);
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 constructCustomCardIndicator() {
let customCard = document.createElement("div");
customCard.classList.add("card", "custom-card");
customCard.setAttribute("id", "custom");
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

@@ -1,20 +1,8 @@
import {cards} from './cards.js'
import {utility} from './util.js'
import {CardManager} from './modules/card-manager.js'
const socket = io();
const finishedArtArray = ["Villager", "Werewolf", "Seer", "Shadow", "Hunter", "Mason", "Minion", "Sorcerer", "Dream Wolf"];
// important declarations
class Card {
constructor(role, team, description, isTypeOfWerewolf) {
this.id = null;
this.role = role;
this.isTypeOfWerewolf = isTypeOfWerewolf;
this.team = team;
this.description = description;
this.quantity = 0;
}
}
class Game {
constructor(accessCode, size, deck, time, hasDreamWolf) {
@@ -58,198 +46,229 @@ function renderAvailableCards() {
});
document.getElementById("card-select").innerHTML = "";
document.getElementById("roles").innerHTML = "";
document.getElementById("custom-roles").innerHTML = "";
for (let i = 0; i < cards.length; i ++) {
const newCard = new Card(cards[i].role, cards[i].team, cards[i].description, cards[i].isTypeOfWerewolf);
// put card info in the informational role description modal
const modalRole = document.createElement("div");
modalRole.setAttribute("class", "modal-role");
const roleClass = cards[i].team === "good" ? "role-village" : "role-wolf";
let roleImage;
if (cards[i].custom === true) {
roleImage = "<img alt='No art' class='card-image-custom' src='/assets/images/custom.svg' />";
} else {
roleImage = finishedArtArray.includes(cards[i].role) ?
"<img alt='No art' src='/assets/images/roles-small/" + cards[i].role.replace(/\s/g, '') + ".png' />"
: "<span>Art soon.</span>";
const card = CardManager.createCard(cards[i]);
if (card.custom) {
renderCustomRoleInModal(cards[i], i);
}
modalRole.innerHTML =
"<div>" +
roleImage +
"<div>" +
"<h2 class='" + roleClass + "'>" + cards[i].role + "</h2>" +
"<p>" + cards[i].team + "</p>" +
"</div>" +
"</div>" +
"<p>" + cards[i].description + "</p>";
fullDeck.push(card);
document.getElementById("roles").appendChild(modalRole);
document.getElementById("roles").appendChild(CardManager.constructModalRoleElement(card));
document.getElementById("card-select").appendChild(CardManager.constructDeckBuilderElement(card, i));
fullDeck.push(newCard);
const cardContainer = document.createElement("div");
const quantityClass = cards[i].team === "good" ? "card-quantity quantity-village" : "card-quantity quantity-wolf";
let cardClass = newCard.isTypeOfWerewolf ? "card card-werewolf" : "card";
cardContainer.setAttribute("class", cardClass);
cardContainer.setAttribute("id", "card-" + i);
cardContainer.innerHTML =
"<div class='card-top'>" +
"<div class='card-header'>" +
"<div>" +
"<p class='card-role'>" + newCard.role + "</p>" +
"<div class='" + quantityClass + "'>" + newCard.quantity + "</div>" +
"</div>" +
"<p>+</p>" +
"</div>" +
"</div>";
cardContainer.innerHTML = cards[i].custom
? cardContainer.innerHTML += "<img class='card-image card-image-custom' src='../assets/images/custom.svg' alt='" + newCard.role + "'/>"
: cardContainer.innerHTML +="<img class='card-image' src='../assets/images/roles-small/" + newCard.role.replace(/\s/g, '') + ".png' alt='" + newCard.role + "'/>";
cardContainer.innerHTML +=
"<div class='card-bottom'>" +
"<p>-</p>" +
"</div>";
document.getElementById("card-select").appendChild(cardContainer);
// 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", incrementCardQuantity, false);
cardBottom.addEventListener("click", decrementCardQuantity, false);
cardTop.card = newCard;
cardTop.card = card;
cardTop.quantityEl = cardQuantity;
cardBottom.card = newCard;
cardBottom.card = card;
cardBottom.quantityEl = cardQuantity;
}
renderCustomCard();
resetCardQuantities();
}
function renderCustomCard() {
let customCard = document.createElement("div");
customCard.classList.add("card", "custom-card");
customCard.setAttribute("id", "custom");
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 cardHeader = document.createElement("h1");
cardHeader.innerText = "Add Custom Role";
let cardBody = document.createElement("div");
cardBody.innerText = "+";
customCard.appendChild(cardHeader);
customCard.appendChild(cardBody);
document.getElementById("card-select").appendChild(customCard);
customCard.addEventListener("click", function() {
let customCardElement = CardManager.constructCustomCardIndicator();
document.getElementById("card-select").appendChild(customCardElement);
customCardElement.addEventListener("click", function() {
displayModal("custom-card-modal");
});
}
function addCustomCardToRoles(e) {
e.preventDefault();
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();
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();
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 (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)));
}
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!")
}
updateCustomRoleModal();
closeModal();
document.getElementById("custom-role-form").reset();
}
function updateCustomRoleModal() {
document.getElementById("custom-roles").innerHTML = "";
cards.forEach((card) => {
if (card.custom) {
renderCustomRoleInModal(card);
for (let i = 0; i < cards.length; i++){
if (cards[i].custom) {
renderCustomRoleInModal(cards[i], i);
}
});
}
}
function readInUserCustomRoles() {
let existingRoles = localStorage.getItem("play-werewolf-custom-roles");
if (existingRoles !== null) {
let rolesArray;
try {
rolesArray = JSON.parse(existingRoles);
} catch(e) {
console.error(e.message);
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]);
}
if (rolesArray) {
rolesArray.forEach((card) => {
renderCustomRoleInModal(card);
cards.push(card);
})
}
}
if (document.getElementById("custom-roles").getElementsByClassName("custom-role-edit").length === 0) {
document.getElementById("custom-roles").innerHTML = "<h2>You haven't added any custom roles.</h2>";
}
}
function renderCustomRoleInModal(card) {
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);
// TODO: add edit functionality
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);
roleElement.appendChild(roleName);
roleElement.appendChild(editRemoveContainer);
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 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) {
let matchingCards = cards.filter((card) => card.role === name);
matchingCards.forEach((card) => {
cards.splice(cards.indexOf(card), 1);
});
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 (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();
}
}
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();
}
}
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));
}
}
if (rolesArray) {
rolesArray = rolesArray.filter((card) => card.role !== name);
}
localStorage.setItem("play-werewolf-custom-roles", JSON.stringify(rolesArray));
}
updateCustomRoleModal();
renderAvailableCards();
}
@@ -305,9 +324,8 @@ function buildDeckFromQuantities() {
let playerDeck = [];
for (const card of fullDeck) {
for (let i = 0; i < card.quantity; i++) {
let newCard = new Card(card.role, card.team, card.description, card.isTypeOfWerewolf);
newCard.id = utility.generateID();
playerDeck.push(newCard);
card.id = utility.generateID();
playerDeck.push(card);
}
}
return playerDeck;

View File

@@ -446,12 +446,13 @@ button {
}
#custom-roles img {
width: 20px;
width: 25px;
}
#custom-roles .custom-role-edit {
display: flex;
width: 100%;
flex-direction: column;
justify-content: space-around;
font-size: 19px;
align-items: center;
@@ -460,6 +461,27 @@ button {
margin: 0.3em;
}
.edit-role-form {
background-color: #1a1a1a;
padding: 1em;
}
.custom-role-edit > div:nth-child(1) {
display: flex;
width: 100%;
justify-content: space-around;
}
.custom-role-edit > div:nth-child(1) div {
display: flex;
align-items: center;
}
.custom-role-edit > div:nth-child(2) {
display: flex;
width: 100%;
}
.custom-role-edit p {
text-overflow: ellipsis;
overflow-x: hidden;
@@ -470,6 +492,7 @@ button {
#custom-roles .custom-role-edit div > img {
margin: 0 1em;
cursor: pointer;
user-select: none;
}
#custom-roles .custom-role-edit div > img:hover {
@@ -977,7 +1000,7 @@ label {
}
.hidden {
display: none;
display: none !important;
}
#lobby-subheader {
@@ -1193,7 +1216,7 @@ label {
padding: 5px;
display: flex;
align-items: center;
justify-content: space-around;
justify-content: space-between;
box-shadow: 3px 10px 10px rgba(0,0,0,0.6);
margin: 0.3em;
position: relative;

View File

@@ -19,5 +19,32 @@ export const utility =
[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;
}
}
}
};

View File

@@ -51,7 +51,7 @@
wolves as villagers, the wolves win (as they can kill a villager at night, and then have the majority to kill the
remaining villager during the day). If the village manages to kill every wolf, then they win. In the scenario
where there is one villager and one wolf remaining, if the remaining villager is a Hunter, then the village wins.
There are several "power roles" such as the Hunter, which can help the village or the wolves. If you are a power
There are several "power cards" such as the Hunter, which can help the village or the wolves. If you are a power
role, you can read the description on your card to find out what your special ability is.
</p>
<a href="/"><button class="app-btn">Back</button></a>