'use strict';
+function jsonclone(x)
+{
+ return JSON.parse(JSON.stringify(x));
+}
+
// Log with deep clone, so that the browser will show the object at time of log,
// instead of what it looks like at time of view.
function dlog()
{
let args = [];
for (const arg of arguments) {
- args.push(JSON.parse(JSON.stringify(arg)));
+ args.push(jsonclone(arg));
}
console.log(args);
}
return games;
};
-function apply_games_to_teams(games, teams)
+function apply_games_to_teams(games, teams, ignored_teams, ret_ignored_games)
{
let teams_to_idx = make_teams_to_idx(teams);
+ let ignored_teams_idx;
+ if (ignored_teams === undefined) {
+ ignored_teams_idx = [];
+ } else {
+ ignored_teams_idx = make_teams_to_idx(ignored_teams);
+ }
+ for (let i = 0; i < teams.length; ++i) {
+ teams[i].nplayed = 0;
+ teams[i].goals = 0;
+ teams[i].gd = 0;
+ teams[i].pts = 0;
+ }
for (let i = 0; i < games.length; ++i) {
let idx1 = teams_to_idx[games[i].name1];
let idx2 = teams_to_idx[games[i].name2];
games[i].score1 == games[i].score2) {
continue;
}
+
+ let ignored_idx1 = ignored_teams_idx[games[i].name1];
+ let ignored_idx2 = ignored_teams_idx[games[i].name2];
+ if (ignored_idx1 !== undefined || ignored_idx2 !== undefined) {
+ if (ret_ignored_games !== undefined) {
+ // Figure out whether the fifth we're ignoring was only picked out arbitrarily
+ // (ie., there's a tie for 5th); if so, mark it as such.
+ let arbitrary = false;
+ if (ignored_idx1 !== undefined && ignored_teams[ignored_idx1].rank < 5) {
+ arbitrary = true;
+ } else if (ignored_idx2 !== undefined && ignored_teams[ignored_idx2].rank < 5) {
+ arbitrary = true;
+ }
+ ret_ignored_games.push([teams[idx1].shortname, teams[idx2].shortname, arbitrary]);
+ }
+ continue;
+ }
++teams[idx1].nplayed;
++teams[idx2].nplayed;
teams[idx1].goals += games[i].score1;
}
for (let simulation_idx = 0; simulation_idx < 100; ++simulation_idx) { // 100 seems to be enough.
- let thirds = [];
+ let thirds = [], ignoreds = [];
+ let groups = [];
for (let group_idx = 0; group_idx < responses.length; ++group_idx) {
// Fill in random results. We deliberately use a uniform [-13,+13]
// model here, since we are interested in the extremal results.
// Of course, not all real games go to 13, but the risk of that
// influencing the tiebreakers is very slim.
- let games_copy = [];
+ let games_copy = [], games_with_synth = [];
for (const game of games[group_idx]) {
games_copy.push(Object.assign({}, game));
}
teams_copy[idx2].gd += score2;
teams_copy[idx1].gd -= score2;
teams_copy[idx2].gd -= score1;
+
+ games_with_synth.push({
+ "name1": games_copy[i].name1,
+ "name2": games_copy[i].name2,
+ "score1": score1,
+ "score2": score2
+ });
} else {
+ games_with_synth.push(games_copy[i]);
continue;
}
}
}
}
}
+ if (ultimateconfig['kick_fifth_from_third'] && teams_copy.length >= 5) {
+ if (teams_copy[4].rank != 5) {
+ // A real tie for fifth; the rules are unclear, so just give up.
+ busted_thirds = true;
+ } else {
+ ignoreds.push(teams_copy[4]);
+ }
+ }
}
+
+ groups.push({
+ "games": games_with_synth,
+ "teams": teams_copy
+ });
}
// Also rank thirds.
if (!busted_thirds) {
let tiebreakers = [];
+ if (ultimateconfig['kick_fifth_from_third']) {
+ // Recompute scores (but not ranks!) without the ignored games. (thirds point to these objects.)
+ for (let group_idx = 0; group_idx < responses.length; ++group_idx) {
+ apply_games_to_teams(groups[group_idx].games, groups[group_idx].teams, ignoreds);
+ }
+ }
let ranked = rank_thirds([], thirds, 1, tiebreakers);
if (simulation_idx == 0) {
third_groups = ranked;
// Pick out everything that is at rank N _or_ avoids rank N by lack of tiebreakers only.
function pick_out_rank(teams, rank, candidates) {
+ if (teams.length < rank) {
+ return;
+ }
+
let lowest_rank = teams[rank - 1].rank;
let count = 0;
}
}
+function addsign(x)
+{
+ if (x < 0) {
+ return "−" + (-x);
+ } else if (x == 0) {
+ return "=0";
+ } else {
+ return "+" + x;
+ }
+}
+
function publish_best_thirds() {
get_group('Group A', function(response_a) {
get_group('Group B', function(response_b) {
pick_out_rank(B, 3, candidates);
pick_out_rank(C, 3, candidates);
+ let ignoreds = [];
+ let ignored_games = [], ignored_games_expl = [];
+ if (ultimateconfig['kick_fifth_from_third']) {
+ let ignoreds_A = [], ignoreds_B = [], ignoreds_C = [];
+ pick_out_rank(A, 5, ignoreds_A);
+ pick_out_rank(B, 5, ignoreds_B);
+ pick_out_rank(C, 5, ignoreds_C);
+
+ if (ignoreds_A.length >= 2) {
+ ignoreds_A = [ ignoreds_A[ignoreds_A.length - 1] ];
+ }
+ if (ignoreds_B.length >= 2) {
+ ignoreds_B = [ ignoreds_B[ignoreds_B.length - 1] ];
+ }
+ if (ignoreds_C.length >= 2) {
+ ignoreds_C = [ ignoreds_C[ignoreds_C.length - 1] ];
+ }
+ ignoreds = ignoreds_A.concat(ignoreds_B).concat(ignoreds_C);
+
+ // Protect the “candidates” array, so that apply_games_to_teams() further down
+ // doesn't modify it (we want to compare old and new).
+ A = jsonclone(A);
+ B = jsonclone(B);
+ C = jsonclone(C);
+
+ // Recompute scores (but not ranks!) without the ignored games.
+ let games_a = parse_games_from_spreadsheet(response_a, 'Group A', false);
+ apply_games_to_teams(games_a, A, ignoreds, ignored_games);
+ let games_b = parse_games_from_spreadsheet(response_b, 'Group B', false);
+ apply_games_to_teams(games_b, B, ignoreds, ignored_games);
+ let games_c = parse_games_from_spreadsheet(response_c, 'Group C', false);
+ apply_games_to_teams(games_c, C, ignoreds, ignored_games);
+
+ // Filter out ignored games involving the candidate thirds.
+ let candidates_to_idx = make_teams_to_idx(candidates);
+ for (const game of ignored_games) {
+ if (candidates_to_idx[game[0]] !== undefined ||
+ candidates_to_idx[game[1]] !== undefined) {
+ if (game[2]) {
+ ignored_games_expl.push("Ignoring (arbitrarily) " + game[0] + "–" + game[1]);
+ } else {
+ ignored_games_expl.push("Ignoring " + game[0] + "–" + game[1]);
+ }
+ }
+ }
+
+ let new_teams = A.concat(B).concat(C);
+ let new_teams_to_idx = make_teams_to_idx(new_teams);
+
+ // Move back the scores (points, gd, goals).
+ for (let cand of candidates) {
+ let new_version = new_teams[new_teams_to_idx[cand.shortname]];
+ if (cand.pts != new_version.pts ||
+ cand.gd != new_version.gd ||
+ cand.goals != new_version.goals) {
+ cand.pts = new_version.pts;
+ cand.gd = new_version.gd;
+ cand.goals = new_version.goals;
+ ignored_games_expl.push(cand.shortname + " at " + cand.pts + " pts, " + addsign(new_version.gd) + " GD");
+ }
+ }
+ }
+
let tiebreakers = [];
let text = "";
if (candidates.length >= 2) {
let best_thirds = ranked.filter(function(team) { return team.rank <= 2; });
if (best_thirds.length == 2) {
text = "Best thirds: " + best_thirds.map(function(team) { return team.mediumname }).join(', ') + "\n";
+ if (ignored_games_expl.length > 0) {
+ text += ignored_games_expl.join("; ") + "\n";
+ }
text += tiebreakers.join("\n");
}
}