From: Steinar H. Gunderson Date: Sat, 22 Feb 2020 15:53:44 +0000 (+0100) Subject: Support the special US4 mode where games against the 5th in the group do not count... X-Git-Url: https://git.sesse.net/?p=ultimatescore;a=commitdiff_plain;h=51ba87c23eea6fc022edbea3bebda6fbfd14ab91 Support the special US4 mode where games against the 5th in the group do not count in the 3rd ranking. --- diff --git a/carousel.js b/carousel.js index f8f2f8c..0dda475 100644 --- a/carousel.js +++ b/carousel.js @@ -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; diff --git a/config.js b/config.js index f394e51..6609828 100644 --- 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', diff --git a/update_sheets.js b/update_sheets.js index 00e4388..3a7b0f7 100644 --- a/update_sheets.js +++ b/update_sheets.js @@ -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"); } }