From 18d39c6c8f129a6b407ff801cbe6e34915b4e01d Mon Sep 17 00:00:00 2001 From: Jacob Cody Wimer Date: Tue, 24 Feb 2026 20:58:36 -0500 Subject: [PATCH] Using eager loading in GenerateTournamentMatches and AdvanceWrestler, generating/manipulating in-memory, and doing a single bulk insert or update at the end. --- app/models/weight.rb | 2 +- .../bracket_advancement/advance_wrestler.rb | 96 ++++++++- .../double_elimination_advance.rb | 83 ++++---- .../modified_double_elimination_advance.rb | 82 ++++---- .../bracket_advancement/pool_advance.rb | 41 ++-- .../bracket_advancement/pool_order.rb | 4 - .../school_services/calculate_school_score.rb | 55 +++++ ...double_elimination_generate_loser_names.rb | 198 ++++++++---------- .../double_elimination_match_generation.rb | 65 +++--- .../generate_tournament_matches.rb | 178 +++++++++++++--- ...dified_sixteen_man_generate_loser_names.rb | 170 ++++++++------- .../modified_sixteen_man_match_generation.rb | 102 +++++---- .../pool_bracket_generation.rb | 18 +- .../tournament_services/pool_generation.rb | 23 +- .../pool_to_bracket_generate_loser_names.rb | 157 +++++++------- .../pool_to_bracket_match_generation.rb | 96 ++++++--- .../tournament_services/tournament_seeding.rb | 29 ++- .../weight_services/generate_pool_numbers.rb | 26 ++- 18 files changed, 904 insertions(+), 521 deletions(-) create mode 100644 app/services/school_services/calculate_school_score.rb diff --git a/app/models/weight.rb b/app/models/weight.rb index f454068..8d236c0 100644 --- a/app/models/weight.rb +++ b/app/models/weight.rb @@ -156,7 +156,7 @@ class Weight < ApplicationRecord end def calculate_bracket_size - num_wrestlers = wrestlers.reload.size + num_wrestlers = wrestlers.size return nil if num_wrestlers <= 0 # Handle invalid input # Find the smallest power of 2 greater than or equal to num_wrestlers diff --git a/app/services/bracket_advancement/advance_wrestler.rb b/app/services/bracket_advancement/advance_wrestler.rb index 8c0eacb..a487823 100644 --- a/app/services/bracket_advancement/advance_wrestler.rb +++ b/app/services/bracket_advancement/advance_wrestler.rb @@ -12,21 +12,97 @@ class AdvanceWrestler end def advance_raw - @last_match.reload - @wrestler.reload - if @last_match && @last_match.finished? - pool_to_bracket_advancement if @tournament.tournament_type == "Pool to bracket" - ModifiedDoubleEliminationAdvance.new(@wrestler, @last_match).bracket_advancement if @tournament.tournament_type.include? "Modified 16 Man Double Elimination" - DoubleEliminationAdvance.new(@wrestler, @last_match).bracket_advancement if @tournament.tournament_type.include? "Regular Double Elimination" + @last_match = Match.find_by(id: @last_match&.id) + @wrestler = Wrestler.includes(:school, :weight).find_by(id: @wrestler.id) + return unless @last_match && @wrestler && @last_match.finished? + + context = preload_advancement_context + matches_to_advance = [] + + if @tournament.tournament_type == "Pool to bracket" + matches_to_advance.concat(pool_to_bracket_advancement(context)) + elsif @tournament.tournament_type.include?("Modified 16 Man Double Elimination") + service = ModifiedDoubleEliminationAdvance.new(@wrestler, @last_match, matches: context[:matches]) + service.bracket_advancement + matches_to_advance.concat(service.matches_to_advance) + elsif @tournament.tournament_type.include?("Regular Double Elimination") + service = DoubleEliminationAdvance.new(@wrestler, @last_match, matches: context[:matches]) + service.bracket_advancement + matches_to_advance.concat(service.matches_to_advance) end + + persist_advancement_changes(context) + advance_pending_matches(matches_to_advance) @wrestler.school.calculate_score end - def pool_to_bracket_advancement - if @wrestler.weight.all_pool_matches_finished(@wrestler.pool) and (@wrestler.finished_bracket_matches.size < 1) - PoolOrder.new(@wrestler.weight.wrestlers_in_pool(@wrestler.pool)).getPoolOrder + def preload_advancement_context + weight = Weight.includes(:matches, :wrestlers).find(@wrestler.weight_id) + { + weight: weight, + matches: weight.matches.to_a, + wrestlers: weight.wrestlers.to_a + } + end + + def persist_advancement_changes(context) + persist_matches(context[:matches]) + persist_wrestlers(context[:wrestlers]) + end + + def persist_matches(matches) + timestamp = Time.current + updates = matches.filter_map do |m| + next unless m.changed? + + { + id: m.id, + w1: m.w1, + w2: m.w2, + winner_id: m.winner_id, + win_type: m.win_type, + score: m.score, + finished: m.finished, + loser1_name: m.loser1_name, + loser2_name: m.loser2_name, + finished_at: m.finished_at, + updated_at: timestamp + } end - PoolAdvance.new(@wrestler).advanceWrestler + Match.upsert_all(updates) if updates.any? + end + + def persist_wrestlers(wrestlers) + timestamp = Time.current + updates = wrestlers.filter_map do |w| + next unless w.changed? + + { + id: w.id, + pool_placement: w.pool_placement, + pool_placement_tiebreaker: w.pool_placement_tiebreaker, + updated_at: timestamp + } + end + Wrestler.upsert_all(updates) if updates.any? + end + + def advance_pending_matches(matches_to_advance) + matches_to_advance.uniq(&:id).each do |match| + match.advance_wrestlers + end + end + + def pool_to_bracket_advancement(context) + matches_to_advance = [] + wrestlers_in_pool = context[:wrestlers].select { |w| w.pool == @wrestler.pool } + if @wrestler.weight.all_pool_matches_finished(@wrestler.pool) && (@wrestler.finished_bracket_matches.size < 1) + PoolOrder.new(wrestlers_in_pool).getPoolOrder + end + service = PoolAdvance.new(@wrestler, @last_match, matches: context[:matches], wrestlers: context[:wrestlers]) + service.advanceWrestler + matches_to_advance.concat(service.matches_to_advance) + matches_to_advance end end diff --git a/app/services/bracket_advancement/double_elimination_advance.rb b/app/services/bracket_advancement/double_elimination_advance.rb index 4fc452b..f0ca10f 100644 --- a/app/services/bracket_advancement/double_elimination_advance.rb +++ b/app/services/bracket_advancement/double_elimination_advance.rb @@ -1,8 +1,12 @@ class DoubleEliminationAdvance - def initialize(wrestler,last_match) + attr_reader :matches_to_advance + + def initialize(wrestler,last_match, matches: nil) @wrestler = wrestler @last_match = last_match + @matches = matches || @wrestler.weight.matches.to_a + @matches_to_advance = [] @next_match_position_number = (@last_match.bracket_position_number / 2.0) end @@ -48,7 +52,7 @@ class DoubleEliminationAdvance end if next_match_bracket_position - next_match = Match.where("bracket_position = ? AND bracket_position_number = ? AND weight_id = ?",next_match_bracket_position,next_match_position_number.ceil,@wrestler.weight_id).first + next_match = @matches.find { |m| m.bracket_position == next_match_bracket_position && m.bracket_position_number == next_match_position_number.ceil && m.weight_id == @wrestler.weight_id } end if next_match @@ -59,18 +63,16 @@ class DoubleEliminationAdvance def update_new_match(match, wrestler_number) if wrestler_number == 2 or (match.loser1_name and match.loser1_name.include? "Loser of") match.w2 = @wrestler.id - match.save elsif wrestler_number == 1 match.w1 = @wrestler.id - match.save end end def update_consolation_bye bout = @wrestler.last_match.bout_number - next_match = Match.where("(loser1_name = ? OR loser2_name = ?) AND weight_id = ?","Loser of #{bout}","Loser of #{bout}",@wrestler.weight_id) - if next_match.size > 0 - next_match.first.replace_loser_name_with_bye("Loser of #{bout}") + next_match = @matches.find { |m| m.weight_id == @wrestler.weight_id && (m.loser1_name == "Loser of #{bout}" || m.loser2_name == "Loser of #{bout}") } + if next_match + replace_loser_name_with_bye(next_match, "Loser of #{bout}") end end @@ -84,27 +86,18 @@ class DoubleEliminationAdvance def losers_bracket_advancement bout = @last_match.bout_number - next_match = Match.where("(loser1_name = ? OR loser2_name = ?) AND weight_id = ?", "Loser of #{bout}", "Loser of #{bout}", @wrestler.weight_id).first + next_match = @matches.find { |m| m.weight_id == @wrestler.weight_id && (m.loser1_name == "Loser of #{bout}" || m.loser2_name == "Loser of #{bout}") } if next_match.present? - next_match.replace_loser_name_with_wrestler(@wrestler, "Loser of #{bout}") - next_match.reload + replace_loser_name_with_wrestler(next_match, @wrestler, "Loser of #{bout}") if next_match.loser1_name == "BYE" || next_match.loser2_name == "BYE" next_match.winner_id = @wrestler.id next_match.win_type = "BYE" next_match.score = "" next_match.finished = 1 - # puts "Before save: winner_id=#{next_match.winner_id}" - - # if next_match.save - # puts "Save successful: winner_id=#{next_match.reload.winner_id}" - # else - # puts "Save failed: #{next_match.errors.full_messages}" - # end - next_match.save - - next_match.advance_wrestlers + next_match.finished_at = Time.current + @matches_to_advance << next_match end end end @@ -112,51 +105,69 @@ class DoubleEliminationAdvance def advance_double_byes weight = @wrestler.weight - weight.matches.select{|m| m.loser1_name == "BYE" and m.loser2_name == "BYE" and m.finished != 1}.each do |match| + @matches.select{|m| m.weight_id == weight.id && m.loser1_name == "BYE" and m.loser2_name == "BYE" and m.finished != 1}.each do |match| match.finished = 1 + match.finished_at = Time.current match.score = "" match.win_type = "BYE" next_match_position_number = (match.bracket_position_number / 2.0).ceil - after_matches = weight.matches.select{|m| m.round > match.round and m.is_consolation_match == match.is_consolation_match }.sort_by{|m| m.round} - next_matches = weight.matches.select{|m| m.round == after_matches.first.round and m.is_consolation_match == match.is_consolation_match } - this_round_matches = weight.matches.select{|m| m.round == match.round and m.is_consolation_match == match.is_consolation_match } + after_matches = @matches.select{|m| m.weight_id == weight.id && m.round > match.round and m.is_consolation_match == match.is_consolation_match }.sort_by{|m| m.round} + next if after_matches.empty? + next_matches = @matches.select{|m| m.weight_id == weight.id && m.round == after_matches.first.round and m.is_consolation_match == match.is_consolation_match } + this_round_matches = @matches.select{|m| m.weight_id == weight.id && m.round == match.round and m.is_consolation_match == match.is_consolation_match } + next_match = nil if next_matches.size == this_round_matches.size next_match = next_matches.select{|m| m.bracket_position_number == match.bracket_position_number}.first - next_match.loser2_name = "BYE" - next_match.save + next_match.loser2_name = "BYE" if next_match elsif next_matches.size < this_round_matches.size and next_matches.size > 0 next_match = next_matches.select{|m| m.bracket_position_number == next_match_position_number}.first - if next_match.bracket_position_number == next_match_position_number + if next_match && next_match.bracket_position_number == next_match_position_number next_match.loser2_name = "BYE" - else + elsif next_match next_match.loser1_name = "BYE" end end - next_match.save - match.save end end def set_bye_for_placement weight = @wrestler.weight - fifth_finals = weight.matches.select{|match| match.bracket_position == '5/6'}.first - seventh_finals = weight.matches.select{|match| match.bracket_position == '7/8'}.first + fifth_finals = @matches.select{|match| match.weight_id == weight.id && match.bracket_position == '5/6'}.first + seventh_finals = @matches.select{|match| match.weight_id == weight.id && match.bracket_position == '7/8'}.first if seventh_finals - conso_quarter = weight.matches.select{|match| match.bracket_position == 'Conso Quarter'} + conso_quarter = @matches.select{|match| match.weight_id == weight.id && match.bracket_position == 'Conso Quarter'} conso_quarter.each do |match| if match.loser1_name == "BYE" or match.loser2_name == "BYE" - seventh_finals.replace_loser_name_with_bye("Loser of #{match.bout_number}") + replace_loser_name_with_bye(seventh_finals, "Loser of #{match.bout_number}") end end end if fifth_finals - conso_semis = weight.matches.select{|match| match.bracket_position == 'Conso Semis'} + conso_semis = @matches.select{|match| match.weight_id == weight.id && match.bracket_position == 'Conso Semis'} conso_semis.each do |match| if match.loser1_name == "BYE" or match.loser2_name == "BYE" - fifth_finals.replace_loser_name_with_bye("Loser of #{match.bout_number}") + replace_loser_name_with_bye(fifth_finals, "Loser of #{match.bout_number}") end end end end + + def replace_loser_name_with_wrestler(match, wrestler, loser_name) + if match.loser1_name == loser_name + match.w1 = wrestler.id + end + if match.loser2_name == loser_name + match.w2 = wrestler.id + end + end + + def replace_loser_name_with_bye(match, loser_name) + if match.loser1_name == loser_name + match.loser1_name = "BYE" + end + if match.loser2_name == loser_name + match.loser2_name = "BYE" + end + end end diff --git a/app/services/bracket_advancement/modified_double_elimination_advance.rb b/app/services/bracket_advancement/modified_double_elimination_advance.rb index 498b914..3afaf09 100644 --- a/app/services/bracket_advancement/modified_double_elimination_advance.rb +++ b/app/services/bracket_advancement/modified_double_elimination_advance.rb @@ -1,8 +1,12 @@ class ModifiedDoubleEliminationAdvance - def initialize(wrestler,last_match) + attr_reader :matches_to_advance + + def initialize(wrestler,last_match, matches: nil) @wrestler = wrestler @last_match = last_match + @matches = matches || @wrestler.weight.matches.to_a + @matches_to_advance = [] @next_match_position_number = (@last_match.bracket_position_number / 2.0) end @@ -25,42 +29,41 @@ class ModifiedDoubleEliminationAdvance update_consolation_bye end if @last_match.bracket_position == "Quarter" - new_match = Match.where("bracket_position = ? AND bracket_position_number = ? AND weight_id = ?","Semis",@next_match_position_number.ceil,@wrestler.weight_id).first + new_match = @matches.find { |m| m.bracket_position == "Semis" && m.bracket_position_number == @next_match_position_number.ceil && m.weight_id == @wrestler.weight_id } update_new_match(new_match, get_wrestler_number) elsif @last_match.bracket_position == "Semis" - new_match = Match.where("bracket_position = ? AND bracket_position_number = ? AND weight_id = ?","1/2",@next_match_position_number.ceil,@wrestler.weight_id).first + new_match = @matches.find { |m| m.bracket_position == "1/2" && m.bracket_position_number == @next_match_position_number.ceil && m.weight_id == @wrestler.weight_id } update_new_match(new_match, get_wrestler_number) elsif @last_match.bracket_position == "Conso Semis" - new_match = Match.where("bracket_position = ? AND bracket_position_number = ? AND weight_id = ?","5/6",@next_match_position_number.ceil,@wrestler.weight_id).first + new_match = @matches.find { |m| m.bracket_position == "5/6" && m.bracket_position_number == @next_match_position_number.ceil && m.weight_id == @wrestler.weight_id } update_new_match(new_match, get_wrestler_number) elsif @last_match.bracket_position == "Conso Quarter" # it's a special bracket where a semi loser is not dropping down - new_match = Match.where("bracket_position = ? AND bracket_position_number = ? AND weight_id = ?","Conso Semis",@next_match_position_number.ceil,@wrestler.weight_id).first + new_match = @matches.find { |m| m.bracket_position == "Conso Semis" && m.bracket_position_number == @next_match_position_number.ceil && m.weight_id == @wrestler.weight_id } update_new_match(new_match, get_wrestler_number) elsif @last_match.bracket_position == "Bracket Round of 16" - new_match = Match.where("bracket_position_number = ? and weight_id = ? and round > ? and bracket_position = ?", @next_match_position_number.ceil,@wrestler.weight_id, @last_match.round , "Quarter").sort_by{|m| m.round}.first + new_match = @matches.select { |m| m.bracket_position_number == @next_match_position_number.ceil && m.weight_id == @wrestler.weight_id && m.round > @last_match.round && m.bracket_position == "Quarter" }.sort_by(&:round).first update_new_match(new_match, get_wrestler_number) elsif @last_match.bracket_position == "Conso Round of 8" - new_match = Match.where("bracket_position_number = ? and weight_id = ? and round > ? and bracket_position = ?", @last_match.bracket_position_number,@wrestler.weight_id, @last_match.round, "Conso Quarter").sort_by{|m| m.round}.first + new_match = @matches.select { |m| m.bracket_position_number == @last_match.bracket_position_number && m.weight_id == @wrestler.weight_id && m.round > @last_match.round && m.bracket_position == "Conso Quarter" }.sort_by(&:round).first update_new_match(new_match, get_wrestler_number) end end def update_new_match(match, wrestler_number) + return unless match if wrestler_number == 2 or (match.loser1_name and match.loser1_name.include? "Loser of") match.w2 = @wrestler.id - match.save elsif wrestler_number == 1 match.w1 = @wrestler.id - match.save end end def update_consolation_bye bout = @wrestler.last_match.bout_number - next_match = Match.where("(loser1_name = ? OR loser2_name = ?) AND weight_id = ?","Loser of #{bout}","Loser of #{bout}",@wrestler.weight_id) - if next_match.size > 0 - next_match.first.replace_loser_name_with_bye("Loser of #{bout}") + next_match = @matches.find { |m| m.weight_id == @wrestler.weight_id && (m.loser1_name == "Loser of #{bout}" || m.loser2_name == "Loser of #{bout}") } + if next_match + replace_loser_name_with_bye(next_match, "Loser of #{bout}") end end @@ -74,27 +77,18 @@ class ModifiedDoubleEliminationAdvance def losers_bracket_advancement bout = @last_match.bout_number - next_match = Match.where("(loser1_name = ? OR loser2_name = ?) AND weight_id = ?", "Loser of #{bout}", "Loser of #{bout}", @wrestler.weight_id).first + next_match = @matches.find { |m| m.weight_id == @wrestler.weight_id && (m.loser1_name == "Loser of #{bout}" || m.loser2_name == "Loser of #{bout}") } if next_match.present? - next_match.replace_loser_name_with_wrestler(@wrestler, "Loser of #{bout}") - next_match.reload + replace_loser_name_with_wrestler(next_match, @wrestler, "Loser of #{bout}") if next_match.loser1_name == "BYE" || next_match.loser2_name == "BYE" next_match.winner_id = @wrestler.id next_match.win_type = "BYE" next_match.score = "" next_match.finished = 1 - # puts "Before save: winner_id=#{next_match.winner_id}" - - # if next_match.save - # puts "Save successful: winner_id=#{next_match.reload.winner_id}" - # else - # puts "Save failed: #{next_match.errors.full_messages}" - # end - next_match.save - - next_match.advance_wrestlers + next_match.finished_at = Time.current + @matches_to_advance << next_match end end end @@ -102,43 +96,53 @@ class ModifiedDoubleEliminationAdvance def advance_double_byes weight = @wrestler.weight - weight.matches.select{|m| m.loser1_name == "BYE" and m.loser2_name == "BYE" and m.finished != 1}.each do |match| + @matches.select{|m| m.weight_id == weight.id && m.loser1_name == "BYE" and m.loser2_name == "BYE" and m.finished != 1}.each do |match| match.finished = 1 + match.finished_at = Time.current match.score = "" match.win_type = "BYE" next_match_position_number = (match.bracket_position_number / 2.0).ceil - after_matches = weight.matches.select{|m| m.round > match.round and m.is_consolation_match == match.is_consolation_match }.sort_by{|m| m.round} - next_matches = weight.matches.select{|m| m.round == after_matches.first.round and m.is_consolation_match == match.is_consolation_match } - this_round_matches = weight.matches.select{|m| m.round == match.round and m.is_consolation_match == match.is_consolation_match } + after_matches = @matches.select{|m| m.weight_id == weight.id && m.round > match.round and m.is_consolation_match == match.is_consolation_match }.sort_by{|m| m.round} + next if after_matches.empty? + next_matches = @matches.select{|m| m.weight_id == weight.id && m.round == after_matches.first.round and m.is_consolation_match == match.is_consolation_match } + this_round_matches = @matches.select{|m| m.weight_id == weight.id && m.round == match.round and m.is_consolation_match == match.is_consolation_match } + next_match = nil if next_matches.size == this_round_matches.size next_match = next_matches.select{|m| m.bracket_position_number == match.bracket_position_number}.first - next_match.loser2_name = "BYE" - next_match.save + next_match.loser2_name = "BYE" if next_match elsif next_matches.size < this_round_matches.size and next_matches.size > 0 next_match = next_matches.select{|m| m.bracket_position_number == next_match_position_number}.first - if next_match.bracket_position_number == next_match_position_number + if next_match && next_match.bracket_position_number == next_match_position_number next_match.loser2_name = "BYE" - else + elsif next_match next_match.loser1_name = "BYE" end end - next_match.save - match.save end end def set_bye_for_placement weight = @wrestler.weight - seventh_finals = weight.matches.select{|match| match.bracket_position == '7/8'}.first + seventh_finals = @matches.select{|match| match.weight_id == weight.id && match.bracket_position == '7/8'}.first if seventh_finals - conso_quarter = weight.matches.select{|match| match.bracket_position == 'Conso Semis'} + conso_quarter = @matches.select{|match| match.weight_id == weight.id && match.bracket_position == 'Conso Semis'} conso_quarter.each do |match| if match.loser1_name == "BYE" or match.loser2_name == "BYE" - seventh_finals.replace_loser_name_with_bye("Loser of #{match.bout_number}") + replace_loser_name_with_bye(seventh_finals, "Loser of #{match.bout_number}") end end end end + + def replace_loser_name_with_wrestler(match, wrestler, loser_name) + match.w1 = wrestler.id if match.loser1_name == loser_name + match.w2 = wrestler.id if match.loser2_name == loser_name + end + + def replace_loser_name_with_bye(match, loser_name) + match.loser1_name = "BYE" if match.loser1_name == loser_name + match.loser2_name = "BYE" if match.loser2_name == loser_name + end end - \ No newline at end of file + diff --git a/app/services/bracket_advancement/pool_advance.rb b/app/services/bracket_advancement/pool_advance.rb index 6481aa3..d3be6a6 100644 --- a/app/services/bracket_advancement/pool_advance.rb +++ b/app/services/bracket_advancement/pool_advance.rb @@ -1,8 +1,13 @@ class PoolAdvance - def initialize(wrestler) + attr_reader :matches_to_advance + + def initialize(wrestler, last_match, matches: nil, wrestlers: nil) @wrestler = wrestler - @last_match = @wrestler.last_match + @last_match = last_match + @matches = matches || @wrestler.weight.matches.to_a + @wrestlers = wrestlers || @wrestler.weight.wrestlers.to_a + @matches_to_advance = [] end def advanceWrestler @@ -17,15 +22,15 @@ class PoolAdvance def poolToBracketAdvancment pool = @wrestler.pool # This has to always run because the last match in a pool might not be a pool winner or runner up - winner = Wrestler.where("weight_id = ? and pool_placement = 1 and pool = ?",@wrestler.weight.id, pool).first - runner_up = Wrestler.where("weight_id = ? and pool_placement = 2 and pool = ?",@wrestler.weight.id, pool).first + winner = @wrestlers.find { |w| w.weight_id == @wrestler.weight.id && w.pool_placement == 1 && w.pool == pool } + runner_up = @wrestlers.find { |w| w.weight_id == @wrestler.weight.id && w.pool_placement == 2 && w.pool == pool } if runner_up - runner_up_match = Match.where("weight_id = ? and (loser1_name = ? or loser2_name = ?)",@wrestler.weight.id, "Runner Up Pool #{pool}", "Runner Up Pool #{pool}").first - runner_up_match.replace_loser_name_with_wrestler(runner_up,"Runner Up Pool #{pool}") + runner_up_match = @matches.find { |m| m.weight_id == @wrestler.weight.id && (m.loser1_name == "Runner Up Pool #{pool}" || m.loser2_name == "Runner Up Pool #{pool}") } + replace_loser_name_with_wrestler(runner_up_match, runner_up, "Runner Up Pool #{pool}") if runner_up_match end if winner - winner_match = Match.where("weight_id = ? and (loser1_name = ? or loser2_name = ?)",@wrestler.weight.id, "Winner Pool #{pool}", "Winner Pool #{pool}").first - winner_match.replace_loser_name_with_wrestler(winner,"Winner Pool #{pool}") + winner_match = @matches.find { |m| m.weight_id == @wrestler.weight.id && (m.loser1_name == "Winner Pool #{pool}" || m.loser2_name == "Winner Pool #{pool}") } + replace_loser_name_with_wrestler(winner_match, winner, "Winner Pool #{pool}") if winner_match end end @@ -45,36 +50,40 @@ class PoolAdvance def winner_advance if @wrestler.last_match.bracket_position == "Quarter" - new_match = Match.where("bracket_position = ? AND bracket_position_number = ? AND weight_id = ?","Semis",@wrestler.next_match_position_number.ceil,@wrestler.weight_id).first + new_match = @matches.find { |m| m.bracket_position == "Semis" && m.bracket_position_number == @wrestler.next_match_position_number.ceil && m.weight_id == @wrestler.weight_id } updateNewMatch(new_match) end if @wrestler.last_match.bracket_position == "Semis" - new_match = Match.where("bracket_position = ? AND bracket_position_number = ? AND weight_id = ?","1/2",@wrestler.next_match_position_number.ceil,@wrestler.weight_id).first + new_match = @matches.find { |m| m.bracket_position == "1/2" && m.bracket_position_number == @wrestler.next_match_position_number.ceil && m.weight_id == @wrestler.weight_id } updateNewMatch(new_match) end if @wrestler.last_match.bracket_position == "Conso Semis" - new_match = Match.where("bracket_position = ? AND bracket_position_number = ? AND weight_id = ?","5/6",@wrestler.next_match_position_number.ceil,@wrestler.weight_id).first + new_match = @matches.find { |m| m.bracket_position == "5/6" && m.bracket_position_number == @wrestler.next_match_position_number.ceil && m.weight_id == @wrestler.weight_id } updateNewMatch(new_match) end end def updateNewMatch(match) + return unless match if @wrestler.next_match_position_number == @wrestler.next_match_position_number.ceil match.w2 = @wrestler.id - match.save end if @wrestler.next_match_position_number != @wrestler.next_match_position_number.ceil match.w1 = @wrestler.id - match.save end end def loser_advance bout = @wrestler.last_match.bout_number - next_match = Match.where("(loser1_name = ? OR loser2_name = ?) AND weight_id = ?","Loser of #{bout}","Loser of #{bout}",@wrestler.weight_id) - if next_match.size > 0 - next_match.first.replace_loser_name_with_wrestler(@wrestler,"Loser of #{bout}") + next_match = @matches.find { |m| m.weight_id == @wrestler.weight_id && (m.loser1_name == "Loser of #{bout}" || m.loser2_name == "Loser of #{bout}") } + if next_match + replace_loser_name_with_wrestler(next_match, @wrestler, "Loser of #{bout}") end end + + def replace_loser_name_with_wrestler(match, wrestler, loser_name) + match.w1 = wrestler.id if match.loser1_name == loser_name + match.w2 = wrestler.id if match.loser2_name == loser_name + end end diff --git a/app/services/bracket_advancement/pool_order.rb b/app/services/bracket_advancement/pool_order.rb index d6aed0a..1b0c7f8 100644 --- a/app/services/bracket_advancement/pool_order.rb +++ b/app/services/bracket_advancement/pool_order.rb @@ -4,8 +4,6 @@ class PoolOrder end def getPoolOrder - # clear caching for weight for bracket page - @wrestlers.first.weight.touch setOriginalPoints while checkForTies(@wrestlers) == true getWrestlersOrderByPoolAdvancePoints.each do |wrestler| @@ -18,7 +16,6 @@ class PoolOrder getWrestlersOrderByPoolAdvancePoints.each_with_index do |wrestler, index| placement = index + 1 wrestler.pool_placement = placement - wrestler.save end @wrestlers.sort_by{|w| w.poolAdvancePoints}.reverse! end @@ -29,7 +26,6 @@ class PoolOrder def setOriginalPoints @wrestlers.each do |w| - matches = w.reload.all_matches w.pool_placement_tiebreaker = nil w.pool_placement = nil w.poolAdvancePoints = w.pool_wins.size diff --git a/app/services/school_services/calculate_school_score.rb b/app/services/school_services/calculate_school_score.rb new file mode 100644 index 0000000..d12ec2d --- /dev/null +++ b/app/services/school_services/calculate_school_score.rb @@ -0,0 +1,55 @@ +class CalculateSchoolScore + def initialize(school) + @school = school + end + + def calculate + school = preload_school_context + score = calculate_score_value(school) + persist_school_score(school.id, score) + score + end + + def calculate_value + school = preload_school_context + calculate_score_value(school) + end + + private + + def preload_school_context + School + .includes( + :deductedPoints, + wrestlers: [ + :deductedPoints, + { matches_as_w1: :winner }, + { matches_as_w2: :winner }, + { weight: [:matches, { tournament: { weights: :wrestlers } }] } + ] + ) + .find(@school.id) + end + + def calculate_score_value(school) + total_points_scored_by_wrestlers(school) - total_points_deducted(school) + end + + def total_points_scored_by_wrestlers(school) + school.wrestlers.sum { |wrestler| CalculateWrestlerTeamScore.new(wrestler).totalScore } + end + + def total_points_deducted(school) + school.deductedPoints.sum(&:points) + end + + def persist_school_score(school_id, score) + School.upsert_all([ + { + id: school_id, + score: score, + updated_at: Time.current + } + ]) + end +end diff --git a/app/services/tournament_services/double_elimination_generate_loser_names.rb b/app/services/tournament_services/double_elimination_generate_loser_names.rb index 5d26ba6..6ad7298 100644 --- a/app/services/tournament_services/double_elimination_generate_loser_names.rb +++ b/app/services/tournament_services/double_elimination_generate_loser_names.rb @@ -3,33 +3,30 @@ class DoubleEliminationGenerateLoserNames @tournament = tournament end - # Entry point: assign loser placeholders and advance any byes - def assign_loser_names + # Compatibility wrapper. Returns transformed rows and does not persist. + def assign_loser_names(match_rows = nil) + rows = match_rows || @tournament.matches.where(tournament_id: @tournament.id).map { |m| m.attributes.symbolize_keys } @tournament.weights.each do |weight| - # only assign loser names if there's conso matches to be had - if weight.calculate_bracket_size > 2 - assign_loser_names_for_weight(weight) - advance_bye_matches_championship(weight) - advance_bye_matches_consolation(weight) - end + next unless weight.calculate_bracket_size > 2 + + assign_loser_names_in_memory(weight, rows) + assign_bye_outcomes_in_memory(weight, rows) end + rows end - private - - # Assign loser names for a single weight bracket - def assign_loser_names_for_weight(weight) + def assign_loser_names_in_memory(weight, match_rows) bracket_size = weight.calculate_bracket_size - matches = weight.matches.reload - num_placers = @tournament.number_of_placers + return if bracket_size <= 2 + + rows = match_rows.select { |row| row[:weight_id] == weight.id } + num_placers = @tournament.number_of_placers - # Build dynamic round definitions champ_rounds = dynamic_championship_rounds(bracket_size) conso_rounds = dynamic_consolation_rounds(bracket_size) - first_round = { bracket_position: first_round_label(bracket_size) } - champ_full = [first_round] + champ_rounds + first_round = { bracket_position: first_round_label(bracket_size) } + champ_full = [first_round] + champ_rounds - # Map championship losers into consolation slots mappings = [] champ_full[0...-1].each_with_index do |champ_info, i| map_idx = i.zero? ? 0 : (2 * i - 1) @@ -37,128 +34,109 @@ class DoubleEliminationGenerateLoserNames mappings << { championship_bracket_position: champ_info[:bracket_position], - consolation_bracket_position: conso_rounds[map_idx][:bracket_position], - both_wrestlers: i.zero?, - champ_round_index: i + consolation_bracket_position: conso_rounds[map_idx][:bracket_position], + both_wrestlers: i.zero?, + champ_round_index: i } end - # Apply loser-name mappings mappings.each do |map| - champ = matches.select { |m| m.bracket_position == map[:championship_bracket_position] } - .sort_by(&:bracket_position_number) - conso = matches.select { |m| m.bracket_position == map[:consolation_bracket_position] } - .sort_by(&:bracket_position_number) - - current_champ_round_index = map[:champ_round_index] - if current_champ_round_index.odd? - conso.reverse! - end + champ = rows.select { |r| r[:bracket_position] == map[:championship_bracket_position] } + .sort_by { |r| r[:bracket_position_number] } + conso = rows.select { |r| r[:bracket_position] == map[:consolation_bracket_position] } + .sort_by { |r| r[:bracket_position_number] } + conso.reverse! if map[:champ_round_index].odd? idx = 0 - # Determine if this mapping is for losers from the first championship round - is_first_champ_round_feed = map[:champ_round_index].zero? - + is_first_feed = map[:champ_round_index].zero? conso.each do |cm| champ_match1 = champ[idx] if champ_match1 - if is_first_champ_round_feed && ((champ_match1.w1 && champ_match1.w2.nil?) || (champ_match1.w1.nil? && champ_match1.w2)) - cm.loser1_name = "BYE" + if is_first_feed && single_competitor_match_row?(champ_match1) + cm[:loser1_name] = "BYE" else - cm.loser1_name = "Loser of #{champ_match1.bout_number}" + cm[:loser1_name] = "Loser of #{champ_match1[:bout_number]}" end else - cm.loser1_name = nil # Should not happen if bracket generation is correct + cm[:loser1_name] = nil end - if map[:both_wrestlers] # This is true only if is_first_champ_round_feed - idx += 1 # Increment for the second championship match + if map[:both_wrestlers] + idx += 1 champ_match2 = champ[idx] if champ_match2 - # BYE check is only relevant for the first championship round feed - if is_first_champ_round_feed && ((champ_match2.w1 && champ_match2.w2.nil?) || (champ_match2.w1.nil? && champ_match2.w2)) - cm.loser2_name = "BYE" + if is_first_feed && single_competitor_match_row?(champ_match2) + cm[:loser2_name] = "BYE" else - cm.loser2_name = "Loser of #{champ_match2.bout_number}" + cm[:loser2_name] = "Loser of #{champ_match2[:bout_number]}" end else - cm.loser2_name = nil # Should not happen + cm[:loser2_name] = nil end end - idx += 1 # Increment for the next consolation match or next pair from championship + idx += 1 end end - # 5th/6th place if bracket_size >= 5 && num_placers >= 6 && weight.wrestlers.size > 4 - conso_semis = matches.select { |m| m.bracket_position == "Conso Semis" } - .sort_by(&:bracket_position_number) - if conso_semis.size >= 2 - m56 = matches.find { |m| m.bracket_position == "5/6" } - m56.loser1_name = "Loser of #{conso_semis[0].bout_number}" - m56.loser2_name = "Loser of #{conso_semis[1].bout_number}" if m56 + conso_semis = rows.select { |r| r[:bracket_position] == "Conso Semis" }.sort_by { |r| r[:bracket_position_number] } + m56 = rows.find { |r| r[:bracket_position] == "5/6" } + if conso_semis.size >= 2 && m56 + m56[:loser1_name] = "Loser of #{conso_semis[0][:bout_number]}" + m56[:loser2_name] = "Loser of #{conso_semis[1][:bout_number]}" end end - # 7th/8th place if bracket_size >= 7 && num_placers >= 8 && weight.wrestlers.size > 6 - conso_quarters = matches.select { |m| m.bracket_position == "Conso Quarter" } - .sort_by(&:bracket_position_number) - if conso_quarters.size >= 2 - m78 = matches.find { |m| m.bracket_position == "7/8" } - m78.loser1_name = "Loser of #{conso_quarters[0].bout_number}" - m78.loser2_name = "Loser of #{conso_quarters[1].bout_number}" if m78 + conso_quarters = rows.select { |r| r[:bracket_position] == "Conso Quarter" }.sort_by { |r| r[:bracket_position_number] } + m78 = rows.find { |r| r[:bracket_position] == "7/8" } + if conso_quarters.size >= 2 && m78 + m78[:loser1_name] = "Loser of #{conso_quarters[0][:bout_number]}" + m78[:loser2_name] = "Loser of #{conso_quarters[1][:bout_number]}" end end - - matches.each(&:save!) end - # Advance first-round byes in championship bracket - def advance_bye_matches_championship(weight) - matches = weight.matches.reload - first_round = matches.map(&:round).min - matches.select { |m| m.round == first_round } - .sort_by(&:bracket_position_number) - .each { |m| handle_bye(m) } - end - - # Advance first-round byes in consolation bracket - def advance_bye_matches_consolation(weight) - matches = weight.matches.reload + def assign_bye_outcomes_in_memory(weight, match_rows) bracket_size = weight.calculate_bracket_size - first_conso = dynamic_consolation_rounds(bracket_size).first + return if bracket_size <= 2 - matches.select { |m| m.round == first_conso[:round] && m.bracket_position == first_conso[:bracket_position] } - .sort_by(&:bracket_position_number) - .each { |m| handle_bye(m) } - end + rows = match_rows.select { |r| r[:weight_id] == weight.id } + first_round = rows.map { |r| r[:round] }.compact.min + rows.select { |r| r[:round] == first_round }.each { |row| apply_bye_to_row(row) } - # Mark bye match, set finished, and advance - def handle_bye(match) - if [match.w1, match.w2].compact.size == 1 - match.finished = 1 - match.win_type = 'BYE' - if match.w1 - match.winner_id = match.w1 - match.loser2_name = 'BYE' - else - match.winner_id = match.w2 - match.loser1_name = 'BYE' - end - match.score = '' - match.save! - match.advance_wrestlers + first_conso = dynamic_consolation_rounds(bracket_size).first + if first_conso + rows.select { |r| r[:round] == first_conso[:round] && r[:bracket_position] == first_conso[:bracket_position] } + .each { |row| apply_bye_to_row(row) } end end - # Helpers for dynamic bracket labels + def apply_bye_to_row(row) + return unless single_competitor_match_row?(row) + + row[:finished] = 1 + row[:win_type] = "BYE" + if row[:w1] + row[:winner_id] = row[:w1] + row[:loser2_name] = "BYE" + else + row[:winner_id] = row[:w2] + row[:loser1_name] = "BYE" + end + row[:score] = "" + end + + def single_competitor_match_row?(row) + [row[:w1], row[:w2]].compact.size == 1 + end + def first_round_label(size) case size - when 2 then 'Final' - when 4 then 'Semis' - when 8 then 'Quarter' - else "Bracket Round of #{size}" + when 2 then "Final" + when 4 then "Semis" + when 8 then "Quarter" + else "Bracket Round of #{size}" end end @@ -173,36 +151,36 @@ class DoubleEliminationGenerateLoserNames def dynamic_consolation_rounds(size) total_log2 = Math.log2(size).to_i return [] if total_log2 <= 1 - + max_j_val = (2 * (total_log2 - 1) - 1) (1..max_j_val).map do |j| current_participants = size / (2**((j.to_f / 2).ceil)) - { + { bracket_position: consolation_label(current_participants, j, size), - round: j + round: j } end end def bracket_label(participants) case participants - when 2 then '1/2' - when 4 then 'Semis' - when 8 then 'Quarter' - else "Bracket Round of #{participants}" + when 2 then "1/2" + when 4 then "Semis" + when 8 then "Quarter" + else "Bracket Round of #{participants}" end end def consolation_label(participants, j, bracket_size) max_j_for_bracket = (2 * (Math.log2(bracket_size).to_i - 1) - 1) - + if participants == 2 && j == max_j_for_bracket - return '3/4' + "3/4" elsif participants == 4 - return j.odd? ? 'Conso Quarter' : 'Conso Semis' + j.odd? ? "Conso Quarter" : "Conso Semis" else suffix = j.odd? ? ".1" : ".2" - return "Conso Round of #{participants}#{suffix}" + "Conso Round of #{participants}#{suffix}" end end end diff --git a/app/services/tournament_services/double_elimination_match_generation.rb b/app/services/tournament_services/double_elimination_match_generation.rb index 08798f4..19a873c 100644 --- a/app/services/tournament_services/double_elimination_match_generation.rb +++ b/app/services/tournament_services/double_elimination_match_generation.rb @@ -1,29 +1,33 @@ class DoubleEliminationMatchGeneration - def initialize(tournament) + def initialize(tournament, weights: nil) @tournament = tournament + @weights = weights end def generate_matches - # - # PHASE 1: Generate matches (with local round definitions). - # - @tournament.weights.each do |weight| - generate_matches_for_weight(weight) + build_match_rows + end + + def build_match_rows + rows_by_weight_id = {} + + generation_weights.each do |weight| + rows_by_weight_id[weight.id] = generate_match_rows_for_weight(weight) end - # - # PHASE 2: Align all rounds to match the largest bracket’s definitions. - # - align_all_rounds_to_largest_bracket + align_rows_to_largest_bracket(rows_by_weight_id) + rows_by_weight_id.values.flatten end ########################################################################### # PHASE 1: Generate all matches for each bracket, using a single definition. ########################################################################### - def generate_matches_for_weight(weight) + def generate_match_rows_for_weight(weight) bracket_size = weight.calculate_bracket_size bracket_info = define_bracket_matches(bracket_size) - return unless bracket_info + return [] unless bracket_info + + rows = [] # 1) Round one matchups bracket_info[:round_one_matchups].each_with_index do |matchup, idx| @@ -32,7 +36,7 @@ class DoubleEliminationMatchGeneration bracket_pos_number = idx + 1 round_number = matchup[:round] - create_matchup_from_seed( + rows << create_matchup_from_seed( seed1, seed2, bracket_position, @@ -49,7 +53,7 @@ class DoubleEliminationMatchGeneration round_number = round_info[:round] matches_this_round.times do |i| - create_matchup( + rows << create_matchup( nil, nil, bracket_position, @@ -67,7 +71,7 @@ class DoubleEliminationMatchGeneration round_number = round_info[:round] matches_this_round.times do |i| - create_matchup( + rows << create_matchup( nil, nil, bracket_position, @@ -79,12 +83,14 @@ class DoubleEliminationMatchGeneration # 5/6, 7/8 placing logic if weight.wrestlers.size >= 5 && @tournament.number_of_placers >= 6 && matches_this_round == 1 - create_matchup(nil, nil, "5/6", 1, round_number, weight) + rows << create_matchup(nil, nil, "5/6", 1, round_number, weight) end if weight.wrestlers.size >= 7 && @tournament.number_of_placers >= 8 && matches_this_round == 1 - create_matchup(nil, nil, "7/8", 1, round_number, weight) + rows << create_matchup(nil, nil, "7/8", 1, round_number, weight) end end + + rows end # Single bracket definition dynamically generated for any power-of-two bracket size. @@ -173,18 +179,18 @@ class DoubleEliminationMatchGeneration ########################################################################### # PHASE 2: Overwrite rounds in all smaller brackets to match the largest one. ########################################################################### - def align_all_rounds_to_largest_bracket - largest_weight = @tournament.weights.max_by { |w| w.calculate_bracket_size } + def align_rows_to_largest_bracket(rows_by_weight_id) + largest_weight = generation_weights.max_by { |w| w.calculate_bracket_size } return unless largest_weight position_to_round = {} - largest_weight.tournament.matches.where(weight_id: largest_weight.id).each do |m| - position_to_round[m.bracket_position] ||= m.round + rows_by_weight_id.fetch(largest_weight.id, []).each do |row| + position_to_round[row[:bracket_position]] ||= row[:round] end - @tournament.matches.find_each do |match| - if position_to_round.key?(match.bracket_position) - match.update(round: position_to_round[match.bracket_position]) + rows_by_weight_id.each_value do |rows| + rows.each do |row| + row[:round] = position_to_round[row[:bracket_position]] if position_to_round.key?(row[:bracket_position]) end end end @@ -192,8 +198,12 @@ class DoubleEliminationMatchGeneration ########################################################################### # Helper methods ########################################################################### + def generation_weights + @weights || @tournament.weights.to_a + end + def wrestler_with_seed(seed, weight) - Wrestler.where("weight_id = ? AND bracket_line = ?", weight.id, seed).first&.id + weight.wrestlers.find { |w| w.bracket_line == seed }&.id end def create_matchup_from_seed(w1_seed, w2_seed, bracket_position, bracket_position_number, round, weight) @@ -208,14 +218,15 @@ class DoubleEliminationMatchGeneration end def create_matchup(w1, w2, bracket_position, bracket_position_number, round, weight) - weight.tournament.matches.create!( + { w1: w1, w2: w2, + tournament_id: weight.tournament_id, weight_id: weight.id, round: round, bracket_position: bracket_position, bracket_position_number: bracket_position_number - ) + } end # Calculates the sequence of seeds for the first round of a power-of-two bracket. diff --git a/app/services/tournament_services/generate_tournament_matches.rb b/app/services/tournament_services/generate_tournament_matches.rb index 4ac47e8..4b211b5 100644 --- a/app/services/tournament_services/generate_tournament_matches.rb +++ b/app/services/tournament_services/generate_tournament_matches.rb @@ -10,63 +10,183 @@ class GenerateTournamentMatches def generate_raw standardStartingActions - PoolToBracketMatchGeneration.new(@tournament).generatePoolToBracketMatches if @tournament.tournament_type == "Pool to bracket" - ModifiedSixteenManMatchGeneration.new(@tournament).generate_matches if @tournament.tournament_type.include? "Modified 16 Man Double Elimination" - DoubleEliminationMatchGeneration.new(@tournament).generate_matches if @tournament.tournament_type.include? "Regular Double Elimination" + generation_context = preload_generation_context + seed_wrestlers_in_memory(generation_context) + match_rows = build_match_rows(generation_context) + post_process_match_rows_in_memory(generation_context, match_rows) + persist_generation_rows(generation_context, match_rows) postMatchCreationActions - PoolToBracketMatchGeneration.new(@tournament).assignLoserNames if @tournament.tournament_type == "Pool to bracket" - ModifiedSixteenManGenerateLoserNames.new(@tournament).assign_loser_names if @tournament.tournament_type.include? "Modified 16 Man Double Elimination" - DoubleEliminationGenerateLoserNames.new(@tournament).assign_loser_names if @tournament.tournament_type.include? "Regular Double Elimination" + advance_bye_matches_after_insert end def standardStartingActions @tournament.curently_generating_matches = 1 @tournament.save WipeTournamentMatches.new(@tournament).setUpMatchGeneration - TournamentSeeding.new(@tournament).set_seeds + end + + def preload_generation_context + weights = @tournament.weights.includes(:wrestlers).order(:max).to_a + wrestlers = weights.flat_map(&:wrestlers) + { + weights: weights, + wrestlers: wrestlers, + wrestlers_by_weight_id: wrestlers.group_by(&:weight_id) + } + end + + def seed_wrestlers_in_memory(generation_context) + TournamentSeeding.new(@tournament).set_seeds(weights: generation_context[:weights], persist: false) + end + + def build_match_rows(generation_context) + return PoolToBracketMatchGeneration.new( + @tournament, + weights: generation_context[:weights], + wrestlers_by_weight_id: generation_context[:wrestlers_by_weight_id] + ).generatePoolToBracketMatches if @tournament.tournament_type == "Pool to bracket" + + return ModifiedSixteenManMatchGeneration.new(@tournament, weights: generation_context[:weights]).generate_matches if @tournament.tournament_type.include? "Modified 16 Man Double Elimination" + + return DoubleEliminationMatchGeneration.new(@tournament, weights: generation_context[:weights]).generate_matches if @tournament.tournament_type.include? "Regular Double Elimination" + + [] + end + + def persist_generation_rows(generation_context, match_rows) + persist_wrestlers(generation_context[:wrestlers]) + persist_matches(match_rows) + end + + def post_process_match_rows_in_memory(generation_context, match_rows) + move_finals_rows_to_last_round(match_rows) unless @tournament.tournament_type.include?("Regular Double Elimination") + assign_bouts_in_memory(match_rows, generation_context[:weights]) + assign_loser_names_in_memory(generation_context, match_rows) + assign_bye_outcomes_in_memory(generation_context, match_rows) + end + + def persist_wrestlers(wrestlers) + return if wrestlers.blank? + + timestamp = Time.current + rows = wrestlers.map do |w| + { + id: w.id, + bracket_line: w.bracket_line, + pool: w.pool, + updated_at: timestamp + } + end + Wrestler.upsert_all(rows) + end + + def persist_matches(match_rows) + return if match_rows.blank? + + timestamp = Time.current + rows_with_timestamps = match_rows.map do |row| + row.to_h.symbolize_keys.merge(created_at: timestamp, updated_at: timestamp) + end + + all_keys = rows_with_timestamps.flat_map(&:keys).uniq + normalized_rows = rows_with_timestamps.map do |row| + all_keys.each_with_object({}) { |key, normalized| normalized[key] = row[key] } + end + + Match.insert_all!(normalized_rows) end def postMatchCreationActions - moveFinalsMatchesToLastRound if ! @tournament.tournament_type.include? "Regular Double Elimination" - assignBouts @tournament.reset_and_fill_bout_board @tournament.curently_generating_matches = nil @tournament.save! Tournament.broadcast_up_matches_board(@tournament.id) end + def move_finals_rows_to_last_round(match_rows) + finals_round = match_rows.map { |row| row[:round] }.compact.max + return unless finals_round + + match_rows.each do |row| + row[:round] = finals_round if ["1/2", "3/4", "5/6", "7/8"].include?(row[:bracket_position]) + end + end + + def assign_bouts_in_memory(match_rows, weights) + bout_counts = Hash.new(0) + weight_max_by_id = weights.each_with_object({}) { |w, memo| memo[w.id] = w.max } + + match_rows + .sort_by { |row| [row[:round].to_i, weight_max_by_id[row[:weight_id]].to_f, row[:weight_id].to_i, row[:bracket_position_number].to_i] } + .each do |row| + round = row[:round].to_i + row[:bout_number] = round * 1000 + bout_counts[round] + bout_counts[round] += 1 + end + end + + def assign_loser_names_in_memory(generation_context, match_rows) + if @tournament.tournament_type == "Pool to bracket" + service = PoolToBracketGenerateLoserNames.new(@tournament) + generation_context[:weights].each { |weight| service.assign_loser_names_in_memory(weight, match_rows) } + elsif @tournament.tournament_type.include?("Modified 16 Man Double Elimination") + service = ModifiedSixteenManGenerateLoserNames.new(@tournament) + generation_context[:weights].each { |weight| service.assign_loser_names_in_memory(weight, match_rows) } + elsif @tournament.tournament_type.include?("Regular Double Elimination") + service = DoubleEliminationGenerateLoserNames.new(@tournament) + generation_context[:weights].each { |weight| service.assign_loser_names_in_memory(weight, match_rows) } + end + end + + def assign_bye_outcomes_in_memory(generation_context, match_rows) + if @tournament.tournament_type.include?("Modified 16 Man Double Elimination") + service = ModifiedSixteenManGenerateLoserNames.new(@tournament) + generation_context[:weights].each { |weight| service.assign_bye_outcomes_in_memory(weight, match_rows) } + elsif @tournament.tournament_type.include?("Regular Double Elimination") + service = DoubleEliminationGenerateLoserNames.new(@tournament) + generation_context[:weights].each { |weight| service.assign_bye_outcomes_in_memory(weight, match_rows) } + end + end + + def advance_bye_matches_after_insert + Match.where(tournament_id: @tournament.id, finished: 1, win_type: "BYE") + .where.not(winner_id: nil) + .find_each(&:advance_wrestlers) + end + def assignBouts bout_counts = Hash.new(0) - @tournament.matches.reload - @tournament.matches.sort_by{|m| [m.round, m.weight_max]}.each do |m| - m.bout_number = m.round * 1000 + bout_counts[m.round] - bout_counts[m.round] += 1 - m.save! + timestamp = Time.current + ordered_matches = Match.joins(:weight) + .where(tournament_id: @tournament.id) + .order("matches.round ASC, weights.max ASC, matches.id ASC") + .pluck("matches.id", "matches.round") + + updates = [] + ordered_matches.each do |match_id, round| + updates << { + id: match_id, + bout_number: round * 1000 + bout_counts[round], + updated_at: timestamp + } + bout_counts[round] += 1 end + + Match.upsert_all(updates) if updates.any? end def moveFinalsMatchesToLastRound finalsRound = @tournament.reload.total_rounds - finalsMatches = @tournament.matches.reload.select{|m| m.bracket_position == "1/2" || m.bracket_position == "3/4" || m.bracket_position == "5/6" || m.bracket_position == "7/8"} - finalsMatches. each do |m| - m.round = finalsRound - m.save - end + @tournament.matches + .where(bracket_position: ["1/2", "3/4", "5/6", "7/8"]) + .update_all(round: finalsRound, updated_at: Time.current) end def unAssignMats - matches = @tournament.matches.reload - matches.each do |m| - m.mat_id = nil - m.save! - end + @tournament.matches.update_all(mat_id: nil, updated_at: Time.current) end def unAssignBouts - bout_counts = Hash.new(0) - @tournament.matches.each do |m| - m.bout_number = nil - m.save! - end + @tournament.matches.update_all(bout_number: nil, updated_at: Time.current) end end diff --git a/app/services/tournament_services/modified_sixteen_man_generate_loser_names.rb b/app/services/tournament_services/modified_sixteen_man_generate_loser_names.rb index 4df4557..d659033 100644 --- a/app/services/tournament_services/modified_sixteen_man_generate_loser_names.rb +++ b/app/services/tournament_services/modified_sixteen_man_generate_loser_names.rb @@ -1,95 +1,91 @@ class ModifiedSixteenManGenerateLoserNames - def initialize( tournament ) - @tournament = tournament + def initialize(tournament) + @tournament = tournament + end + + # Compatibility wrapper. Returns transformed rows and does not persist. + def assign_loser_names(match_rows = nil) + rows = match_rows || @tournament.matches.where(tournament_id: @tournament.id).map { |m| m.attributes.symbolize_keys } + @tournament.weights.each do |weight| + assign_loser_names_in_memory(weight, rows) + assign_bye_outcomes_in_memory(weight, rows) + end + rows + end + + def assign_loser_names_in_memory(weight, match_rows) + rows = match_rows.select { |row| row[:weight_id] == weight.id } + round_16 = rows.select { |r| r[:bracket_position] == "Bracket Round of 16" } + conso_8 = rows.select { |r| r[:bracket_position] == "Conso Round of 8" }.sort_by { |r| r[:bracket_position_number] } + + conso_8.each do |row| + if row[:bracket_position_number] == 1 + m1 = round_16.find { |m| m[:bracket_position_number] == 1 } + m2 = round_16.find { |m| m[:bracket_position_number] == 2 } + row[:loser1_name] = "Loser of #{m1[:bout_number]}" if m1 + row[:loser2_name] = "Loser of #{m2[:bout_number]}" if m2 + elsif row[:bracket_position_number] == 2 + m3 = round_16.find { |m| m[:bracket_position_number] == 3 } + m4 = round_16.find { |m| m[:bracket_position_number] == 4 } + row[:loser1_name] = "Loser of #{m3[:bout_number]}" if m3 + row[:loser2_name] = "Loser of #{m4[:bout_number]}" if m4 + elsif row[:bracket_position_number] == 3 + m5 = round_16.find { |m| m[:bracket_position_number] == 5 } + m6 = round_16.find { |m| m[:bracket_position_number] == 6 } + row[:loser1_name] = "Loser of #{m5[:bout_number]}" if m5 + row[:loser2_name] = "Loser of #{m6[:bout_number]}" if m6 + elsif row[:bracket_position_number] == 4 + m7 = round_16.find { |m| m[:bracket_position_number] == 7 } + m8 = round_16.find { |m| m[:bracket_position_number] == 8 } + row[:loser1_name] = "Loser of #{m7[:bout_number]}" if m7 + row[:loser2_name] = "Loser of #{m8[:bout_number]}" if m8 + end end - def assign_loser_names - matches_by_weight = nil - @tournament.weights.each do |w| - matches_by_weight = @tournament.matches.where(weight_id: w.id) - conso_round_2(matches_by_weight) - conso_round_3(matches_by_weight) - third_fourth(matches_by_weight) - seventh_eighth(matches_by_weight) - save_matches(matches_by_weight) - matches_by_weight = @tournament.matches.where(weight_id: w.id).reload - advance_bye_matches_championship(matches_by_weight) - save_matches(matches_by_weight) + quarters = rows.select { |r| r[:bracket_position] == "Quarter" } + conso_quarters = rows.select { |r| r[:bracket_position] == "Conso Quarter" }.sort_by { |r| r[:bracket_position_number] } + conso_quarters.each do |row| + source = case row[:bracket_position_number] + when 1 then quarters.find { |q| q[:bracket_position_number] == 4 } + when 2 then quarters.find { |q| q[:bracket_position_number] == 3 } + when 3 then quarters.find { |q| q[:bracket_position_number] == 2 } + when 4 then quarters.find { |q| q[:bracket_position_number] == 1 } + end + row[:loser1_name] = "Loser of #{source[:bout_number]}" if source end - end - def conso_round_2(matches) - matches.select{|m| m.bracket_position == "Conso Round of 8"}.sort_by{|m| m.bracket_position_number}.each do |match| - if match.bracket_position_number == 1 - match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position_number == 1 and m.bracket_position == "Bracket Round of 16"}.first.bout_number}" - match.loser2_name = "Loser of #{matches.select{|m| m.bracket_position_number == 2 and m.bracket_position == "Bracket Round of 16"}.first.bout_number}" - elsif match.bracket_position_number == 2 - match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position_number == 3 and m.bracket_position == "Bracket Round of 16"}.first.bout_number}" - match.loser2_name = "Loser of #{matches.select{|m| m.bracket_position_number == 4 and m.bracket_position == "Bracket Round of 16"}.first.bout_number}" - elsif match.bracket_position_number == 3 - match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position_number == 5 and m.bracket_position == "Bracket Round of 16"}.first.bout_number}" - match.loser2_name = "Loser of #{matches.select{|m| m.bracket_position_number == 6 and m.bracket_position == "Bracket Round of 16"}.first.bout_number}" - elsif match.bracket_position_number == 4 - match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position_number == 7 and m.bracket_position == "Bracket Round of 16"}.first.bout_number}" - match.loser2_name = "Loser of #{matches.select{|m| m.bracket_position_number == 8 and m.bracket_position == "Bracket Round of 16"}.first.bout_number}" - end - end - end + semis = rows.select { |r| r[:bracket_position] == "Semis" } + third_fourth = rows.find { |r| r[:bracket_position] == "3/4" } + if third_fourth + third_fourth[:loser1_name] = "Loser of #{semis.first[:bout_number]}" if semis.first + third_fourth[:loser2_name] = "Loser of #{semis.second[:bout_number]}" if semis.second + end - def conso_round_3(matches) - matches.select{|m| m.bracket_position == "Conso Quarter"}.sort_by{|m| m.bracket_position_number}.each do |match| - if match.bracket_position_number == 1 - match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position_number == 4 and m.bracket_position == "Quarter"}.first.bout_number}" - elsif match.bracket_position_number == 2 - match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position_number == 3 and m.bracket_position == "Quarter"}.first.bout_number}" - elsif match.bracket_position_number == 3 - match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position_number == 2 and m.bracket_position == "Quarter"}.first.bout_number}" - elsif match.bracket_position_number == 4 - match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position_number == 1 and m.bracket_position == "Quarter"}.first.bout_number}" - end - end - end + conso_semis = rows.select { |r| r[:bracket_position] == "Conso Semis" } + seventh_eighth = rows.find { |r| r[:bracket_position] == "7/8" } + if seventh_eighth + seventh_eighth[:loser1_name] = "Loser of #{conso_semis.first[:bout_number]}" if conso_semis.first + seventh_eighth[:loser2_name] = "Loser of #{conso_semis.second[:bout_number]}" if conso_semis.second + end + end - def third_fourth(matches) - matches.select{|m| m.bracket_position == "3/4"}.sort_by{|m| m.bracket_position_number}.each do |match| - match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position == "Semis"}.first.bout_number}" - match.loser2_name = "Loser of #{matches.select{|m| m.bracket_position == "Semis"}.second.bout_number}" - end - end - - def seventh_eighth(matches) - matches.select{|m| m.bracket_position == "7/8"}.sort_by{|m| m.bracket_position_number}.each do |match| - match.loser1_name = "Loser of #{matches.select{|m| m.bracket_position == "Conso Semis"}.first.bout_number}" - match.loser2_name = "Loser of #{matches.select{|m| m.bracket_position == "Conso Semis"}.second.bout_number}" - end - end + def assign_bye_outcomes_in_memory(weight, match_rows) + rows = match_rows.select { |r| r[:weight_id] == weight.id && r[:bracket_position] == "Bracket Round of 16" } + rows.each { |row| apply_bye_to_row(row) } + end - def advance_bye_matches_championship(matches) - matches.select{|m| m.bracket_position == "Bracket Round of 16"}.sort_by{|m| m.bracket_position_number}.each do |match| - if match.w1 == nil or match.w2 == nil - match.finished = 1 - match.win_type = "BYE" - if match.w1 != nil - match.winner_id = match.w1 - match.loser2_name = "BYE" - match.score = "" - match.save - match.advance_wrestlers - elsif match.w2 != nil - match.winner_id = match.w2 - match.loser1_name = "BYE" - match.score = "" - match.save - match.advance_wrestlers - end - end - end - end - - def save_matches(matches) - matches.each do |m| - m.save! - end - end - -end \ No newline at end of file + def apply_bye_to_row(row) + return unless [row[:w1], row[:w2]].compact.size == 1 + + row[:finished] = 1 + row[:win_type] = "BYE" + if row[:w1] + row[:winner_id] = row[:w1] + row[:loser2_name] = "BYE" + else + row[:winner_id] = row[:w2] + row[:loser1_name] = "BYE" + end + row[:score] = "" + end +end diff --git a/app/services/tournament_services/modified_sixteen_man_match_generation.rb b/app/services/tournament_services/modified_sixteen_man_match_generation.rb index 6571269..38630b4 100644 --- a/app/services/tournament_services/modified_sixteen_man_match_generation.rb +++ b/app/services/tournament_services/modified_sixteen_man_match_generation.rb @@ -1,70 +1,75 @@ class ModifiedSixteenManMatchGeneration - def initialize( tournament ) + def initialize( tournament, weights: nil ) @tournament = tournament @number_of_placers = @tournament.number_of_placers + @weights = weights end def generate_matches - @tournament.weights.each do |weight| - generate_matches_for_weight(weight) + rows = [] + generation_weights.each do |weight| + rows.concat(generate_match_rows_for_weight(weight)) end + rows end - def generate_matches_for_weight(weight) - round_one(weight) - round_two(weight) - round_three(weight) - round_four(weight) - round_five(weight) + def generate_match_rows_for_weight(weight) + rows = [] + round_one(weight, rows) + round_two(weight, rows) + round_three(weight, rows) + round_four(weight, rows) + round_five(weight, rows) + rows end - def round_one(weight) - create_matchup_from_seed(1,16, "Bracket Round of 16", 1, 1,weight) - create_matchup_from_seed(8,9, "Bracket Round of 16", 2, 1,weight) - create_matchup_from_seed(5,12, "Bracket Round of 16", 3, 1,weight) - create_matchup_from_seed(4,14, "Bracket Round of 16", 4, 1,weight) - create_matchup_from_seed(3,13, "Bracket Round of 16", 5, 1,weight) - create_matchup_from_seed(6,11, "Bracket Round of 16", 6, 1,weight) - create_matchup_from_seed(7,10, "Bracket Round of 16", 7, 1,weight) - create_matchup_from_seed(2,15, "Bracket Round of 16", 8, 1,weight) + def round_one(weight, rows) + rows << create_matchup_from_seed(1,16, "Bracket Round of 16", 1, 1,weight) + rows << create_matchup_from_seed(8,9, "Bracket Round of 16", 2, 1,weight) + rows << create_matchup_from_seed(5,12, "Bracket Round of 16", 3, 1,weight) + rows << create_matchup_from_seed(4,14, "Bracket Round of 16", 4, 1,weight) + rows << create_matchup_from_seed(3,13, "Bracket Round of 16", 5, 1,weight) + rows << create_matchup_from_seed(6,11, "Bracket Round of 16", 6, 1,weight) + rows << create_matchup_from_seed(7,10, "Bracket Round of 16", 7, 1,weight) + rows << create_matchup_from_seed(2,15, "Bracket Round of 16", 8, 1,weight) end - def round_two(weight) - create_matchup(nil,nil,"Quarter",1,2,weight) - create_matchup(nil,nil,"Quarter",2,2,weight) - create_matchup(nil,nil,"Quarter",3,2,weight) - create_matchup(nil,nil,"Quarter",4,2,weight) - create_matchup(nil,nil,"Conso Round of 8",1,2,weight) - create_matchup(nil,nil,"Conso Round of 8",2,2,weight) - create_matchup(nil,nil,"Conso Round of 8",3,2,weight) - create_matchup(nil,nil,"Conso Round of 8",4,2,weight) + def round_two(weight, rows) + rows << create_matchup(nil,nil,"Quarter",1,2,weight) + rows << create_matchup(nil,nil,"Quarter",2,2,weight) + rows << create_matchup(nil,nil,"Quarter",3,2,weight) + rows << create_matchup(nil,nil,"Quarter",4,2,weight) + rows << create_matchup(nil,nil,"Conso Round of 8",1,2,weight) + rows << create_matchup(nil,nil,"Conso Round of 8",2,2,weight) + rows << create_matchup(nil,nil,"Conso Round of 8",3,2,weight) + rows << create_matchup(nil,nil,"Conso Round of 8",4,2,weight) end - def round_three(weight) - create_matchup(nil,nil,"Semis",1,3,weight) - create_matchup(nil,nil,"Semis",2,3,weight) - create_matchup(nil,nil,"Conso Quarter",1,3,weight) - create_matchup(nil,nil,"Conso Quarter",2,3,weight) - create_matchup(nil,nil,"Conso Quarter",3,3,weight) - create_matchup(nil,nil,"Conso Quarter",4,3,weight) + def round_three(weight, rows) + rows << create_matchup(nil,nil,"Semis",1,3,weight) + rows << create_matchup(nil,nil,"Semis",2,3,weight) + rows << create_matchup(nil,nil,"Conso Quarter",1,3,weight) + rows << create_matchup(nil,nil,"Conso Quarter",2,3,weight) + rows << create_matchup(nil,nil,"Conso Quarter",3,3,weight) + rows << create_matchup(nil,nil,"Conso Quarter",4,3,weight) end - def round_four(weight) - create_matchup(nil,nil,"Conso Semis",1,4,weight) - create_matchup(nil,nil,"Conso Semis",2,4,weight) + def round_four(weight, rows) + rows << create_matchup(nil,nil,"Conso Semis",1,4,weight) + rows << create_matchup(nil,nil,"Conso Semis",2,4,weight) end - def round_five(weight) - create_matchup(nil,nil,"1/2",1,5,weight) - create_matchup(nil,nil,"3/4",1,5,weight) - create_matchup(nil,nil,"5/6",1,5,weight) + def round_five(weight, rows) + rows << create_matchup(nil,nil,"1/2",1,5,weight) + rows << create_matchup(nil,nil,"3/4",1,5,weight) + rows << create_matchup(nil,nil,"5/6",1,5,weight) if @number_of_placers >= 8 - create_matchup(nil,nil,"7/8",1,5,weight) + rows << create_matchup(nil,nil,"7/8",1,5,weight) end end def wrestler_with_seed(seed,weight) - wrestler = Wrestler.where("weight_id = ? and bracket_line = ?", weight.id, seed).first + wrestler = weight.wrestlers.find { |w| w.bracket_line == seed } if wrestler return wrestler.id else @@ -79,13 +84,18 @@ class ModifiedSixteenManMatchGeneration end def create_matchup(w1, w2, bracket_position, bracket_position_number,round,weight) - @tournament.matches.create( + { w1: w1, w2: w2, + tournament_id: @tournament.id, weight_id: weight.id, round: round, bracket_position: bracket_position, bracket_position_number: bracket_position_number - ) + } end -end \ No newline at end of file + + def generation_weights + @weights || @tournament.weights.to_a + end +end diff --git a/app/services/tournament_services/pool_bracket_generation.rb b/app/services/tournament_services/pool_bracket_generation.rb index e540123..d4ec0ef 100644 --- a/app/services/tournament_services/pool_bracket_generation.rb +++ b/app/services/tournament_services/pool_bracket_generation.rb @@ -12,18 +12,19 @@ class PoolBracketGeneration end def generateBracketMatches() + @rows = [] if @pool_bracket_type == "twoPoolsToSemi" - return twoPoolsToSemi() + twoPoolsToSemi() elsif @pool_bracket_type == "twoPoolsToFinal" - return twoPoolsToFinal() + twoPoolsToFinal() elsif @pool_bracket_type == "fourPoolsToQuarter" - return fourPoolsToQuarter() + fourPoolsToQuarter() elsif @pool_bracket_type == "fourPoolsToSemi" - return fourPoolsToSemi() + fourPoolsToSemi() elsif @pool_bracket_type == "eightPoolsToQuarter" - return eightPoolsToQuarter() + eightPoolsToQuarter() end - return [] + @rows end def twoPoolsToSemi() @@ -86,14 +87,15 @@ class PoolBracketGeneration end def createMatchup(w1_name, w2_name, bracket_position, bracket_position_number) - @tournament.matches.create( + @rows << { loser1_name: w1_name, loser2_name: w2_name, + tournament_id: @tournament.id, weight_id: @weight.id, round: @round, bracket_position: bracket_position, bracket_position_number: bracket_position_number - ) + } end end diff --git a/app/services/tournament_services/pool_generation.rb b/app/services/tournament_services/pool_generation.rb index b2bbad2..e7530bc 100644 --- a/app/services/tournament_services/pool_generation.rb +++ b/app/services/tournament_services/pool_generation.rb @@ -1,35 +1,46 @@ class PoolGeneration - def initialize(weight) + def initialize(weight, wrestlers: nil) @weight = weight @tournament = @weight.tournament @pool = 1 + @wrestlers = wrestlers end def generatePools - GeneratePoolNumbers.new(@weight).savePoolNumbers + GeneratePoolNumbers.new(@weight).savePoolNumbers(wrestlers: wrestlers_for_weight, persist: false) + rows = [] pools = @weight.pools while @pool <= pools - roundRobin + rows.concat(roundRobin) @pool += 1 end + rows end def roundRobin - wrestlers = @weight.wrestlers_in_pool(@pool) + rows = [] + wrestlers = wrestlers_for_weight.select { |w| w.pool == @pool } pool_matches = RoundRobinTournament.schedule(wrestlers).reverse pool_matches.each_with_index do |b, index| round = index + 1 bouts = b.map bouts.each do |bout| if bout[0] != nil and bout[1] != nil - @tournament.matches.create( + rows << { w1: bout[0].id, w2: bout[1].id, + tournament_id: @tournament.id, weight_id: @weight.id, bracket_position: "Pool", - round: round) + round: round + } end end end + rows end + + def wrestlers_for_weight + @wrestlers || @weight.wrestlers.to_a + end end diff --git a/app/services/tournament_services/pool_to_bracket_generate_loser_names.rb b/app/services/tournament_services/pool_to_bracket_generate_loser_names.rb index 3ed7a9a..5d221f0 100644 --- a/app/services/tournament_services/pool_to_bracket_generate_loser_names.rb +++ b/app/services/tournament_services/pool_to_bracket_generate_loser_names.rb @@ -1,80 +1,97 @@ class PoolToBracketGenerateLoserNames - def initialize( tournament ) - @tournament = tournament - end - - def assignLoserNamesWeight(weight) - matches_by_weight = @tournament.matches.where(weight_id: weight.id) - if weight.pool_bracket_type == "twoPoolsToSemi" - twoPoolsToSemiLoser(matches_by_weight) - elsif (weight.pool_bracket_type == "fourPoolsToQuarter") or (weight.pool_bracket_type == "eightPoolsToQuarter") - fourPoolsToQuarterLoser(matches_by_weight) - elsif weight.pool_bracket_type == "fourPoolsToSemi" - fourPoolsToSemiLoser(matches_by_weight) - end - saveMatches(matches_by_weight) + def initialize(tournament) + @tournament = tournament end - def assignLoserNames - matches_by_weight = nil - @tournament.weights.each do |w| - matches_by_weight = @tournament.matches.where(weight_id: w.id) - if w.pool_bracket_type == "twoPoolsToSemi" - twoPoolsToSemiLoser(matches_by_weight) - elsif (w.pool_bracket_type == "fourPoolsToQuarter") or (w.pool_bracket_type == "eightPoolsToQuarter") - fourPoolsToQuarterLoser(matches_by_weight) - elsif w.pool_bracket_type == "fourPoolsToSemi" - fourPoolsToSemiLoser(matches_by_weight) - end - saveMatches(matches_by_weight) - end - end - - def twoPoolsToSemiLoser(matches_by_weight) - match1 = matches_by_weight.select{|m| m.loser1_name == "Winner Pool 1"}.first - match2 = matches_by_weight.select{|m| m.loser1_name == "Winner Pool 2"}.first - matchChange = matches_by_weight.select{|m| m.bracket_position == "3/4"}.first - matchChange.loser1_name = "Loser of #{match1.bout_number}" - matchChange.loser2_name = "Loser of #{match2.bout_number}" + # Compatibility wrapper. Returns transformed rows and does not persist. + def assignLoserNamesWeight(weight, match_rows = nil) + rows = match_rows || @tournament.matches.where(weight_id: weight.id).map { |m| m.attributes.symbolize_keys } + assign_loser_names_in_memory(weight, rows) + rows end - def fourPoolsToQuarterLoser(matches_by_weight) - quarters = matches_by_weight.select{|m| m.bracket_position == "Quarter"} - consoSemis = matches_by_weight.select{|m| m.bracket_position == "Conso Semis"} - semis = matches_by_weight.select{|m| m.bracket_position == "Semis"} - thirdFourth = matches_by_weight.select{|m| m.bracket_position == "3/4"}.first - seventhEighth = matches_by_weight.select{|m| m.bracket_position == "7/8"}.first - consoSemis.each do |m| - if m.bracket_position_number == 1 - m.loser1_name = "Loser of #{quarters.select{|m| m.bracket_position_number == 1}.first.bout_number}" - m.loser2_name = "Loser of #{quarters.select{|m| m.bracket_position_number == 2}.first.bout_number}" - elsif m.bracket_position_number == 2 - m.loser1_name = "Loser of #{quarters.select{|m| m.bracket_position_number == 3}.first.bout_number}" - m.loser2_name = "Loser of #{quarters.select{|m| m.bracket_position_number == 4}.first.bout_number}" + # Compatibility wrapper. Returns transformed rows and does not persist. + def assignLoserNames + @tournament.weights.each_with_object([]) do |weight, all_rows| + all_rows.concat(assignLoserNamesWeight(weight)) + end + end + + def assign_loser_names_in_memory(weight, match_rows) + rows = match_rows.select { |row| row[:weight_id] == weight.id } + if weight.pool_bracket_type == "twoPoolsToSemi" + two_pools_to_semi_loser_rows(rows) + elsif (weight.pool_bracket_type == "fourPoolsToQuarter") || (weight.pool_bracket_type == "eightPoolsToQuarter") + four_pools_to_quarter_loser_rows(rows) + elsif weight.pool_bracket_type == "fourPoolsToSemi" + four_pools_to_semi_loser_rows(rows) + end + end + + def two_pools_to_semi_loser_rows(rows) + match1 = rows.find { |m| m[:loser1_name] == "Winner Pool 1" } + match2 = rows.find { |m| m[:loser1_name] == "Winner Pool 2" } + match_change = rows.find { |m| m[:bracket_position] == "3/4" } + return unless match1 && match2 && match_change + + match_change[:loser1_name] = "Loser of #{match1[:bout_number]}" + match_change[:loser2_name] = "Loser of #{match2[:bout_number]}" + end + + def four_pools_to_quarter_loser_rows(rows) + quarters = rows.select { |m| m[:bracket_position] == "Quarter" } + conso_semis = rows.select { |m| m[:bracket_position] == "Conso Semis" } + semis = rows.select { |m| m[:bracket_position] == "Semis" } + third_fourth = rows.find { |m| m[:bracket_position] == "3/4" } + seventh_eighth = rows.find { |m| m[:bracket_position] == "7/8" } + + conso_semis.each do |m| + if m[:bracket_position_number] == 1 + q1 = quarters.find { |q| q[:bracket_position_number] == 1 } + q2 = quarters.find { |q| q[:bracket_position_number] == 2 } + m[:loser1_name] = "Loser of #{q1[:bout_number]}" if q1 + m[:loser2_name] = "Loser of #{q2[:bout_number]}" if q2 + elsif m[:bracket_position_number] == 2 + q3 = quarters.find { |q| q[:bracket_position_number] == 3 } + q4 = quarters.find { |q| q[:bracket_position_number] == 4 } + m[:loser1_name] = "Loser of #{q3[:bout_number]}" if q3 + m[:loser2_name] = "Loser of #{q4[:bout_number]}" if q4 end end - thirdFourth.loser1_name = "Loser of #{semis.select{|m| m.bracket_position_number == 1}.first.bout_number}" - thirdFourth.loser2_name = "Loser of #{semis.select{|m| m.bracket_position_number == 2}.first.bout_number}" - consoSemis = matches_by_weight.select{|m| m.bracket_position == "Conso Semis"} - seventhEighth.loser1_name = "Loser of #{consoSemis.select{|m| m.bracket_position_number == 1}.first.bout_number}" - seventhEighth.loser2_name = "Loser of #{consoSemis.select{|m| m.bracket_position_number == 2}.first.bout_number}" + + if third_fourth + s1 = semis.find { |s| s[:bracket_position_number] == 1 } + s2 = semis.find { |s| s[:bracket_position_number] == 2 } + third_fourth[:loser1_name] = "Loser of #{s1[:bout_number]}" if s1 + third_fourth[:loser2_name] = "Loser of #{s2[:bout_number]}" if s2 + end + + if seventh_eighth + c1 = conso_semis.find { |c| c[:bracket_position_number] == 1 } + c2 = conso_semis.find { |c| c[:bracket_position_number] == 2 } + seventh_eighth[:loser1_name] = "Loser of #{c1[:bout_number]}" if c1 + seventh_eighth[:loser2_name] = "Loser of #{c2[:bout_number]}" if c2 + end end - def fourPoolsToSemiLoser(matches_by_weight) - semis = matches_by_weight.select{|m| m.bracket_position == "Semis"} - thirdFourth = matches_by_weight.select{|m| m.bracket_position == "3/4"}.first - consoSemis = matches_by_weight.select{|m| m.bracket_position == "Conso Semis"} - seventhEighth = matches_by_weight.select{|m| m.bracket_position == "7/8"}.first - thirdFourth.loser1_name = "Loser of #{semis.select{|m| m.bracket_position_number == 1}.first.bout_number}" - thirdFourth.loser2_name = "Loser of #{semis.select{|m| m.bracket_position_number == 2}.first.bout_number}" - seventhEighth.loser1_name = "Loser of #{consoSemis.select{|m| m.bracket_position_number == 1}.first.bout_number}" - seventhEighth.loser2_name = "Loser of #{consoSemis.select{|m| m.bracket_position_number == 2}.first.bout_number}" + def four_pools_to_semi_loser_rows(rows) + semis = rows.select { |m| m[:bracket_position] == "Semis" } + conso_semis = rows.select { |m| m[:bracket_position] == "Conso Semis" } + third_fourth = rows.find { |m| m[:bracket_position] == "3/4" } + seventh_eighth = rows.find { |m| m[:bracket_position] == "7/8" } + + if third_fourth + s1 = semis.find { |s| s[:bracket_position_number] == 1 } + s2 = semis.find { |s| s[:bracket_position_number] == 2 } + third_fourth[:loser1_name] = "Loser of #{s1[:bout_number]}" if s1 + third_fourth[:loser2_name] = "Loser of #{s2[:bout_number]}" if s2 + end + + if seventh_eighth + c1 = conso_semis.find { |c| c[:bracket_position_number] == 1 } + c2 = conso_semis.find { |c| c[:bracket_position_number] == 2 } + seventh_eighth[:loser1_name] = "Loser of #{c1[:bout_number]}" if c1 + seventh_eighth[:loser2_name] = "Loser of #{c2[:bout_number]}" if c2 + end end - - def saveMatches(matches) - matches.each do |m| - m.save! - end - end - -end \ No newline at end of file +end diff --git a/app/services/tournament_services/pool_to_bracket_match_generation.rb b/app/services/tournament_services/pool_to_bracket_match_generation.rb index c3e6dc6..07710fa 100644 --- a/app/services/tournament_services/pool_to_bracket_match_generation.rb +++ b/app/services/tournament_services/pool_to_bracket_match_generation.rb @@ -1,44 +1,92 @@ class PoolToBracketMatchGeneration - def initialize( tournament ) + def initialize(tournament, weights: nil, wrestlers_by_weight_id: nil) @tournament = tournament + @weights = weights + @wrestlers_by_weight_id = wrestlers_by_weight_id end def generatePoolToBracketMatches - @tournament.weights.order(:max).each do |weight| - PoolGeneration.new(weight).generatePools() - last_match = @tournament.matches.where(weight: weight).order(round: :desc).limit(1).first - highest_round = last_match.round - PoolBracketGeneration.new(weight, highest_round).generateBracketMatches() + rows = [] + generation_weights.each do |weight| + wrestlers = wrestlers_for_weight(weight) + pool_rows = PoolGeneration.new(weight, wrestlers: wrestlers).generatePools + rows.concat(pool_rows) + + highest_round = pool_rows.map { |row| row[:round] }.max || 0 + bracket_rows = PoolBracketGeneration.new(weight, highest_round).generateBracketMatches + rows.concat(bracket_rows) end - movePoolSeedsToFinalPoolRound + + movePoolSeedsToFinalPoolRound(rows) + rows end - def movePoolSeedsToFinalPoolRound - @tournament.weights.each do |w| - setOriginalSeedsToWrestleLastPoolRound(w) + def movePoolSeedsToFinalPoolRound(match_rows) + generation_weights.each do |w| + setOriginalSeedsToWrestleLastPoolRound(w, match_rows) end end - def setOriginalSeedsToWrestleLastPoolRound(weight) + def setOriginalSeedsToWrestleLastPoolRound(weight, match_rows) pool = 1 - until pool > weight.pools - wrestler1 = weight.pool_wrestlers_sorted_by_bracket_line(pool).first - wrestler2 = weight.pool_wrestlers_sorted_by_bracket_line(pool).second - match = wrestler1.pool_matches.sort_by{|m| m.round}.last - if match.w1 != wrestler2.id or match.w2 != wrestler2.id - if match.w1 == wrestler1.id - SwapWrestlers.new.swap_wrestlers_bracket_lines(match.w2,wrestler2.id) - elsif match.w2 == wrestler1.id - SwapWrestlers.new.swap_wrestlers_bracket_lines(match.w1,wrestler2.id) - end + wrestlers = wrestlers_for_weight(weight) + weight_pools = weight.pools + until pool > weight_pools + pool_wrestlers = wrestlers.select { |w| w.pool == pool }.sort_by(&:bracket_line) + wrestler1 = pool_wrestlers.first + wrestler2 = pool_wrestlers.second + if wrestler1 && wrestler2 + pool_matches = match_rows.select { |row| row[:weight_id] == weight.id && row[:bracket_position] == "Pool" && (row[:w1] == wrestler1.id || row[:w2] == wrestler1.id) } + match = pool_matches.max_by { |row| row[:round] } + if match && (match[:w1] != wrestler2.id || match[:w2] != wrestler2.id) + if match[:w1] == wrestler1.id + swap_wrestlers_in_memory(match_rows, wrestlers, match[:w2], wrestler2.id) + elsif match[:w2] == wrestler1.id + swap_wrestlers_in_memory(match_rows, wrestlers, match[:w1], wrestler2.id) + end + end end pool += 1 end end - - + + def swap_wrestlers_in_memory(match_rows, wrestlers, wrestler1_id, wrestler2_id) + w1 = wrestlers.find { |w| w.id == wrestler1_id } + w2 = wrestlers.find { |w| w.id == wrestler2_id } + return unless w1 && w2 + + w1_bracket_line, w1_pool = w1.bracket_line, w1.pool + w1.bracket_line, w1.pool = w2.bracket_line, w2.pool + w2.bracket_line, w2.pool = w1_bracket_line, w1_pool + + swap_match_rows(match_rows, wrestler1_id, wrestler2_id) + end + + def swap_match_rows(match_rows, wrestler1_id, wrestler2_id) + match_rows.each do |row| + row[:w1] = swap_id(row[:w1], wrestler1_id, wrestler2_id) + row[:w2] = swap_id(row[:w2], wrestler1_id, wrestler2_id) + row[:winner_id] = swap_id(row[:winner_id], wrestler1_id, wrestler2_id) + end + end + + def swap_id(value, wrestler1_id, wrestler2_id) + return wrestler2_id if value == wrestler1_id + return wrestler1_id if value == wrestler2_id + + value + end + + def generation_weights + @weights || @tournament.weights.order(:max).to_a + end + + def wrestlers_for_weight(weight) + @wrestlers_by_weight_id&.fetch(weight.id, nil) || weight.wrestlers.to_a + end + def assignLoserNames PoolToBracketGenerateLoserNames.new(@tournament).assignLoserNames end -end \ No newline at end of file +end diff --git a/app/services/tournament_services/tournament_seeding.rb b/app/services/tournament_services/tournament_seeding.rb index 3219bbd..890d0b4 100644 --- a/app/services/tournament_services/tournament_seeding.rb +++ b/app/services/tournament_services/tournament_seeding.rb @@ -3,16 +3,22 @@ class TournamentSeeding @tournament = tournament end - def set_seeds - @tournament.weights.each do |weight| + def set_seeds(weights: nil, persist: true) + weights_to_seed = weights || @tournament.weights.includes(:wrestlers) + updated_wrestlers = [] + + weights_to_seed.each do |weight| wrestlers = weight.wrestlers bracket_size = weight.calculate_bracket_size wrestlers = reset_bracket_line_for_lines_higher_than_bracket_size(wrestlers, bracket_size) wrestlers = set_original_seed_to_bracket_line(wrestlers) wrestlers = random_seeding(wrestlers, bracket_size) - wrestlers.each(&:save) + updated_wrestlers.concat(wrestlers) end + + persist_bracket_lines(updated_wrestlers) if persist + updated_wrestlers end def random_seeding(wrestlers, bracket_size) @@ -96,4 +102,19 @@ class TournamentSeeding end result end -end \ No newline at end of file + + def persist_bracket_lines(wrestlers) + return if wrestlers.blank? + + timestamp = Time.current + updates = wrestlers.map do |wrestler| + { + id: wrestler.id, + bracket_line: wrestler.bracket_line, + updated_at: timestamp + } + end + + Wrestler.upsert_all(updates) + end +end diff --git a/app/services/weight_services/generate_pool_numbers.rb b/app/services/weight_services/generate_pool_numbers.rb index dbed187..4a92882 100644 --- a/app/services/weight_services/generate_pool_numbers.rb +++ b/app/services/weight_services/generate_pool_numbers.rb @@ -3,11 +3,13 @@ class GeneratePoolNumbers @weight = weight end - def savePoolNumbers - @weight.wrestlers.each do |wrestler| + def savePoolNumbers(wrestlers: nil, persist: true) + wrestlers_to_update = wrestlers || @weight.wrestlers.to_a + wrestlers_to_update.each do |wrestler| wrestler.pool = get_wrestler_pool_number(@weight.pools, wrestler.bracket_line) - wrestler.save end + persist_pool_numbers(wrestlers_to_update) if persist + wrestlers_to_update end def get_wrestler_pool_number(number_of_pools, wrestler_seed) @@ -36,4 +38,20 @@ class GeneratePoolNumbers pool end -end \ No newline at end of file + + private + + def persist_pool_numbers(wrestlers) + return if wrestlers.blank? + + timestamp = Time.current + rows = wrestlers.map do |w| + { + id: w.id, + pool: w.pool, + updated_at: timestamp + } + end + Wrestler.upsert_all(rows) + end +end