I mentioned that I considered myself a slightly above average programmer. That maybe a stretch with Ruby. I don’t know Ruby as good as I should for the amount of time I’ve been using it. With the exception of GOTOs, you can write Ruby code that looks like BASIC - full of IF ELSE’s that tunnel down into an unreadable, unmanageable mess. I started that way, but I have gotten a lot better.

In my many versions of my golf group management application (PtGolf or GolfGaggle) I’ve spent an exorbitant amount of time in three areas:

  • Forming Teams
  • Scoring Teams
  • Paying Teams

I’ll deal with forming teams. If you play with a group that keeps scores by a “Points” scoring method, you usually play with more than your normal foursome, and the game is usually between teams. Even though the point system should allow me to have a competitive game with Tiger Woods, the trend is to make up teams that are as equal has you can (damn liberals!). Equal being not all the best or worst players are on the same team, but a mixture of what is often referred to as A, B, C and D players. A’s being the players who have the lowest handicap (or highest points to pull) and D’s being the highest handicappers (lowest points to pull). So how do you do that? I’ve tried many ways in the past that worked, but in looking at the code several months after I wrote it, I wondered “How in the hell does this work???”. The last few attempts of refactoring the concept I’ve zeroed in on a “Deck of Cards” analogy.

Years ago, when golf was thriving at a course I played at, we’d have 3 or 4 ABCD scrambles a year where nearly 100 players would sign up. You didn’t play with you normal group, you had an ABCD draw. After registering you’d get half of a raffle ticket. The course would take the other half and put them into 4 buckets (A, B, C and D) based on your handicap. (they had to do some form of sorting!).

The course would draw an A ticket and then the A player would draw a tickets from the other three buckets. That was a team. This was also a little bit of a social event in that everyone was gathered for the drawing. Let’s say Benny was the A player called to draw his team - there would be a hush because Benny was an asshole - hope he doesn’t pick me. When Mike was drawing it was the opposite reaction - hope he picks me. The teams would be picked and everyone played with people they may have not known, but most had a good time (except Benny’s team - if he was his normal self!).

The group I played with at that time used a similar technique. When you showed up and paid your dues, there’d be a number by your name. Either before or after the game you’d use numbered cards or poker chips to draw the teams.

The ABCD draw was a semi-random draw. The A player may have drawn the best (or worst) B, C and D players. Again, it should not make any difference, but we can stack the deck so that does not happen. Lets say we have 16 players show up for a game. Nice number - 4 foursomes. Using a 16 cards numbered from 1 to 16 I can do this:

  1. Layout cards in order: [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16]
  2. Divide cards into 4 groups: [1 2 3 4] [5 6 7 8] [9 10 11 12] [13 14 15 16]
  3. Reverse the order of the 2nd and the 4th group: [1 2 3 4] [8 7 6 5] [9 10 11 12] [16 15 14 13]
  4. Draw one card from each group in order into 4 groups/teams: [1 8 9 16] [2 7 10 15] [3 5 11 14] [4 5 12 13]

This would be a ‘seeded’ draw. The highest A player plays with the lowest B player, the highest C player and the lowest D player.

We could also do an ABCD draw. Just draw at random one card from each group.

How about just a random draw? Shuffle the cards first, then divide into the four groups and draw one from each group.

Then there’s what I call and ABCD Battle. To prove that Points are Points. Make up the teams using #2 above where the A players are team 1 and the D players are team 4. If you put your money on the A players will always win, or the D players will always win - you’d probably lose three out of four times - that’s why they call it Golf!

If only 14 players show up there still would be 4 teams, but there would be 2 threesomes and 2 foursomes. Just a slight change in the concept:

  1. Layout cards in order: [1 2 3 4 5 6 7 8 9 10 11 12 13 14]
  2. Divide cards into 4 groups: [1 2 3 4] [5 6 7 8] [9 10 11 12] [13 14]
  3. Reverse the order of the 2nd and 4th group: [1 2 3 4] [8 7 6 5] [9 10 11 12] [14 13]
  4. Since groups are not even, save of odd group: [14 13]
  5. Teams.times, draw one card from remaining group in order: [1 8 9] [2 7 10] [3 5 11] [4 5 12]
  6. Either random pop from saved group or place in highest seeds: [1 8 9 14] [2 7 10 13] [3 5 11] [4 5 12]

The above technique is turned into the class Forming below. Method ‘options’ returns the valid forming options based on the number of players that show up. Method ‘new_suffle’ forms the teams based on the option picked. The array ‘teams’ is then mapped to players participating in the event that are sorted by their quota (how many points they have to pull).

class Forming

  attr_accessor  :numb_players,:teams, :options, :numb_teams
  TeamMethods = [:individuals,:twosomes,:threesomes,:foursomes,:mixed34,:mixed23,:assigned]
  FormingMethods = [:seeded,:random,:draw,:abcd_battle]

  def options(numb_players)
    @numb_players = numb_players
    @options = {}
    TeamMethods.each do |method|
      response = send(method)
      @options[method] = response if response.present?
    end
    @options
  end

  def new_shuffle(team_option,numb_players,forming_method)
    @teams = nil # default to error - highly unlikly in production - mainily for testing
    team_option = team_option.to_sym # comes in as string from form
    forming_method = forming_method.to_sym # comes in as string from form
    return nil unless valid?(team_option,forming_method)
    @numb_players = numb_players
    cards = stack_deck(team_option,forming_method) # also sets numb_teams
    return cards if cards.nil? # error with team_options for number of players
    return teams if teams.present?  # team options was indivdual or assigned, no seeding needed
    abcd = set_seeds(cards)
    send(forming_method,abcd) # sets teams from seeds based on method
    teams.sort! { |x,y| x.count <=> y.count }
    teams.each{|t| t.sort!}
    return teams
  end

  private

  def valid?(team_option,forming_method)
    valid = TeamMethods.include?(team_option) && FormingMethods.include?(forming_method) 
  end

  def stack_deck(team_option,forming_method)
    cards = (1..numb_players).to_a
    cards.shuffle! if forming_method == :random
    option = send(team_option)
    return option if option.blank?
    @numb_teams = option.map{|k,v| v}.sum
    if numb_teams == numb_players # team options was indivdual or assigned, no stacking needed
        @teams = players.mao{|p| [p]}
    end
    cards
  end    

  def set_seeds(cards)
    abcd = []
    numb_teams.times{abcd << cards.shift(numb_teams)}
    abcd << cards # There may be more cards (6 players threesomes 2 teams)
    abcd.delete([])
    abcd
  end

  def seeds_to_teams(abcd)
    @teams = []
    @numb_teams.times{@teams << []}
    if abcd.first.count != abcd.last.count
      @last_seed = abcd.pop
    end
    abcd.each do |seed|
      0.upto(@numb_teams - 1) do |t|
        @teams[t] << seed.shift
      end
    end
    if @last_seed.present?
      dplayers = (0..(@numb_teams -1)).to_a.sample(@last_seed.count)
      dplayers.each{|i| @teams[i] << @last_seed.pop}
    end
    @teams.sort! { |x,y| x.count <=> y.count }
  end

  # this are the forming methods. They modify the stacked deck if needed

  def seeded(abcd)
    # highest A player get lowest B and D players and highest C player
    # lowest A player get highest B and D players and lowest C player

    abcd.each_with_index do |t,i|
      unless (i % 2).zero?
        t.reverse!
      end
    end
    seeds_to_teams(abcd)
   end

  def random(abcd)
    # cards already shuffled if random forming
    seeds_to_teams(abcd)
  end

  def draw(abcd)
    # A players draw random B,C and D players
    abcd.each_with_index do |t|
      t.shuffle!
    end
    seeds_to_teams(abcd)
  end

  def abcd_battle(abcd)
    # Teams made up of A,B,C, and D players. It points, I should be able to compete with Tiger Woods
    players = abcd.flatten
    seeds_to_teams(abcd) 
    nteams = []
    @teams.each do |t|
      nteams << players.shift(t.count)
    end
    @teams = nteams
  end

  # Following methods define the valid team makups that are possible based on the number 
  # of players participating in an event/game. All these methods are called form the options
  # method that provides the valid options. The method selected from method new_suffle is
  # called to validate the option

  def individuals
    # number of teams = numbe of players - individual play
    {individual:numb_players}
  end

  def assigned
    # group makes up their own teams 
    {assigned:numb_players}
  end

  def twosomes
    return nil if numb_players < 4
    numb_players.modulo(2).zero? ? {twosome:numb_players / 2} : nil
  end

  def threesomes
    return nil if numb_players < 6
    numb_players.modulo(3).zero? ? {threesome:numb_players / 3} : nil
  end

  def foursomes
    return nil if numb_players < 8
    numb_players.modulo(4).zero? ? {foursome:numb_players / 4} : nil
  end

  def mixed23
    return nil if twosomes.present? || foursomes.present? || numb_players < 5
    s3 = (numb_players / 3) - (3 - numb_players.modulo(3)) + 1
    return nil if s3*3 == numb_players
    s2 = (numb_players - (s3 * 3)) / 2
    {twosome:s2,threesome:s3}
  end

  def mixed34
    return nil if foursomes.present? || numb_players < 7
    s4 = (numb_players / 4) - (4 - numb_players.modulo(4)) + 1
    return nil if s4*4 == numb_players || s4.zero?
    s3 = (numb_players - (s4 * 4)) / 3
    {threesome:s3,foursome:s4}
  end

end

My problems with this in the past is my ‘Old School’ background. I knew some of what needed to be done so I started to code. Then something didn’t work and I started adding conditions. This is still not pure, but I’ll show you one of the past version if you’d like!