mirror of https://github.com/bra1n/townsquare.git
added role selection
This commit is contained in:
parent
559f7f1e98
commit
6722053d9c
76
src/App.vue
76
src/App.vue
|
@ -6,7 +6,12 @@
|
|||
:players="players"
|
||||
:roles="roles"
|
||||
></TownSquare>
|
||||
<Modal v-show="showEditionModal" @close="showEditionModal = false">
|
||||
|
||||
<Modal
|
||||
class="editions"
|
||||
v-show="isEditionModalOpen"
|
||||
@close="isEditionModalOpen = false"
|
||||
>
|
||||
<h2>Select an edition:</h2>
|
||||
<ul class="editions">
|
||||
<li
|
||||
|
@ -20,6 +25,14 @@
|
|||
</li>
|
||||
</ul>
|
||||
</Modal>
|
||||
|
||||
<RoleSelectionModal
|
||||
:players="players"
|
||||
:roles="roles"
|
||||
:is-open="isRoleModalOpen"
|
||||
@close="isRoleModalOpen = false"
|
||||
></RoleSelectionModal>
|
||||
|
||||
<div class="controls">
|
||||
<font-awesome-icon icon="cogs" @click="isControlOpen = !isControlOpen" />
|
||||
<ul v-if="isControlOpen">
|
||||
|
@ -33,9 +46,15 @@
|
|||
<li @click="clearPlayers" v-if="players.length">
|
||||
Clear Players
|
||||
</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
|
||||
</li>
|
||||
<li @click="isRoleModalOpen = true" v-if="players.length > 4">
|
||||
Select Roles
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -45,24 +64,27 @@
|
|||
import TownSquare from "./components/TownSquare";
|
||||
import TownInfo from "./components/TownInfo";
|
||||
import Modal from "./components/Modal";
|
||||
import RoleSelectionModal from "./components/RoleSelectionModal";
|
||||
import rolesJSON from "./roles";
|
||||
import editions from "./editions";
|
||||
import editionJSON from "./editions";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TownSquare,
|
||||
TownInfo,
|
||||
Modal
|
||||
Modal,
|
||||
RoleSelectionModal
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
editions,
|
||||
editions: editionJSON,
|
||||
isPublic: true,
|
||||
isControlOpen: false,
|
||||
isEditionModalOpen: false,
|
||||
isRoleModalOpen: false,
|
||||
players: [],
|
||||
roles: this.getRolesByEdition(),
|
||||
edition: "tb",
|
||||
showEditionModal: false
|
||||
edition: "tb"
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -89,7 +111,32 @@ export default {
|
|||
}
|
||||
},
|
||||
clearPlayers() {
|
||||
if (confirm("Are you sure you want to remove all 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 }) {
|
||||
switch (key) {
|
||||
|
@ -103,21 +150,6 @@ export default {
|
|||
this.randomizeSeatings();
|
||||
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() {
|
||||
|
|
|
@ -1,12 +1,3 @@
|
|||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
close() {
|
||||
this.$emit("close");
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<transition name="modal-fade">
|
||||
<div class="modal-backdrop" @click="close">
|
||||
|
@ -22,6 +13,17 @@ export default {
|
|||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
close() {
|
||||
this.$emit("close");
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
|
|
|
@ -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>
|
|
@ -1,35 +1,35 @@
|
|||
<template>
|
||||
<ul class="info">
|
||||
<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>
|
||||
{{ players.length }} <font-awesome-icon class="players" icon="users" />
|
||||
{{ teams.alive }} <font-awesome-icon class="alive" icon="heartbeat" />
|
||||
{{ teams.votes }} <font-awesome-icon class="votes" icon="vote-yea" />
|
||||
</li>
|
||||
<li v-if="players.length >= 5">
|
||||
<li v-if="players.length - teams.traveler >= 5">
|
||||
{{ teams.townsfolk }}
|
||||
<font-awesome-icon class="townsfolk" icon="user-friends" />
|
||||
{{ teams.outsiders }}
|
||||
{{ teams.outsider }}
|
||||
<font-awesome-icon
|
||||
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
|
||||
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
|
||||
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">
|
||||
{{ teams.travelers }}
|
||||
<template v-if="teams.traveler">
|
||||
{{ teams.traveler }}
|
||||
<font-awesome-icon
|
||||
class="traveler"
|
||||
v-bind:icon="teams.travelers > 1 ? 'user-friends' : 'user'"
|
||||
v-bind:icon="teams.traveler > 1 ? 'user-friends' : 'user'"
|
||||
/>
|
||||
</template>
|
||||
</li>
|
||||
|
@ -59,7 +59,7 @@ export default {
|
|||
.length;
|
||||
return {
|
||||
...gameJSON[nontravelers - 5],
|
||||
travelers: this.players.length - nontravelers,
|
||||
traveler: this.players.length - nontravelers,
|
||||
alive,
|
||||
votes:
|
||||
alive +
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
[
|
||||
{"townsfolk": 3, "outsiders": 0, "minions": 1, "demons": 1},
|
||||
{"townsfolk": 3, "outsiders": 1, "minions": 1, "demons": 1},
|
||||
{"townsfolk": 5, "outsiders": 0, "minions": 1, "demons": 1},
|
||||
{"townsfolk": 5, "outsiders": 1, "minions": 1, "demons": 1},
|
||||
{"townsfolk": 5, "outsiders": 2, "minions": 1, "demons": 1},
|
||||
{"townsfolk": 7, "outsiders": 0, "minions": 2, "demons": 1},
|
||||
{"townsfolk": 7, "outsiders": 1, "minions": 2, "demons": 1},
|
||||
{"townsfolk": 7, "outsiders": 2, "minions": 2, "demons": 1},
|
||||
{"townsfolk": 9, "outsiders": 0, "minions": 3, "demons": 1},
|
||||
{"townsfolk": 9, "outsiders": 1, "minions": 3, "demons": 1},
|
||||
{"townsfolk": 9, "outsiders": 2, "minions": 3, "demons": 1}
|
||||
{"townsfolk": 3, "outsider": 0, "minion": 1, "demon": 1},
|
||||
{"townsfolk": 3, "outsider": 1, "minion": 1, "demon": 1},
|
||||
{"townsfolk": 5, "outsider": 0, "minion": 1, "demon": 1},
|
||||
{"townsfolk": 5, "outsider": 1, "minion": 1, "demon": 1},
|
||||
{"townsfolk": 5, "outsider": 2, "minion": 1, "demon": 1},
|
||||
{"townsfolk": 7, "outsider": 0, "minion": 2, "demon": 1},
|
||||
{"townsfolk": 7, "outsider": 1, "minion": 2, "demon": 1},
|
||||
{"townsfolk": 7, "outsider": 2, "minion": 2, "demon": 1},
|
||||
{"townsfolk": 9, "outsider": 0, "minion": 3, "demon": 1},
|
||||
{"townsfolk": 9, "outsider": 1, "minion": 3, "demon": 1},
|
||||
{"townsfolk": 9, "outsider": 2, "minion": 3, "demon": 1}
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue