+ // Write the ranking table, from scratch.
+ for (let i = 0; i < teams.length; ++i) {
+ let row = config['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] + config['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);
+ });
+}
+
+function montecarlo(responses) {
+ let pseudo_group_names = ['X', 'Y', 'Z'];
+ let real_group_names = ['A', 'B', 'C'];
+ let teams = [], games = [], teams_to_idx = [];
+
+ 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;
+ }
+ }
+ }
+ }