]> git.sesse.net Git - ultimatescore/blobdiff - update_sheets.js
Ole and Eivind want config changes.
[ultimatescore] / update_sheets.js
index f0a927410e235021bd834132123442f206337dd5..3a7b0f7f9611decc700e18dfd4b201017e45bb9d 100644 (file)
@@ -68,16 +68,20 @@ function possibly_update_oauth_key(cb) {
 function publish_group_rank(response, group_name)
 {
        let updates = [];
-       let cols = ultimateconfig['score_sheet_cols'][group_name];
+       let config = ultimateconfig['group_cells'][group_name];
+       let cols = config['score_sheet_cols'];
 
        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);
 
        // Write the points total to the unsorted columns.
-       for (let i = 0; i < teams.length; ++i) {
-               let row = ultimateconfig['point_total_start_row'] + i;
-               updates.push({ "range": cols[2] + row, "values": [ [ teams[i].pts ] ] });
+       if (config['point_total_start_row'] !== null) {
+               for (let i = 0; i < teams.length; ++i) {
+                       let row = config['point_total_start_row'] + i;
+                       updates.push({ "range": cols[2] + row, "values": [ [ teams[i].pts ] ] });
+               }
        }
 
        let tiebreakers = [];
@@ -85,8 +89,8 @@ function publish_group_rank(response, group_name)
 
        // Write the ranking table, from scratch.
        for (let i = 0; i < teams.length; ++i) {
-               let row = ultimateconfig['ranking_list_start_row'] + i;
-               updates.push({ "range": cols[0] + row, "values": [ [ teams[i].rank ] ] });
+               let row = config['ranking_list_start_row'] + i;
+               updates.push({ "range": cols[0] + row, "values": [ [ teams[i].rank + config['rank_offset'] - 1] ] });
                updates.push({ "range": cols[1] + row, "values": [ [ teams[i].mediumname ] ] });
                updates.push({ "range": cols[2] + row, "values": [ [ teams[i].pts ] ] });
        }
@@ -95,7 +99,7 @@ function publish_group_rank(response, group_name)
        if (tiebreakers.length != 0) {
                tb_str = tiebreakers.join("\n");
        }
-       updates.push({ "range": cols[0] + ultimateconfig['ranking_list_explain_row'], "values": [ [ tb_str ] ]});
+       updates.push({ "range": config['ranking_list_explain_cell'], "values": [ [ tb_str ] ]});
 
        let json = {
                "valueInputOption": "USER_ENTERED",
@@ -125,13 +129,14 @@ function montecarlo(responses) {
        }
 
        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));
                        }
@@ -169,7 +174,15 @@ function montecarlo(responses) {
                                        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;
                                }
                        }
@@ -207,12 +220,31 @@ function montecarlo(responses) {
                                                }
                                        }
                                }
+                               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;
@@ -254,7 +286,7 @@ function names_for_team(team, expansions) {
 
 function expand_mediumname_if_single_team(team, expansions) {
        if (expansions.hasOwnProperty(team)) {
-               return expansions[team].mediumname;
+               return expansions[team][1];
        }
        return team;
 }
@@ -277,7 +309,7 @@ function fill_playoff(replacements, teams) {
 
        let games = ultimateconfig['playoff_games'];
        get_results('Results', function(response) {
-               let updates = [];
+               let updates = [], meta_updates = [];
                let game_num = 0;
                for (const game of games) {
                        let team1 = do_replacements(game[0], replacements);
@@ -297,6 +329,18 @@ function fill_playoff(replacements, teams) {
                        let score2 = response['values'][row - 1][cols[2]];
                        let game_name = game[4];
                        let game_name2 = game_name.replace("Semi", "semi");
+                       let game_day = game[7];
+                       if (game_day === undefined) {
+                               game_day = 7;  // Sunday.
+                       }
+
+                       let range = {
+                               "sheetId": ultimateconfig['score_sheet_index'],
+                               "startColumnIndex": cols[1],
+                               "endColumnIndex": cols[2] + 1,
+                               "startRowIndex": row - 1,
+                               "endRowIndex": row
+                       };
 
                        if (parseInt(score1) >= 0 && parseInt(score2) >= 0 && score1 != score2) {
                                if (parseInt(score1) > parseInt(score2)) {
@@ -310,16 +354,21 @@ function fill_playoff(replacements, teams) {
                                        replacements.unshift(["W " + game_name2, team2]);
                                        replacements.unshift(["L " + game_name2, team1]);
                                }
+                               meta_updates.push({ "unmergeCells": { "range": range }});
                        } else if (game[5]) {
+                               // No score yet, so write the name of the game (e.g. “L-semi 1”)
+                               // where the score would normally be, to mark what this game is called.
+                               // This is useful with the limited space on the tablet.
                                score1 = score2 = "";
                                updates.push({ "range": cell_score1, "values": [ [ game[4] ] ] });
+                               meta_updates.push({ "mergeCells": { "range": range, "mergeType": "MERGE_ALL" }});
                        }
 
                        if (game[2] == 0) {  // Stream field.
                                // Game.
                                updates.push({
                                        "range": "Playoffs!A" + (game_num + 32) + ":J" + (game_num + 32),
-                                       "values": [ [ team1, team2, score1, score2, "", "", "", 7, response['values'][row - 1][1].replace(".",":"), game[6] ] ]
+                                       "values": [ [ team1, team2, score1, score2, "", "", "", game_day, response['values'][row - 1][1].replace(".",":"), game[6] ] ]
                                });
 
                                // Team codes.
@@ -339,8 +388,14 @@ function fill_playoff(replacements, teams) {
                        "valueInputOption": "USER_ENTERED",
                        "data": updates 
                };
+               let meta_json = {
+                       "requests": meta_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);
+                       post_json('https://sheets.googleapis.com/v4/spreadsheets/' + ultimateconfig['score_sheet_id'] + '/values:batchUpdate?key=' + ultimateconfig['api_key'], json, function(response) {
+                               get_group('Playoffs 9th-13th', function(response_l) { publish_group_rank(response_l, 'Playoffs 9th-13th'); });
+                       }, current_oauth_access_token);
+                       post_json('https://sheets.googleapis.com/v4/spreadsheets/' + ultimateconfig['score_sheet_id'] + ':batchUpdate?key=' + ultimateconfig['api_key'], meta_json, function(response) {}, current_oauth_access_token);
                });
        });
 }
@@ -377,6 +432,7 @@ 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;
@@ -384,6 +440,10 @@ function get_ranked(response, group_name) {
 
 // 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;
@@ -405,6 +465,17 @@ function pick_out_rank(teams, rank, candidates) {
        }
 }
 
+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) {
@@ -418,11 +489,81 @@ function publish_best_thirds() {
                                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);
-                                       text = "Best thirds: " + ranked[0].mediumname + ", " + ranked[1].mediumname + "\n" + tiebreakers.join("\n");
+                                       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 ] ] });