'point_total_start_row': 10,
'ranking_list_start_row': 17,
- 'ranking_list_explain_row': 23
+ 'ranking_list_explain_row': 23,
+ 'explain_third_cell': 'Results!X36',
+
+ 'playoff_games': [
+ // Teams Field Row Match name Mark name Group name
+ [ 'X5', 'Y5', 0, 0, 'X5/Y5', false, 'Playoffs 13th–15th' ],
+ [ 'X1', 'Y2', 1, 0, 'X1/Y2', false, 'Quarterfinals' ],
+ [ 'X2', 'Y3', 0, 1, 'X2/Y3', false, 'Quarterfinals' ],
+ [ 'Y1', 'X2', 1, 1, 'Y1/X2', false, 'Quarterfinals' ],
+ [ 'Z1', 'X3', 2, 1, 'Z1/X3', false, 'Quarterfinals' ],
+ [ 'X4', 'Z3', 0, 2, 'X4/Z3', false, 'Playoffs 9th–12th' ],
+ [ 'X5', 'Z5', 1, 2, 'X5/Z5', false, 'Playoffs 13th–15th' ],
+ [ 'Y3', 'Z3', 2, 2, 'Y3/Z3', false, 'Playoffs 9th–12th' ],
+ [ 'W X1/Y2', 'W Z1/X3', 0, 3, 'Semi 1', true, 'Semifinals' ],
+ [ 'L X1/Y2', 'L Z1/X3', 1, 3, 'L-semi 1', true, 'Playoffs 5th–8th' ],
+ [ 'W X2/Y3', 'W Y2/X2', 0, 4, 'Semi 2', true, 'Semifinals' ],
+ [ 'L X2/Y3', 'L Y2/X2', 1, 4, 'L-semi 2', true, 'Playoffs 5th–8th' ],
+ [ 'Y5', 'Z5', 2, 4, 'Y5/Z5', false, 'Playoffs 13th–15th' ],
+ [ 'W X4/Z3', 'W Y3/Z3', 0, 5, '9th', true, 'Match for 9th' ],
+ [ 'L X4/Z3', 'L Y3/Z3', 1, 5, '11th', true, 'Match for 11th' ],
+ [ 'L semi 1', 'L semi 2', 0, 6, '3rd', true, 'Bronze final' ],
+ [ 'W L-semi 1', 'W L-semi 2', 1, 6, '5th', true, 'Match for 5th' ],
+ [ 'L L-semi 1', 'L L-semi 2', 2, 6, '7th', true, 'Match for 7th' ],
+ [ 'W semi 1', 'W semi 2', 0, 7, 'Final', true, 'Final' ]
+ ],
+ 'playoff_games_start_row': 26,
+ 'playoff_games_cols': [
+ [ 3, 4, 5, 6 ],
+ [ 8, 9, 10, 11 ],
+ [ 13, 14, 15, 16 ],
+ ]
};
}
}
-function publish_group_rank(group_name)
+function publish_group_rank(response, group_name)
{
- get_group(group_name, function(response, group_name) {
- let updates = [];
- let cols = ultimateconfig['score_sheet_cols'][group_name];
+ let updates = [];
+ let cols = ultimateconfig['score_sheet_cols'][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);
+
+ // 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 ] ] });
+ }
+
+ let tiebreakers = [];
+ teams = rank(games, teams, 1, tiebreakers);
+
+ // 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 ] ] });
+ updates.push({ "range": cols[1] + row, "values": [ [ teams[i].mediumname ] ] });
+ updates.push({ "range": cols[2] + row, "values": [ [ teams[i].pts ] ] });
+ }
+
+ let tb_str = "";
+ if (tiebreakers.length != 0) {
+ tb_str = tiebreakers.join("\n");
+ }
+ updates.push({ "range": cols[0] + ultimateconfig['ranking_list_explain_row'], "values": [ [ tb_str ] ]});
+
+ 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);
+ });
+}
- let teams = parse_teams_from_spreadsheet(response);
- let games = parse_games_from_spreadsheet(response, group_name, false);
- apply_games_to_teams(games, teams);
+function montecarlo(responses) {
+ let pseudo_group_names = ['X', 'Y', 'Z'];
+ let real_group_names = ['A', 'B', 'C'];
+ let teams = [], games = [], teams_to_idx = [];
- // 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 ] ] });
+ let third_groups = [];
+ let busted_thirds = false;
+
+ for (const response of responses) {
+ let teams_group = parse_teams_from_spreadsheet(response);
+ let games_group = parse_games_from_spreadsheet(response, 'irrelevant group name', true);
+ apply_games_to_teams(games_group, teams_group);
+
+ teams.push(teams_group);
+ games.push(games_group);
+ teams_to_idx.push(make_teams_to_idx(teams_group));
+ }
+
+ for (let simulation_idx = 0; simulation_idx < 100; ++simulation_idx) { // 100 seems to be enough.
+ let thirds = [];
+ 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 = [];
+ for (const game of games[group_idx]) {
+ games_copy.push(Object.assign({}, game));
+ }
+ let teams_copy = [];
+ for (const team of teams[group_idx]) {
+ teams_copy.push(Object.assign({}, team));
+ }
+
+ for (let i = 0; i < games_copy.length; ++i) {
+ let idx1 = teams_to_idx[group_idx][games_copy[i].name1];
+ let idx2 = teams_to_idx[group_idx][games_copy[i].name2];
+ if (idx1 === undefined || idx2 === undefined) continue;
+ if (games_copy[i].score1 === undefined || games_copy[i].score2 === undefined ||
+ isNaN(games_copy[i].score1) || isNaN(games_copy[i].score2) ||
+ games_copy[i].score1 == games_copy[i].score2) {
+ // These were skipped by apply_games_to_teams() above.
+ let score1 = 0, score2 = 0;
+ let r = Math.floor(Math.random() * 26);
+ if (r < 13) {
+ score1 = 13;
+ score2 = r;
+ teams_copy[idx1].pts += 2;
+ } else {
+ score1 = r - 13;
+ score2 = 13;
+ teams_copy[idx2].pts += 2;
+ }
+ games_copy[i].score1 = score1;
+ games_copy[i].score2 = score2;
+ ++teams_copy[idx1].nplayed;
+ ++teams_copy[idx2].nplayed;
+ teams_copy[idx1].goals += score1;
+ teams_copy[idx2].goals += score2;
+ teams_copy[idx1].gd += score1;
+ teams_copy[idx2].gd += score2;
+ teams_copy[idx1].gd -= score2;
+ teams_copy[idx2].gd -= score1;
+ } else {
+ continue;
+ }
+ }
+
+ // Now rank according to the simulation.
+ let tiebreakers = [];
+ teams_copy = rank(games_copy, teams_copy, 1, tiebreakers);
+
+ // See if we have conflicting information with other simulations.
+ if (simulation_idx == 0) {
+ for (let i = 0; i < teams[group_idx].length; ++i) {
+ let idx = teams_to_idx[group_idx][teams_copy[i].name];
+ teams[group_idx][idx].simulated_rank = teams_copy[i].rank;
+ }
+ } else {
+ for (let i = 0; i < teams[group_idx].length; ++i) {
+ let idx = teams_to_idx[group_idx][teams_copy[i].name];
+ if (teams[group_idx][idx].simulated_rank !== teams_copy[i].rank) {
+ teams[group_idx][idx].simulated_rank = null;
+ }
+ }
+ }
+
+ if (!busted_thirds) {
+ let any_third_found = false;
+ for (let i = 0; i < teams[group_idx].length; ++i) {
+ // Store the third.
+ if (i == 2 || teams_copy[i].rank == 3) {
+ if (any_third_found) {
+ busted_thirds = true;
+ } else {
+ teams_copy[i].group_idx = group_idx;
+ thirds.push(teams_copy[i]);
+ any_third_found = true;
+ }
+ }
+ }
+ }
+ }
+
+ // Also rank thirds.
+ if (!busted_thirds) {
+ let tiebreakers = [];
+ let ranked = rank_thirds([], thirds, 1, tiebreakers);
+ if (simulation_idx == 0) {
+ third_groups = ranked;
+ } else {
+ for (let i = 0; i < responses.length; ++i) {
+ if (third_groups[i].group_idx !== ranked[i].group_idx) {
+ third_groups[i].group_idx = null;
+ }
+ }
+ }
}
+ }
- let tiebreakers = [];
- teams = rank(games, teams, 1, tiebreakers);
+ let replacements = [];
+ for (let group_idx = 0; group_idx < responses.length; ++group_idx) {
+ if (third_groups[group_idx].group_idx !== null) {
+ replacements.push([ pseudo_group_names[group_idx], real_group_names[third_groups[group_idx].group_idx] ]);
+ }
+ }
- // 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 ] ] });
- updates.push({ "range": cols[1] + row, "values": [ [ teams[i].mediumname ] ] });
- updates.push({ "range": cols[2] + row, "values": [ [ teams[i].pts ] ] });
+ for (let group_idx = 0; group_idx < responses.length; ++group_idx) {
+ for (let i = 0; i < teams[group_idx].length; ++i) {
+ if (teams[group_idx][i].simulated_rank !== null) {
+ replacements.push([ real_group_names[group_idx] + teams[group_idx][i].simulated_rank, teams[group_idx][i].shortname ]);
+ }
}
+ }
+
+ return replacements;
+}
+
+function names_for_team(team, expansions) {
+ if (expansions.hasOwnProperty(team)) {
+ return expansions[team];
+ }
+ let longteam = team.replace("W ", "Win. ").replace("L ", "Los. ");
+ return [ longteam, longteam, team ];
+}
+
+function do_replacements(str, replacements) {
+ for (const r of replacements) {
+ str = str.replace(r[0], r[1]);
+ }
+ return str;
+}
- let tb_str = "";
- if (tiebreakers.length != 0) {
- tb_str = tiebreakers.join("\n");
+function fill_playoff(replacements, teams) {
+ let team_expansions = {};
+ for (const group of teams) {
+ for (const team of group) {
+ team_expansions[team.name] = team_expansions[team.mediumname] = team_expansions[team.shortname] =
+ [ team.name, team.mediumname, team.shortname ];
}
- updates.push({ "range": cols[0] + ultimateconfig['ranking_list_explain_row'], "values": [ [ tb_str ] ]});
+ }
+
+ let games = ultimateconfig['playoff_games'];
+ get_results('Results', function(response) {
+ let updates = [];
+ let game_num = 0;
+ for (const game of games) {
+ let team1 = do_replacements(game[0], replacements);
+ let team2 = do_replacements(game[1], replacements);
+ let row = ultimateconfig['playoff_games_start_row'] + game[3];
+ let cols = ultimateconfig['playoff_games_cols'][game[2]];
+ let cell_team1 = "Results!" + String.fromCharCode(cols[0] + 65) + row;
+ let cell_score1 = "Results!" + String.fromCharCode(cols[1] + 65) + row;
+ let cell_score2 = "Results!" + String.fromCharCode(cols[2] + 65) + row;
+ let cell_team2 = "Results!" + String.fromCharCode(cols[3] + 65) + row;
+ updates.push({ "range": cell_team1, "values": [ [ team1 ] ] });
+ updates.push({ "range": cell_team2, "values": [ [ team2 ] ] });
+ let score1 = response['values'][row - 1][cols[1]];
+ let score2 = response['values'][row - 1][cols[2]];
+ let game_name = game[4];
+ let game_name2 = game_name.replace("Semi", "semi");
+
+ if (parseInt(score1) >= 0 && parseInt(score2) >= 0 && score1 != score2) {
+ if (parseInt(score1) > parseInt(score2)) {
+ replacements.unshift(["W " + game_name, team1]);
+ replacements.unshift(["L " + game_name, team2]);
+ replacements.unshift(["W " + game_name2, team1]);
+ replacements.unshift(["L " + game_name2, team2]);
+ } else {
+ replacements.unshift(["W " + game_name, team2]);
+ replacements.unshift(["L " + game_name, team1]);
+ replacements.unshift(["W " + game_name2, team2]);
+ replacements.unshift(["L " + game_name2, team1]);
+ }
+ } else if (game[5]) {
+ score1 = score2 = "";
+ updates.push({ "range": cell_score1, "values": [ [ game[4] ] ] });
+ }
+
+ 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] ] ]
+ });
+
+ // Team codes.
+ updates.push({
+ "range": "Playoffs!A" + (2 * game_num + 3) + ":C" + (2 * game_num + 3),
+ "values": [ names_for_team(team1, team_expansions) ]
+ });
+ updates.push({
+ "range": "Playoffs!A" + (2 * game_num + 4) + ":C" + (2 * game_num + 4),
+ "values": [ names_for_team(team2, team_expansions) ]
+ });
+
+ ++game_num;
+ }
+ }
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);
});
-
});
}
+function get_results(sheet_name, cb)
+{
+ let req = new XMLHttpRequest();
+ req.onload = function(e) {
+ cb(JSON.parse(req.responseText), sheet_name);
+ };
+ req.open('GET', 'https://sheets.googleapis.com/v4/spreadsheets/' + ultimateconfig['score_sheet_id'] + '/values/\'' + sheet_name + '\'!A1:Q50?key=' + ultimateconfig['api_key']);
+ req.send();
+}
+
function publish_group_ranks() {
- publish_group_rank('Group A');
- publish_group_rank('Group B');
- publish_group_rank('Group C');
+ 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) {