Minecraft-STC-Modpack/showdown/server/tournaments/generator-elimination.js
2023-08-14 21:45:09 -04:00

382 lines
13 KiB
JavaScript

"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var generator_elimination_exports = {};
__export(generator_elimination_exports, {
Elimination: () => Elimination
});
module.exports = __toCommonJS(generator_elimination_exports);
var import_lib = require("../../lib");
class ElimNode {
constructor(options) {
this.children = null;
this.user = options.user || null;
this.state = options.state || "";
this.result = options.result || "";
this.score = options.score || null;
this.losersBracketNode = options.losersBracketNode || null;
this.losersBracketIndex = options.losersBracketIndex || 0;
this.parent = options.parent || null;
this.fromNode = options.fromNode || null;
}
setChildren(children) {
if (this.children) {
for (const child of this.children)
child.parent = null;
}
if (children) {
for (const child of children)
child.parent = this;
}
this.children = children;
}
traverse(multiCallback) {
const queue = [this];
let node;
while (node = queue.shift()) {
multiCallback(node);
if (node.children)
queue.push(...node.children);
}
}
find(multiCallback) {
const queue = [this];
let node;
while (node = queue.shift()) {
const value = multiCallback(node);
if (value) {
return value;
}
if (node.children)
queue.push(...node.children);
}
return void 0;
}
// eslint-disable-next-line no-restricted-globals
[Symbol.iterator]() {
const results = [this];
for (const result of results) {
if (result.children)
results.push(...result.children);
}
return results[Symbol.iterator]();
}
toJSON() {
const node = {};
if (!this.children) {
node.team = this.user || (this.losersBracketIndex <= 1 ? `(loser's bracket)` : `(loser's bracket ${this.losersBracketIndex})`);
} else {
node.children = this.children.map((child) => child.toJSON());
node.state = this.state || "unavailable";
if (node.state === "finished") {
node.team = this.user;
node.result = this.result;
node.score = this.score;
}
}
return node;
}
}
const nameMap = [
"",
"Single",
"Double",
"Triple",
"Quadruple",
"Quintuple",
"Sextuple"
// Feel free to add more
];
class Elimination {
constructor(maxSubtrees) {
this.name = "Elimination";
this.isDrawingSupported = false;
this.isBracketFrozen = false;
this.players = [];
maxSubtrees = maxSubtrees || 1;
if (typeof maxSubtrees === "string" && maxSubtrees.toLowerCase() === "infinity") {
maxSubtrees = Infinity;
} else if (typeof maxSubtrees !== "number") {
maxSubtrees = parseInt(maxSubtrees);
}
if (!maxSubtrees || maxSubtrees < 1)
maxSubtrees = 1;
this.maxSubtrees = maxSubtrees;
this.treeRoot = null;
if (nameMap[maxSubtrees]) {
this.name = `${nameMap[maxSubtrees]} ${this.name}`;
} else if (maxSubtrees === Infinity) {
this.name = `N-${this.name}`;
} else {
this.name = `${maxSubtrees}-tuple ${this.name}`;
}
}
getPendingBracketData(players) {
return {
type: "tree",
rootNode: null
};
}
getBracketData() {
return {
type: "tree",
rootNode: this.treeRoot.toJSON()
};
}
freezeBracket(players) {
if (!players.length)
throw new Error(`No players in tournament`);
this.players = players;
this.isBracketFrozen = true;
let tree = null;
for (const user of import_lib.Utils.shuffle(players)) {
if (!tree) {
tree = {
root: new ElimNode({ user }),
currentLayerLeafNodes: [],
nextLayerLeafNodes: []
};
tree.currentLayerLeafNodes.push(tree.root);
continue;
}
const targetNode = tree.currentLayerLeafNodes.shift();
if (!targetNode)
throw new Error(`TypeScript bug: no ! in checkJs`);
const newLeftChild = new ElimNode({ user: targetNode.user });
tree.nextLayerLeafNodes.push(newLeftChild);
const newRightChild = new ElimNode({ user });
tree.nextLayerLeafNodes.push(newRightChild);
targetNode.setChildren([newLeftChild, newRightChild]);
targetNode.user = null;
if (tree.currentLayerLeafNodes.length === 0) {
tree.currentLayerLeafNodes = tree.nextLayerLeafNodes;
tree.nextLayerLeafNodes = [];
}
}
this.maxSubtrees = Math.min(this.maxSubtrees, players.length - 1);
for (let losersBracketIndex = 1; losersBracketIndex < this.maxSubtrees; losersBracketIndex++) {
const matchesByDepth = {};
const queue = [{ node: tree.root, depth: 0 }];
let frame;
while (frame = queue.shift()) {
if (!frame.node.children || frame.node.losersBracketNode)
continue;
if (!matchesByDepth[frame.depth])
matchesByDepth[frame.depth] = [];
matchesByDepth[frame.depth].push(frame.node);
queue.push({ node: frame.node.children[0], depth: frame.depth + 1 });
queue.push({ node: frame.node.children[1], depth: frame.depth + 1 });
}
const newTree = {
root: new ElimNode({ losersBracketIndex, fromNode: matchesByDepth[0][0] }),
currentLayerLeafNodes: [],
nextLayerLeafNodes: []
};
newTree.currentLayerLeafNodes.push(newTree.root);
for (const depth in matchesByDepth) {
if (depth === "0")
continue;
const matchesThisDepth = matchesByDepth[depth];
let n = 0;
for (; n < matchesThisDepth.length - 1; n += 2) {
const oldLeaf = newTree.currentLayerLeafNodes.shift();
if (!oldLeaf)
throw new Error(`TypeScript bug: no ! in checkJs`);
const oldLeafFromNode = oldLeaf.fromNode;
oldLeaf.fromNode = null;
const newBranch = new ElimNode({ losersBracketIndex });
oldLeaf.setChildren([new ElimNode({ losersBracketIndex, fromNode: oldLeafFromNode }), newBranch]);
const newLeftChild = new ElimNode({ losersBracketIndex, fromNode: matchesThisDepth[n] });
newTree.nextLayerLeafNodes.push(newLeftChild);
const newRightChild = new ElimNode({ losersBracketIndex, fromNode: matchesThisDepth[n + 1] });
newTree.nextLayerLeafNodes.push(newRightChild);
newBranch.setChildren([newLeftChild, newRightChild]);
}
if (n < matchesThisDepth.length) {
const oldLeaf = newTree.currentLayerLeafNodes.shift();
const oldLeafFromNode = oldLeaf.fromNode;
oldLeaf.fromNode = null;
const newLeaf = new ElimNode({ fromNode: matchesThisDepth[n] });
newTree.nextLayerLeafNodes.push(newLeaf);
oldLeaf.setChildren([new ElimNode({ fromNode: oldLeafFromNode }), newLeaf]);
}
newTree.currentLayerLeafNodes = newTree.nextLayerLeafNodes;
newTree.nextLayerLeafNodes = [];
}
newTree.root.traverse((node) => {
if (node.fromNode) {
node.fromNode.losersBracketNode = node;
node.fromNode = null;
}
});
const newRoot = new ElimNode({});
newRoot.setChildren([tree.root, newTree.root]);
tree.root = newRoot;
}
tree.root.traverse((node) => {
if (node.children && node.children[0].user && node.children[1].user) {
node.state = "available";
}
});
this.treeRoot = tree.root;
}
disqualifyUser(user) {
if (!this.isBracketFrozen)
return "BracketNotFrozen";
const found = this.treeRoot.find((node) => {
if (node.state === "available") {
if (!node.children)
throw new Error(`no children`);
if (node.children[0].user === user) {
return {
match: [user, node.children[1].user],
result: "loss",
score: [0, 1]
};
} else if (node.children[1].user === user) {
return {
match: [node.children[0].user, user],
result: "win",
score: [1, 0]
};
}
}
return void 0;
});
if (found) {
const error = this.setMatchResult(found.match, found.result, found.score);
if (error) {
throw new Error(`Unexpected ${error} from setMatchResult([${found.match.join(", ")}], ${found.result})`);
}
}
user.unlinkUser();
}
getAvailableMatches() {
if (!this.isBracketFrozen)
return "BracketNotFrozen";
const matches = [];
this.treeRoot.traverse((node) => {
if (node.state !== "available")
return;
const p1 = node.children[0].user;
const p2 = node.children[1].user;
if (!p1.isBusy && !p2.isBusy) {
matches.push([p1, p2]);
}
});
return matches;
}
setMatchResult([p1, p2], result, score) {
if (!this.isBracketFrozen)
return "BracketNotFrozen";
if (!["win", "loss"].includes(result))
return "InvalidMatchResult";
if (!this.players.includes(p1) || !this.players.includes(p2))
return "UserNotAdded";
const targetNode = this.treeRoot.find((node) => {
if (node.state === "available" && (node.children[0].user === p1 && node.children[1].user === p2)) {
return node;
}
return void 0;
});
if (!targetNode)
return "InvalidMatch";
if (!targetNode.children)
throw new Error(`invalid available state`);
targetNode.state = "finished";
targetNode.result = result;
targetNode.score = score.slice();
const winner = targetNode.children[result === "win" ? 0 : 1].user;
const loser = targetNode.children[result === "loss" ? 0 : 1].user;
targetNode.user = winner;
if (!winner || !loser)
throw new Error(`invalid available state`);
if (loser.losses === this.maxSubtrees) {
loser.isEliminated = true;
loser.sendRoom(`|tournament|update|{"isJoined":false}`);
loser.unlinkUser();
}
if (targetNode.parent) {
const parent = targetNode.parent;
if (loser.losses <= winner.losses && !loser.isDisqualified) {
const newNode = new ElimNode({ state: "available", losersBracketNode: targetNode.losersBracketNode });
newNode.setChildren([targetNode, new ElimNode({ user: loser })]);
parent.setChildren([newNode, parent.children[1]]);
return;
}
const userA = parent.children[0].user;
const userB = parent.children[1].user;
if (userA && userB) {
parent.state = "available";
let error = "";
if (userA.isDisqualified) {
error = this.setMatchResult([userA, userB], "loss", [0, 1]);
} else if (userB.isDisqualified) {
error = this.setMatchResult([userA, userB], "win", [1, 0]);
}
if (error) {
throw new Error(`Unexpected ${error} from setMatchResult([${userA},${userB}], ...)`);
}
}
} else if (loser.losses < this.maxSubtrees && !loser.isDisqualified) {
const newRoot = new ElimNode({ state: "available" });
newRoot.setChildren([targetNode, new ElimNode({ user: loser })]);
this.treeRoot = newRoot;
}
if (targetNode.losersBracketNode) {
targetNode.losersBracketNode.user = loser;
const userA = targetNode.losersBracketNode.parent.children[0].user;
const userB = targetNode.losersBracketNode.parent.children[1].user;
if (userA && userB) {
targetNode.losersBracketNode.parent.state = "available";
let error = "";
if (userA.isDisqualified) {
error = this.setMatchResult([userA, userB], "loss", [0, 1]);
} else if (userB.isDisqualified) {
error = this.setMatchResult([userA, userB], "win", [1, 0]);
}
if (error) {
throw new Error(`Unexpected ${error} from setMatchResult([${userA}, ${userB}], ...)`);
}
}
}
}
isTournamentEnded() {
return this.treeRoot.state === "finished";
}
getResults() {
if (!this.isTournamentEnded())
return "TournamentNotEnded";
const results = [];
let currentNode = this.treeRoot;
for (let n = 0; n < this.maxSubtrees; ++n) {
results.push([currentNode.user]);
if (!currentNode.children)
break;
currentNode = currentNode.children[currentNode.result === "loss" ? 0 : 1];
if (!currentNode)
break;
}
if (this.players.length - 1 === this.maxSubtrees && currentNode) {
results.push([currentNode.user]);
}
return results;
}
}
//# sourceMappingURL=generator-elimination.js.map