mirror of https://github.com/bra1n/townsquare.git
added role selection
This commit is contained in:
parent
559f7f1e98
commit
6722053d9c
78
src/App.vue
78
src/App.vue
|
@ -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() {
|
||||||
this.players = [];
|
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 }) {
|
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() {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
<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 +
|
||||||
|
|
|
@ -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}
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue