mirror of https://github.com/bra1n/townsquare.git
fixed voting race condition
added session ping cleaned up vote text
This commit is contained in:
parent
dda8192e2e
commit
1409bea1f3
|
@ -7,7 +7,7 @@
|
||||||
v-if="session.sessionId"
|
v-if="session.sessionId"
|
||||||
@click="leaveSession"
|
@click="leaveSession"
|
||||||
:title="
|
:title="
|
||||||
`You're currently in a live game with ${session.playerCount} other players!`
|
`${session.playerCount} other players in this session (${session.ping}ms latency)`
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<font-awesome-icon icon="broadcast-tower" />
|
<font-awesome-icon icon="broadcast-tower" />
|
||||||
|
@ -91,6 +91,10 @@
|
||||||
<li @click="joinSession" v-if="!session.sessionId">
|
<li @click="joinSession" v-if="!session.sessionId">
|
||||||
Join (Player)<em>[J]</em>
|
Join (Player)<em>[J]</em>
|
||||||
</li>
|
</li>
|
||||||
|
<li v-if="session.sessionId && session.ping">
|
||||||
|
Latency to {{ session.isSpectator ? "storyteller" : "players" }}
|
||||||
|
<em>{{ session.ping }}ms</em>
|
||||||
|
</li>
|
||||||
<li v-if="session.sessionId" @click="copySessionUrl">
|
<li v-if="session.sessionId" @click="copySessionUrl">
|
||||||
Copy player link
|
Copy player link
|
||||||
<em><font-awesome-icon icon="copy"/></em>
|
<em><font-awesome-icon icon="copy"/></em>
|
||||||
|
|
|
@ -10,8 +10,9 @@
|
||||||
>!
|
>!
|
||||||
<br />
|
<br />
|
||||||
<template v-if="nominee.role.team !== 'traveler'">
|
<template v-if="nominee.role.team !== 'traveler'">
|
||||||
<em class="blue">{{ Math.ceil(alive / 2) }} votes</em> required for a
|
<em class="blue">{{ voters.length }} vote{{ voters.length != 1 ? 's':'' }}</em>
|
||||||
<em>majority</em>.
|
in favor
|
||||||
|
<em>(majority is {{ Math.ceil(alive / 2) }})</em>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<em>{{ Math.ceil(players.length / 2) }} votes</em> required for a
|
<em>{{ Math.ceil(players.length / 2) }} votes</em> required for a
|
||||||
|
|
|
@ -3,6 +3,14 @@ const set = key => (state, val) => {
|
||||||
state[key] = val;
|
state[key] = val;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a vote request.
|
||||||
|
* If the vote is from a seat that is already locked, ignore it.
|
||||||
|
* @param state session state
|
||||||
|
* @param index seat of the player in the circle
|
||||||
|
* @param vote true or false
|
||||||
|
* @param indexAdjusted seat of the player counted from the nominated player
|
||||||
|
*/
|
||||||
const handleVote = (state, [index, vote]) => {
|
const handleVote = (state, [index, vote]) => {
|
||||||
if (!state.nomination) return;
|
if (!state.nomination) return;
|
||||||
state.votes = [...state.votes];
|
state.votes = [...state.votes];
|
||||||
|
@ -13,6 +21,7 @@ const state = () => ({
|
||||||
sessionId: "",
|
sessionId: "",
|
||||||
isSpectator: false,
|
isSpectator: false,
|
||||||
playerCount: 0,
|
playerCount: 0,
|
||||||
|
ping: 0,
|
||||||
playerId: "",
|
playerId: "",
|
||||||
claimedSeat: -1,
|
claimedSeat: -1,
|
||||||
nomination: false,
|
nomination: false,
|
||||||
|
@ -30,6 +39,7 @@ const mutations = {
|
||||||
setPlayerId: set("playerId"),
|
setPlayerId: set("playerId"),
|
||||||
setSpectator: set("isSpectator"),
|
setSpectator: set("isSpectator"),
|
||||||
setPlayerCount: set("playerCount"),
|
setPlayerCount: set("playerCount"),
|
||||||
|
setPing: set("ping"),
|
||||||
setVotingSpeed: set("votingSpeed"),
|
setVotingSpeed: set("votingSpeed"),
|
||||||
claimSeat: set("claimedSeat"),
|
claimSeat: set("claimedSeat"),
|
||||||
nomination(state, { nomination, votes, votingSpeed, lockedVote } = {}) {
|
nomination(state, { nomination, votes, votingSpeed, lockedVote } = {}) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ class LiveSession {
|
||||||
this._pingInterval = 30 * 1000; // 30 seconds between pings
|
this._pingInterval = 30 * 1000; // 30 seconds between pings
|
||||||
this._pingTimer = null;
|
this._pingTimer = null;
|
||||||
this._players = {}; // map of players connected to a session
|
this._players = {}; // map of players connected to a session
|
||||||
|
this._pings = {}; // map of player IDs to ping
|
||||||
// reconnect to previous session
|
// reconnect to previous session
|
||||||
if (this._store.state.session.sessionId) {
|
if (this._store.state.session.sessionId) {
|
||||||
this.connect(this._store.state.session.sessionId);
|
this.connect(this._store.state.session.sessionId);
|
||||||
|
@ -65,7 +66,11 @@ class LiveSession {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_ping() {
|
_ping() {
|
||||||
this._send("ping", [this._isSpectator, this._store.state.session.playerId]);
|
this._send("ping", [
|
||||||
|
this._isSpectator,
|
||||||
|
this._store.state.session.playerId,
|
||||||
|
new Date().getTime()
|
||||||
|
]);
|
||||||
this._handlePing();
|
this._handlePing();
|
||||||
clearTimeout(this._pingTimer);
|
clearTimeout(this._pingTimer);
|
||||||
this._pingTimer = setTimeout(this._ping.bind(this), this._pingInterval);
|
this._pingTimer = setTimeout(this._ping.bind(this), this._pingInterval);
|
||||||
|
@ -149,7 +154,9 @@ class LiveSession {
|
||||||
.substr(2)
|
.substr(2)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
this._pings = {};
|
||||||
this._store.commit("session/setPlayerCount", 0);
|
this._store.commit("session/setPlayerCount", 0);
|
||||||
|
this._store.commit("session/setPing", 0);
|
||||||
this._isSpectator = this._store.state.session.isSpectator;
|
this._isSpectator = this._store.state.session.isSpectator;
|
||||||
this._open(channel);
|
this._open(channel);
|
||||||
}
|
}
|
||||||
|
@ -158,7 +165,9 @@ class LiveSession {
|
||||||
* Close the current session, if any.
|
* Close the current session, if any.
|
||||||
*/
|
*/
|
||||||
disconnect() {
|
disconnect() {
|
||||||
|
this._pings = {};
|
||||||
this._store.commit("session/setPlayerCount", 0);
|
this._store.commit("session/setPlayerCount", 0);
|
||||||
|
this._store.commit("session/setPing", 0);
|
||||||
if (this._socket) {
|
if (this._socket) {
|
||||||
this._send("bye", this._store.state.session.playerId);
|
this._send("bye", this._store.state.session.playerId);
|
||||||
this._socket.close();
|
this._socket.close();
|
||||||
|
@ -366,20 +375,16 @@ class LiveSession {
|
||||||
* Handle a ping message by another player / storyteller
|
* Handle a ping message by another player / storyteller
|
||||||
* @param isSpectator
|
* @param isSpectator
|
||||||
* @param playerId
|
* @param playerId
|
||||||
|
* @param timestamp
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_handlePing([isSpectator, playerId] = []) {
|
_handlePing([isSpectator, playerId, timestamp] = []) {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
if (playerId) {
|
|
||||||
this._players[playerId] = now;
|
|
||||||
if (!this._isSpectator && !isSpectator) {
|
|
||||||
alert("Another storyteller joined the session!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// remove players that haven't sent a ping in twice the timespan
|
// remove players that haven't sent a ping in twice the timespan
|
||||||
for (let player in this._players) {
|
for (let player in this._players) {
|
||||||
if (now - this._players[player] > this._pingInterval * 2) {
|
if (now - this._players[player] > this._pingInterval * 2) {
|
||||||
delete this._players[player];
|
delete this._players[player];
|
||||||
|
delete this._pings[player];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// remove claimed seats from players that are no longer connected
|
// remove claimed seats from players that are no longer connected
|
||||||
|
@ -392,6 +397,24 @@ class LiveSession {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// store new player data
|
||||||
|
if (playerId) {
|
||||||
|
this._players[playerId] = now;
|
||||||
|
if (!this._isSpectator && !isSpectator) {
|
||||||
|
alert("Another storyteller joined the session!");
|
||||||
|
} else if (this._isSpectator && !isSpectator) {
|
||||||
|
// ping to ST
|
||||||
|
this._store.commit("session/setPing", now - timestamp);
|
||||||
|
} else {
|
||||||
|
// ping to Players
|
||||||
|
this._pings[playerId] = now - timestamp;
|
||||||
|
const pings = Object.values(this._pings);
|
||||||
|
this._store.commit(
|
||||||
|
"session/setPing",
|
||||||
|
Math.round(pings.reduce((a, b) => a + b, 0) / pings.length)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
this._store.commit(
|
this._store.commit(
|
||||||
"session/setPlayerCount",
|
"session/setPlayerCount",
|
||||||
Object.keys(this._players).length
|
Object.keys(this._players).length
|
||||||
|
@ -491,18 +514,29 @@ class LiveSession {
|
||||||
!this._isSpectator
|
!this._isSpectator
|
||||||
) {
|
) {
|
||||||
// send vote only if it is your own vote or you are the storyteller
|
// send vote only if it is your own vote or you are the storyteller
|
||||||
this._send("vote", [index, this._store.state.session.votes[index]]);
|
this._send("vote", [
|
||||||
|
index,
|
||||||
|
this._store.state.session.votes[index],
|
||||||
|
!this._isSpectator
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle an incoming vote, but not if it is for your own seat.
|
* Handle an incoming vote, but only if it is from ST or unlocked.
|
||||||
* @param index
|
* @param index
|
||||||
* @param vote
|
* @param vote
|
||||||
|
* @param fromST
|
||||||
*/
|
*/
|
||||||
_handleVote([index, vote]) {
|
_handleVote([index, vote, fromST]) {
|
||||||
|
const { session, players } = this._store.state;
|
||||||
|
const playerCount = players.players.length;
|
||||||
|
const indexAdjusted =
|
||||||
|
(index - 1 + playerCount - session.nomination[1]) % playerCount;
|
||||||
|
if (fromST || indexAdjusted >= session.lockedVote - 1) {
|
||||||
this._store.commit("session/vote", [index, vote]);
|
this._store.commit("session/vote", [index, vote]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lock a vote. ST only
|
* Lock a vote. ST only
|
||||||
|
|
Loading…
Reference in New Issue