diff --git a/app/models/tournament.rb b/app/models/tournament.rb index fa38da25..51ca38e2 100644 --- a/app/models/tournament.rb +++ b/app/models/tournament.rb @@ -110,59 +110,12 @@ def runner_counts end def id_and_faction_data - sql = <<~SQL - WITH corp_ids AS ( - SELECT - 1 AS side, - COALESCE(corp_ids.name, 'Unspecified') as id, - COALESCE(corp_ids.faction, 'unspecified') AS faction - FROM - players AS p - LEFT JOIN identities AS corp_ids - ON p.corp_identity_ref_id = corp_ids.id - WHERE p.tournament_id = ? - ), - runner_ids AS ( - SELECT - 2 AS side, - COALESCE(runner_ids.name, 'Unspecified') as id, - COALESCE(runner_ids.faction, 'unspecified') AS faction - FROM - players AS p - LEFT JOIN identities AS runner_ids - ON p.runner_identity_ref_id = runner_ids.id - WHERE p.tournament_id = ? - ) - SELECT side, id, faction, COUNT(*) AS num_ids FROM corp_ids GROUP BY 1,2,3 - UNION ALL - SELECT side, id, faction, COUNT(*) AS num_ids FROM runner_ids GROUP BY 1,2,3 - SQL - sql = ActiveRecord::Base.sanitize_sql([sql, id, id]) - - results = { - num_players: 0, - corp: { - ids: {}, - factions: {} - }, - runner: { - ids: {}, - factions: {} - } - } - ActiveRecord::Base.connection.exec_query(sql).each do |row| - side = row['side'] == 1 ? :corp : :runner - - # We only need to use 1 side to get the total number of players - results[:num_players] += row['num_ids'] if side == :corp - - # Only 1 row per id - results[side][:ids][row['id']] = { count: row['num_ids'], faction: row['faction'] } - - # Multiple rows per faction so we need to sum them up - results[side][:factions][row['faction']] ||= 0 - results[side][:factions][row['faction']] += row['num_ids'] - end + results = build_id_stats(id) + results[:cut] = if stages.last.single_elim? || stages.last.double_elim? + build_id_stats(id, is_cut: true) + else + default_id_stats + end results end @@ -276,4 +229,82 @@ def create_stage format: single_sided? ? :single_sided_swiss : :swiss ) end + + # Default data structure for the id and faction data, shared between swiss and elimination stages. + def default_id_stats + { + num_players: 0, + corp: { + ids: {}, + factions: {} + }, + runner: { + ids: {}, + factions: {} + } + } + end + + def build_id_stats(id, is_cut: false) + results = default_id_stats + + sql = build_id_stats_sql(id, is_cut:) + ActiveRecord::Base.connection.exec_query(sql).each do |row| + side = row['side'] == 1 ? :corp : :runner + + # We only need to use 1 side to get the total number of players + results[:num_players] += row['num_ids'] if side == :corp + + # Only 1 row per id + results[side][:ids][row['id']] = { count: row['num_ids'], faction: row['faction'] } + + # Multiple rows per faction so we need to sum them up + results[side][:factions][row['faction']] ||= 0 + results[side][:factions][row['faction']] += row['num_ids'] + end + results + end + + def build_id_stats_sql(id, is_cut: false) + ids_where = if is_cut + 'p.tournament_id = ? AND p.id IN (SELECT player_id FROM registrations WHERE stage_id IN (' \ + 'SELECT MAX(id) FROM stages WHERE tournament_id = ?))' + else + 'p.tournament_id = ?' + end + + sql = <<~SQL + WITH corp_ids AS ( + SELECT + 1 AS side, + COALESCE(corp_ids.name, 'Unspecified') as id, + COALESCE(corp_ids.faction, 'unspecified') AS faction + FROM + players AS p + LEFT JOIN identities AS corp_ids + ON p.corp_identity_ref_id = corp_ids.id + WHERE #{ids_where} + ), + runner_ids AS ( + SELECT + 2 AS side, + COALESCE(runner_ids.name, 'Unspecified') as id, + COALESCE(runner_ids.faction, 'unspecified') AS faction + FROM + players AS p + LEFT JOIN identities AS runner_ids + ON p.runner_identity_ref_id = runner_ids.id + WHERE #{ids_where} + ) + SELECT side, id, faction, COUNT(*) AS num_ids FROM corp_ids GROUP BY 1,2,3 + UNION ALL + SELECT side, id, faction, COUNT(*) AS num_ids FROM runner_ids GROUP BY 1,2,3 + SQL + + if is_cut + ActiveRecord::Base.sanitize_sql([sql, id, id, id, id]) + else + ActiveRecord::Base.sanitize_sql([sql, id, id]) + end + end end diff --git a/app/views/tournaments/_player_counts.html.slim b/app/views/tournaments/_player_counts.html.slim index f7bd197f..9bc55a0e 100644 --- a/app/views/tournaments/_player_counts.html.slim +++ b/app/views/tournaments/_player_counts.html.slim @@ -1,4 +1,6 @@ - if @tournament.rounds.any? || policy(@tournament).edit? + p + h3 Swiss Rounds .row .col-md-6 table.table @@ -21,27 +23,59 @@ .row.mt-3.dontprint .col-md-6 - table.table + table.table id="swiss_corp_table" thead tr th Corp th Players tbody - - @tournament.corp_counts.each do |id, count, total| - tr - td= render id - td= "#{count} (#{"%0.1f" % (count / total.to_f * 100)}%)" .col-md-6 - table.table + table.table id="swiss_runner_table" thead tr th Runner th Players tbody - - @tournament.runner_counts.each do |id, count, total| + + - if @tournament.stages.size > 1 + h3 Elimination Rounds + .row + .col-md-6 + table.table + thead + tr + th Corp Factions + tbody + tr + td + div id="cut_corp_faction_chart" + .col-md-6 + table.table + thead + tr + th Runner Factions + tbody + tr + td + div id="cut_runner_faction_chart" + .row + .col-md-6 + table.table id="cut_corp_table" + thead + tr + th Corp + th Players + tbody + + .col-md-6 + table.table id="cut_runner_table" + thead tr - td= render id - td= "#{count} (#{"%0.1f" % (count / total.to_f * 100)}%)" + th Runner + th Players + tbody + + javascript: function drawPieChart(element, series, labels, colors=null) { const options = { @@ -81,6 +115,51 @@ return faction.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' '); } + function buildSeriesData(data, colorsMap) { + results = { + series: [], + labels: [], + colors: [], + }; + for (faction in data) { + results.series.push(data[faction]); + results.labels.push(displayFaction(faction)); + results.colors.push(colorsMap.get(faction)); + } + return results; + } + + function populateIdRows(data, tableId, numPlayers) { + const ids = []; + for (id in data) { + ids.push({id: id, count: data[id].count, faction: data[id].faction}); + } + // Sort by count in descending order, then by id in ascending order + ids.sort((a, b) => { + if (b.count !== a.count) { + return b.count - a.count; // Descending order by count + } else { + return a.id.localeCompare(b.id); // Ascending order by id + } + }); + + // Add rows to the tableId table + const tableBody = document.querySelector(`#${tableId} tbody`); + ids.forEach(id => { + const row = document.createElement("tr"); + + const idCell = document.createElement("td"); + idCell.innerHTML = `
${id.id}
`; + row.appendChild(idCell); + + const countCell = document.createElement("td"); + countCell.innerHTML = `${id.count} (${(id.count / numPlayers * 100).toFixed(1)}%)`; + row.appendChild(countCell); + + tableBody.appendChild(row); + }); + } + fetch('/tournaments/#{@tournament.id}/id_and_faction_data') .then(response => response.json()) .then(data => { @@ -99,24 +178,25 @@ ['weyland-consortium', 'darkgreen'], ['unspecified', 'charcoal'], ]); - let corpFactionSeries = []; - let corpFactionLabels = []; - let corpFactionColors = []; - for (faction in data.corp.factions) { - corpFactionSeries.push(data.corp.factions[faction]); - corpFactionLabels.push(displayFaction(faction)); - corpFactionColors.push(factionColors.get(faction)); - } - drawPieChart("#corp_faction_chart", corpFactionSeries, corpFactionLabels, corpFactionColors); - - let runnerFactionSeries = []; - let runnerFactionLabels = []; - let runnerFactionColors = []; - for (faction in data.runner.factions) { - runnerFactionSeries.push(data.runner.factions[faction]); - runnerFactionLabels.push(displayFaction(faction)); - runnerFactionColors.push(factionColors.get(faction)); + const corpSeriesData = buildSeriesData(data.corp.factions, factionColors); + drawPieChart("#corp_faction_chart", corpSeriesData.series, corpSeriesData.labels, corpSeriesData.colors); + + const runnerSeriesData = buildSeriesData(data.runner.factions, factionColors); + drawPieChart("#runner_faction_chart", runnerSeriesData.series, runnerSeriesData.labels, runnerSeriesData.colors); + + populateIdRows(data.corp.ids, "swiss_corp_table", data.num_players); + populateIdRows(data.runner.ids, "swiss_runner_table", data.num_players); + + if (data.cut.num_players > 0) { + const cutCorpSeriesData = buildSeriesData(data.cut.corp.factions, factionColors); + drawPieChart("#cut_corp_faction_chart", cutCorpSeriesData.series, cutCorpSeriesData.labels, cutCorpSeriesData.colors); + + const cutRunnerSeriesData = buildSeriesData(data.cut.runner.factions, factionColors); + drawPieChart("#cut_runner_faction_chart", cutRunnerSeriesData.series, cutRunnerSeriesData.labels, cutRunnerSeriesData.colors); + + populateIdRows(data.cut.corp.ids, "cut_corp_table", data.cut.num_players); + populateIdRows(data.cut.runner.ids, "cut_runner_table", data.cut.num_players); + } - drawPieChart("#runner_faction_chart", runnerFactionSeries, runnerFactionLabels, runnerFactionColors); }) - .catch(error => console.error('Error:', error)); \ No newline at end of file + .catch(error => console.error('Error:', error)); diff --git a/spec/requests/tournaments_controller_spec.rb b/spec/requests/tournaments_controller_spec.rb index 02b99ffb..3d76335d 100644 --- a/spec/requests/tournaments_controller_spec.rb +++ b/spec/requests/tournaments_controller_spec.rb @@ -120,6 +120,11 @@ 'Sable' => { 'count' => 1, 'faction' => 'criminal' }, 'Unspecified' => { 'count' => 1, 'faction' => 'unspecified' } } + }, + 'cut' => { + 'num_players' => 0, + 'corp' => { 'factions' => {}, 'ids' => {} }, + 'runner' => { 'factions' => {}, 'ids' => {} } } ) end