added role selection

This commit is contained in:
Steffen 2020-04-11 22:55:37 +02:00
parent 559f7f1e98
commit 6722053d9c
No known key found for this signature in database
GPG Key ID: 764D74E98267DFC6
5 changed files with 245 additions and 55 deletions

View File

@ -6,7 +6,12 @@
:players="players" :players="players"
:roles="roles" :roles="roles"
></TownSquare> ></TownSquare>
<Modal v-show="showEditionModal" @close="showEditionModal = false">
<Modal
class="editions"
v-show="isEditionModalOpen"
@close="isEditionModalOpen = false"
>
<h2>Select an edition:</h2> <h2>Select an edition:</h2>
<ul class="editions"> <ul class="editions">
<li <li
@ -20,6 +25,14 @@
</li> </li>
</ul> </ul>
</Modal> </Modal>
<RoleSelectionModal
:players="players"
:roles="roles"
:is-open="isRoleModalOpen"
@close="isRoleModalOpen = false"
></RoleSelectionModal>
<div class="controls"> <div class="controls">
<font-awesome-icon icon="cogs" @click="isControlOpen = !isControlOpen" /> <font-awesome-icon icon="cogs" @click="isControlOpen = !isControlOpen" />
<ul v-if="isControlOpen"> <ul v-if="isControlOpen">
@ -33,9 +46,15 @@
<li @click="clearPlayers" v-if="players.length"> <li @click="clearPlayers" v-if="players.length">
Clear Players Clear Players
</li> </li>
<li @click="showEditionModal = true" v-if="players.length > 4"> <li @click="clearRoles" v-if="players.length">
Clear Roles
</li>
<li @click="isEditionModalOpen = true" v-if="players.length > 4">
Select Edition Select Edition
</li> </li>
<li @click="isRoleModalOpen = true" v-if="players.length > 4">
Select Roles
</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -45,24 +64,27 @@
import TownSquare from "./components/TownSquare"; import TownSquare from "./components/TownSquare";
import TownInfo from "./components/TownInfo"; import TownInfo from "./components/TownInfo";
import Modal from "./components/Modal"; import Modal from "./components/Modal";
import RoleSelectionModal from "./components/RoleSelectionModal";
import rolesJSON from "./roles"; import rolesJSON from "./roles";
import editions from "./editions"; import editionJSON from "./editions";
export default { export default {
components: { components: {
TownSquare, TownSquare,
TownInfo, TownInfo,
Modal Modal,
RoleSelectionModal
}, },
data: function() { data: function() {
return { return {
editions, editions: editionJSON,
isPublic: true, isPublic: true,
isControlOpen: false, isControlOpen: false,
isEditionModalOpen: false,
isRoleModalOpen: false,
players: [], players: [],
roles: this.getRolesByEdition(), roles: this.getRolesByEdition(),
edition: "tb", edition: "tb"
showEditionModal: false
}; };
}, },
methods: { methods: {
@ -89,7 +111,32 @@ export default {
} }
}, },
clearPlayers() { clearPlayers() {
if (confirm("Are you sure you want to remove all players?")) {
this.players = []; this.players = [];
}
},
clearRoles() {
if (confirm("Are you sure you want to remove all player roles?")) {
this.players.forEach(player => {
player.role = {};
player.reminders = [];
});
}
},
getRolesByEdition(edition = "tb") {
const selectedEdition = editionJSON.find(({ id }) => id === edition);
return new Map(
rolesJSON
.filter(
r => r.edition === edition || selectedEdition.roles.includes(r.id)
)
.sort((a, b) => b.team.localeCompare(a.team))
.map(role => [role.id, role])
);
},
setEdition(edition) {
this.edition = edition;
this.isEditionModalOpen = false;
}, },
keyup({ key }) { keyup({ key }) {
switch (key) { switch (key) {
@ -103,21 +150,6 @@ export default {
this.randomizeSeatings(); this.randomizeSeatings();
break; break;
} }
},
getRolesByEdition(edition = "tb") {
const selectedEdition = editions.find(({ id }) => id === edition);
return new Map(
rolesJSON
.filter(
r => r.edition === edition || selectedEdition.roles.includes(r.id)
)
.sort((a, b) => b.team.localeCompare(a.team))
.map(role => [role.id, role])
);
},
setEdition(edition) {
this.edition = edition;
this.showEditionModal = false;
} }
}, },
mounted() { mounted() {

View File

@ -1,12 +1,3 @@
<script>
export default {
methods: {
close() {
this.$emit("close");
}
}
};
</script>
<template> <template>
<transition name="modal-fade"> <transition name="modal-fade">
<div class="modal-backdrop" @click="close"> <div class="modal-backdrop" @click="close">
@ -22,6 +13,17 @@ export default {
</div> </div>
</transition> </transition>
</template> </template>
<script>
export default {
methods: {
close() {
this.$emit("close");
}
}
};
</script>
<style lang="scss"> <style lang="scss">
.modal-backdrop { .modal-backdrop {
position: fixed; position: fixed;

View File

@ -0,0 +1,156 @@
<template>
<Modal class="roles" v-show="isOpen" @close="close()">
<h2>Select the roles for {{ nontravelerPlayers }} players:</h2>
<ul
class="tokens"
v-for="(teamRoles, team) in roleSelection"
v-bind:key="team"
>
<li class="count" v-bind:class="[team]">
{{ teamRoles.filter(role => role.selected).length }} /
{{ game[nontravelerPlayers - 5][team] }}
</li>
<li
v-for="role in teamRoles"
class="token"
v-bind:class="[role.id, role.team, role.selected ? 'selected' : '']"
v-bind:key="role.id"
@click="role.selected = !role.selected"
>
{{ role.name }}
</li>
</ul>
<button
@click="assignRoles()"
v-bind:disabled="selectedRoles > nontravelerPlayers || !selectedRoles"
>
Assign {{ selectedRoles }} roles randomly
</button>
</Modal>
</template>
<script>
import Modal from "./Modal";
import gameJSON from "./../game";
const randomElement = arr => arr[Math.floor(Math.random() * arr.length)];
export default {
components: {
Modal
},
props: {
players: {
type: Array,
required: true
},
roles: {
type: Map,
required: true
},
isOpen: {
type: Boolean,
required: true
}
},
data: function() {
return {
roleSelection: {},
game: gameJSON
};
},
computed: {
nontravelerPlayers: function() {
return this.players.filter(({ role }) => role && role.team !== "traveler")
.length;
},
selectedRoles: function() {
return Object.values(this.roleSelection)
.map(roles => roles.filter(role => role.selected).length)
.reduce((a, b) => a + b, 0);
}
},
methods: {
close() {
this.$emit("close");
},
showRoleSelectionModal() {
this.roleSelection = {};
this.roles.forEach(role => {
if (!this.roleSelection[role.team]) {
this.$set(this.roleSelection, role.team, []);
}
this.roleSelection[role.team].push(role);
this.$set(role, "selected", false);
});
delete this.roleSelection["traveler"];
const playerCount = Math.max(5, this.nontravelerPlayers);
const composition = this.game[playerCount - 5];
Object.keys(composition).forEach(team => {
for (let x = 0; x < composition[team]; x++) {
const available = this.roleSelection[team].filter(
role => role.selected !== true
);
if (available.length) {
randomElement(available).selected = true;
}
}
});
},
assignRoles() {
// generate list of selected roles and randomize it
const roles = Object.values(this.roleSelection)
.map(roles => roles.filter(role => role.selected))
.reduce((a, b) => [...a, ...b], [])
.map(a => [Math.random(), a])
.sort((a, b) => a[0] - b[0])
.map(a => a[1]);
this.players.forEach(player => {
if (player.role.team !== "traveler" && roles.length) {
player.role = roles.pop();
}
});
this.close();
}
},
watch: {
isOpen(newIsOpen) {
if (newIsOpen) {
this.showRoleSelectionModal();
}
}
}
};
</script>
<style lang="scss">
@import "../vars.scss";
.roles .modal ul.tokens {
padding-left: 20px;
.count {
position: absolute;
left: 0;
font-weight: bold;
padding: 5px;
&.townsfolk {
color: $townsfolk;
}
&.outsider {
color: $outsider;
}
&.minion {
color: $minion;
}
&.demon {
color: $demon;
}
}
.token {
opacity: 0.5;
&.selected {
opacity: 1;
}
}
}
</style>

View File

@ -1,35 +1,35 @@
<template> <template>
<ul class="info"> <ul class="info">
<li class="edition" v-bind:class="['edition-' + edition]"></li> <li class="edition" v-bind:class="['edition-' + edition]"></li>
<li v-if="players.length < 5">Please add more players!</li> <li v-if="players.length - teams.traveler < 5">Please add more players!</li>
<li> <li>
{{ players.length }} <font-awesome-icon class="players" icon="users" /> {{ players.length }} <font-awesome-icon class="players" icon="users" />
{{ teams.alive }} <font-awesome-icon class="alive" icon="heartbeat" /> {{ teams.alive }} <font-awesome-icon class="alive" icon="heartbeat" />
{{ teams.votes }} <font-awesome-icon class="votes" icon="vote-yea" /> {{ teams.votes }} <font-awesome-icon class="votes" icon="vote-yea" />
</li> </li>
<li v-if="players.length >= 5"> <li v-if="players.length - teams.traveler >= 5">
{{ teams.townsfolk }} {{ teams.townsfolk }}
<font-awesome-icon class="townsfolk" icon="user-friends" /> <font-awesome-icon class="townsfolk" icon="user-friends" />
{{ teams.outsiders }} {{ teams.outsider }}
<font-awesome-icon <font-awesome-icon
class="outsider" class="outsider"
v-bind:icon="teams.outsiders > 1 ? 'user-friends' : 'user'" v-bind:icon="teams.outsider > 1 ? 'user-friends' : 'user'"
/> />
{{ teams.minions }} {{ teams.minion }}
<font-awesome-icon <font-awesome-icon
class="minion" class="minion"
v-bind:icon="teams.minions > 1 ? 'user-friends' : 'user'" v-bind:icon="teams.minion > 1 ? 'user-friends' : 'user'"
/> />
{{ teams.demons }} {{ teams.demon }}
<font-awesome-icon <font-awesome-icon
class="demon" class="demon"
v-bind:icon="teams.demons > 1 ? 'user-friends' : 'user'" v-bind:icon="teams.demon > 1 ? 'user-friends' : 'user'"
/> />
<template v-if="teams.travelers"> <template v-if="teams.traveler">
{{ teams.travelers }} {{ teams.traveler }}
<font-awesome-icon <font-awesome-icon
class="traveler" class="traveler"
v-bind:icon="teams.travelers > 1 ? 'user-friends' : 'user'" v-bind:icon="teams.traveler > 1 ? 'user-friends' : 'user'"
/> />
</template> </template>
</li> </li>
@ -59,7 +59,7 @@ export default {
.length; .length;
return { return {
...gameJSON[nontravelers - 5], ...gameJSON[nontravelers - 5],
travelers: this.players.length - nontravelers, traveler: this.players.length - nontravelers,
alive, alive,
votes: votes:
alive + alive +

View File

@ -1,13 +1,13 @@
[ [
{"townsfolk": 3, "outsiders": 0, "minions": 1, "demons": 1}, {"townsfolk": 3, "outsider": 0, "minion": 1, "demon": 1},
{"townsfolk": 3, "outsiders": 1, "minions": 1, "demons": 1}, {"townsfolk": 3, "outsider": 1, "minion": 1, "demon": 1},
{"townsfolk": 5, "outsiders": 0, "minions": 1, "demons": 1}, {"townsfolk": 5, "outsider": 0, "minion": 1, "demon": 1},
{"townsfolk": 5, "outsiders": 1, "minions": 1, "demons": 1}, {"townsfolk": 5, "outsider": 1, "minion": 1, "demon": 1},
{"townsfolk": 5, "outsiders": 2, "minions": 1, "demons": 1}, {"townsfolk": 5, "outsider": 2, "minion": 1, "demon": 1},
{"townsfolk": 7, "outsiders": 0, "minions": 2, "demons": 1}, {"townsfolk": 7, "outsider": 0, "minion": 2, "demon": 1},
{"townsfolk": 7, "outsiders": 1, "minions": 2, "demons": 1}, {"townsfolk": 7, "outsider": 1, "minion": 2, "demon": 1},
{"townsfolk": 7, "outsiders": 2, "minions": 2, "demons": 1}, {"townsfolk": 7, "outsider": 2, "minion": 2, "demon": 1},
{"townsfolk": 9, "outsiders": 0, "minions": 3, "demons": 1}, {"townsfolk": 9, "outsider": 0, "minion": 3, "demon": 1},
{"townsfolk": 9, "outsiders": 1, "minions": 3, "demons": 1}, {"townsfolk": 9, "outsider": 1, "minion": 3, "demon": 1},
{"townsfolk": 9, "outsiders": 2, "minions": 3, "demons": 1} {"townsfolk": 9, "outsider": 2, "minion": 3, "demon": 1}
] ]