+ get_teams(function(teams) {
+ get_groups(function(groups) {
+ get_all_group_games(teams, groups, function(games) {
+ let teams_a = filter_teams_by_group(teams, groups, 'Group A');
+ let teams_b = filter_teams_by_group(teams, groups, 'Group B');
+ let teams_c = filter_teams_by_group(teams, groups, 'Group C');
+ publish_group_rank(teams_a, games, 'Group A');
+ publish_group_rank(teams_b, games, 'Group B');
+ publish_group_rank(teams_c, games, 'Group C');
+
+ let replacements = montecarlo(teams, groups, games, ['Group A', 'Group B', 'Group C']);
+ fill_playoff(teams, groups, replacements);
+ });
+ });
+ });
+}
+
+function get_all_playoff_games(teams, groups, group_games, cb) {
+ let replacements = montecarlo(teams, groups, group_games, ['Group A', 'Group B', 'Group C']);
+ fill_playoff(teams, groups, replacements); // To get the replacements.
+ let games = ultimateconfig['playoff_games'];
+ get_results('Results', function(response) {
+ let playoff_games = [];
+ for (const game of games) {
+ let team1 = do_replacements(game[0], replacements);
+ let team2 = do_replacements(game[1], replacements);
+ let row = ultimateconfig['playoff_games_start_row'] + game[3];
+ let cols = ultimateconfig['playoff_games_cols'][game[2]];
+ let score1 = response['values'][row - 1][cols[1]];
+ let score2 = response['values'][row - 1][cols[2]];
+ let streamday = game[7];
+ if (streamday === undefined && game[2] === 0) { // Stream field is by default on stream.
+ streamday = 7;
+ }
+ playoff_games.push({
+ "name1": team1,
+ "name2": team2,
+ "score1": parseInt(score1),
+ "score2": parseInt(score2),
+ "streamday": streamday,
+ "streamtime": response['values'][row - 1][1].replace('.', ':'),
+ "group_name": game[6]
+ });
+ }
+ cb(playoff_games);
+ });
+}
+
+function get_ranked(teams, games, group_name) {
+ apply_games_to_teams(games, teams, group_name);
+ let tiebreakers = [];
+ teams = rank(games, teams, 1, tiebreakers);
+ return teams;
+}
+
+// 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;
+ for (const team of teams) {
+ if (team.rank >= lowest_rank && team.rank <= rank) {
+ ++count;
+ }
+ }
+
+ if (count >= teams.length / 2) {
+ // We have no info yet, ignore this group.
+ return;
+ }
+
+ for (const team of teams) {
+ if (team.rank >= lowest_rank && team.rank <= rank) {
+ candidates.push(team);
+ }
+ }
+}
+
+function addsign(x)
+{
+ if (x < 0) {
+ return "−" + (-x);
+ } else if (x == 0) {
+ return "=0";
+ } else {
+ return "+" + x;
+ }
+}
+
+function publish_best_thirds() {
+ if (!ultimateconfig['best_thirds']) return;
+ get_teams(function(teams) {
+ get_groups(function(groups) {
+ get_all_group_games(teams, groups, function(games) {
+ let teams_a = filter_teams_by_group(teams, groups, 'Group A');
+ let teams_b = filter_teams_by_group(teams, groups, 'Group B');
+ let teams_c = filter_teams_by_group(teams, groups, 'Group C');
+ let A = get_ranked(teams_a, games, 'Group A');
+ let B = get_ranked(teams_b, games, 'Group B');
+ let C = get_ranked(teams_c, games, 'Group C');
+
+ let candidates = [];
+ pick_out_rank(A, 3, candidates);
+ 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.
+ apply_games_to_teams(games, A, 'Group A', ignoreds, ignored_games);
+ apply_games_to_teams(games, B, 'Group B', ignoreds, ignored_games);
+ apply_games_to_teams(games, C, 'Group 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 ranked = rank_thirds([], candidates, 1, tiebreakers);
+ 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");
+ }
+ }
+ let updates = [];
+ updates.push({ "range": ultimateconfig['explain_third_cell'], "values": [ [ text ] ] });
+ let json = {
+ "valueInputOption": "USER_ENTERED",
+ "data": updates
+ };
+ possibly_update_oauth_key(function() {
+ post_json('https://sheets.googleapis.com/v4/spreadsheets/' + ultimateconfig['score_sheet_id'] + '/values:batchUpdate?key=' + ultimateconfig['api_key'], json, function(response) {}, current_oauth_access_token);
+ });
+ });
+ });
+ });