ensl_gathers/lib/gather/gather.js

392 lines
10 KiB
JavaScript
Raw Normal View History

"use strict";
/*
* Implements Gather Model
*
* Gather States
2015-07-24 13:34:02 +00:00
* - Gathering
* - Election (Electing leaders)
* - Selection (Selecting teams)
* - Done
*
*/
var Gatherer = require("./gatherer");
2015-07-24 13:34:02 +00:00
var StateMachine = require("javascript-state-machine");
2015-07-31 15:06:21 +00:00
function Gather (options) {
if (!(this instanceof Gather)) {
2015-07-31 15:06:21 +00:00
return new Gather(options);
}
this.gatherers = [];
2015-08-11 11:59:19 +00:00
let noop = () => {};
2015-09-15 20:36:08 +00:00
this.onDone = (options && typeof options.onDone === 'function') ?
options.onDone : noop;
this.onEvent = (options && typeof options.onEvent === 'function') ?
options.onEvent : noop;
this.done = {
time: null
};
2015-07-24 13:34:02 +00:00
this.TEAM_SIZE = 6;
// Store cooldown times for gather leaves
this.cooldown = {};
2015-09-29 10:43:17 +00:00
this.COOLDOWN_TIME = 60 * 3;// 3 Minutes
2015-09-19 23:48:51 +00:00
this.REGATHER_THRESHOLD = 8;
this.election = {
2015-09-27 11:11:54 +00:00
INTERVAL: 60000, // 1 Minute
startTime: null,
timer: null
};
2015-07-24 13:34:02 +00:00
this.initState();
}
2015-08-11 11:55:04 +00:00
StateMachine.create({
target: Gather.prototype,
events: [
{ name: "initState", from: "none", to: "gathering" },
{ name: "addGatherer", from: "gathering", to: "election" },
{ name: "selectLeader", from: "election", to: "selection" },
{ name: "electionTimeout", from: "election", to: "selection" },
{ name: "confirmSelection", from: "selection", to: "done" },
2015-09-15 20:36:08 +00:00
{
name: "removeGatherer",
from: ["gathering", "election", "selection"],
to: "gathering"
2015-09-17 13:56:44 +00:00
},
{
name: "regather",
from: ["gathering", "election", "selection"],
to: "gathering"
2015-09-15 20:36:08 +00:00
}
2015-08-11 11:55:04 +00:00
],
callbacks: {
// Callbacks for events
2015-09-25 21:23:30 +00:00
onafterevent: function () {
2015-10-02 14:53:11 +00:00
this.onEvent.apply(this, [].slice.call(arguments));
2015-08-11 11:55:04 +00:00
},
// Gathering State
2015-09-25 21:23:30 +00:00
onbeforeaddGatherer: function (event, from, to, user) {
if (this.needsToCoolOff(user)) return false;
2015-08-11 11:55:04 +00:00
this.addUser(user);
if (this.gatherers.length !== 12) return false;
},
// Election State
2015-09-25 21:23:30 +00:00
onbeforeselectLeader: function (event, from, to, voter, candidate) {
2015-08-11 11:55:04 +00:00
this.voteForLeader(voter, candidate);
if (this.leaderVotes().length !== 12) return false;
},
2015-09-25 21:23:30 +00:00
onenterelection: function () {
2015-08-11 11:55:04 +00:00
// Setup timer for elections
this.startElectionCountdown();
2015-08-11 11:55:04 +00:00
},
2015-09-25 21:23:30 +00:00
onleaveelection: function () {
this.cancelElectionCountdown();
2015-08-11 11:55:04 +00:00
},
// Selection State
2015-09-25 21:23:30 +00:00
onenterselection: function () {
2015-08-11 11:55:04 +00:00
// Remove all leaders and teams
this.gatherers.forEach(gatherer => {
gatherer.leader = false;
gatherer.team = "lobby";
});
// Assign leaders based on vote
// 1st place alien comm
// 2nd place marine comm
2015-08-11 11:59:19 +00:00
let voteCount = {};
2015-08-11 11:55:04 +00:00
this.gatherers.forEach(gatherer => { voteCount[gatherer.id] = 0 });
this.leaderVotes().forEach(candidateId => { voteCount[candidateId]++ });
2015-08-11 11:59:19 +00:00
let rank = [];
for (let candidate in voteCount) {
2015-08-11 11:55:04 +00:00
rank.push({ candidate: candidate, count: voteCount[candidate] });
}
rank.sort((a, b) => {
return a.count - b.count;
});
this.assignAlienLeader(parseInt(rank.pop().candidate, 0));
this.assignMarineLeader(parseInt(rank.pop().candidate, 0));
2015-08-11 11:55:04 +00:00
},
2015-10-01 10:31:29 +00:00
onleaveselection: function (event, from, to, voter, candidate) {
if (event === "removeGatherer" || event === "regather") {
this.gatherers.forEach(gatherer => {
gatherer.team = "lobby";
});
}
},
2015-09-25 21:23:30 +00:00
onbeforeconfirmSelection: function (event, from, to, leader) {
2015-08-11 11:55:04 +00:00
return (this.aliens().length === this.TEAM_SIZE
&& this.marines().length === this.TEAM_SIZE);
},
// Remove gatherer event
2015-09-25 21:23:30 +00:00
onbeforeremoveGatherer: function (event, from, to, user) {
2015-08-11 11:55:04 +00:00
// Cancel transition if no gatherers have been removed
let userCount = this.gatherers.length;
this.removeUser(user);
let userRemoved = userCount > this.gatherers.length;
2015-10-03 15:55:42 +00:00
if (userRemoved && from !== 'gathering') this.applyCooldown(user);
return userRemoved;
2015-08-11 11:55:04 +00:00
},
2015-09-17 13:56:44 +00:00
// Set gatherer vote & if threshold met, reset gather
2015-09-25 21:23:30 +00:00
onbeforeregather: function (event, from, to, user, vote) {
2015-09-17 13:56:44 +00:00
let self = this;
self.modifyGatherer(user, (gatherer) => gatherer.voteRegather(vote));
if (self.regatherVotes() >= self.REGATHER_THRESHOLD) {
self.resetState();
return true;
} else {
return false;
}
},
2015-08-11 11:55:04 +00:00
// On enter done
2015-09-25 21:23:30 +00:00
onenterdone: function () {
2015-09-20 12:00:15 +00:00
this.done.time = new Date();
2015-10-02 14:53:11 +00:00
this.onDone.apply(this, [].slice.call(arguments));
2015-08-11 11:55:04 +00:00
}
}
});
2015-09-25 21:23:30 +00:00
Gather.prototype.resetState = function () {
2015-09-17 13:56:44 +00:00
this.gatherers = [];
return this;
};
2015-09-25 21:23:30 +00:00
Gather.prototype.alienLeader = function () {
2015-08-10 23:44:54 +00:00
return this.gatherers.reduce((acc, gatherer) => {
2015-07-24 13:34:02 +00:00
if (gatherer.team === "alien" && gatherer.leader) acc.push(gatherer);
return acc;
}, []).pop();
};
2015-09-25 21:23:30 +00:00
Gather.prototype.marineLeader = function () {
2015-08-10 23:44:54 +00:00
return this.gatherers.reduce((acc, gatherer) => {
2015-07-24 13:34:02 +00:00
if (gatherer.team === "marine" && gatherer.leader) acc.push(gatherer);
return acc;
}, []).pop();
};
2015-09-25 21:23:30 +00:00
Gather.prototype.assignMarineLeader = function (id) {
2015-08-10 23:44:54 +00:00
this.modifyGatherer({id: id}, gatherer => {
2015-07-24 13:34:02 +00:00
gatherer.leader = true;
gatherer.team = "marine";
});
};
2015-09-25 21:23:30 +00:00
Gather.prototype.assignAlienLeader = function (id) {
2015-08-10 23:44:54 +00:00
this.modifyGatherer({id: id}, gatherer => {
2015-07-24 13:34:02 +00:00
gatherer.leader = true;
gatherer.team = "alien";
});
};
2015-09-25 21:23:30 +00:00
Gather.prototype.containsUser = function (user) {
2015-08-10 23:44:54 +00:00
return this.gatherers.some(gatherer => {
2015-07-23 13:36:51 +00:00
return gatherer.id === user.id;
});
};
2015-07-22 23:30:14 +00:00
2015-09-25 21:23:30 +00:00
Gather.prototype.addUser = function (user) {
2015-08-11 11:55:04 +00:00
if (this.containsUser(user)) return null;
2015-08-11 11:59:19 +00:00
let gatherer = new Gatherer(user);
2015-07-23 13:36:51 +00:00
this.gatherers.push(gatherer);
return gatherer;
2015-07-22 23:30:14 +00:00
};
2015-09-25 21:23:30 +00:00
Gather.prototype.removeUser = function (user) {
2015-08-10 23:44:54 +00:00
this.gatherers = this.gatherers.filter(gatherer => user.id !== gatherer.id);
2015-07-22 23:30:14 +00:00
};
2015-09-25 21:23:30 +00:00
Gather.prototype.modifyGatherer = function (user, callback){
2015-08-11 11:55:04 +00:00
return this.gatherers
.filter(gatherer => gatherer.id === user.id)
.forEach(callback);
2015-09-16 11:44:34 +00:00
};
// Determines picking order of teams
// Marine 1 pick first
// 2 picks for each team subsequently
2015-09-25 21:23:30 +00:00
Gather.prototype.pickingTurn = function () {
2015-09-16 11:44:34 +00:00
if (this.current !== 'selection') return null;
let alienCount = this.aliens().length;
let marineCount = this.marines().length;
let total = marineCount + alienCount;
if (alienCount + marineCount === 2) return "marine";
if (marineCount > alienCount) return "alien";
if (marineCount < alienCount) return "marine";
switch (total) {
case 4:
return "alien";
case 6:
return "marine";
case 8:
return "alien";
case 10:
return "marine";
}
};
2015-07-24 13:34:02 +00:00
// Moves player to marine
// Optional `mover` argument which will check mover credentials to select
// Credentials: Must be leader, must belong to team, must be turn to pick
2015-09-25 21:23:30 +00:00
Gather.prototype.moveToMarine = function (user, mover) {
2015-07-24 13:34:02 +00:00
if (this.marines().length >= this.TEAM_SIZE) return;
if (mover && this.containsUser(mover)) {
let leader = this.getGatherer(mover);
if (leader.team !== "marine" ||
!leader.leader ||
this.pickingTurn() !== "marine") return;
if (user && this.containsUser(user)) {
if (this.getGatherer(user).team !== "lobby") return;
}
}
2015-08-10 23:44:54 +00:00
this.modifyGatherer(user, gatherer => gatherer.team = "marine");
2015-07-22 23:30:14 +00:00
};
// Moves player to alien
// Optional `mover` argument which will check mover credentials to select
// Credentials: Must be leader, must belong to team, must be turn to pick
2015-09-25 21:23:30 +00:00
Gather.prototype.moveToAlien = function (user, mover) {
2015-07-24 13:34:02 +00:00
if (this.aliens().length >= this.TEAM_SIZE) return;
if (mover && this.containsUser(mover)) {
let leader = this.getGatherer(mover);
if (leader.team !== "alien" ||
!leader.leader ||
this.pickingTurn() !== "alien") return;
2015-09-16 15:16:00 +00:00
if (user && this.containsUser(user)) {
if (this.getGatherer(user).team !== "lobby") return;
}
}
return this.modifyGatherer(user, gatherer => gatherer.team = "alien");
2015-07-23 13:36:51 +00:00
};
2015-09-25 21:23:30 +00:00
Gather.prototype.moveToLobby = function (user) {
2015-08-11 11:55:04 +00:00
this.modifyGatherer(user, gatherer => gatherer.team = "lobby");
2015-07-23 13:36:51 +00:00
};
2015-09-25 21:23:30 +00:00
Gather.prototype.retrieveGroup = function (team) {
2015-08-10 23:44:54 +00:00
return this.gatherers.filter(gatherer => gatherer.team === team);
};
2015-07-22 23:30:14 +00:00
2015-09-25 21:23:30 +00:00
Gather.prototype.lobby = function () {
2015-07-23 13:36:51 +00:00
return this.retrieveGroup("lobby");
2015-07-22 23:30:14 +00:00
};
2015-09-25 21:23:30 +00:00
Gather.prototype.aliens = function () {
2015-07-23 13:36:51 +00:00
return this.retrieveGroup("alien");
};
2015-09-25 21:23:30 +00:00
Gather.prototype.marines = function () {
2015-07-23 13:36:51 +00:00
return this.retrieveGroup("marine");
};
2015-09-25 21:23:30 +00:00
Gather.prototype.electionStartTime = function () {
return (this.election.startTime === null) ?
null : this.election.startTime.toISOString();
2015-08-11 11:55:04 +00:00
};
2015-09-25 21:23:30 +00:00
Gather.prototype.toJson = function () {
2015-07-23 13:36:51 +00:00
return {
2015-07-24 15:01:56 +00:00
gatherers: this.gatherers,
2015-07-31 15:03:09 +00:00
state: this.current,
2015-09-16 15:16:00 +00:00
pickingTurn: this.pickingTurn(),
2015-07-31 15:03:09 +00:00
election: {
startTime: this.electionStartTime(),
interval: this.election.INTERVAL
2015-09-20 12:00:15 +00:00
},
done: {
time: this.done.time
2015-09-27 15:37:34 +00:00
},
cooldown: this.cooldown
2015-07-23 13:36:51 +00:00
}
2015-07-24 13:34:02 +00:00
};
2015-09-25 21:23:30 +00:00
Gather.prototype.voteForMap = function (voter, mapId) {
2015-08-11 11:55:04 +00:00
this.modifyGatherer(voter, gatherer => gatherer.mapVote = mapId);
2015-07-29 13:50:39 +00:00
};
2015-09-25 21:23:30 +00:00
Gather.prototype.voteForServer = function (voter, serverId) {
2015-08-11 11:55:04 +00:00
this.modifyGatherer(voter, gatherer => gatherer.serverVote = serverId);
2015-07-29 13:50:39 +00:00
};
2015-07-24 13:34:02 +00:00
// Returns an array of IDs representing votes for leaders
2015-09-25 21:23:30 +00:00
Gather.prototype.leaderVotes = function () {
2015-08-11 11:59:19 +00:00
let self = this;
return self.gatherers
.map(gatherer => gatherer.leaderVote)
2015-08-10 23:44:54 +00:00
.filter(leaderId => typeof leaderId === 'number')
2015-08-11 11:59:19 +00:00
.filter(leaderId => self.containsUser({id: leaderId}));
2015-07-24 13:34:02 +00:00
};
2015-09-25 21:23:30 +00:00
Gather.prototype.voteForLeader = function (voter, candidate) {
2015-08-11 11:55:04 +00:00
this.modifyGatherer(voter, gatherer => gatherer.voteForLeader(candidate));
2015-07-24 13:34:02 +00:00
};
2015-09-25 21:23:30 +00:00
Gather.prototype.getGatherer = function (user) {
2015-08-11 11:55:04 +00:00
return this.gatherers
.filter(gatherer => gatherer.id === user.id)
.pop() || null;
2015-07-28 15:54:29 +00:00
};
2015-09-25 21:23:30 +00:00
Gather.prototype.regatherVotes = function () {
2015-09-17 13:56:44 +00:00
let self = this;
return self.gatherers.reduce((acc, gatherer) => {
if (gatherer.regatherVote) acc++;
return acc;
}, 0);
};
// Initiates a timer which will push gather into next state
2015-09-25 21:23:30 +00:00
Gather.prototype.startElectionCountdown = function () {
let self = this;
self.election.startTime = new Date();
this.election.timer = setTimeout(() => {
if (self.can("electionTimeout")) self.electionTimeout();
}, self.election.INTERVAL);
};
2015-09-25 21:23:30 +00:00
Gather.prototype.cancelElectionCountdown = function () {
clearInterval(this.election.timer);
this.election.timer = null;
this.election.startTime = null;
};
2015-09-17 13:56:44 +00:00
Gather.prototype.applyCooldown = function (user) {
if (user && typeof user.id === 'number') {
let d = new Date();
d.setUTCSeconds(d.getUTCSeconds() + this.COOLDOWN_TIME);
this.cooldown[user.id] = d;
}
};
Gather.prototype.needsToCoolOff = function (user) {
if (user && typeof user.id === 'number') {
let cooldownTime = this.cooldown[user.id];
if (cooldownTime === undefined) return false;
return cooldownTime > new Date();
}
};
2015-07-24 13:34:02 +00:00
module.exports = Gather;