function buildMatches(pool) { var matches = []; for (var index = 0; index < pool.length; index += 2) { matches.push({ left: pool[index], right: pool[index + 1] || null, winner: pool[index + 1] ? null : pool[index] }); } return matches; } function create(candidates) { var pool = candidates.slice(); return { round: 1, pool: pool, matches: buildMatches(pool), eliminated: [], decisions: [] }; } function current(state) { var match = state.matches.filter(function (item) { return item.right && !item.winner; })[0]; return match ? [match.left, match.right] : null; } function advance(state) { if (state.matches.some(function (item) { return !item.winner; })) return false; state.pool = state.matches.map(function (item) { return item.winner; }); if (state.pool.length === 1) return true; state.round += 1; state.matches = buildMatches(state.pool); return false; } function chooseAt(state, matchIndex, winner) { var match = state.matches[matchIndex]; if (!match || !match.right || match.winner) throw new Error('invalid bracket match'); if (winner.id !== match.left.id && winner.id !== match.right.id) throw new Error('winner is not in match'); var loser = match.left.id === winner.id ? match.right : match.left; match.winner = winner; state.eliminated.unshift(loser); state.decisions.push({ round: state.round, left_id: match.left.id, right_id: match.right.id, winner_id: winner.id }); return advance(state); } function choose(state, winner) { var index = state.matches.findIndex(function (item) { return item.right && !item.winner; }); return chooseAt(state, index, winner); } function ranking(state) { return state.pool.concat(state.eliminated).map(function (item) { return item.id; }); } module.exports = { create: create, current: current, choose: choose, chooseAt: chooseAt, ranking: ranking };