diff --git a/client/src/modules/DeckStateManager.js b/client/src/modules/DeckStateManager.js
index 23a79bf..dede3bc 100644
--- a/client/src/modules/DeckStateManager.js
+++ b/client/src/modules/DeckStateManager.js
@@ -1,10 +1,43 @@
import { globals } from '../config/globals.js';
import { HTMLFragments } from './HTMLFragments.js';
import { toast } from './Toast.js';
+import {ModalManager} from "./ModalManager.js";
export class DeckStateManager {
constructor () {
this.deck = [];
+ this.templates = {
+ '5 Players': {
+ 'Villager': 1,
+ 'Werewolf': 1,
+ 'Sorceress': 1,
+ 'Parity Hunter': 1,
+ 'Seer': 1
+ },
+ '7 Players': {
+ 'Villager': 6,
+ 'Werewolf': 1
+ },
+ '9 Players': {
+ 'Villager': 7,
+ 'Werewolf': 2
+ },
+ '11 Players': {
+ 'Villager': 8,
+ 'Werewolf': 2,
+ 'Seer': 1
+ },
+ '13 Players': {
+ 'Villager': 10,
+ 'Werewolf': 2,
+ 'Seer': 1
+ },
+ '15 Players': {
+ 'Villager': 12,
+ 'Werewolf': 2,
+ 'Seer': 1
+ }
+ }
}
addToDeck (role) {
@@ -54,6 +87,46 @@ export class DeckStateManager {
return total;
}
+ loadDeckTemplates = (roleBox) => {
+ if (document.querySelectorAll('.template-option').length === 0) {
+ for (let templateName of Object.keys(this.templates)) {
+ let templateOption = document.createElement('div');
+ templateOption.classList.add('template-option');
+ templateOption.innerHTML = HTMLFragments.DECK_TEMPLATE;
+ templateOption.querySelector('.template-option-name').innerText = templateName;
+ for (let i = 0; i < Object.keys(this.templates[templateName]).length; i++) {
+ let role = Object.keys(this.templates[templateName])[i];
+ let roleEl = document.createElement('span');
+ roleEl.innerText = this.templates[templateName][role] + ' ' + role;
+ if (i < Object.keys(this.templates[templateName]).length - 1) { // construct comma-delimited list
+ roleEl.innerText += ', ';
+ }
+ roleEl.classList.add(roleBox.defaultRoles.find((entry) => entry.role === role).team);
+ templateOption.querySelector('.template-option-roles').appendChild(roleEl);
+ }
+ templateOption.addEventListener('click', (e) => {
+ e.preventDefault();
+ for (let card of this.deck) {
+ card.quantity = 0;
+ }
+ for (let role of Object.keys(this.templates[templateName])) {
+ let roleObj = roleBox.getDefaultRole(role);
+ if (!this.hasRole(roleObj.role)) {
+ this.addToDeck(roleObj);
+ }
+ for (let i = roleObj.quantity; i < this.templates[templateName][role]; i++) {
+ this.addCopyOfCard(roleObj.role);
+ }
+ }
+ this.updateDeckStatus();
+ ModalManager.dispelModal('deck-template-modal', 'modal-background');
+ toast('Template loaded', 'success', true, true, 'short');
+ });
+ document.getElementById('deck-template-container').appendChild(templateOption);
+ }
+ }
+ }
+
displayDeckPlaceHolder = () => {
const placeholder = document.createElement('div');
placeholder.setAttribute('id', 'deck-list-placeholder');
@@ -69,7 +142,7 @@ export class DeckStateManager {
}
const sortedDeck = this.deck.sort((a, b) => {
if (a.team !== b.team) {
- return a.team === globals.ALIGNMENT.GOOD ? 1 : -1;
+ return a.team === globals.ALIGNMENT.GOOD ? -1 : 1;
}
return a.role.localeCompare(b.role);
});
diff --git a/client/src/modules/GameCreationStepManager.js b/client/src/modules/GameCreationStepManager.js
index bc26b5b..1eb9399 100644
--- a/client/src/modules/GameCreationStepManager.js
+++ b/client/src/modules/GameCreationStepManager.js
@@ -198,6 +198,7 @@ export class GameCreationStepManager {
}
renderRoleSelectionStep = (game, containerId, step, deckManager) => {
+
const stepContainer = document.createElement('div');
setAttributes(stepContainer, { id: 'step-' + step, class: 'flex-row-container-left-align step' });
@@ -205,11 +206,14 @@ export class GameCreationStepManager {
stepContainer.innerHTML += HTMLFragments.CREATE_GAME_DECK_STATUS;
document.getElementById(containerId).appendChild(stepContainer);
+
this.roleBox = new RoleBox(stepContainer, deckManager);
this.roleBox.loadDefaultRoles();
this.roleBox.loadCustomRolesFromCookies();
this.roleBox.displayDefaultRoles(document.getElementById('role-select'));
+ deckManager.loadDeckTemplates(this.roleBox);
+
const exportHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
e.preventDefault();
@@ -502,6 +506,13 @@ function initializeRemainingEventListeners (deckManager, roleBox) {
}
}
};
+ document.getElementById('deck-template-button').addEventListener('click', () => {
+ ModalManager.displayModal(
+ 'deck-template-modal',
+ 'modal-background',
+ 'close-deck-template-modal-button'
+ );
+ });
document.getElementById('custom-role-btn').addEventListener(
'click', () => {
const createBtn = document.getElementById('create-role-button');
diff --git a/client/src/modules/GameStateRenderer.js b/client/src/modules/GameStateRenderer.js
index 1ae1bdd..4538989 100644
--- a/client/src/modules/GameStateRenderer.js
+++ b/client/src/modules/GameStateRenderer.js
@@ -234,7 +234,9 @@ export class GameStateRenderer {
});
}
document.querySelectorAll('.game-player').forEach((el) => el.remove());
- sortPeopleByStatus(this.stateBucket.currentGameState.people);
+ /* TODO: UX issue - it's easier to parse visually when players are sorted this way,
+ but shifting players around when they are killed or revealed is bad UX for the moderator. */
+ // sortPeopleByStatus(this.stateBucket.currentGameState.people);
const modType = tempMod ? this.stateBucket.currentGameState.moderator.userType : null;
renderGroupOfPlayers(
this.stateBucket.currentGameState.people,
diff --git a/client/src/modules/HTMLFragments.js b/client/src/modules/HTMLFragments.js
index 2dd103b..604d6ac 100644
--- a/client/src/modules/HTMLFragments.js
+++ b/client/src/modules/HTMLFragments.js
@@ -100,7 +100,7 @@ export const HTMLFragments = {
-
+
@@ -238,6 +238,9 @@ export const HTMLFragments = {
`,
+ DECK_TEMPLATE:
+ `
+ `,
CREATE_GAME_CUSTOM_ROLES:
`
`,
CREATE_GAME_DECK_STATUS:
``,
DECK_SELECT_ROLE:
diff --git a/client/src/modules/RoleBox.js b/client/src/modules/RoleBox.js
index 82eeee7..d5bbe5a 100644
--- a/client/src/modules/RoleBox.js
+++ b/client/src/modules/RoleBox.js
@@ -11,7 +11,6 @@ export class RoleBox {
this.category = 'default';
this.deckManager = deckManager;
this.defaultRoles = [];
- console.log('hi');
this.customRoles = [];
container.innerHTML += HTMLFragments.CREATE_GAME_CUSTOM_ROLES;
this.defaultButton = document.getElementById('role-category-default');
@@ -36,7 +35,7 @@ export class RoleBox {
loadDefaultRoles = () => {
this.defaultRoles = defaultRoles.sort((a, b) => {
if (a.team !== b.team) {
- return a.team === globals.ALIGNMENT.GOOD ? 1 : -1;
+ return a.team === globals.ALIGNMENT.GOOD ? -1 : 1;
}
return a.role.localeCompare(b.role);
}).map((role) => {
@@ -162,7 +161,7 @@ export class RoleBox {
this.categoryTransition.play();
this.customRoles.sort((a, b) => {
if (a.team !== b.team) {
- return a.team === globals.ALIGNMENT.GOOD ? 1 : -1;
+ return a.team === globals.ALIGNMENT.GOOD ? -1 : 1;
}
return a.role.localeCompare(b.role);
});
diff --git a/client/src/scripts/game.js b/client/src/scripts/game.js
index 22b7f7a..995b34c 100644
--- a/client/src/scripts/game.js
+++ b/client/src/scripts/game.js
@@ -383,7 +383,7 @@ function updateDOMWithNameChange (gameState, gameStateRenderer) {
function activateRoleInfoButton (deck) {
deck.sort((a, b) => {
- return a.team === globals.ALIGNMENT.GOOD ? 1 : -1;
+ return a.team === globals.ALIGNMENT.GOOD ? -1 : 1;
});
document.getElementById('role-info-button').addEventListener('click', (e) => {
e.preventDefault();
diff --git a/client/src/styles/create.css b/client/src/styles/create.css
index fa1a988..02902e5 100644
--- a/client/src/styles/create.css
+++ b/client/src/styles/create.css
@@ -101,6 +101,42 @@
width: fit-content;
}
+.template-option {
+ color: #d7d7d7;
+ display: flex;
+ justify-content: space-between;
+ background-color: #0f0f10;
+ border: 2px solid #333243;
+ padding: 5px;
+ border-radius: 3px;
+ font-size: 16px;
+ flex-direction: column;
+ align-items: flex-start;
+ text-align: left;
+ height: 4em;
+ margin: 10px 0;
+}
+
+#deck-template-container {
+ margin: 1em 0;
+ max-height: 30em;
+ overflow-y: auto;
+}
+
+.template-option-name {
+ font-size: 20px;
+}
+
+.template-option:hover, .template-option:active {
+ border: 2px solid #e7e7e7;
+ background-color: #33343c;
+ cursor: pointer;
+}
+
+#deck-template-modal h2 {
+ font-size: 20px;
+}
+
#custom-roles-container {
width: 95%;
max-width: 25em;
@@ -136,15 +172,20 @@
#deck-count {
font-size: 30px;
- position: sticky;
- top: 0;
- left: 5px;
background-color: #333243;
width: fit-content;
padding: 0 5px;
border-radius: 3px;
}
+#deck-status-header {
+ position: sticky;
+ z-index: 25;
+ top: 0;
+ display: flex;
+ justify-content: space-between;
+}
+
#deck-list {
margin-top: 0.5em;
}
@@ -486,6 +527,10 @@ input[type="number"] {
margin-bottom: 4em;
}
+#step-2 .app-button {
+ padding: 5px;
+}
+
#tracker-container {
display: flex;
align-items: center;
diff --git a/client/src/styles/game.css b/client/src/styles/game.css
index b3d187f..140f950 100644
--- a/client/src/styles/game.css
+++ b/client/src/styles/game.css
@@ -505,6 +505,7 @@ label[for='moderator'] {
.paused {
animation: pulse 0.75s linear infinite alternate;
+ border: 1px solid #ffc83d !important;
}
.paused-low {
@@ -515,11 +516,11 @@ label[for='moderator'] {
display: flex;
flex-wrap: wrap;
flex-direction: column;
- align-items: flex-start;
+ align-items: center;
}
#game-header button {
- min-width: 10em;
+ min-width: 12em;
}
#timer-container-moderator {
@@ -595,7 +596,7 @@ label[for='moderator'] {
background-color: #586a6e;
}
-.reveal-role-button:hover, #mod-transfer-button:hover {
+.reveal-role-button:hover {
background-color: #4e5664;
}
@@ -627,15 +628,15 @@ label[for='moderator'] {
align-items: center;
}
-.make-mod-button {
- background-color: #3f5256;
- font-size: 18px;
- padding: 10px;
- border: 2px transparent;
- border-radius: 3px;
- color: #d7d7d7;
- font-family: signika-negative, sans-serif;
-}
+/*.make-mod-button {*/
+/* background-color: #3f5256;*/
+/* font-size: 18px;*/
+/* padding: 10px;*/
+/* border: 2px transparent;*/
+/* border-radius: 3px;*/
+/* color: #d7d7d7;*/
+/* font-family: signika-negative, sans-serif;*/
+/*}*/
.make-mod-button:hover {
cursor: pointer;
@@ -734,10 +735,10 @@ label[for='moderator'] {
width: 45px;
}
- .make-mod-button {
- font-size: 16px;
- padding: 5px;
- }
+ /*.make-mod-button {*/
+ /* font-size: 16px;*/
+ /* padding: 5px;*/
+ /*}*/
.game-player-name {
font-size: 16px;
@@ -749,7 +750,7 @@ label[for='moderator'] {
height: 30px;
}
- #role-info-button {
+ #role-info-button, #mod-transfer-button {
padding: 7px;
}
diff --git a/client/src/styles/modal.css b/client/src/styles/modal.css
index 06e1044..0a86111 100644
--- a/client/src/styles/modal.css
+++ b/client/src/styles/modal.css
@@ -11,7 +11,7 @@
align-items: center;
justify-content: center;
max-width: 25em;
- font-family: sans-serif;
+ font-family: 'signika-negative', sans-serif;
flex-direction: column;
padding: 1em;
display: none;
diff --git a/client/src/view_templates/CreateTemplate.js b/client/src/view_templates/CreateTemplate.js
index 3d43086..87a6bc2 100644
--- a/client/src/view_templates/CreateTemplate.js
+++ b/client/src/view_templates/CreateTemplate.js
@@ -41,6 +41,13 @@ const template =
+
+
Choose a pre-built game:
+
+
+
+
+
Create A Game
diff --git a/spec/e2e/create_spec.js b/spec/e2e/create_spec.js
index bd7cc5b..f1c6884 100644
--- a/spec/e2e/create_spec.js
+++ b/spec/e2e/create_spec.js
@@ -95,5 +95,14 @@ describe('Create page', function () {
.querySelector('.role-name').innerText
).role).toEqual('Test name edited');
});
+
+ it('should load a deck template', () => {
+ document.getElementById('role-category-default').click();
+ document.getElementById('deck-template-button').click();
+ document.querySelectorAll('.template-option')[0].click();
+
+ expect(gameCreationStepManager.deckManager.deck.length).toEqual(5);
+ expect(document.querySelectorAll('.added-role').length).toEqual(5);
+ });
});
});