tournament-pairings
Version:
Functions to generate pairings for tournaments
1,279 lines (1,247 loc) • 50.2 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["tournament-pairings"] = {}));
})(this, (function (exports) { 'use strict';
function shuffle(arr) {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const z = Math.floor(Math.random() * (i + 1));
[a[i], a[z]] = [a[z], a[i]];
}
return a;
}
function SingleElimination(players, startingRound = 1, consolation = false, ordered = false) {
const matches = [];
let playerArray = [];
if (Array.isArray(players)) {
playerArray = ordered ? players : shuffle(players);
}
else {
playerArray = [...new Array(players)].map((_, i) => i + 1);
}
const exponent = Math.log2(playerArray.length);
const remainder = Math.round(2 ** exponent) % (2 ** Math.floor(exponent));
const bracket = exponent < 2 ? [1, 2] : [1, 4, 2, 3];
for (let i = 3; i <= Math.floor(exponent); i++) {
for (let j = 0; j < bracket.length; j += 2) {
bracket.splice(j + 1, 0, 2 ** i + 1 - bracket[j]);
}
}
let round = startingRound;
if (remainder !== 0) {
for (let i = 0; i < remainder; i++) {
matches.push({
round: round,
match: i + 1,
player1: null,
player2: null
});
}
round++;
}
let matchExponent = Math.floor(exponent) - 1;
let iterated = false;
do {
for (let i = 0; i < 2 ** matchExponent; i++) {
matches.push({
round: round,
match: i + 1,
player1: null,
player2: null
});
}
if (!iterated) {
iterated = true;
}
else {
matches.filter(m => m.round === round - 1).forEach(m => m.win = {
round: round,
match: Math.ceil(m.match / 2)
});
}
round++;
matchExponent--;
} while (round < startingRound + Math.ceil(exponent));
const startRound = startingRound + (remainder === 0 ? 0 : 1);
matches.filter(m => m.round === startRound).forEach((m, i) => {
m.player1 = playerArray[bracket[2 * i] - 1];
m.player2 = playerArray[bracket[2 * i + 1] - 1];
});
if (remainder !== 0) {
const initialRound = matches.filter(m => m.round === startingRound);
let counter = 0;
matches.filter(m => m.round === startingRound + 1).forEach((m, i) => {
const [index1, index2] = [playerArray.indexOf(m.player1), playerArray.indexOf(m.player2)];
if (index1 >= Math.pow(2, Math.floor(exponent)) - remainder) {
const initialMatch = initialRound[counter];
initialMatch.player1 = m.player1;
initialMatch.player2 = playerArray[Math.pow(2, Math.ceil(exponent)) - index1 - 1];
initialMatch.win = {
round: startingRound + 1,
match: m.match
};
m.player1 = null;
counter++;
}
if (index2 >= Math.pow(2, Math.floor(exponent)) - remainder) {
const initialMatch = initialRound[counter];
initialMatch.player1 = m.player2;
initialMatch.player2 = playerArray[Math.pow(2, Math.ceil(exponent)) - index2 - 1];
initialMatch.win = {
round: startingRound + 1,
match: m.match
};
m.player2 = null;
counter++;
}
});
}
if (consolation) {
const lastRound = matches.reduce((max, curr) => Math.max(max, curr.round), 0);
const lastMatch = matches.filter(m => m.round === lastRound).reduce((max, curr) => Math.max(max, curr.match), 0);
matches.push({
round: lastRound,
match: lastMatch + 1,
player1: null,
player2: null
});
matches.filter(m => m.round === lastRound - 1).forEach(m => m.loss = {
round: lastRound,
match: lastMatch + 1
});
}
return matches;
}
function DoubleElimination(players, startingRound = 1, ordered = false) {
const matches = [];
let playerArray = [];
if (Array.isArray(players)) {
playerArray = ordered ? players : shuffle(players);
}
else {
playerArray = [...new Array(players)].map((_, i) => i + 1);
}
const exponent = Math.log2(playerArray.length);
const remainder = Math.round(2 ** exponent) % (2 ** Math.floor(exponent));
const bracket = [1, 4, 2, 3];
for (let i = 3; i <= Math.floor(exponent); i++) {
for (let j = 0; j < bracket.length; j += 2) {
bracket.splice(j + 1, 0, 2 ** i + 1 - bracket[j]);
}
}
let round = startingRound;
if (remainder !== 0) {
for (let i = 0; i < remainder; i++) {
matches.push({
round: round,
match: i + 1,
player1: null,
player2: null
});
}
round++;
}
let matchExponent = Math.floor(exponent) - 1;
let iterated = false;
do {
for (let i = 0; i < 2 ** matchExponent; i++) {
matches.push({
round: round,
match: i + 1,
player1: null,
player2: null
});
}
if (!iterated) {
iterated = true;
}
else {
matches.filter(m => m.round === round - 1).forEach(m => m.win = {
round: round,
match: Math.ceil(m.match / 2)
});
}
round++;
matchExponent--;
} while (round < startingRound + Math.ceil(exponent));
const startRound = startingRound + (remainder === 0 ? 0 : 1);
matches.filter(m => m.round === startRound).forEach((m, i) => {
m.player1 = playerArray[bracket[2 * i] - 1];
m.player2 = playerArray[bracket[2 * i + 1] - 1];
});
if (remainder !== 0) {
const initialRound = matches.filter(m => m.round === startingRound);
let counter = 0;
matches.filter(m => m.round === startingRound + 1).forEach((m, i) => {
const [index1, index2] = [playerArray.indexOf(m.player1), playerArray.indexOf(m.player2)];
if (index1 >= Math.pow(2, Math.floor(exponent)) - remainder) {
const initialMatch = initialRound[counter];
initialMatch.player1 = m.player1;
initialMatch.player2 = playerArray[Math.pow(2, Math.ceil(exponent)) - index1 - 1];
initialMatch.win = {
round: startingRound + 1,
match: m.match
};
m.player1 = null;
counter++;
}
if (index2 >= Math.pow(2, Math.floor(exponent)) - remainder) {
const initialMatch = initialRound[counter];
initialMatch.player1 = m.player2;
initialMatch.player2 = playerArray[Math.pow(2, Math.ceil(exponent)) - index2 - 1];
initialMatch.win = {
round: startingRound + 1,
match: m.match
};
m.player2 = null;
counter++;
}
});
}
matches.push({
round: round,
match: 1,
player1: null,
player2: null,
});
matches.find(m => m.round === round - 1).win = {
round: round,
match: 1
};
round++;
const roundDiff = round - 1;
if (remainder !== 0) {
if (remainder <= 2 ** Math.floor(exponent) / 2) {
for (let i = 0; i < remainder; i++) {
matches.push({
round: round,
match: i + 1,
player1: null,
player2: null
});
}
round++;
}
else {
for (let i = 0; i < remainder - 2 ** (Math.floor(exponent) - 1); i++) {
matches.push({
round: round,
match: i + 1,
player1: null,
player2: null
});
}
round++;
for (let i = 0; i < 2 ** (Math.floor(exponent) - 1); i++) {
matches.push({
round: round,
match: i + 1,
player1: null,
player2: null
});
}
round++;
}
}
let loserExponent = Math.floor(exponent) - 2;
do {
for (let i = 0; i < 2; i++) {
for (let j = 0; j < 2 ** loserExponent; j++) {
matches.push({
round: round,
match: j + 1,
player1: null,
player2: null
});
}
round++;
}
loserExponent--;
} while (loserExponent > -1);
const fillPattern = (matchCount, fillCount) => {
const a = [...new Array(matchCount)].map((_, i) => i + 1);
const c = fillCount % 4;
const x = a.slice(0, a.length / 2);
const y = a.slice(a.length / 2);
return c === 0 ? a : c === 1 ? a.reverse() : c === 2 ? x.reverse().concat(y.reverse()) : y.concat(x);
};
let fillCount = 0;
let winRound = startingRound;
let loseRound = roundDiff + 1;
if (remainder === 0) {
const winMatches = matches.filter(m => m.round === winRound);
const fill = fillPattern(winMatches.length, fillCount);
fillCount++;
let counter = 0;
matches.filter(m => m.round === loseRound).forEach(m => {
for (let i = 0; i < 2; i++) {
const match = winMatches.find(m => m.match === fill[counter]);
match.loss = {
round: m.round,
match: m.match
};
counter++;
}
});
winRound++;
loseRound++;
}
else if (remainder <= 2 ** Math.floor(exponent) / 2) {
let winMatches = matches.filter(m => m.round === winRound);
let fill = fillPattern(winMatches.length, fillCount);
fillCount++;
matches.filter(m => m.round === loseRound).forEach((m, i) => {
const match = winMatches.find(m => m.match === fill[i]);
match.loss = {
round: m.round,
match: m.match
};
});
winRound++;
loseRound++;
winMatches = matches.filter(m => m.round === winRound);
fill = fillPattern(winMatches.length, fillCount);
fillCount++;
let countA = 0;
let countB = 0;
let routeNumbers = matches.filter(m => m.round === 2 && (m.player1 === null || m.player2 === null)).map(m => Math.ceil(m.match / 2));
let routeCopy = [...routeNumbers];
matches.filter(m => m.round === loseRound).forEach(m => {
for (let i = 0; i < 2; i++) {
const match = winMatches.find(m => m.match === fill[countA]);
if (routeCopy.some(n => n === m.match)) {
const lossMatch = matches.filter(x => x.round === loseRound - 1)[countB];
countB++;
match.loss = {
round: lossMatch.round,
match: lossMatch.match
};
routeCopy.splice(routeCopy.indexOf(m.match), 1);
}
else {
match.loss = {
round: m.round,
match: m.match
};
}
countA++;
}
});
winRound++;
loseRound++;
matches.filter(m => m.round === roundDiff + 1).forEach((m, i) => {
const match = matches.find(x => x.round === m.round + 1 && x.match === routeNumbers[i]);
m.win = {
round: match.round,
match: match.match
};
});
}
else {
const winMatches = matches.filter(m => m.round === winRound);
const loseMatchesA = matches.filter(m => m.round === loseRound);
loseRound++;
const loseMatchesB = matches.filter(m => m.round === loseRound);
const fill = fillPattern(winMatches.length, fillCount);
fillCount++;
let countA = 0;
let countB = 0;
let routeNumbers = matches.filter(m => m.round === 2 && m.player1 === null && m.player2 === null).map(m => m.match);
loseMatchesB.forEach(m => {
const winMatchA = winMatches.find(x => x.match === fill[countA]);
if (routeNumbers.some(n => n === m.match)) {
const lossMatch = loseMatchesA[countB];
winMatchA.loss = {
round: lossMatch.round,
match: lossMatch.match
};
countA++;
countB++;
const winMatchB = winMatches.find(x => x.match === fill[countA]);
winMatchB.loss = {
round: lossMatch.round,
match: lossMatch.match
};
}
else {
winMatchA.loss = {
round: m.round,
match: m.match
};
}
countA++;
});
winRound++;
matches.filter(m => m.round === roundDiff + 1).forEach((m, i) => {
const match = matches.find(x => x.round === m.round + 1 && x.match === routeNumbers[i]);
m.win = {
round: match.round,
match: match.match
};
});
}
let ffwd = 0;
for (let i = winRound; i < roundDiff; i++) {
let loseMatchesA = matches.filter(m => m.round === loseRound - winRound + ffwd + i);
const lostMatchesB = matches.filter(m => m.round === loseRound - winRound + ffwd + i + 1);
if (loseMatchesA.length === lostMatchesB.length) {
loseMatchesA = lostMatchesB;
ffwd++;
}
const winMatches = matches.filter(m => m.round === i);
const fill = fillPattern(winMatches.length, fillCount);
fillCount++;
loseMatchesA.forEach((m, j) => {
const match = winMatches.find(m => m.match === fill[j]);
match.loss = {
round: m.round,
match: m.match
};
});
}
for (let i = remainder === 0 ? roundDiff + 1 : roundDiff + 2; i < matches.reduce((max, curr) => Math.max(max, curr.round), 0); i++) {
const loseMatchesA = matches.filter(m => m.round === i);
const loseMatchesB = matches.filter(m => m.round === i + 1);
loseMatchesA.forEach((m, j) => {
const match = loseMatchesA.length === loseMatchesB.length ? loseMatchesB[j] : loseMatchesB[Math.floor(j / 2)];
m.win = {
round: match.round,
match: match.match
};
});
}
matches.filter(m => m.round === matches.reduce((max, curr) => Math.max(max, curr.round), 0))[0].win = {
round: roundDiff,
match: 1
};
return matches;
}
function RoundRobin(players, startingRound = 1, ordered = false) {
let matches = [];
let playerArray = [];
if (Array.isArray(players)) {
playerArray = ordered ? players : shuffle(players);
}
else {
playerArray = [...new Array(players)].map((_, i) => i + 1);
}
if (playerArray.length % 2 === 1) {
playerArray.push(null);
}
for (let r = startingRound; r < startingRound + playerArray.length - 1; r++) {
let round = [];
for (let i = 0; i < playerArray.length / 2; i++) {
round.push({
round: r,
match: i + 1,
player1: null,
player2: null
});
}
if (r === startingRound) {
round.forEach((m, i) => {
m.player1 = playerArray[i];
m.player2 = playerArray[playerArray.length - i - 1];
});
}
else {
const prevRound = matches.filter(m => m.round === r - 1);
const indexFind = idx => {
if (idx + (playerArray.length / 2) > playerArray.length - 2) {
return idx + 1 - (playerArray.length / 2);
}
else {
return idx + (playerArray.length / 2);
}
};
for (let i = 0; i < round.length; i++) {
const prev = prevRound[i];
const curr = round[i];
if (i === 0) {
if (prev.player2 === playerArray[playerArray.length - 1]) {
curr.player1 = playerArray[playerArray.length - 1];
curr.player2 = playerArray[indexFind(playerArray.findIndex(p => p === prev.player1))];
}
else {
curr.player2 = playerArray[playerArray.length - 1];
curr.player1 = playerArray[indexFind(playerArray.findIndex(p => p === prev.player2))];
}
}
else {
curr.player1 = playerArray[indexFind(playerArray.findIndex(p => p === prev.player1))];
curr.player2 = playerArray[indexFind(playerArray.findIndex(p => p === prev.player2))];
}
}
}
matches = [...matches, ...round];
}
return matches;
}
function Stepladder(players, startingRound = 1, ordered = true) {
const matches = [];
let playerArray = [];
if (Array.isArray(players)) {
playerArray = ordered ? players : shuffle(players);
}
else {
playerArray = [...new Array(players)].map((_, i) => i + 1);
}
const rounds = playerArray.length - 1;
for (let i = startingRound; i < startingRound + rounds; i++) {
const match = {
round: i,
match: 1,
player1: playerArray[playerArray.length - (i - startingRound) - 2],
player2: i === startingRound ? playerArray[playerArray.length - (i - startingRound) - 1] : null
};
if (i < startingRound + rounds - 1) {
match.win = {
round: i + 1,
match: 1
};
}
matches.push(match);
}
return matches;
}
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
/*Converted to JS from Python by Matt Krick. Original: http://jorisvr.nl/maximummatching.html*/
var blossom$1;
var hasRequiredBlossom;
function requireBlossom () {
if (hasRequiredBlossom) return blossom$1;
hasRequiredBlossom = 1;
blossom$1 = function (edges, maxCardinality) {
if (edges.length === 0) {
return edges;
}
var edmonds = new Edmonds(edges, maxCardinality);
return edmonds.maxWeightMatching();
};
var Edmonds = function (edges, maxCardinality) {
this.edges = edges;
this.maxCardinality = maxCardinality;
this.nEdge = edges.length;
this.init();
};
Edmonds.prototype.maxWeightMatching = function () {
for (var t = 0; t < this.nVertex; t++) {
//console.log('DEBUG: STAGE ' + t);
this.label = filledArray(2 * this.nVertex, 0);
this.bestEdge = filledArray(2 * this.nVertex, -1);
this.blossomBestEdges = initArrArr(2 * this.nVertex);
this.allowEdge = filledArray(this.nEdge, false);
this.queue = [];
for (var v = 0; v < this.nVertex; v++) {
if (this.mate[v] === -1 && this.label[this.inBlossom[v]] === 0) {
this.assignLabel(v, 1, -1);
}
}
var augmented = false;
while (true) {
//console.log('DEBUG: SUBSTAGE');
while (this.queue.length > 0 && !augmented) {
v = this.queue.pop();
//console.log('DEBUG: POP ', 'v=' + v);
//console.assert(this.label[this.inBlossom[v]] == 1);
for (var ii = 0; ii < this.neighbend[v].length; ii++) {
var p = this.neighbend[v][ii];
var k = ~~(p / 2);
var w = this.endpoint[p];
if (this.inBlossom[v] === this.inBlossom[w]) continue;
if (!this.allowEdge[k]) {
var kSlack = this.slack(k);
if (kSlack <= 0) {
this.allowEdge[k] = true;
}
}
if (this.allowEdge[k]) {
if (this.label[this.inBlossom[w]] === 0) {
this.assignLabel(w, 2, p ^ 1);
} else if (this.label[this.inBlossom[w]] === 1) {
var base = this.scanBlossom(v, w);
if (base >= 0) {
this.addBlossom(base, k);
} else {
this.augmentMatching(k);
augmented = true;
break;
}
} else if (this.label[w] === 0) {
//console.assert(this.label[this.inBlossom[w]] === 2);
this.label[w] = 2;
this.labelEnd[w] = p ^ 1;
}
} else if (this.label[this.inBlossom[w]] === 1) {
var b = this.inBlossom[v];
if (this.bestEdge[b] === -1 || kSlack < this.slack(this.bestEdge[b])) {
this.bestEdge[b] = k;
}
} else if (this.label[w] === 0) {
if (this.bestEdge[w] === -1 || kSlack < this.slack(this.bestEdge[w])) {
this.bestEdge[w] = k;
}
}
}
}
if (augmented) break;
var deltaType = -1;
var delta = [];
var deltaEdge = [];
var deltaBlossom = [];
if (!this.maxCardinality) {
deltaType = 1;
delta = getMin(this.dualVar, 0, this.nVertex - 1);
}
for (v = 0; v < this.nVertex; v++) {
if (this.label[this.inBlossom[v]] === 0 && this.bestEdge[v] !== -1) {
var d = this.slack(this.bestEdge[v]);
if (deltaType === -1 || d < delta) {
delta = d;
deltaType = 2;
deltaEdge = this.bestEdge[v];
}
}
}
for (b = 0; b < 2 * this.nVertex; b++) {
if (this.blossomParent[b] === -1 && this.label[b] === 1 && this.bestEdge[b] !== -1) {
kSlack = this.slack(this.bestEdge[b]);
////console.assert((kSlack % 2) == 0);
d = kSlack / 2;
if (deltaType === -1 || d < delta) {
delta = d;
deltaType = 3;
deltaEdge = this.bestEdge[b];
}
}
}
for (b = this.nVertex; b < this.nVertex * 2; b++) {
if (this.blossomBase[b] >= 0 && this.blossomParent[b] === -1 && this.label[b] === 2 && (deltaType === -1 || this.dualVar[b] < delta)) {
delta = this.dualVar[b];
deltaType = 4;
deltaBlossom = b;
}
}
if (deltaType === -1) {
//console.assert(this.maxCardinality);
deltaType = 1;
delta = Math.max(0, getMin(this.dualVar, 0, this.nVertex - 1));
}
for (v = 0; v < this.nVertex; v++) {
var curLabel = this.label[this.inBlossom[v]];
if (curLabel === 1) {
this.dualVar[v] -= delta;
} else if (curLabel === 2) {
this.dualVar[v] += delta;
}
}
for (b = this.nVertex; b < this.nVertex * 2; b++) {
if (this.blossomBase[b] >= 0 && this.blossomParent[b] === -1) {
if (this.label[b] === 1) {
this.dualVar[b] += delta;
} else if (this.label[b] === 2) {
this.dualVar[b] -= delta;
}
}
}
//console.log('DEBUG: deltaType', deltaType, ' delta: ', delta);
if (deltaType === 1) {
break;
} else if (deltaType === 2) {
this.allowEdge[deltaEdge] = true;
var i = this.edges[deltaEdge][0];
var j = this.edges[deltaEdge][1];
this.edges[deltaEdge][2];
if (this.label[this.inBlossom[i]] === 0) {
i = i ^ j;
j = j ^ i;
i = i ^ j;
}
//console.assert(this.label[this.inBlossom[i]] == 1);
this.queue.push(i);
} else if (deltaType === 3) {
this.allowEdge[deltaEdge] = true;
i = this.edges[deltaEdge][0];
j = this.edges[deltaEdge][1];
this.edges[deltaEdge][2];
//console.assert(this.label[this.inBlossom[i]] == 1);
this.queue.push(i);
} else if (deltaType === 4) {
this.expandBlossom(deltaBlossom, false);
}
}
if (!augmented) break;
for (b = this.nVertex; b < this.nVertex * 2; b++) {
if (this.blossomParent[b] === -1 && this.blossomBase[b] >= 0 && this.label[b] === 1 && this.dualVar[b] === 0) {
this.expandBlossom(b, true);
}
}
}
for (v = 0; v < this.nVertex; v++) {
if (this.mate[v] >= 0) {
this.mate[v] = this.endpoint[this.mate[v]];
}
}
for (v = 0; v < this.nVertex; v++) {
//console.assert(this.mate[v] == -1 || this.mate[this.mate[v]] == v);
}
return this.mate;
};
Edmonds.prototype.slack = function (k) {
var i = this.edges[k][0];
var j = this.edges[k][1];
var wt = this.edges[k][2];
return this.dualVar[i] + this.dualVar[j] - 2 * wt;
};
Edmonds.prototype.blossomLeaves = function (b) {
if (b < this.nVertex) {
return [b];
}
var leaves = [];
var childList = this.blossomChilds[b];
for (var t = 0; t < childList.length; t++) {
if (childList[t] <= this.nVertex) {
leaves.push(childList[t]);
} else {
var leafList = this.blossomLeaves(childList[t]);
for (var v = 0; v < leafList.length; v++) {
leaves.push(leafList[v]);
}
}
}
return leaves;
};
Edmonds.prototype.assignLabel = function (w, t, p) {
//console.log('DEBUG: assignLabel(' + w + ',' + t + ',' + p + '}');
var b = this.inBlossom[w];
//console.assert(this.label[w] === 0 && this.label[b] === 0);
this.label[w] = this.label[b] = t;
this.labelEnd[w] = this.labelEnd[b] = p;
this.bestEdge[w] = this.bestEdge[b] = -1;
if (t === 1) {
this.queue.push.apply(this.queue, this.blossomLeaves(b));
//console.log('DEBUG: PUSH ' + this.blossomLeaves(b).toString());
} else if (t === 2) {
var base = this.blossomBase[b];
//console.assert(this.mate[base] >= 0);
this.assignLabel(this.endpoint[this.mate[base]], 1, this.mate[base] ^ 1);
}
};
Edmonds.prototype.scanBlossom = function (v, w) {
//console.log('DEBUG: scanBlossom(' + v + ',' + w + ')');
var path = [];
var base = -1;
while (v !== -1 || w !== -1) {
var b = this.inBlossom[v];
if ((this.label[b] & 4)) {
base = this.blossomBase[b];
break;
}
//console.assert(this.label[b] === 1);
path.push(b);
this.label[b] = 5;
//console.assert(this.labelEnd[b] === this.mate[this.blossomBase[b]]);
if (this.labelEnd[b] === -1) {
v = -1;
} else {
v = this.endpoint[this.labelEnd[b]];
b = this.inBlossom[v];
//console.assert(this.label[b] === 2);
//console.assert(this.labelEnd[b] >= 0);
v = this.endpoint[this.labelEnd[b]];
}
if (w !== -1) {
v = v ^ w;
w = w ^ v;
v = v ^ w;
}
}
for (var ii = 0; ii < path.length; ii++) {
b = path[ii];
this.label[b] = 1;
}
return base;
};
Edmonds.prototype.addBlossom = function (base, k) {
var v = this.edges[k][0];
var w = this.edges[k][1];
this.edges[k][2];
var bb = this.inBlossom[base];
var bv = this.inBlossom[v];
var bw = this.inBlossom[w];
var b = this.unusedBlossoms.pop();
//console.log('DEBUG: addBlossom(' + base + ',' + k + ')' + ' (v=' + v + ' w=' + w + ')' + ' -> ' + b);
this.blossomBase[b] = base;
this.blossomParent[b] = -1;
this.blossomParent[bb] = b;
var path = this.blossomChilds[b] = [];
var endPs = this.blossomEndPs[b] = [];
while (bv !== bb) {
this.blossomParent[bv] = b;
path.push(bv);
endPs.push(this.labelEnd[bv]);
//console.assert(this.label[bv] === 2 || (this.label[bv] === 1 && this.labelEnd[bv] === this.mate[this.blossomBase[bv]]));
//console.assert(this.labelEnd[bv] >= 0);
v = this.endpoint[this.labelEnd[bv]];
bv = this.inBlossom[v];
}
path.push(bb);
path.reverse();
endPs.reverse();
endPs.push((2 * k));
while (bw !== bb) {
this.blossomParent[bw] = b;
path.push(bw);
endPs.push(this.labelEnd[bw] ^ 1);
//console.assert(this.label[bw] === 2 || (this.label[bw] === 1 && this.labelEnd[bw] === this.mate[this.blossomBase[bw]]));
//console.assert(this.labelEnd[bw] >= 0);
w = this.endpoint[this.labelEnd[bw]];
bw = this.inBlossom[w];
}
//console.assert(this.label[bb] === 1);
this.label[b] = 1;
this.labelEnd[b] = this.labelEnd[bb];
this.dualVar[b] = 0;
var leaves = this.blossomLeaves(b);
for (var ii = 0; ii < leaves.length; ii++) {
v = leaves[ii];
if (this.label[this.inBlossom[v]] === 2) {
this.queue.push(v);
}
this.inBlossom[v] = b;
}
var bestEdgeTo = filledArray(2 * this.nVertex, -1);
for (ii = 0; ii < path.length; ii++) {
bv = path[ii];
if (this.blossomBestEdges[bv].length === 0) {
var nbLists = [];
leaves = this.blossomLeaves(bv);
for (var x = 0; x < leaves.length; x++) {
v = leaves[x];
nbLists[x] = [];
for (var y = 0; y < this.neighbend[v].length; y++) {
var p = this.neighbend[v][y];
nbLists[x].push(~~(p / 2));
}
}
} else {
nbLists = [this.blossomBestEdges[bv]];
}
//console.log('DEBUG: nbLists ' + nbLists.toString());
for (x = 0; x < nbLists.length; x++) {
var nbList = nbLists[x];
for (y = 0; y < nbList.length; y++) {
k = nbList[y];
var i = this.edges[k][0];
var j = this.edges[k][1];
this.edges[k][2];
if (this.inBlossom[j] === b) {
i = i ^ j;
j = j ^ i;
i = i ^ j;
}
var bj = this.inBlossom[j];
if (bj !== b && this.label[bj] === 1 && (bestEdgeTo[bj] === -1 || this.slack(k) < this.slack(bestEdgeTo[bj]))) {
bestEdgeTo[bj] = k;
}
}
}
this.blossomBestEdges[bv] = [];
this.bestEdge[bv] = -1;
}
var be = [];
for (ii = 0; ii < bestEdgeTo.length; ii++) {
k = bestEdgeTo[ii];
if (k !== -1) {
be.push(k);
}
}
this.blossomBestEdges[b] = be;
//console.log('DEBUG: blossomBestEdges[' + b + ']= ' + this.blossomBestEdges[b].toString());
this.bestEdge[b] = -1;
for (ii = 0; ii < this.blossomBestEdges[b].length; ii++) {
k = this.blossomBestEdges[b][ii];
if (this.bestEdge[b] === -1 || this.slack(k) < this.slack(this.bestEdge[b])) {
this.bestEdge[b] = k;
}
}
//console.log('DEBUG: blossomChilds[' + b + ']= ' + this.blossomChilds[b].toString());
};
Edmonds.prototype.expandBlossom = function (b, endStage) {
//console.log('DEBUG: expandBlossom(' + b + ',' + endStage + ') ' + this.blossomChilds[b].toString());
for (var ii = 0; ii < this.blossomChilds[b].length; ii++) {
var s = this.blossomChilds[b][ii];
this.blossomParent[s] = -1;
if (s < this.nVertex) {
this.inBlossom[s] = s;
} else if (endStage && this.dualVar[s] === 0) {
this.expandBlossom(s, endStage);
} else {
var leaves = this.blossomLeaves(s);
for (var jj = 0; jj < leaves.length; jj++) {
var v = leaves[jj];
this.inBlossom[v] = s;
}
}
}
if (!endStage && this.label[b] === 2) {
//console.assert(this.labelEnd[b] >= 0);
var entryChild = this.inBlossom[this.endpoint[this.labelEnd[b] ^ 1]];
var j = this.blossomChilds[b].indexOf(entryChild);
if ((j & 1)) {
j -= this.blossomChilds[b].length;
var jStep = 1;
var endpTrick = 0;
} else {
jStep = -1;
endpTrick = 1;
}
var p = this.labelEnd[b];
while (j !== 0) {
this.label[this.endpoint[p ^ 1]] = 0;
this.label[this.endpoint[pIndex(this.blossomEndPs[b], j - endpTrick) ^ endpTrick ^ 1]] = 0;
this.assignLabel(this.endpoint[p ^ 1], 2, p);
this.allowEdge[~~(pIndex(this.blossomEndPs[b], j - endpTrick) / 2)] = true;
j += jStep;
p = pIndex(this.blossomEndPs[b], j - endpTrick) ^ endpTrick;
this.allowEdge[~~(p / 2)] = true;
j += jStep;
}
var bv = pIndex(this.blossomChilds[b], j);
this.label[this.endpoint[p ^ 1]] = this.label[bv] = 2;
this.labelEnd[this.endpoint[p ^ 1]] = this.labelEnd[bv] = p;
this.bestEdge[bv] = -1;
j += jStep;
while (pIndex(this.blossomChilds[b], j) !== entryChild) {
bv = pIndex(this.blossomChilds[b], j);
if (this.label[bv] === 1) {
j += jStep;
continue;
}
leaves = this.blossomLeaves(bv);
for (ii = 0; ii < leaves.length; ii++) {
v = leaves[ii];
if (this.label[v] !== 0) break;
}
if (this.label[v] !== 0) {
//console.assert(this.label[v] === 2);
//console.assert(this.inBlossom[v] === bv);
this.label[v] = 0;
this.label[this.endpoint[this.mate[this.blossomBase[bv]]]] = 0;
this.assignLabel(v, 2, this.labelEnd[v]);
}
j += jStep;
}
}
this.label[b] = this.labelEnd[b] = -1;
this.blossomEndPs[b] = this.blossomChilds[b] = [];
this.blossomBase[b] = -1;
this.blossomBestEdges[b] = [];
this.bestEdge[b] = -1;
this.unusedBlossoms.push(b);
};
Edmonds.prototype.augmentBlossom = function (b, v) {
//console.log('DEBUG: augmentBlossom(' + b + ',' + v + ')');
var i, j;
var t = v;
while (this.blossomParent[t] !== b) {
t = this.blossomParent[t];
}
if (t > this.nVertex) {
this.augmentBlossom(t, v);
}
i = j = this.blossomChilds[b].indexOf(t);
if ((i & 1)) {
j -= this.blossomChilds[b].length;
var jStep = 1;
var endpTrick = 0;
} else {
jStep = -1;
endpTrick = 1;
}
while (j !== 0) {
j += jStep;
t = pIndex(this.blossomChilds[b], j);
var p = pIndex(this.blossomEndPs[b], j - endpTrick) ^ endpTrick;
if (t >= this.nVertex) {
this.augmentBlossom(t, this.endpoint[p]);
}
j += jStep;
t = pIndex(this.blossomChilds[b], j);
if (t >= this.nVertex) {
this.augmentBlossom(t, this.endpoint[p ^ 1]);
}
this.mate[this.endpoint[p]] = p ^ 1;
this.mate[this.endpoint[p ^ 1]] = p;
}
//console.log('DEBUG: PAIR ' + this.endpoint[p] + ' ' + this.endpoint[p^1] + '(k=' + ~~(p/2) + ')');
this.blossomChilds[b] = this.blossomChilds[b].slice(i).concat(this.blossomChilds[b].slice(0, i));
this.blossomEndPs[b] = this.blossomEndPs[b].slice(i).concat(this.blossomEndPs[b].slice(0, i));
this.blossomBase[b] = this.blossomBase[this.blossomChilds[b][0]];
//console.assert(this.blossomBase[b] === v);
};
Edmonds.prototype.augmentMatching = function (k) {
var v = this.edges[k][0];
var w = this.edges[k][1];
//console.log('DEBUG: augmentMatching(' + k + ')' + ' (v=' + v + ' ' + 'w=' + w);
//console.log('DEBUG: PAIR ' + v + ' ' + w + '(k=' + k + ')');
for (var ii = 0; ii < 2; ii++) {
if (ii === 0) {
var s = v;
var p = 2 * k + 1;
} else {
s = w;
p = 2 * k;
}
while (true) {
var bs = this.inBlossom[s];
//console.assert(this.label[bs] === 1);
//console.assert(this.labelEnd[bs] === this.mate[this.blossomBase[bs]]);
if (bs >= this.nVertex) {
this.augmentBlossom(bs, s);
}
this.mate[s] = p;
if (this.labelEnd[bs] === -1) break;
var t = this.endpoint[this.labelEnd[bs]];
var bt = this.inBlossom[t];
//console.assert(this.label[bt] === 2);
//console.assert(this.labelEnd[bt] >= 0);
s = this.endpoint[this.labelEnd[bt]];
var j = this.endpoint[this.labelEnd[bt] ^ 1];
//console.assert(this.blossomBase[bt] === t);
if (bt >= this.nVertex) {
this.augmentBlossom(bt, j);
}
this.mate[j] = this.labelEnd[bt];
p = this.labelEnd[bt] ^ 1;
//console.log('DEBUG: PAIR ' + s + ' ' + t + '(k=' + ~~(p/2) + ')');
}
}
};
//INIT STUFF//
Edmonds.prototype.init = function () {
this.nVertexInit();
this.maxWeightInit();
this.endpointInit();
this.neighbendInit();
this.mate = filledArray(this.nVertex, -1);
this.label = filledArray(2 * this.nVertex, 0); //remove?
this.labelEnd = filledArray(2 * this.nVertex, -1);
this.inBlossomInit();
this.blossomParent = filledArray(2 * this.nVertex, -1);
this.blossomChilds = initArrArr(2 * this.nVertex);
this.blossomBaseInit();
this.blossomEndPs = initArrArr(2 * this.nVertex);
this.bestEdge = filledArray(2 * this.nVertex, -1); //remove?
this.blossomBestEdges = initArrArr(2 * this.nVertex); //remove?
this.unusedBlossomsInit();
this.dualVarInit();
this.allowEdge = filledArray(this.nEdge, false); //remove?
this.queue = []; //remove?
};
Edmonds.prototype.blossomBaseInit = function () {
var base = [];
for (var i = 0; i < this.nVertex; i++) {
base[i] = i;
}
var negs = filledArray(this.nVertex, -1);
this.blossomBase = base.concat(negs);
};
Edmonds.prototype.dualVarInit = function () {
var mw = filledArray(this.nVertex, this.maxWeight);
var zeros = filledArray(this.nVertex, 0);
this.dualVar = mw.concat(zeros);
};
Edmonds.prototype.unusedBlossomsInit = function () {
var i, unusedBlossoms = [];
for (i = this.nVertex; i < 2 * this.nVertex; i++) {
unusedBlossoms.push(i);
}
this.unusedBlossoms = unusedBlossoms;
};
Edmonds.prototype.inBlossomInit = function () {
var i, inBlossom = [];
for (i = 0; i < this.nVertex; i++) {
inBlossom[i] = i;
}
this.inBlossom = inBlossom;
};
Edmonds.prototype.neighbendInit = function () {
var k, i, j;
var neighbend = initArrArr(this.nVertex);
for (k = 0; k < this.nEdge; k++) {
i = this.edges[k][0];
j = this.edges[k][1];
neighbend[i].push(2 * k + 1);
neighbend[j].push(2 * k);
}
this.neighbend = neighbend;
};
Edmonds.prototype.endpointInit = function () {
var p;
var endpoint = [];
for (p = 0; p < 2 * this.nEdge; p++) {
endpoint[p] = this.edges[~~(p / 2)][p % 2];
}
this.endpoint = endpoint;
};
Edmonds.prototype.nVertexInit = function () {
var nVertex = 0;
for (var k = 0; k < this.nEdge; k++) {
var i = this.edges[k][0];
var j = this.edges[k][1];
if (i >= nVertex) nVertex = i + 1;
if (j >= nVertex) nVertex = j + 1;
}
this.nVertex = nVertex;
};
Edmonds.prototype.maxWeightInit = function () {
var maxWeight = 0;
for (var k = 0; k < this.nEdge; k++) {
var weight = this.edges[k][2];
if (weight > maxWeight) {
maxWeight = weight;
}
}
this.maxWeight = maxWeight;
};
//HELPERS//
function filledArray(len, fill) {
var i, newArray = [];
for (i = 0; i < len; i++) {
newArray[i] = fill;
}
return newArray;
}
function initArrArr(len) {
var arr = [];
for (var i = 0; i < len; i++) {
arr[i] = [];
}
return arr;
}
function getMin(arr, start, end) {
var min = Infinity;
for (var i = start; i <= end; i++) {
if (arr[i] < min) {
min = arr[i];
}
}
return min;
}
function pIndex(arr, idx) {
//if idx is negative, go from the back
return idx < 0 ? arr[arr.length + idx] : arr[idx];
}
return blossom$1;
}
var blossomExports = requireBlossom();
var blossom = /*@__PURE__*/getDefaultExportFromCjs(blossomExports);
function Swiss(players, round, rated = false, seating = false) {
const matches = [];
let playerArray = [];
if (Array.isArray(players)) {
playerArray = players;
}
else {
playerArray = [...new Array(players)].map((_, i) => i + 1);
}
if (rated) {
playerArray.filter(p => !p.hasOwnProperty('rating') || p.rating === null).forEach(p => p.rating = 0);
}
if (seating) {
playerArray.filter(p => !p.hasOwnProperty('seating')).forEach(p => p.seating = []);
}
playerArray = shuffle(playerArray);
playerArray.forEach((p, i) => p.index = i);
const scoreGroups = [...new Set(playerArray.map(p => p.score))].sort((a, b) => a - b);
const scoreSums = [...new Set(scoreGroups.map((s, i, a) => {
let sums = [];
for (let j = i; j < a.length; j++) {
sums.push(s + a[j]);
}
return sums;
}).flat())].sort((a, b) => a - b);
let pairs = [];
for (let i = 0; i < playerArray.length; i++) {
const curr = playerArray[i];
const next = playerArray.slice(i + 1);
const sorted = rated ? [...next].sort((a, b) => Math.abs(curr.rating - a.rating) - Math.abs(curr.rating - b.rating)) : [];
for (let j = 0; j < next.length; j++) {
const opp = next[j];
if (curr.hasOwnProperty('avoid') && curr.avoid.includes(opp.id)) {
continue;
}
let wt = 75 - 75 / (scoreGroups.findIndex(s => s === Math.min(curr.score, opp.score)) + 2);
wt += 5 - 5 / (scoreSums.findIndex(s => s === curr.score + opp.score) + 1);
let scoreGroupDiff = Math.abs(scoreGroups.findIndex(s => s === curr.score) - scoreGroups.findIndex(s => s === opp.score));
if (scoreGroupDiff === 1 && curr.hasOwnProperty('pairedUpDown') && curr.pairedUpDown === false && opp.hasOwnProperty('pairedUpDown') && opp.pairedUpDown === false) {
scoreGroupDiff -= 0.65;
}
else if (scoreGroupDiff > 0 && ((curr.hasOwnProperty('pairedUpDown') && curr.pairedUpDown === true) || (opp.hasOwnProperty('pairedUpDown') && opp.pairedUpDown === true))) {
scoreGroupDiff += 0.2;
}
wt += 23 / (2 * (scoreGroupDiff + 2));
if (rated) {
wt += 4 / (sorted.findIndex(p => p.id === opp.id) + 2);
}
if (seating) {
let seatingDiff = Math.abs(curr.seating.reduce((sum, seat) => sum + seat, 0) - opp.seating.reduce((sum, seat) => sum + seat, 0));
if (curr.seating.slice(-1)[0] !== opp.seating.slice(-1)[0]) {
seatingDiff += 0.5;
}
wt += Math.pow(2, seatingDiff - 1);
}
if ((curr.hasOwnProperty('receivedBye') && curr.receivedBye) || (opp.hasOwnProperty('receivedBye') && opp.receivedBye)) {
wt += 40;
}
pairs.push([curr.index, opp.index, wt]);
}
}
const blossomPairs = blossom(pairs, true);
let playerCopy = [...playerArray];
let byeArray = [];
let match = 1;
do {
const indexA = playerCopy[0].index;
const indexB = blossomPairs[indexA];
if (indexB === -1) {
byeArray.push(playerCopy.splice(0, 1)[0]);
continue;
}
playerCopy.splice(0, 1);
playerCopy.splice(playerCopy.findIndex(p => p.index === indexB), 1);
let playerA = playerArray.find(p => p.index === indexA);
let playerB = playerArray.find(p => p.index === indexB);
if (seating) {
const aScore = playerA.seating.reduce((sum, seat) => sum + seat, 0);
const bScore = playerB.seating.reduce((sum, seat) => sum + seat, 0);
if (JSON.stringify(playerB.seating.slice(-2)) === '[-1,-1]' ||
JSON.stringify(playerA.seating.slice(-2)) === '[1,1]' ||
(playerB.seating.slice(-1)[0] === -1 && playerA.seating.slice(-1)[0] === 1) ||
bScore < aScore) {
[playerA, playerB] = [playerB, playerA];
}
}
matches.push({
round: round,
match: match++,
player1: playerA.id,
player2: playerB.id
});
} while (playerCopy.length > blossomPairs.reduce((sum, idx) => idx === -1 ? sum + 1 : sum, 0));
byeArray = [...byeArray, ...playerCopy];
for (let i = 0; i < byeArray.length; i++) {
matches.push({
round: round,
match: match++,
player1: byeArray[i].id,
player2: null
});
}
return matches;
}
exports.DoubleElimination = Double