# frozen_string_literal: true module EuchreCamp module Services class TournamentGenerator # Generate matchups for round-robin tournament # Uses circle method for scheduling def self.round_robin(teams) n = teams.length return [] if n < 2 # Handle odd number of teams by adding a bye teams_with_bye = teams.dup bye_team = { id: nil, name: "BYE" } teams_with_bye << bye_team if n.odd? total_teams = teams_with_bye.length rounds = [] # Circle method: fix first team, rotate others (total_teams - 1).times do |round_num| round_matchups = [] half = total_teams / 2 half.times do |i| team_a = teams_with_bye[i] team_b = teams_with_bye[total_teams - 1 - i] # Skip if either is BYE next if team_a[:id].nil? || team_b[:id].nil? round_matchups << { team_1_id: team_a[:id], team_2_id: team_b[:id] } end rounds << { round_number: round_num + 1, matchups: round_matchups } # Rotate teams (circle method) teams_with_bye = [teams_with_bye[0]] + teams_with_bye[1..-1].rotate(-1) end rounds end # Generate single elimination bracket def self.single_elimination(seeded_teams) # seeded_teams should be array of { id: team_id, seed: number } teams = seeded_teams.sort_by { |t| t[:seed] || 0 } # Ensure power of 2 or add byes bracket_size = 1 while bracket_size < teams.length bracket_size *= 2 end # Create bracket structure bracket = [] positions = {} # Calculate bracket positions (1..bracket_size).each do |pos| positions[pos] = pos end # Fill bracket with teams and byes team_index = 0 (1..bracket_size).each do |pos| if team_index < teams.length positions[pos] = teams[team_index][:id] team_index += 1 else positions[pos] = nil # BYE end end # Generate matchups for first round matchups = [] num_matchups = bracket_size / 2 num_matchups.times do |i| pos1 = i * 2 + 1 pos2 = i * 2 + 2 team1 = positions[pos1] team2 = positions[pos2] # Only add matchup if both teams exist (no BYE vs BYE) unless team1.nil? && team2.nil? matchups << { team_1_id: team1, team_2_id: team2, bracket_position: i + 1, winner_position: (i / 2) + 1 # Where winner advances to } end end { bracket_size: bracket_size, first_round_matchups: matchups, bracket_positions: positions } end # Generate double elimination bracket def self.double_elimination(seeded_teams) # For simplicity, returning structure for winner's bracket and loser's bracket teams = seeded_teams.sort_by { |t| t[:seed] || 0 } # Generate winner's bracket (same as single elim) winners_bracket = single_elimination(teams) # Loser's bracket structure (simplified) # In full implementation, this would be more complex loser_bracket_rounds = [] { winners_bracket: winners_bracket, loser_bracket: loser_bracket_rounds } end # Generate Swiss system pairings def self.swiss(teams, round_number, standings = {}) # standings should be hash of team_id => { wins, losses, points } return [] if teams.empty? # Sort teams by current score (descending) sorted_teams = teams.sort_by do |team| score = standings[team[:id]] || { wins: 0, losses: 0, points: 0 } [-score[:wins], -score[:points]] end # Pair similar scores matchups = [] paired = Set.new sorted_teams.each_with_index do |team, i| next if paired.include?(team[:id]) # Find opponent with similar score opponent = nil (i + 1...sorted_teams.length).each do |j| candidate = sorted_teams[j] unless paired.include?(candidate[:id]) opponent = candidate break end end if opponent matchups << { team_1_id: team[:id], team_2_id: opponent[:id] } paired << team[:id] paired << opponent[:id] end end [{ round_number: round_number, matchups: matchups }] end # Calculate number of matches for each format def self.match_count(format, participant_count) case format when 'round_robin' participant_count * (participant_count - 1) / 2 when 'single_elim' participant_count - 1 when 'double_elim' (participant_count * 2) - 2 when 'swiss' # Assuming log2(participant_count) rounds rounds = Math.log2(participant_count).ceil (participant_count * rounds) / 2 else 0 end end # Validate participant count for format def self.valid_participant_count?(format, count) case format when 'round_robin' count >= 2 when 'single_elim', 'double_elim' # Must be power of 2 for pure bracket, or allow byes count >= 2 when 'swiss' count >= 2 else false end end end end end