further adjustments to store

This commit is contained in:
Steffen 2020-05-03 23:05:17 +02:00
parent 84e533640b
commit ca91112b26
No known key found for this signature in database
GPG Key ID: 764D74E98267DFC6
14 changed files with 417 additions and 286 deletions

View File

@ -11,11 +11,11 @@
}" }"
> >
<Intro v-if="!players.length"></Intro> <Intro v-if="!players.length"></Intro>
<TownInfo :players="players" v-if="players.length"></TownInfo> <TownInfo v-if="players.length"></TownInfo>
<TownSquare :players="players" @screenshot="takeScreenshot"></TownSquare> <TownSquare @screenshot="takeScreenshot"></TownSquare>
<Menu ref="menu" :players="players"></Menu> <Menu ref="menu"></Menu>
<EditionSelectionModal :players="players"></EditionSelectionModal> <EditionModal></EditionModal>
<RoleSelectionModal :players="players"></RoleSelectionModal> <RolesModal></RolesModal>
</div> </div>
</template> </template>
@ -24,26 +24,23 @@ import { mapState } from "vuex";
import TownSquare from "./components/TownSquare"; import TownSquare from "./components/TownSquare";
import TownInfo from "./components/TownInfo"; import TownInfo from "./components/TownInfo";
import Menu from "./components/Menu"; import Menu from "./components/Menu";
import RoleSelectionModal from "./components/RoleSelectionModal"; import RolesModal from "./components/modals/RolesModal";
import EditionSelectionModal from "./components/EditionSelectionModal"; import EditionModal from "./components/modals/EditionModal";
import Intro from "./components/Intro"; import Intro from "./components/Intro";
export default { export default {
components: { components: {
Intro, Intro,
EditionSelectionModal,
Menu,
TownSquare,
TownInfo, TownInfo,
RoleSelectionModal TownSquare,
Menu,
EditionModal,
RolesModal
}, },
computed: mapState({ computed: mapState({
grimoire: state => state.grimoire, grimoire: state => state.grimoire,
players: state => state.players.players players: state => state.players.players
}), }),
data: function() {
return {};
},
methods: { methods: {
takeScreenshot(dimensions) { takeScreenshot(dimensions) {
this.$refs.menu.takeScreenshot(dimensions); this.$refs.menu.takeScreenshot(dimensions);

View File

@ -80,11 +80,10 @@ export default {
components: { components: {
Screenshot Screenshot
}, },
props: ["players"], computed: mapState({
data: function() { grimoire: state => state.grimoire,
return {}; players: state => state.players.players
}, }),
computed: mapState(["grimoire"]),
methods: { methods: {
takeScreenshot(dimensions = {}) { takeScreenshot(dimensions = {}) {
this.$store.commit("updateScreenshot"); this.$store.commit("updateScreenshot");
@ -99,34 +98,23 @@ export default {
addPlayer() { addPlayer() {
const name = prompt("Player name"); const name = prompt("Player name");
if (name) { if (name) {
this.players.push({ this.$store.commit("players/add", name);
name,
role: {},
reminders: []
});
} }
}, },
randomizeSeatings() { randomizeSeatings() {
if (confirm("Are you sure you want to randomize seatings?")) { if (confirm("Are you sure you want to randomize seatings?")) {
this.players = this.players this.$store.dispatch("players/randomize");
.map(a => [Math.random(), a])
.sort((a, b) => a[0] - b[0])
.map(a => a[1]);
} }
}, },
clearPlayers() { clearPlayers() {
if (confirm("Are you sure you want to remove all players?")) { if (confirm("Are you sure you want to remove all players?")) {
this.players = []; this.$store.commit("players/clear");
} }
}, },
clearRoles() { clearRoles() {
this.$store.commit("showGrimoire"); this.$store.commit("showGrimoire");
if (confirm("Are you sure you want to remove all player roles?")) { if (confirm("Are you sure you want to remove all player roles?")) {
this.players.forEach(player => { this.$store.dispatch("players/clearRoles");
player.role = {};
player.hasDied = false;
player.reminders = [];
});
} }
}, },
...mapMutations([ ...mapMutations([

View File

@ -31,7 +31,7 @@
}}</span> }}</span>
</div> </div>
<Token :role="player.role" @set-role="setRole" /> <Token :role="player.role" @set-role="$emit('set-role')" />
<div class="name" @click="changeName"> <div class="name" @click="changeName">
<span class="screenshot" @click.stop="takeScreenshot"> <span class="screenshot" @click.stop="takeScreenshot">
@ -40,7 +40,7 @@
<span class="name"> <span class="name">
{{ player.name }} {{ player.name }}
</span> </span>
<span class="remove" @click.stop="$emit('remove-player', player)"> <span class="remove" @click.stop="$emit('remove-player')">
<font-awesome-icon icon="times-circle" /> <font-awesome-icon icon="times-circle" />
</span> </span>
</div> </div>
@ -64,7 +64,7 @@
{{ reminder.name }} {{ reminder.name }}
</div> </div>
</template> </template>
<div class="reminder add" @click="$emit('add-reminder', player)"> <div class="reminder add" @click="$emit('add-reminder')">
<span class="icon"></span> <span class="icon"></span>
</div> </div>
</li> </li>
@ -97,7 +97,7 @@ export default {
this.$emit("screenshot", { width, height, x, y }); this.$emit("screenshot", { width, height, x, y });
}, },
toggleStatus() { toggleStatus() {
if (this.$store.state.grimoire.isPublic) { if (this.grimoire.isPublic) {
if (!this.player.hasDied) { if (!this.player.hasDied) {
this.$set(this.player, "hasDied", true); this.$set(this.player, "hasDied", true);
} else if (this.player.hasVoted) { } else if (this.player.hasVoted) {
@ -110,9 +110,6 @@ export default {
this.$set(this.player, "hasDied", !this.player.hasDied); this.$set(this.player, "hasDied", !this.player.hasDied);
} }
}, },
setRole() {
this.$emit("set-role", this.player);
},
changeName() { changeName() {
const name = prompt("Player name", this.player.name); const name = prompt("Player name", this.player.name);
this.player.name = name || this.player.name; this.player.name = name || this.player.name;

View File

@ -50,7 +50,7 @@ export default {
props: { props: {
role: { role: {
type: Object, type: Object,
required: true default: () => ({})
} }
}, },
data() { data() {

View File

@ -43,32 +43,26 @@ import gameJSON from "./../game";
import { mapState } from "vuex"; import { mapState } from "vuex";
export default { export default {
props: {
players: {
type: Array,
required: true
}
},
computed: { computed: {
teams: function() { teams: function() {
const nontravelers = Math.min( const { players } = this.$store.state.players;
this.players.filter(player => player.role.team !== "traveler").length, const nonTravelers = this.$store.getters["players/nonTravelers"];
15 const alive = players.filter(player => player.hasDied !== true).length;
);
const alive = this.players.filter(player => player.hasDied !== true)
.length;
return { return {
...gameJSON[nontravelers - 5], ...gameJSON[nonTravelers - 5],
traveler: this.players.length - nontravelers, traveler: players.length - nonTravelers,
alive, alive,
votes: votes:
alive + alive +
this.players.filter( players.filter(
player => player.hasDied === true && player.hasVoted !== true player => player.hasDied === true && player.hasVoted !== true
).length ).length
}; };
}, },
...mapState(["edition"]) ...mapState({
edition: state => state.edition,
players: state => state.players.players
})
} }
}; };
</script> </script>

View File

@ -10,97 +10,54 @@
v-for="(player, index) in players" v-for="(player, index) in players"
:key="index" :key="index"
:player="player" :player="player"
@add-reminder="openReminderModal" @add-reminder="openReminderModal(index)"
@set-role="openRoleModal" @set-role="openRoleModal(index)"
@remove-player="removePlayer" @remove-player="removePlayer(index)"
@screenshot="$emit('screenshot', $event)" @screenshot="$emit('screenshot', $event)"
></Player> ></Player>
</ul> </ul>
<div class="bluffs" v-if="players.length > 6" ref="bluffs"> <div class="bluffs" v-if="players.length > 6" ref="bluffs">
<h3>Demon bluffs</h3> <h3>Demon bluffs</h3>
<font-awesome-icon icon="camera" @click.stop="takeScreenshot" /> <font-awesome-icon icon="camera" @click.stop="takeScreenshot" />
<ul> <ul>
<li @click="openRoleModal(bluffs[0])"> <li
<Token :role="bluffs[0].role"></Token> v-for="index in bluffs"
</li> :key="index"
<li @click="openRoleModal(bluffs[1])"> @click="openRoleModal(index * -1)"
<Token :role="bluffs[1].role"></Token> >
</li> <Token :role="grimoire.bluffs[index - 1]"></Token>
<li @click="openRoleModal(bluffs[2])">
<Token :role="bluffs[2].role"></Token>
</li> </li>
</ul> </ul>
</div> </div>
<Modal <ReminderModal :player-index="selectedPlayer"></ReminderModal>
v-show="availableReminders.length && selectedPlayer" <RoleModal :player-index="selectedPlayer"></RoleModal>
@close="closeModal"
>
<h3>Choose a reminder token:</h3>
<ul class="reminders">
<li
v-for="reminder in availableReminders"
class="reminder"
v-bind:class="[reminder.role]"
v-bind:key="reminder.role + ' ' + reminder.name"
@click="addReminder(reminder)"
>
<span
class="icon"
v-bind:style="{
backgroundImage: `url(${require('../assets/icons/' +
reminder.role +
'.png')})`
}"
></span>
{{ reminder.name }}
</li>
</ul>
</Modal>
<Modal v-show="availableRoles.length && selectedPlayer" @close="closeModal">
<h3>Choose a new character:</h3>
<ul class="tokens">
<li
v-for="role in availableRoles"
v-bind:class="[role.team]"
v-bind:key="role.id"
@click="setRole(role)"
>
<Token :role="role" />
</li>
</ul>
</Modal>
</div> </div>
</template> </template>
<script> <script>
import Player from "./Player";
import Modal from "./Modal";
import Token from "./Token";
import { mapState } from "vuex"; import { mapState } from "vuex";
import Player from "./Player";
import Token from "./Token";
import ReminderModal from "./modals/ReminderModal";
import RoleModal from "./modals/RoleModal";
export default { export default {
components: { components: {
Player,
Token, Token,
Modal, RoleModal,
Player ReminderModal
}, },
props: { computed: {
players: { ...mapState(["grimoire", "roles"]),
type: Array, ...mapState("players", ["players"])
required: true
}
}, },
computed: mapState(["grimoire"]),
data() { data() {
return { return {
selectedPlayer: false, selectedPlayer: 0,
availableReminders: [], bluffs: 3
availableRoles: [],
bluffs: Array(3)
.fill({})
.map(() => ({ role: {} }))
}; };
}, },
methods: { methods: {
@ -108,46 +65,22 @@ export default {
const { width, height, x, y } = this.$refs.bluffs.getBoundingClientRect(); const { width, height, x, y } = this.$refs.bluffs.getBoundingClientRect();
this.$emit("screenshot", { width, height, x, y }); this.$emit("screenshot", { width, height, x, y });
}, },
openReminderModal(player) { openReminderModal(playerIndex) {
this.availableRoles = []; this.selectedPlayer = playerIndex;
this.availableReminders = []; this.$store.commit("toggleModal", "reminder");
this.selectedPlayer = player;
this.$store.state.roles.forEach(role => {
if (this.players.some(p => p.role.id === role.id)) {
this.availableReminders = [
...this.availableReminders,
...role.reminders.map(name => ({ role: role.id, name }))
];
}
});
this.availableReminders.push({ role: "good", name: "Good" });
this.availableReminders.push({ role: "evil", name: "Evil" });
}, },
openRoleModal(player) { openRoleModal(playerIndex) {
this.availableRoles = []; this.selectedPlayer = playerIndex;
this.availableReminders = []; this.$store.commit("toggleModal", "role");
this.selectedPlayer = player;
this.$store.state.roles.forEach(role => {
if (player.role && role.id !== player.role.id) {
this.availableRoles.push(role);
}
});
this.availableRoles.push({});
}, },
addReminder(reminder) { removePlayer(playerIndex) {
this.selectedPlayer.reminders.push(reminder); if (
this.closeModal(); confirm(
}, `Do you really want to remove ${this.players[playerIndex].name}?`
setRole(role) { )
this.selectedPlayer.role = role; ) {
this.closeModal(); this.$store.commit("players/remove", playerIndex);
}, this.$store.dispatch("players/updateNightOrder");
closeModal() {
this.selectedPlayer = false;
},
removePlayer(player) {
if (confirm(`Do you really want to remove ${player.name}?`)) {
this.players.splice(this.players.indexOf(player), 1);
} }
} }
} }
@ -155,8 +88,6 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
@import "../vars.scss";
.circle { .circle {
padding: 0; padding: 0;
width: 100%; width: 100%;
@ -286,68 +217,4 @@ export default {
font-size: 18px; font-size: 18px;
} }
} }
/***** Role token modal ******/
ul.tokens li {
border-radius: 50%;
height: 120px;
width: 120px;
margin: 5px;
transition: transform 500ms ease;
&.townsfolk {
box-shadow: 0 0 10px $townsfolk, 0 0 10px #004cff;
}
&.outsider {
box-shadow: 0 0 10px $outsider, 0 0 10px $outsider;
}
&.minion {
box-shadow: 0 0 10px $minion, 0 0 10px $minion;
}
&.demon {
box-shadow: 0 0 10px $demon, 0 0 10px $demon;
}
&.traveler {
box-shadow: 0 0 10px $traveler, 0 0 10px $traveler;
}
&:hover {
transform: scale(1.2);
z-index: 10;
}
}
/***** Reminder token modal ******/
ul.reminders .reminder {
background: url("../assets/reminder.png") center center;
background-size: 100%;
width: 100px;
height: 100px;
color: black;
font-size: 65%;
font-weight: bold;
display: block;
margin: 5px;
text-align: center;
border-radius: 50%;
border: 3px solid black;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
cursor: pointer;
padding: 65px 9px 0;
line-height: 100%;
transition: transform 500ms ease;
.icon {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-size: 100%;
background-position: center 0;
background-repeat: no-repeat;
}
&:hover {
transform: scale(1.2);
}
}
</style> </style>

View File

@ -20,7 +20,7 @@
</template> </template>
<script> <script>
import editionJSON from "../editions"; import editionJSON from "../../editions";
import { mapMutations, mapState } from "vuex"; import { mapMutations, mapState } from "vuex";
import Modal from "./Modal"; import Modal from "./Modal";
@ -39,25 +39,25 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "../vars"; @import "../../vars";
// Editions // Editions
@each $img, $skipIcons in $editions { @each $img, $skipIcons in $editions {
.edition-#{$img} { .edition-#{$img} {
background-image: url("../assets/editions/#{$img}.png"); background-image: url("../../assets/editions/#{$img}.png");
} }
@if $skipIcons != true { @if $skipIcons != true {
.edition-#{$img}.townsfolk { .edition-#{$img}.townsfolk {
background-image: url("../assets/editions/#{$img}-townsfolk.png"); background-image: url("../../assets/editions/#{$img}-townsfolk.png");
} }
.edition-#{$img}.outsider { .edition-#{$img}.outsider {
background-image: url("../assets/editions/#{$img}-outsider.png"); background-image: url("../../assets/editions/#{$img}-outsider.png");
} }
.edition-#{$img}.minion { .edition-#{$img}.minion {
background-image: url("../assets/editions/#{$img}-minion.png"); background-image: url("../../assets/editions/#{$img}-minion.png");
} }
.edition-#{$img}.demon { .edition-#{$img}.demon {
background-image: url("../assets/editions/#{$img}-demon.png"); background-image: url("../../assets/editions/#{$img}-demon.png");
} }
} }
} }

View File

@ -0,0 +1,103 @@
<template>
<Modal
v-show="modals.reminder && availableReminders.length"
v-if="players[playerIndex]"
@close="toggleModal('reminder')"
>
<h3>Choose a reminder token:</h3>
<ul class="reminders">
<li
v-for="reminder in availableReminders"
class="reminder"
v-bind:class="[reminder.role]"
v-bind:key="reminder.role + ' ' + reminder.name"
@click="addReminder(reminder)"
>
<span
class="icon"
v-bind:style="{
backgroundImage: `url(${require('../../assets/icons/' +
reminder.role +
'.png')})`
}"
></span>
{{ reminder.name }}
</li>
</ul>
</Modal>
</template>
<script>
import Modal from "./Modal";
import { mapMutations, mapState } from "vuex";
export default {
components: { Modal },
props: ["playerIndex"],
computed: {
availableReminders() {
let reminders = [];
const players = this.$store.state.players.players;
this.$store.state.roles.forEach(role => {
if (players.some(p => p.role.id === role.id)) {
reminders = [
...reminders,
...role.reminders.map(name => ({ role: role.id, name }))
];
}
});
reminders.push({ role: "good", name: "Good" });
reminders.push({ role: "evil", name: "Evil" });
return reminders;
},
...mapState(["modals"]),
...mapState("players", ["players"])
},
methods: {
addReminder(reminder) {
const player = this.$store.state.players.players[this.playerIndex];
player.reminders.push(reminder);
this.$store.commit("players/update", { index: this.playerIndex, player });
this.$store.commit("toggleModal", "reminder");
},
...mapMutations(["toggleModal"])
}
};
</script>
<style scoped lang="scss">
ul.reminders .reminder {
background: url("../../assets/reminder.png") center center;
background-size: 100%;
width: 100px;
height: 100px;
color: black;
font-size: 65%;
font-weight: bold;
display: block;
margin: 5px;
text-align: center;
border-radius: 50%;
border: 3px solid black;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
cursor: pointer;
padding: 65px 9px 0;
line-height: 100%;
transition: transform 500ms ease;
.icon {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-size: 100%;
background-position: center 0;
background-repeat: no-repeat;
}
&:hover {
transform: scale(1.2);
}
}
</style>

View File

@ -0,0 +1,103 @@
<template>
<Modal
v-show="modals.role && availableRoles.length"
@close="toggleModal('role')"
>
<h3>Choose a new character: {{playerIndex}}</h3>
<ul class="tokens">
<li
v-for="role in availableRoles"
v-bind:class="[role.team]"
v-bind:key="role.id"
@click="setRole(role)"
>
<Token :role="role" />
</li>
</ul>
</Modal>
</template>
<script>
import { mapMutations, mapState } from "vuex";
import Modal from "./Modal";
import Token from "../Token";
export default {
components: { Token, Modal },
props: ["playerIndex"],
computed: {
availableRoles() {
const availableRoles = [];
const players = this.$store.state.players.players;
this.$store.state.roles.forEach(role => {
// don't show bluff roles that are already assigned to players
if (
this.playerIndex >= 0 ||
(this.playerIndex < 0 &&
!players.some(player => player.role.id === role.id))
) {
availableRoles.push(role);
}
});
availableRoles.push({});
return availableRoles;
},
...mapState(["modals", "roles"]),
...mapState("players", ["players"])
},
methods: {
setRole(role) {
if (this.playerIndex < 0) {
// assign to bluff slot
this.$store.commit("setBluff", {
index: this.playerIndex * -1 - 1,
role
});
} else {
// assign to player
const player = this.$store.state.players.players[this.playerIndex];
player.role = role;
this.$store.commit("players/update", {
index: this.playerIndex,
player
});
this.$store.dispatch("players/updateNightOrder");
}
this.$store.commit("toggleModal", "role");
},
...mapMutations(["toggleModal"])
}
};
</script>
<style scoped lang="scss">
@import "../../vars.scss";
ul.tokens li {
border-radius: 50%;
height: 120px;
width: 120px;
margin: 5px;
transition: transform 500ms ease;
&.townsfolk {
box-shadow: 0 0 10px $townsfolk, 0 0 10px #004cff;
}
&.outsider {
box-shadow: 0 0 10px $outsider, 0 0 10px $outsider;
}
&.minion {
box-shadow: 0 0 10px $minion, 0 0 10px $minion;
}
&.demon {
box-shadow: 0 0 10px $demon, 0 0 10px $demon;
}
&.traveler {
box-shadow: 0 0 10px $traveler, 0 0 10px $traveler;
}
&:hover {
transform: scale(1.2);
z-index: 10;
}
}
</style>

View File

@ -3,9 +3,9 @@
class="roles" class="roles"
v-show="modals.roles" v-show="modals.roles"
@close="toggleModal('roles')" @close="toggleModal('roles')"
v-if="nontravelerPlayers >= 5" v-if="nonTravelers >= 5"
> >
<h3>Select the characters for {{ nontravelerPlayers }} players:</h3> <h3>Select the characters for {{ nonTravelers }} players:</h3>
<ul <ul
class="tokens" class="tokens"
v-for="(teamRoles, team) in roleSelection" v-for="(teamRoles, team) in roleSelection"
@ -13,7 +13,7 @@
> >
<li class="count" v-bind:class="[team]"> <li class="count" v-bind:class="[team]">
{{ teamRoles.filter(role => role.selected).length }} / {{ teamRoles.filter(role => role.selected).length }} /
{{ game[nontravelerPlayers - 5][team] }} {{ game[nonTravelers - 5][team] }}
</li> </li>
<li <li
v-for="role in teamRoles" v-for="role in teamRoles"
@ -33,7 +33,7 @@
class="button" class="button"
@click="assignRoles" @click="assignRoles"
v-bind:class="{ v-bind:class="{
disabled: selectedRoles > nontravelerPlayers || !selectedRoles disabled: selectedRoles > nonTravelers || !selectedRoles
}" }"
> >
<font-awesome-icon icon="people-arrows" /> <font-awesome-icon icon="people-arrows" />
@ -49,9 +49,9 @@
<script> <script>
import Modal from "./Modal"; import Modal from "./Modal";
import gameJSON from "./../game"; import gameJSON from "./../../game";
import Token from "./Token"; import Token from "./../Token";
import { mapMutations, mapState } from "vuex"; import { mapGetters, mapMutations, mapState } from "vuex";
const randomElement = arr => arr[Math.floor(Math.random() * arr.length)]; const randomElement = arr => arr[Math.floor(Math.random() * arr.length)];
@ -60,12 +60,6 @@ export default {
Token, Token,
Modal Modal
}, },
props: {
players: {
type: Array,
required: true
}
},
data: function() { data: function() {
return { return {
roleSelection: {}, roleSelection: {},
@ -73,13 +67,6 @@ export default {
}; };
}, },
computed: { computed: {
nontravelerPlayers: function() {
return Math.min(
this.players.filter(({ role }) => role && role.team !== "traveler")
.length,
15
);
},
selectedRoles: function() { selectedRoles: function() {
return Object.values(this.roleSelection) return Object.values(this.roleSelection)
.map(roles => roles.filter(role => role.selected).length) .map(roles => roles.filter(role => role.selected).length)
@ -90,7 +77,9 @@ export default {
roles.some(role => role.selected && role.setup) roles.some(role => role.selected && role.setup)
); );
}, },
...mapState(["roles", "modals"]) ...mapState(["roles", "modals"]),
...mapState("players", ["players"]),
...mapGetters({ nonTravelers: "players/nonTravelers" })
}, },
methods: { methods: {
selectRandomRoles() { selectRandomRoles() {
@ -103,7 +92,7 @@ export default {
this.$set(role, "selected", false); this.$set(role, "selected", false);
}); });
delete this.roleSelection["traveler"]; delete this.roleSelection["traveler"];
const playerCount = Math.max(5, this.nontravelerPlayers); const playerCount = Math.max(5, this.nonTravelers);
const composition = this.game[playerCount - 5]; const composition = this.game[playerCount - 5];
Object.keys(composition).forEach(team => { Object.keys(composition).forEach(team => {
for (let x = 0; x < composition[team]; x++) { for (let x = 0; x < composition[team]; x++) {
@ -117,7 +106,7 @@ export default {
}); });
}, },
assignRoles() { assignRoles() {
if (this.selectedRoles <= this.nontravelerPlayers && this.selectedRoles) { if (this.selectedRoles <= this.nonTravelers && this.selectedRoles) {
// generate list of selected roles and randomize it // generate list of selected roles and randomize it
const roles = Object.values(this.roleSelection) const roles = Object.values(this.roleSelection)
.map(roles => roles.filter(role => role.selected)) .map(roles => roles.filter(role => role.selected))
@ -125,12 +114,14 @@ export default {
.map(a => [Math.random(), a]) .map(a => [Math.random(), a])
.sort((a, b) => a[0] - b[0]) .sort((a, b) => a[0] - b[0])
.map(a => a[1]); .map(a => a[1]);
this.players.forEach(player => { this.players.forEach((player, index) => {
if (player.role.team !== "traveler" && roles.length) { if (player.role.team !== "traveler" && roles.length) {
player.role = roles.pop(); player.role = roles.pop();
this.$store.commit("players/update", { index, player });
} }
}); });
this.close(); this.$store.dispatch("players/updateNightOrder");
this.$store.commit("toggleModal", "roles");
} }
}, },
...mapMutations(["toggleModal"]) ...mapMutations(["toggleModal"])
@ -148,17 +139,40 @@ export default {
}; };
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
@import "../vars.scss"; @import "../../vars.scss";
.roles .modal ul.tokens { ul.tokens {
padding-left: 55px; padding-left: 55px;
li { li {
border-radius: 50%;
height: 120px;
width: 120px;
margin: 5px;
opacity: 0.5; opacity: 0.5;
transition: all 250ms; transition: all 250ms;
&.selected { &.selected {
opacity: 1; opacity: 1;
} }
&.townsfolk {
box-shadow: 0 0 10px $townsfolk, 0 0 10px #004cff;
}
&.outsider {
box-shadow: 0 0 10px $outsider, 0 0 10px $outsider;
}
&.minion {
box-shadow: 0 0 10px $minion, 0 0 10px $minion;
}
&.demon {
box-shadow: 0 0 10px $demon, 0 0 10px $demon;
}
&.traveler {
box-shadow: 0 0 10px $traveler, 0 0 10px $traveler;
}
&:hover {
transform: scale(1.2);
z-index: 10;
}
} }
.count { .count {
opacity: 1; opacity: 1;

View File

@ -32,11 +32,14 @@ export default new Vuex.Store({
isScreenshot: false, isScreenshot: false,
isScreenshotSuccess: false, isScreenshotSuccess: false,
zoom: 1, zoom: 1,
background: "" background: "",
bluffs: []
}, },
modals: { modals: {
edition: false, edition: false,
roles: false roles: false,
role: false,
reminder: false
}, },
edition: "tb", edition: "tb",
roles: getRolesByEdition() roles: getRolesByEdition()
@ -61,6 +64,9 @@ export default new Vuex.Store({
setBackground({ grimoire }, background) { setBackground({ grimoire }, background) {
grimoire.background = background; grimoire.background = background;
}, },
setBluff({ grimoire }, { index, role }) {
grimoire.bluffs.splice(index, 1, role);
},
toggleModal({ modals }, name) { toggleModal({ modals }, name) {
modals[name] = !modals[name]; modals[name] = !modals[name];
}, },

View File

@ -1,7 +1,25 @@
const NEWPLAYER = {
role: {},
reminders: [],
hasVoted: false,
hasDied: false,
firstNight: 0,
otherNight: 0
};
const state = () => ({ const state = () => ({
players: [] players: []
}); });
const getters = {};
const getters = {
nonTravelers({ players }) {
const nonTravelers = players.filter(
player => player.role.team !== "traveler"
);
return Math.min(nonTravelers.length, 15);
}
};
const actions = { const actions = {
// recalculate night order for all players // recalculate night order for all players
updateNightOrder({ state, commit }) { updateNightOrder({ state, commit }) {
@ -23,26 +41,44 @@ const actions = {
if (player.firstNight !== first || player.otherNight !== other) { if (player.firstNight !== first || player.otherNight !== other) {
player.firstNight = first; player.firstNight = first;
player.otherNight = other; player.otherNight = other;
commit("updatePlayer", index, player); commit("update", { index, player });
console.log("updated night order for player", player.name);
} }
}); });
},
randomize({ state, commit }) {
const players = state.players
.map(a => [Math.random(), a])
.sort((a, b) => a[0] - b[0])
.map(a => a[1]);
commit("set", players);
},
clearRoles({ state, commit }) {
const players = state.players.map(({ name }) => ({
name,
...NEWPLAYER
}));
commit("set", players);
} }
}; };
const mutations = { const mutations = {
setPlayers(state, players = []) { clear(state) {
state.players = [];
},
set(state, players = []) {
state.players = players; state.players = players;
}, },
updatePlayer(state, index, player) { update(state, { index, player }) {
state.players[index] = player; state.players[index] = player;
}, },
addPlayer(state, name) { add(state, name) {
state.players.push({ state.players.push({
name, name,
role: {}, ...NEWPLAYER
reminders: []
}); });
}, },
removePlayer(state, index) { remove(state, index) {
state.players.splice(index, 1); state.players.splice(index, 1);
} }
}; };

View File

@ -7,16 +7,27 @@ module.exports = store => {
store.commit("showGrimoire", JSON.parse(localStorage.isPublic)); store.commit("showGrimoire", JSON.parse(localStorage.isPublic));
} }
if (localStorage.edition !== undefined) { if (localStorage.edition !== undefined) {
// this will initialize state.roles!
store.commit("setEdition", localStorage.edition); store.commit("setEdition", localStorage.edition);
} }
if (localStorage.bluffs !== undefined) {
JSON.parse(localStorage.bluffs).forEach((role, index) => {
store.commit("setBluff", {
index,
role: store.state.roles.get(role) || {}
});
});
}
if (localStorage.players) { if (localStorage.players) {
store.commit( store.commit(
"players/setPlayers", "players/set",
JSON.parse(localStorage.players).map(player => ({ JSON.parse(localStorage.players).map(player => ({
...player, ...player,
role: store.state.roles.get(player.role) || {} role: store.state.roles.get(player.role) || {}
})) }))
); );
// recalculate night order
store.dispatch("players/updateNightOrder");
} }
// listen to mutations // listen to mutations
@ -39,20 +50,35 @@ module.exports = store => {
case "setEdition": case "setEdition":
localStorage.setItem("edition", payload); localStorage.setItem("edition", payload);
break; break;
case "addPlayer": case "setBluff":
case "updatePlayer":
case "removePlayer":
localStorage.setItem( localStorage.setItem(
"players", "bluffs",
JSON.stringify( JSON.stringify(state.grimoire.bluffs.map(({ id }) => id))
state.players.players.map(player => ({
...player,
role: player.role.id || {}
}))
)
); );
break; break;
case "players/add":
case "players/update":
case "players/remove":
case "players/clear":
case "players/set":
if (state.players.players.length) {
localStorage.setItem(
"players",
JSON.stringify(
state.players.players.map(player => ({
...player,
// simplify the stored data
role: player.role.id || {},
firstNight: undefined,
otherNight: undefined
}))
)
);
} else {
localStorage.removeItem("players");
}
break;
} }
console.log(type, payload); console.log("persistance", type, payload);
}); });
}; };