### Shuffling and Stacking the Deck

Posted Feb 22, 2017 in category Rails and golf

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:

- Layout cards in order: [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16]
- Divide cards into 4 groups: [1 2 3 4] [5 6 7 8] [9 10 11 12] [13 14 15 16]
- 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]
- 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:

- Layout cards in order: [1 2 3 4 5 6 7 8 9 10 11 12 13 14]
- Divide cards into 4 groups: [1 2 3 4] [5 6 7 8] [9 10 11 12] [13 14]
- Reverse the order of the 2nd and 4th group: [1 2 3 4] [8 7 6 5] [9 10 11 12] [14 13]
- Since groups are not even, save of odd group: [14 13]
- Teams.times, draw one card from remaining group in order: [1 8 9] [2 7 10] [3 5 11] [4 5 12]
- 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!