]> git.sesse.net Git - ultimatescore/commitdiff
Support the special US4 mode where games against the 5th in the group do not count...
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 22 Feb 2020 15:53:44 +0000 (16:53 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 22 Feb 2020 15:53:44 +0000 (16:53 +0100)
carousel.js
config.js
update_sheets.js

index f8f2f8c69728e54f9a820f3c29d84be01fd5e3d6..0dda4750cfa8f57b2a5809ba4ab03bb1f2b58ad6 100644 (file)
@@ -1,12 +1,17 @@
 '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);
 }
@@ -404,9 +409,21 @@ function parse_games_from_spreadsheet(response, group_name, include_unplayed) {
        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];
@@ -416,6 +433,23 @@ function apply_games_to_teams(games, teams)
                    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;
index f394e518ca7bcdb5f8cdf9ae1386f0b35dff5cce..66098286969c0167e23d8959e43d55a3a0b545a6 100644 (file)
--- a/config.js
+++ b/config.js
@@ -2,6 +2,7 @@ var ultimateconfig = {
        'tournament_title': 'Battle for Oak Hill 2020',
        'tournament_footer': 'www.plastkast.no | #oakhill',
        'exohack': true,
+       'kick_fifth_from_third': true,
 
        // Share both sheets with ultimate-nm-2018@solskogen-cubemap.iam.gserviceaccount.com.
        'score_sheet_id': '1vEKBGGG0DYkQUcePY9EHFaT1Nj_Ops57xHzKvIBCloU',
index 00e4388ad1bc43b296666aeced8eb07ea65f847f..3a7b0f7f9611decc700e18dfd4b201017e45bb9d 100644 (file)
@@ -129,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));
                        }
@@ -173,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;
                                }
                        }
@@ -211,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;
@@ -412,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;
@@ -433,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) {
@@ -446,6 +489,69 @@ 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) {
@@ -453,6 +559,9 @@ function publish_best_thirds() {
                                        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");
                                        }
                                }