Everyone knows engineers can’t write! I
is
an
engineeer!
But...
I Wish I Could Write!
Back to articles
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] 3. Since groups are not even, save of odd group: [14 13] 4. Teams.times, draw one card from remaining group in order: [1 8 9] [2 7 10] [3 5 11] [4 5 12] 5. 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). ```ruby 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! .md
Shuffling and Stacking the Deck
February 23, 2017 02:10