+ get_group('Group A', function(response_a) {
+ get_group('Group B', function(response_b) {
+ get_group('Group C', function(response_c) {
+ publish_group_rank(response_a, 'Group A');
+ publish_group_rank(response_b, 'Group B');
+ publish_group_rank(response_c, 'Group C');
+
+ let replacements = montecarlo([response_a, response_b, response_c]);
+ let team_a = parse_teams_from_spreadsheet(response_a);
+ let team_b = parse_teams_from_spreadsheet(response_b);
+ let team_c = parse_teams_from_spreadsheet(response_c);
+ fill_playoff(replacements, [team_a, team_b, team_c]);
+ });
+ });
+ });
+}
+
+function get_ranked(response, group_name) {
+ let teams = parse_teams_from_spreadsheet(response);
+ let games = parse_games_from_spreadsheet(response, group_name, false);
+ apply_games_to_teams(games, teams);
+ teams = filter_teams(teams, response);
+ 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() {
+ get_group('Group A', function(response_a) {
+ get_group('Group B', function(response_b) {
+ get_group('Group C', function(response_c) {
+ let A = get_ranked(response_a, 'Group A');
+ let B = get_ranked(response_b, 'Group B');
+ let C = get_ranked(response_c, '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.
+ 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 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);
+ });
+ });
+ });
+ });