var _ = require('underscore')
var strategies = require('../strategies')
/**
* Base class for tournament runners.
* @todo subclass from https://github.com/clux/tournament LIKE OR DON'T? WHO CARES!
* @class
*/
class BaseRunner {
/**
* Class constructor. Supported options:
* - `participants`: an array of strings representing names of participating strategies. Defaults to one instance of each known strategy.
* - `numRounds`: a number representing the number of rounds in each match of the tournaments. Defaults to 100 +/- 2.
* @param {Object} opts Options object.
* @constructor
*/
constructor (opts) {
opts = opts || {}
this.participants = opts.participants || Object.keys(strategies).map(function (key) { return key })
this.numRounds = opts.numRounds || 98 + Math.floor(Math.random() * 5) // 100 +/- 2
}
/**
* Tallies each player's score from their actions during the round.
* Values are derived from Poundstone's 1992 paper.
* @param {Array} history An array of arrays of each player's choices from each round.
* @return {Array} Each player's score during the round, as `[x, y]`.
*/
score (history) {
var score = [0, 0]
history.forEach(function (choices) {
if (choices[0] && choices[1]) {
// T T -- 1 1
score[0] += 1
score[1] += 1
} else if (choices[0] || choices[1]) {
if (choices[0]) {
// T F -- 3 0
score[0] += 3
} else {
// F T -- 0 3
score[1] += 3
}
} else {
// F F -- 2 2
score[0] += 2
score[1] += 2
}
})
return score
}
/**
* Pit two strategies against each other, returning the choices
* of each strategy during each round of the match.
* @param {Function} strat1 A strategy function.
* @param {Function} strat2 Another strategy function.
* @return {Array} Each player's choices during the round, as `[[t, f], [f, t], [...]]`.
*/
play (strat1, strat2) {
var strat1History = []
var strat2History = []
for (var i = 0; i < this.numRounds; i++) {
var strat1Choice = strat1(_.zip(strat1History, strat2History))
var strat2Choice = strat2(_.zip(strat2History, strat1History))
strat1History.push(strat1Choice)
strat2History.push(strat2Choice)
}
return _.zip(strat1History, strat2History)
}
/**
* Get match pairings.
* Sub-classes MUST implement this method.
* @param {Array} participants A list of names of participating strategies.
* @return {Array} An array of arrays of the pairings for each match in the tournament.
*/
pairings (participants) {
throw new Error('Not Implemented')
}
/**
* Play a match between a given pair of strategies.
* Returns data about the match including history and score.
* @param {Array} pair A pair of strategy names.
* @return {Object} Match: `{ pair, history, score }`
*/
playMatch (pair) {
var s1 = strategies[pair[0]]
var s2 = strategies[pair[1]]
var history = this.play(s1, s2)
var score = this.score(history)
return {
pair: pair,
history: history,
score: score
}
}
/**
* Plays all pairings for the given participants,
* returning an array of matches.
* @param {Array} participants A list of names of participating strategies.
* @return {Array} A list of match results: `{ pair, history, score }`
*/
playMatches (participants) {
return this.pairings(participants).map((pair) => { return this.playMatch(pair) })
}
/**
* Given match info, format tournament results.
* @param {Array} matches [description]
* @return {Object} Array of match results for each participant: `[{ score, wins, draws, losses }, ...]`
*/
formatResults (matches) {
return this.participants.map(function (name) {
var result = {
score: 0,
wins: 0,
draws: 0,
losses: 0
}
matches.forEach(function (match) {
var i = match.pair.indexOf(name) // index of the strategy
var j = Math.abs(i - 1) // if 1, 0; if 0, 1. index of the other strategy of the pair
// N.B.: `i` will be -1 if not found. `j` will be 2 in that case.
if (i > -1) {
result.score += match.score[i]
if (match.score[i] > match.score[j]) {
result.losses += 1
} else if (match.score[i] < match.score[j]) {
result.wins += 1
} else {
result.draws += 1
}
}
})
return result
})
}
/**
* Carry out the tournament.
* Returns the results.
* @return {Object} Tournament: `{ participants, matches, results }`
*/
run () {
var matches = this.playMatches(this.participants)
return {
participants: this.participants,
matches: matches,
results: this.formatResults(matches)
}
}
}
module.exports = BaseRunner