tournament-pairings
Version:
Functions to generate pairings for tournaments
306 lines • 11.3 kB
JavaScript
import { shuffle } from './Shuffle.js';
export 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;
}
//# sourceMappingURL=DoubleElimination.js.map