From 342bae451d406b44d8fb47ceca5213f308d5e797 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Tue, 15 Oct 2019 00:04:50 +0200 Subject: [PATCH] Add code for computing the best thirds. --- carousel.js | 56 ++++++++++++++++++++++++++++++++------- update_sheets.js | 69 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 115 insertions(+), 10 deletions(-) diff --git a/carousel.js b/carousel.js index bbab9aa..aa195c6 100644 --- a/carousel.js +++ b/carousel.js @@ -24,10 +24,10 @@ function addth(tr, className, content) { tr.appendChild(th); }; -function subrank_partitions(games, parts, start_rank, tiebreakers) { +function subrank_partitions(games, parts, start_rank, tiebreakers, func) { let result = []; for (let i = 0; i < parts.length; ++i) { - let part = rank(games, parts[i], start_rank, tiebreakers); + let part = func(games, parts[i], start_rank, tiebreakers); for (let j = 0; j < part.length; ++j) { result.push(part[j]); } @@ -152,7 +152,7 @@ function rank(games, teams, start_rank, tiebreakers) { // Rule #0: Partition the teams by score. let score_parts = partition(teams, function(a, b) { return b.pts - a.pts }); if (score_parts.length > 1) { - return subrank_partitions(games, score_parts, start_rank, tiebreakers); + return subrank_partitions(games, score_parts, start_rank, tiebreakers, rank); } // Rule #1: Head-to-head wins. @@ -174,7 +174,7 @@ function rank(games, teams, start_rank, tiebreakers) { }); if (beat_parts.length > 1) { tiebreakers.push(explain_tiebreaker(beat_parts, 'head-to-head')); - return subrank_partitions(games, beat_parts, start_rank, tiebreakers); + return subrank_partitions(games, beat_parts, start_rank, tiebreakers, rank); } // Rule #2: Number of games played (fewer is better). @@ -183,7 +183,7 @@ function rank(games, teams, start_rank, tiebreakers) { let nplayed_parts = partition(teams, function(a, b) { return a.nplayed - b.nplayed }); if (nplayed_parts.length > 1) { tiebreakers.push(explain_tiebreaker(nplayed_parts, 'fewer losses')); - return subrank_partitions(games, nplayed_parts, start_rank, tiebreakers); + return subrank_partitions(games, nplayed_parts, start_rank, tiebreakers, rank); } // Rule #3: Head-to-head goal difference (if all have played). @@ -210,7 +210,7 @@ function rank(games, teams, start_rank, tiebreakers) { let h2h_gd_parts = partition(teams, function(a, b) { return b.h2h_gd - a.h2h_gd }); if (h2h_gd_parts.length > 1) { tiebreakers.push(explain_tiebreaker(h2h_gd_parts, 'head-to-head goal difference')); - return subrank_partitions(games, h2h_gd_parts, start_rank, tiebreakers); + return subrank_partitions(games, h2h_gd_parts, start_rank, tiebreakers, rank); } } @@ -253,7 +253,7 @@ function rank(games, teams, start_rank, tiebreakers) { }); if (gd_parts.length > 1) { tiebreakers.push(explain_tiebreaker(gd_parts, 'goal difference versus common opponents')); - return subrank_partitions(games, gd_parts, start_rank, tiebreakers); + return subrank_partitions(games, gd_parts, start_rank, tiebreakers, rank); } // Rule #5: Head-to-head scored goals (if all have played). @@ -261,7 +261,7 @@ function rank(games, teams, start_rank, tiebreakers) { let h2h_goals_parts = partition(teams, function(a, b) { return b.h2h_goals - a.h2h_goals }); if (h2h_goals_parts.length > 1) { tiebreakers.push(explain_tiebreaker(h2h_goals_parts, 'head-to-head scored goals')); - return subrank_partitions(games, h2h_goals_parts, start_rank, tiebreakers); + return subrank_partitions(games, h2h_goals_parts, start_rank, tiebreakers, rank); } } @@ -293,7 +293,45 @@ function rank(games, teams, start_rank, tiebreakers) { }); if (goals_parts.length > 1) { tiebreakers.push(explain_tiebreaker(goals_parts, 'goals scored against common opponents')); - return subrank_partitions(games, goals_parts, start_rank, tiebreakers); + return subrank_partitions(games, goals_parts, start_rank, tiebreakers, rank); + } + + // OK, it's a tie. Give them all the same rank. + let result = []; + for (let i = 0; i < teams.length; ++i) { + result.push(teams[i]); + result[i].rank = start_rank; + } + return result; +}; + +// Same, but with the simplified rules for ranking thirds. games isn't used and can be empty. +function rank_thirds(games, teams, start_rank, tiebreakers) { + if (teams.length <= 1) { + // Only one team, so trivial. + teams[0].rank = start_rank; + return teams; + } + + // Rule #1: Partition the teams by score. + let score_parts = partition(teams, function(a, b) { return b.pts - a.pts }); + if (score_parts.length > 1) { + tiebreakers.push(explain_tiebreaker(score_parts, 'most games won')); + return subrank_partitions(games, score_parts, start_rank, tiebreakers, rank_thirds); + } + + // Rule #2: Goal difference against common opponents. + let gd_parts = partition(teams, function(a, b) { return b.gd - a.gd }); + if (gd_parts.length > 1) { + tiebreakers.push(explain_tiebreaker(gd_parts, 'goal difference')); + return subrank_partitions(games, gd_parts, start_rank, tiebreakers, rank_thirds); + } + + // Rule #3: Goals scored. + let goal_parts = partition(teams, function(a, b) { return b.goals - a.goals }); + if (goal_parts.length > 1) { + tiebreakers.push(explain_tiebreaker(goal_parts, 'goals scored')); + return subrank_partitions(games, goal_parts, start_rank, tiebreakers, rank_thirds); } // OK, it's a tie. Give them all the same rank. diff --git a/update_sheets.js b/update_sheets.js index 7867141..641c94d 100644 --- a/update_sheets.js +++ b/update_sheets.js @@ -112,10 +112,77 @@ function publish_group_rank(group_name) function publish_group_ranks() { publish_group_rank('Group A'); publish_group_rank('Group B'); + publish_group_rank('Group 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); + 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) { + 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 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 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 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); + }); + }); + }); + }); } update_oauth_key(); setTimeout(function() { publish_group_ranks(); - setInterval(function() { publish_group_ranks(); }, 60000); + publish_best_thirds(); + setInterval(function() { publish_group_ranks(); publish_best_thirds(); }, 60000); }, 5000); -- 2.39.2