ruby-poker 0.3.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG +14 -9
- data/README.rdoc +11 -7
- data/Rakefile +6 -67
- data/examples/deck.rb +3 -3
- data/lib/ruby-poker.rb +0 -1
- data/lib/ruby-poker/card.rb +56 -56
- data/lib/ruby-poker/poker_hand.rb +191 -90
- data/ruby-poker.gemspec +20 -22
- data/test/test_card.rb +53 -20
- data/test/test_helper.rb +3 -4
- data/test/test_poker_hand.rb +344 -34
- metadata +44 -44
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: 9070959501edf045c9d745325c51573314ecbc11
         | 
| 4 | 
            +
              data.tar.gz: 8616e190cbcce32c2ad4f9455df760f0649fbb59
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: f0dae2e671b57b3c98d092b200544b8e4423adedf4d66a4c360f202e2df39b9ef96f1e62b66286c1a78d1bd8f2cbd4f335df2c8831bffbd14fbccf292bc6a0ba
         | 
| 7 | 
            +
              data.tar.gz: deb4dba594571df9ec74a1b368f11d86e10db12003bc849cecf19810eed73d94c252d14c4d77354332399d3876dfb2c5bfd3ef0b8346d0badc893019ddf001bc
         | 
    
        data/CHANGELOG
    CHANGED
    
    | @@ -1,3 +1,8 @@ | |
| 1 | 
            +
            2013-12-28 (1.0.0)
         | 
| 2 | 
            +
                * Add Gemfile
         | 
| 3 | 
            +
                * Add "Empty Hand" rank for empty hands. Fixes #1.
         | 
| 4 | 
            +
                * Simplify internal calculation of `value` in Card class. Fixes #2.
         | 
| 5 | 
            +
             | 
| 1 6 | 
             
            2009-07-12 (0.3.2)
         | 
| 2 7 | 
             
                * Reorganized ruby-poker's lib folder to match the standard layout for gems. This makes ruby-poker compatible with Rip.
         | 
| 3 8 | 
             
                * Bug [#26276] improper two_pair? behavior. Applied patch by Uro.
         | 
| @@ -6,7 +11,7 @@ | |
| 6 11 |  | 
| 7 12 | 
             
            2009-01-24 (0.3.1)
         | 
| 8 13 | 
             
                * Bug [#23623] undefined method <=> for nil:NilClass
         | 
| 9 | 
            -
             | 
| 14 | 
            +
             | 
| 10 15 | 
             
            2008-12-30 (0.3.1)
         | 
| 11 16 | 
             
                * Bug (#20407) Raise an exception when creating a new hand with duplicates
         | 
| 12 17 | 
             
                * Added PokerHand#uniq method
         | 
| @@ -18,34 +23,34 @@ | |
| 18 23 | 
             
                * Bug [#20196] 'rank' goes into an infinite loop.
         | 
| 19 24 | 
             
                * Bug [#20195] Allows the same card to be entered into the hand.
         | 
| 20 25 | 
             
                * Bug [#20344] sort_using_rank does not return expected results
         | 
| 21 | 
            -
             | 
| 26 | 
            +
             | 
| 22 27 | 
             
            2008-04-20 (0.2.4)
         | 
| 23 28 | 
             
                * Modernized the Rakefile
         | 
| 24 29 | 
             
                * Updated to be compatible with Ruby 1.9    
         | 
| 25 | 
            -
             | 
| 30 | 
            +
             | 
| 26 31 | 
             
            2008-04-06 (0.2.2)
         | 
| 27 32 | 
             
                * Fixed bug where two hands that had the same values but different suits returned not equal    
         | 
| 28 | 
            -
             | 
| 33 | 
            +
             | 
| 29 34 | 
             
            2008-02-08 (0.2.1)
         | 
| 30 35 | 
             
                * Cards can be added to a hand after it is created by using (<<) on a PokerHand
         | 
| 31 36 | 
             
                * Cards can be deleted from a hand with PokerHand.delete()
         | 
| 32 | 
            -
             | 
| 37 | 
            +
             | 
| 33 38 | 
             
            2008-01-21 (0.2.0)
         | 
| 34 39 | 
             
                * Merged Patrick Hurley's poker solver
         | 
| 35 40 | 
             
                * Added support for hands with >5 cards
         | 
| 36 41 | 
             
                * Straights with a low Ace count now
         | 
| 37 42 | 
             
                * to_s on a PokerHand now includes the rank after the card list
         | 
| 38 43 | 
             
                * Finally wrote the Unit Tests suite      
         | 
| 39 | 
            -
             | 
| 44 | 
            +
             | 
| 40 45 | 
             
            2008-01-12 (0.1.2)
         | 
| 41 | 
            -
                * Fixed critical bug that was  | 
| 46 | 
            +
                * Fixed critical bug that was causing the whole program to not work
         | 
| 42 47 | 
             
                * Added some test cases as a result
         | 
| 43 48 | 
             
                * More test cases coming soon      
         | 
| 44 | 
            -
             | 
| 49 | 
            +
             | 
| 45 50 | 
             
            2008-01-12 (0.1.1)
         | 
| 46 51 | 
             
                * Ranks are now a class.
         | 
| 47 52 | 
             
                * Extracted card, rank, and arrays methods to individual files
         | 
| 48 53 | 
             
                * Added gem packaging
         | 
| 49 | 
            -
             | 
| 54 | 
            +
             | 
| 50 55 | 
             
            2008-01-10 (0.1.0)
         | 
| 51 56 | 
             
                * Initial version
         | 
    
        data/README.rdoc
    CHANGED
    
    | @@ -1,9 +1,9 @@ | |
| 1 1 | 
             
            = Poker library in Ruby
         | 
| 2 2 | 
             
            ===
         | 
| 3 3 |  | 
| 4 | 
            -
            Author:: {Rob Olson}[ | 
| 4 | 
            +
            Author:: {Rob Olson}[https://github.com/robolson]
         | 
| 5 5 | 
             
            Email:: [first name] [at] thinkingdigitally.com
         | 
| 6 | 
            -
            GitHub::  | 
| 6 | 
            +
            GitHub:: https://github.com/robolson/ruby-poker
         | 
| 7 7 |  | 
| 8 8 | 
             
            == Description
         | 
| 9 9 |  | 
| @@ -13,13 +13,13 @@ Card representations can be passed to the PokerHand constructor as a string or a | |
| 13 13 |  | 
| 14 14 | 
             
            == Install
         | 
| 15 15 |  | 
| 16 | 
            -
                 | 
| 16 | 
            +
                gem install ruby-poker
         | 
| 17 17 |  | 
| 18 18 | 
             
            == Example
         | 
| 19 19 |  | 
| 20 20 | 
             
                require 'rubygems'
         | 
| 21 21 | 
             
                require 'ruby-poker'
         | 
| 22 | 
            -
             | 
| 22 | 
            +
             | 
| 23 23 | 
             
                hand1 = PokerHand.new("8H 9C TC JD QH")
         | 
| 24 24 | 
             
                hand2 = PokerHand.new(["3D", "3C", "3S", "KD", "AH"])
         | 
| 25 25 | 
             
                puts hand1                => 8h 9c Tc Jd Qh (Straight)
         | 
| @@ -34,12 +34,16 @@ Card representations can be passed to the PokerHand constructor as a string or a | |
| 34 34 | 
             
            By default ruby-poker will not raise an exception if you add the same card to a hand twice. You can tell ruby-poker to not allow duplicates by doing the following
         | 
| 35 35 |  | 
| 36 36 | 
             
                PokerHand.allow_duplicates = false
         | 
| 37 | 
            -
             | 
| 37 | 
            +
             | 
| 38 38 | 
             
            Place that line near the beginning of your program. The change is program wide so once allow_duplicates is set to false, _all_ poker hands will raise an exception if a duplicate card is added to the hand.
         | 
| 39 39 |  | 
| 40 40 | 
             
            == Compatibility
         | 
| 41 41 |  | 
| 42 | 
            -
            Ruby-Poker is compatible with Ruby 1.8 | 
| 42 | 
            +
            Ruby-Poker is compatible with Ruby 1.8, Ruby 1.9, Ruby 2.0.
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            == RDoc
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            View the {generated documentation for ruby-poker}[http://rdoc.info/gems/ruby-poker/frames] on rdoc.info.
         | 
| 43 47 |  | 
| 44 48 | 
             
            == History
         | 
| 45 49 |  | 
| @@ -47,4 +51,4 @@ In the 0.2.0 release Patrick Hurley's Texas Holdem code from http://www.rubyquiz | |
| 47 51 |  | 
| 48 52 | 
             
            == License
         | 
| 49 53 |  | 
| 50 | 
            -
            This is free software; you can redistribute it and/or modify it under the terms of the BSD license. See LICENSE for more details.
         | 
| 54 | 
            +
            This is free software; you can redistribute it and/or modify it under the terms of the BSD license. See LICENSE for more details.
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -1,70 +1,9 @@ | |
| 1 | 
            -
            require 'rubygems'
         | 
| 2 | 
            -
            require 'rake'
         | 
| 3 | 
            -
             | 
| 4 | 
            -
            begin
         | 
| 5 | 
            -
              require 'metric_fu'
         | 
| 6 | 
            -
            rescue LoadError
         | 
| 7 | 
            -
            end
         | 
| 8 | 
            -
             | 
| 9 | 
            -
            RUBYPOKER_VERSION = "0.3.2"
         | 
| 10 | 
            -
             | 
| 11 | 
            -
            spec = Gem::Specification.new do |s|
         | 
| 12 | 
            -
              s.name     = "ruby-poker"
         | 
| 13 | 
            -
              s.version  = RUBYPOKER_VERSION
         | 
| 14 | 
            -
              s.date     = "2009-07-27"
         | 
| 15 | 
            -
              s.rubyforge_project = "rubypoker"
         | 
| 16 | 
            -
              s.platform = Gem::Platform::RUBY
         | 
| 17 | 
            -
              s.summary = "Poker library in Ruby"
         | 
| 18 | 
            -
              s.description = "Ruby library for comparing poker hands and determining the winner."
         | 
| 19 | 
            -
              s.author  = "Rob Olson"
         | 
| 20 | 
            -
              s.email    = "rob@thinkingdigitally.com"
         | 
| 21 | 
            -
              s.homepage = "http://github.com/robolson/ruby-poker"
         | 
| 22 | 
            -
              s.has_rdoc = true
         | 
| 23 | 
            -
              s.files    = ["CHANGELOG", 
         | 
| 24 | 
            -
                            "examples/deck.rb", 
         | 
| 25 | 
            -
                            "examples/quick_example.rb", 
         | 
| 26 | 
            -
                            "lib/ruby-poker.rb",
         | 
| 27 | 
            -
                            "lib/ruby-poker/card.rb", 
         | 
| 28 | 
            -
                            "lib/ruby-poker/poker_hand.rb", 
         | 
| 29 | 
            -
                            "LICENSE", 
         | 
| 30 | 
            -
                            "Rakefile", 
         | 
| 31 | 
            -
                            "README.rdoc", 
         | 
| 32 | 
            -
                            "ruby-poker.gemspec"]
         | 
| 33 | 
            -
              s.test_files = ["test/test_helper.rb", "test/test_card.rb", "test/test_poker_hand.rb"]
         | 
| 34 | 
            -
              s.require_paths << 'lib'
         | 
| 35 | 
            -
             | 
| 36 | 
            -
              s.extra_rdoc_files = ["README.rdoc", "CHANGELOG", "LICENSE"]
         | 
| 37 | 
            -
              s.rdoc_options << '--title' << 'Ruby Poker Documentation' <<
         | 
| 38 | 
            -
                                '--main'  << 'README.rdoc' <<
         | 
| 39 | 
            -
                                '--inline-source' << '-q'
         | 
| 40 | 
            -
             | 
| 41 | 
            -
              s.add_development_dependency('thoughtbot-shoulda', '> 2.0.0')
         | 
| 42 | 
            -
            end
         | 
| 43 | 
            -
             | 
| 44 | 
            -
            require 'rake/gempackagetask'
         | 
| 45 | 
            -
            Rake::GemPackageTask.new(spec) do |pkg| 
         | 
| 46 | 
            -
              pkg.need_tar = true 
         | 
| 47 | 
            -
              pkg.need_zip = true
         | 
| 48 | 
            -
            end
         | 
| 49 | 
            -
             | 
| 50 1 | 
             
            require 'rake/testtask'
         | 
| 51 | 
            -
            Rake::TestTask.new | 
| 52 | 
            -
               | 
| 53 | 
            -
               | 
| 54 | 
            -
               | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
            desc "Start autotest"
         | 
| 58 | 
            -
            task :autotest do
         | 
| 59 | 
            -
              ruby "-I lib -w /usr/bin/autotest"
         | 
| 60 | 
            -
            end
         | 
| 61 | 
            -
             | 
| 62 | 
            -
            require 'rake/rdoctask'
         | 
| 63 | 
            -
            Rake::RDocTask.new(:docs) do |rdoc|
         | 
| 64 | 
            -
              rdoc.main     = 'README.rdoc'
         | 
| 65 | 
            -
              rdoc.rdoc_dir = 'rdoc'
         | 
| 66 | 
            -
              rdoc.title    = "Ruby Poker #{RUBYPOKER_VERSION}"
         | 
| 67 | 
            -
              rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG', 'LICENSE', 'lib/**/*.rb')
         | 
| 2 | 
            +
            Rake::TestTask.new do |t|
         | 
| 3 | 
            +
              t.libs = ['lib']
         | 
| 4 | 
            +
              t.verbose = true
         | 
| 5 | 
            +
              t.warning = true
         | 
| 6 | 
            +
              t.test_files = FileList['test/test_card.rb', 'test/test_poker_hand.rb']
         | 
| 68 7 | 
             
            end
         | 
| 69 8 |  | 
| 70 | 
            -
            task :default => :test
         | 
| 9 | 
            +
            task :default => :test
         | 
    
        data/examples/deck.rb
    CHANGED
    
    | @@ -10,7 +10,7 @@ class Deck | |
| 10 10 | 
             
                end
         | 
| 11 11 | 
             
                shuffle
         | 
| 12 12 | 
             
              end
         | 
| 13 | 
            -
             | 
| 13 | 
            +
             | 
| 14 14 | 
             
              def shuffle
         | 
| 15 15 | 
             
                @cards = @cards.sort_by { rand }
         | 
| 16 16 | 
             
                return self
         | 
| @@ -21,7 +21,7 @@ class Deck | |
| 21 21 | 
             
              def deal
         | 
| 22 22 | 
             
                @cards.pop
         | 
| 23 23 | 
             
              end
         | 
| 24 | 
            -
             | 
| 24 | 
            +
             | 
| 25 25 | 
             
              # delete an array or a single card from the deck
         | 
| 26 26 | 
             
              # converts a string to a new card, if a string is given
         | 
| 27 27 | 
             
              def burn(burn_cards)
         | 
| @@ -29,7 +29,7 @@ class Deck | |
| 29 29 | 
             
                if burn_cards.is_a?(Card) || burn_cards.is_a?(String)
         | 
| 30 30 | 
             
                  burn_cards = [burn_cards]
         | 
| 31 31 | 
             
                end
         | 
| 32 | 
            -
             | 
| 32 | 
            +
             | 
| 33 33 | 
             
                burn_cards.map! do |c|
         | 
| 34 34 | 
             
                  c = Card.new(c) unless c.class == Card
         | 
| 35 35 | 
             
                  @cards.delete(c)
         | 
    
        data/lib/ruby-poker.rb
    CHANGED
    
    
    
        data/lib/ruby-poker/card.rb
    CHANGED
    
    | @@ -8,98 +8,98 @@ class Card | |
| 8 8 | 
             
                's' => 3
         | 
| 9 9 | 
             
              }
         | 
| 10 10 | 
             
              FACE_VALUES = {
         | 
| 11 | 
            -
                'L' =>   | 
| 12 | 
            -
                '2' =>   | 
| 13 | 
            -
                '3' =>   | 
| 14 | 
            -
                '4' =>   | 
| 15 | 
            -
                '5' =>   | 
| 16 | 
            -
                '6' =>   | 
| 17 | 
            -
                '7' =>   | 
| 18 | 
            -
                '8' =>   | 
| 19 | 
            -
                '9' =>   | 
| 20 | 
            -
                'T' => | 
| 21 | 
            -
                'J' =>  | 
| 22 | 
            -
                'Q' =>  | 
| 23 | 
            -
                'K' =>  | 
| 24 | 
            -
                'A' =>  | 
| 11 | 
            +
                'L' =>  0,   # this is a low ace
         | 
| 12 | 
            +
                '2' =>  1,
         | 
| 13 | 
            +
                '3' =>  2,
         | 
| 14 | 
            +
                '4' =>  3,
         | 
| 15 | 
            +
                '5' =>  4,
         | 
| 16 | 
            +
                '6' =>  5,
         | 
| 17 | 
            +
                '7' =>  6,
         | 
| 18 | 
            +
                '8' =>  7,
         | 
| 19 | 
            +
                '9' =>  8,
         | 
| 20 | 
            +
                'T' =>  9,
         | 
| 21 | 
            +
                'J' => 10,
         | 
| 22 | 
            +
                'Q' => 11,
         | 
| 23 | 
            +
                'K' => 12,
         | 
| 24 | 
            +
                'A' => 13
         | 
| 25 25 | 
             
              }
         | 
| 26 26 |  | 
| 27 27 | 
             
              def Card.face_value(face)
         | 
| 28 | 
            -
                face.upcase | 
| 29 | 
            -
                if face == 'L' || !FACE_VALUES.has_key?(face)
         | 
| 30 | 
            -
                  nil
         | 
| 31 | 
            -
                else
         | 
| 32 | 
            -
                  FACE_VALUES[face] - 1
         | 
| 33 | 
            -
                end
         | 
| 28 | 
            +
                FACE_VALUES[face.upcase]
         | 
| 34 29 | 
             
              end
         | 
| 35 30 |  | 
| 36 31 | 
             
              private
         | 
| 37 | 
            -
             | 
| 38 | 
            -
              def build_from_value( | 
| 39 | 
            -
                @ | 
| 40 | 
            -
                @ | 
| 41 | 
            -
                @face  = (value % FACES.size())
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              def build_from_value(given_value)
         | 
| 34 | 
            +
                @suit  = given_value / 13
         | 
| 35 | 
            +
                @face  = given_value % 13
         | 
| 42 36 | 
             
              end
         | 
| 43 37 |  | 
| 44 38 | 
             
              def build_from_face_suit(face, suit)
         | 
| 45 39 | 
             
                suit.downcase!
         | 
| 46 40 | 
             
                @face  = Card::face_value(face)
         | 
| 47 41 | 
             
                @suit  = SUIT_LOOKUP[suit]
         | 
| 48 | 
            -
                 | 
| 42 | 
            +
                raise ArgumentError, "Invalid card: \"#{face}#{suit}\"" unless @face and @suit
         | 
| 49 43 | 
             
              end
         | 
| 50 44 |  | 
| 51 | 
            -
              def build_from_face_suit_values( | 
| 52 | 
            -
                 | 
| 45 | 
            +
              def build_from_face_suit_values(face_int, suit_int)
         | 
| 46 | 
            +
                @face = face_int
         | 
| 47 | 
            +
                @suit = suit_int
         | 
| 53 48 | 
             
              end
         | 
| 54 | 
            -
             | 
| 49 | 
            +
             | 
| 55 50 | 
             
              def build_from_string(card)
         | 
| 56 51 | 
             
                build_from_face_suit(card[0,1], card[1,1])
         | 
| 57 52 | 
             
              end
         | 
| 58 | 
            -
             | 
| 53 | 
            +
             | 
| 59 54 | 
             
              # Constructs this card object from another card object
         | 
| 60 55 | 
             
              def build_from_card(card)
         | 
| 61 | 
            -
                @value = card.value
         | 
| 62 56 | 
             
                @suit = card.suit
         | 
| 63 57 | 
             
                @face = card.face
         | 
| 64 58 | 
             
              end
         | 
| 65 | 
            -
             | 
| 59 | 
            +
             | 
| 66 60 | 
             
              public
         | 
| 67 61 |  | 
| 68 | 
            -
              def initialize(* | 
| 69 | 
            -
                if ( | 
| 70 | 
            -
                   | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 62 | 
            +
              def initialize(*args)
         | 
| 63 | 
            +
                if (args.size == 1)
         | 
| 64 | 
            +
                  arg = args.first
         | 
| 65 | 
            +
                  if (arg.respond_to?(:to_card))
         | 
| 66 | 
            +
                    build_from_card(arg)
         | 
| 67 | 
            +
                  elsif (arg.respond_to?(:to_str))
         | 
| 68 | 
            +
                    build_from_string(arg)
         | 
| 69 | 
            +
                  elsif (arg.respond_to?(:to_int))
         | 
| 70 | 
            +
                    build_from_value(arg)
         | 
| 76 71 | 
             
                  end
         | 
| 77 | 
            -
                elsif ( | 
| 78 | 
            -
                   | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 72 | 
            +
                elsif (args.size == 2)
         | 
| 73 | 
            +
                  arg1, arg2 = args
         | 
| 74 | 
            +
                  if (arg1.respond_to?(:to_str) &&
         | 
| 75 | 
            +
                      arg2.respond_to?(:to_str))
         | 
| 76 | 
            +
                    build_from_face_suit(arg1, arg2)
         | 
| 77 | 
            +
                  elsif (arg1.respond_to?(:to_int) &&
         | 
| 78 | 
            +
                         arg2.respond_to?(:to_int))
         | 
| 79 | 
            +
                    build_from_face_suit_values(arg1, arg2)
         | 
| 84 80 | 
             
                  end
         | 
| 85 81 | 
             
                end
         | 
| 86 82 | 
             
              end
         | 
| 87 83 |  | 
| 88 | 
            -
              attr_reader :suit, :face | 
| 84 | 
            +
              attr_reader :suit, :face
         | 
| 89 85 | 
             
              include Comparable
         | 
| 90 86 |  | 
| 87 | 
            +
              def value
         | 
| 88 | 
            +
                (@suit * 13) + @face
         | 
| 89 | 
            +
              end
         | 
| 90 | 
            +
             | 
| 91 91 | 
             
              # Returns a string containing the representation of Card
         | 
| 92 92 | 
             
              #
         | 
| 93 93 | 
             
              # Card.new("7c").to_s                   # => "7c"
         | 
| 94 94 | 
             
              def to_s
         | 
| 95 95 | 
             
                FACES[@face].chr + SUITS[@suit].chr
         | 
| 96 96 | 
             
              end
         | 
| 97 | 
            -
             | 
| 97 | 
            +
             | 
| 98 98 | 
             
              # If to_card is called on a `Card` it should return itself
         | 
| 99 99 | 
             
              def to_card
         | 
| 100 100 | 
             
                self
         | 
| 101 101 | 
             
              end
         | 
| 102 | 
            -
             | 
| 102 | 
            +
             | 
| 103 103 | 
             
              # Compare the face value of this card with another card. Returns:
         | 
| 104 104 | 
             
              # -1 if self is less than card2
         | 
| 105 105 | 
             
              # 0 if self is the same face value of card2
         | 
| @@ -107,20 +107,20 @@ class Card | |
| 107 107 | 
             
              def <=> card2
         | 
| 108 108 | 
             
                @face <=> card2.face
         | 
| 109 109 | 
             
              end
         | 
| 110 | 
            -
             | 
| 110 | 
            +
             | 
| 111 111 | 
             
              # Returns true if the cards are the same card. Meaning they
         | 
| 112 112 | 
             
              # have the same suit and the same face value.
         | 
| 113 113 | 
             
              def == card2
         | 
| 114 | 
            -
                 | 
| 114 | 
            +
                value == card2.value
         | 
| 115 115 | 
             
              end
         | 
| 116 116 | 
             
              alias :eql? :==
         | 
| 117 | 
            -
             | 
| 117 | 
            +
             | 
| 118 118 | 
             
              # Compute a hash-code for this Card. Two Cards with the same
         | 
| 119 | 
            -
              # content will have the same hash code (and will compare using eql?). | 
| 119 | 
            +
              # content will have the same hash code (and will compare using eql?).
         | 
| 120 120 | 
             
              def hash
         | 
| 121 | 
            -
                 | 
| 121 | 
            +
                value.hash
         | 
| 122 122 | 
             
              end
         | 
| 123 | 
            -
             | 
| 123 | 
            +
             | 
| 124 124 | 
             
              # A card's natural value is the closer to it's intuitive value in a deck
         | 
| 125 125 | 
             
              # in the range of 1 to 52. Aces are low with a value of 1. Uses the bridge
         | 
| 126 126 | 
             
              # order of suits: clubs, diamonds, hearts, and spades. The formula used is:
         | 
| @@ -1,32 +1,34 @@ | |
| 1 1 | 
             
            class PokerHand
         | 
| 2 2 | 
             
              include Comparable
         | 
| 3 | 
            +
              include Enumerable
         | 
| 3 4 | 
             
              attr_reader :hand
         | 
| 4 | 
            -
             | 
| 5 | 
            +
             | 
| 5 6 | 
             
              @@allow_duplicates = true    # true by default
         | 
| 6 7 | 
             
              def self.allow_duplicates; @@allow_duplicates; end
         | 
| 7 8 | 
             
              def self.allow_duplicates=(v); @@allow_duplicates = v; end
         | 
| 8 | 
            -
             | 
| 9 | 
            +
             | 
| 9 10 | 
             
              # Returns a new PokerHand object. Accepts the cards represented
         | 
| 10 11 | 
             
              # in a string or an array
         | 
| 11 12 | 
             
              #
         | 
| 12 13 | 
             
              #     PokerHand.new("3d 5c 8h Ks")   # => #<PokerHand:0x5c673c ...
         | 
| 13 14 | 
             
              #     PokerHand.new(["3d", "5c", "8h", "Ks"])  # => #<PokerHand:0x5c2d6c ...
         | 
| 14 15 | 
             
              def initialize(cards = [])
         | 
| 15 | 
            -
                 | 
| 16 | 
            -
             | 
| 16 | 
            +
                @hand = case cards
         | 
| 17 | 
            +
                when Array
         | 
| 18 | 
            +
                  cards.map do |card|
         | 
| 17 19 | 
             
                    if card.is_a? Card
         | 
| 18 20 | 
             
                      card
         | 
| 19 21 | 
             
                    else
         | 
| 20 22 | 
             
                      Card.new(card.to_s)
         | 
| 21 23 | 
             
                    end
         | 
| 22 24 | 
             
                  end
         | 
| 23 | 
            -
                 | 
| 24 | 
            -
                   | 
| 25 | 
            +
                when String
         | 
| 26 | 
            +
                  cards.scan(/\S{2}/).map { |str| Card.new(str) }
         | 
| 25 27 | 
             
                else
         | 
| 26 | 
            -
                   | 
| 28 | 
            +
                  cards
         | 
| 27 29 | 
             
                end
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                check_for_duplicates  | 
| 30 | 
            +
             | 
| 31 | 
            +
                check_for_duplicates unless allow_duplicates
         | 
| 30 32 | 
             
              end
         | 
| 31 33 |  | 
| 32 34 | 
             
              # Returns a new PokerHand object with the cards sorted by suit
         | 
| @@ -37,14 +39,14 @@ class PokerHand | |
| 37 39 | 
             
                PokerHand.new(@hand.sort_by { |c| [c.suit, c.face] }.reverse)
         | 
| 38 40 | 
             
              end
         | 
| 39 41 |  | 
| 40 | 
            -
              # Returns a new PokerHand object with the cards sorted by value
         | 
| 42 | 
            +
              # Returns a new PokerHand object with the cards sorted by face value
         | 
| 41 43 | 
             
              # with the highest value first.
         | 
| 42 44 | 
             
              #
         | 
| 43 45 | 
             
              #     PokerHand.new("3d 5c 8h Ks").by_face.just_cards   # => "Ks 8h 5c 3d"
         | 
| 44 46 | 
             
              def by_face
         | 
| 45 47 | 
             
                PokerHand.new(@hand.sort_by { |c| [c.face, c.suit] }.reverse)
         | 
| 46 48 | 
             
              end
         | 
| 47 | 
            -
             | 
| 49 | 
            +
             | 
| 48 50 | 
             
              # Returns string representation of the hand without the rank
         | 
| 49 51 | 
             
              #
         | 
| 50 52 | 
             
              #     PokerHand.new(["3c", "Kh"]).just_cards     # => "3c Kh"
         | 
| @@ -52,7 +54,7 @@ class PokerHand | |
| 52 54 | 
             
                @hand.join(" ")
         | 
| 53 55 | 
             
              end
         | 
| 54 56 | 
             
              alias :cards :just_cards
         | 
| 55 | 
            -
             | 
| 57 | 
            +
             | 
| 56 58 | 
             
              # Returns an array of the card values in the hand.
         | 
| 57 59 | 
             
              # The values returned are 1 less than the value on the card.
         | 
| 58 60 | 
             
              # For example: 2's will be shown as 1.
         | 
| @@ -94,14 +96,11 @@ class PokerHand | |
| 94 96 | 
             
              def four_of_a_kind?
         | 
| 95 97 | 
             
                if (md = (by_face =~ /(.). \1. \1. \1./))
         | 
| 96 98 | 
             
                  # get kicker
         | 
| 97 | 
            -
                   | 
| 98 | 
            -
                   | 
| 99 | 
            -
             | 
| 100 | 
            -
                    arrange_hand(md)
         | 
| 101 | 
            -
                  ]
         | 
| 102 | 
            -
                else
         | 
| 103 | 
            -
                  false
         | 
| 99 | 
            +
                  result = [8, Card::face_value(md[1])]
         | 
| 100 | 
            +
                  result << Card::face_value($1) if (md.pre_match + md.post_match).match(/(\S)/)
         | 
| 101 | 
            +
                  return [result, arrange_hand(md)]
         | 
| 104 102 | 
             
                end
         | 
| 103 | 
            +
                false
         | 
| 105 104 | 
             
              end
         | 
| 106 105 |  | 
| 107 106 | 
             
              def full_house?
         | 
| @@ -164,19 +163,17 @@ class PokerHand | |
| 164 163 | 
             
                if (md = (by_face =~ /(.). \1. \1./))
         | 
| 165 164 | 
             
                  # get kicker
         | 
| 166 165 | 
             
                  arranged_hand = arrange_hand(md)
         | 
| 167 | 
            -
                  arranged_hand.match(/(?:\S\S ){ | 
| 168 | 
            -
                   | 
| 169 | 
            -
                    [
         | 
| 170 | 
            -
             | 
| 171 | 
            -
             | 
| 172 | 
            -
             | 
| 173 | 
            -
             | 
| 174 | 
            -
                    ] | 
| 175 | 
            -
             | 
| 176 | 
            -
                  ]
         | 
| 177 | 
            -
                else
         | 
| 178 | 
            -
                  false
         | 
| 166 | 
            +
                  matches = arranged_hand.match(/(?:\S\S ){2}(\S\S)/)
         | 
| 167 | 
            +
                  if matches
         | 
| 168 | 
            +
                    result = [4, Card::face_value(md[1])]
         | 
| 169 | 
            +
                    matches = arranged_hand.match(/(?:\S\S ){3}(\S)/)
         | 
| 170 | 
            +
                    result << Card::face_value($1) if matches
         | 
| 171 | 
            +
                    matches = arranged_hand.match(/(?:\S\S ){3}(\S)\S (\S)/)
         | 
| 172 | 
            +
                    result << Card::face_value($2) if matches
         | 
| 173 | 
            +
                    return [result, arranged_hand]
         | 
| 174 | 
            +
                  end
         | 
| 179 175 | 
             
                end
         | 
| 176 | 
            +
                false
         | 
| 180 177 | 
             
              end
         | 
| 181 178 |  | 
| 182 179 | 
             
              def two_pair?
         | 
| @@ -191,44 +188,52 @@ class PokerHand | |
| 191 188 | 
             
                  # that were in-between, and the cards that came after.
         | 
| 192 189 | 
             
                  arranged_hand = arrange_hand(md[0].sub(md[2], '') + ' ' +
         | 
| 193 190 | 
             
                      md.pre_match + ' ' + md[2] + ' ' + md.post_match)
         | 
| 194 | 
            -
                  arranged_hand.match(/(?:\S\S ){ | 
| 195 | 
            -
                   | 
| 196 | 
            -
                    [
         | 
| 197 | 
            -
             | 
| 198 | 
            -
             | 
| 199 | 
            -
             | 
| 200 | 
            -
             | 
| 201 | 
            -
                     | 
| 202 | 
            -
             | 
| 203 | 
            -
                   | 
| 204 | 
            -
                else
         | 
| 205 | 
            -
                  false
         | 
| 191 | 
            +
                  matches = arranged_hand.match(/(?:\S\S ){3}(\S\S)/)
         | 
| 192 | 
            +
                  if matches
         | 
| 193 | 
            +
                    result = []
         | 
| 194 | 
            +
                    result << 3
         | 
| 195 | 
            +
                    result << Card::face_value(md[1])    # face value of the first pair
         | 
| 196 | 
            +
                    result << Card::face_value(md[3])    # face value of the second pair
         | 
| 197 | 
            +
                    matches = arranged_hand.match(/(?:\S\S ){4}(\S)/)
         | 
| 198 | 
            +
                    result << Card::face_value($1) if matches    # face value of the kicker
         | 
| 199 | 
            +
                  return [result, arranged_hand]
         | 
| 200 | 
            +
                  end
         | 
| 206 201 | 
             
                end
         | 
| 202 | 
            +
                false
         | 
| 207 203 | 
             
              end
         | 
| 208 204 |  | 
| 209 205 | 
             
              def pair?
         | 
| 210 206 | 
             
                if (md = (by_face =~ /(.). \1./))
         | 
| 211 | 
            -
                   | 
| 212 | 
            -
                  arranged_hand =  | 
| 213 | 
            -
             | 
| 214 | 
            -
                  [
         | 
| 215 | 
            -
             | 
| 216 | 
            -
             | 
| 217 | 
            -
             | 
| 218 | 
            -
             | 
| 219 | 
            -
             | 
| 220 | 
            -
             | 
| 221 | 
            -
                    ] | 
| 222 | 
            -
             | 
| 223 | 
            -
                  ]
         | 
| 207 | 
            +
                  arranged_hand_str = arrange_hand(md)
         | 
| 208 | 
            +
                  arranged_hand = PokerHand.new(arranged_hand_str)
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                  if arranged_hand.hand[0].face == arranged_hand.hand[1].face &&
         | 
| 211 | 
            +
                      arranged_hand.hand[0].suit != arranged_hand.hand[1].suit
         | 
| 212 | 
            +
                    result = [2, arranged_hand.hand[0].face]
         | 
| 213 | 
            +
                    result << arranged_hand.hand[2].face if arranged_hand.size > 2
         | 
| 214 | 
            +
                    result << arranged_hand.hand[3].face if arranged_hand.size > 3
         | 
| 215 | 
            +
                    result << arranged_hand.hand[4].face if arranged_hand.size > 4
         | 
| 216 | 
            +
             | 
| 217 | 
            +
                    return [result, arranged_hand_str]
         | 
| 218 | 
            +
                  end
         | 
| 224 219 | 
             
                else
         | 
| 225 220 | 
             
                  false
         | 
| 226 221 | 
             
                end
         | 
| 227 222 | 
             
              end
         | 
| 228 223 |  | 
| 229 224 | 
             
              def highest_card?
         | 
| 230 | 
            -
                 | 
| 231 | 
            -
             | 
| 225 | 
            +
                if size > 0
         | 
| 226 | 
            +
                  result = by_face
         | 
| 227 | 
            +
                  [[1, *result.face_values[0..result.face_values.length]], result.hand.join(' ')]
         | 
| 228 | 
            +
                else
         | 
| 229 | 
            +
                  false
         | 
| 230 | 
            +
                end
         | 
| 231 | 
            +
              end
         | 
| 232 | 
            +
             | 
| 233 | 
            +
              def empty_hand?
         | 
| 234 | 
            +
                if size == 0
         | 
| 235 | 
            +
                  [[0]]
         | 
| 236 | 
            +
                end
         | 
| 232 237 | 
             
              end
         | 
| 233 238 |  | 
| 234 239 | 
             
              OPS = [
         | 
| @@ -242,6 +247,7 @@ class PokerHand | |
| 242 247 | 
             
                ['Two pair',        :two_pair? ],
         | 
| 243 248 | 
             
                ['Pair',            :pair? ],
         | 
| 244 249 | 
             
                ['Highest Card',    :highest_card? ],
         | 
| 250 | 
            +
                ['Empty Hand',      :empty_hand? ],
         | 
| 245 251 | 
             
              ]
         | 
| 246 252 |  | 
| 247 253 | 
             
              # Returns the verbose hand rating
         | 
| @@ -252,18 +258,16 @@ class PokerHand | |
| 252 258 | 
             
                  (method(op[1]).call()) ? op[0] : false
         | 
| 253 259 | 
             
                }.find { |v| v }
         | 
| 254 260 | 
             
              end
         | 
| 255 | 
            -
             | 
| 261 | 
            +
             | 
| 256 262 | 
             
              alias :rank :hand_rating
         | 
| 257 | 
            -
             | 
| 263 | 
            +
             | 
| 258 264 | 
             
              def score
         | 
| 259 | 
            -
                # OPS.map returns an array containing the result of calling each OPS method  | 
| 260 | 
            -
                # the poker hand. The  | 
| 265 | 
            +
                # OPS.map returns an array containing the result of calling each OPS method against
         | 
| 266 | 
            +
                # the poker hand. The truthy cell closest to the front of the array represents
         | 
| 261 267 | 
             
                # the highest ranking.
         | 
| 262 | 
            -
                # find([0]) returns [0] instead of nil if the hand does not match any of the rankings
         | 
| 263 | 
            -
                # which is not likely to occur since every hand should at least have a highest card
         | 
| 264 268 | 
             
                OPS.map { |op|
         | 
| 265 269 | 
             
                  method(op[1]).call()
         | 
| 266 | 
            -
                }.find | 
| 270 | 
            +
                }.find { |score| score }
         | 
| 267 271 | 
             
              end
         | 
| 268 272 |  | 
| 269 273 | 
             
              # Returns a string of the hand arranged based on its rank. Usually this will be the
         | 
| @@ -271,11 +275,11 @@ class PokerHand | |
| 271 275 | 
             
              #
         | 
| 272 276 | 
             
              #     ph = PokerHand.new("As 3s 5s 2s 4s")
         | 
| 273 277 | 
             
              #     ph.sort_using_rank        # => "5s 4s 3s 2s As"
         | 
| 274 | 
            -
              #     ph.by_face.just_cards       # => "As 5s 4s 3s 2s" | 
| 278 | 
            +
              #     ph.by_face.just_cards       # => "As 5s 4s 3s 2s"
         | 
| 275 279 | 
             
              def sort_using_rank
         | 
| 276 280 | 
             
                score[1]
         | 
| 277 281 | 
             
              end
         | 
| 278 | 
            -
             | 
| 282 | 
            +
             | 
| 279 283 | 
             
              # Returns string with a listing of the cards in the hand followed by the hand's rank.
         | 
| 280 284 | 
             
              #
         | 
| 281 285 | 
             
              #     h = PokerHand.new("8c 8s")
         | 
| @@ -283,20 +287,19 @@ class PokerHand | |
| 283 287 | 
             
              def to_s
         | 
| 284 288 | 
             
                just_cards + " (" + hand_rating + ")"
         | 
| 285 289 | 
             
              end
         | 
| 286 | 
            -
             | 
| 290 | 
            +
             | 
| 287 291 | 
             
              # Returns an array of `Card` objects that make up the `PokerHand`.
         | 
| 288 292 | 
             
              def to_a
         | 
| 289 293 | 
             
                @hand
         | 
| 290 294 | 
             
              end
         | 
| 291 | 
            -
              
         | 
| 292 295 | 
             
              alias :to_ary :to_a
         | 
| 293 | 
            -
             | 
| 296 | 
            +
             | 
| 294 297 | 
             
              def <=> other_hand
         | 
| 295 298 | 
             
                self.score[0].compact <=> other_hand.score[0].compact
         | 
| 296 299 | 
             
              end
         | 
| 297 | 
            -
             | 
| 300 | 
            +
             | 
| 298 301 | 
             
              # Add a card to the hand
         | 
| 299 | 
            -
              # | 
| 302 | 
            +
              #
         | 
| 300 303 | 
             
              #     hand = PokerHand.new("5d")
         | 
| 301 304 | 
             
              #     hand << "6s"          # => Add a six of spades to the hand by passing a string
         | 
| 302 305 | 
             
              #     hand << ["7h", "8d"]  # => Add multiple cards to the hand using an array
         | 
| @@ -304,16 +307,16 @@ class PokerHand | |
| 304 307 | 
             
                if new_cards.is_a?(Card) || new_cards.is_a?(String)
         | 
| 305 308 | 
             
                  new_cards = [new_cards]
         | 
| 306 309 | 
             
                end
         | 
| 307 | 
            -
             | 
| 310 | 
            +
             | 
| 308 311 | 
             
                new_cards.each do |nc|
         | 
| 309 | 
            -
                  unless  | 
| 312 | 
            +
                  unless allow_duplicates
         | 
| 310 313 | 
             
                    raise "A card with the value #{nc} already exists in this hand. Set PokerHand.allow_duplicates to true if you want to be able to add a card more than once." if self =~ /#{nc}/
         | 
| 311 314 | 
             
                  end
         | 
| 312 | 
            -
             | 
| 315 | 
            +
             | 
| 313 316 | 
             
                  @hand << Card.new(nc)
         | 
| 314 317 | 
             
                end
         | 
| 315 318 | 
             
              end
         | 
| 316 | 
            -
             | 
| 319 | 
            +
             | 
| 317 320 | 
             
              # Remove a card from the hand.
         | 
| 318 321 | 
             
              #
         | 
| 319 322 | 
             
              #     hand = PokerHand.new("5d Jd")
         | 
| @@ -322,14 +325,14 @@ class PokerHand | |
| 322 325 | 
             
              def delete card
         | 
| 323 326 | 
             
                @hand.delete(Card.new(card))
         | 
| 324 327 | 
             
              end
         | 
| 325 | 
            -
             | 
| 328 | 
            +
             | 
| 326 329 | 
             
              # Same concept as Array#uniq
         | 
| 327 330 | 
             
              def uniq
         | 
| 328 331 | 
             
                PokerHand.new(@hand.uniq)
         | 
| 329 332 | 
             
              end
         | 
| 330 | 
            -
             | 
| 333 | 
            +
             | 
| 331 334 | 
             
              # Resolving methods are just passed directly down to the @hand array
         | 
| 332 | 
            -
              RESOLVING_METHODS = [:size,  | 
| 335 | 
            +
              RESOLVING_METHODS = [:each, :size, :-]
         | 
| 333 336 | 
             
              RESOLVING_METHODS.each do |method|
         | 
| 334 337 | 
             
                class_eval %{
         | 
| 335 338 | 
             
                  def #{method}(*args, &block)
         | 
| @@ -337,20 +340,106 @@ class PokerHand | |
| 337 340 | 
             
                  end
         | 
| 338 341 | 
             
                }
         | 
| 339 342 | 
             
              end
         | 
| 340 | 
            -
             | 
| 343 | 
            +
             | 
| 344 | 
            +
              def allow_duplicates
         | 
| 345 | 
            +
                @@allow_duplicates
         | 
| 346 | 
            +
              end
         | 
| 347 | 
            +
             | 
| 348 | 
            +
              # Checks whether the hand matches usual expressions like AA, AK, AJ+, 66+, AQs, AQo...
         | 
| 349 | 
            +
              #
         | 
| 350 | 
            +
              # Valid expressions:
         | 
| 351 | 
            +
              # * "AJ": Matches exact faces (in this case an Ace and a Jack), suited or not
         | 
| 352 | 
            +
              # * "AJs": Same but suited only
         | 
| 353 | 
            +
              # * "AJo": Same but offsuit only
         | 
| 354 | 
            +
              # * "AJ+": Matches an Ace with any card >= Jack, suited or not
         | 
| 355 | 
            +
              # * "AJs+": Same but suited only
         | 
| 356 | 
            +
              # * "AJo+": Same but offsuit only
         | 
| 357 | 
            +
              # * "JJ+": Matches any pair >= "JJ".
         | 
| 358 | 
            +
              # * "8T+": Matches connectors (in this case with 1 gap : 8T, 9J, TQ, JK, QA)
         | 
| 359 | 
            +
              # * "8Ts+": Same but suited only
         | 
| 360 | 
            +
              # * "8To+": Same but offsuit only
         | 
| 361 | 
            +
              #
         | 
| 362 | 
            +
              # The order of the cards in the expression is important (8T+ is not the same as T8+), but the order of the cards in the hand is not ("AK" will match "Ad Kc" and "Kc Ad").
         | 
| 363 | 
            +
              #
         | 
| 364 | 
            +
              # The expression can be an array of expressions. In this case the method returns true if any expression matches.
         | 
| 365 | 
            +
              #
         | 
| 366 | 
            +
              # This method only works on hands with 2 cards.
         | 
| 367 | 
            +
              #
         | 
| 368 | 
            +
              #     PokerHand.new('Ah Ad').match? 'AA' # => true
         | 
| 369 | 
            +
              #     PokerHand.new('Ah Kd').match? 'AQ+' # => true
         | 
| 370 | 
            +
              #     PokerHand.new('Jc Qc').match? '89s+' # => true
         | 
| 371 | 
            +
              #     PokerHand.new('Ah Jd').match? %w( 22+ A6s+ AJ+ ) # => true
         | 
| 372 | 
            +
              #     PokerHand.new('Ah Td').match? %w( 22+ A6s+ AJ+ ) # => false
         | 
| 373 | 
            +
              #
         | 
| 374 | 
            +
              def match? expression
         | 
| 375 | 
            +
                raise "Hands with #{@hand.size} cards is not supported" unless @hand.size == 2
         | 
| 376 | 
            +
             | 
| 377 | 
            +
                if expression.is_a? Array
         | 
| 378 | 
            +
                  return expression.any? { |e| match?(e) }
         | 
| 379 | 
            +
                end
         | 
| 380 | 
            +
             | 
| 381 | 
            +
                faces = @hand.map { |card| card.face }.sort.reverse
         | 
| 382 | 
            +
                suited = @hand.map { |card| card.suit }.uniq.size == 1
         | 
| 383 | 
            +
                if expression =~ /^(.)(.)(s|o|)(\+|)$/
         | 
| 384 | 
            +
                  face1 = Card.face_value($1)
         | 
| 385 | 
            +
                  face2 = Card.face_value($2)
         | 
| 386 | 
            +
                  raise ArgumentError, "Invalid expression: #{expression.inspect}" unless face1 and face2
         | 
| 387 | 
            +
                  suit_match = $3
         | 
| 388 | 
            +
                  plus = ($4 != "")
         | 
| 389 | 
            +
             | 
| 390 | 
            +
                  if plus
         | 
| 391 | 
            +
                    if face1 == face2
         | 
| 392 | 
            +
                      face_match = (faces.first == faces.last and faces.first >= face1)
         | 
| 393 | 
            +
                    elsif face1 > face2
         | 
| 394 | 
            +
                      face_match = (faces.first == face1 and faces.last >= face2)
         | 
| 395 | 
            +
                    else
         | 
| 396 | 
            +
                      face_match = ((faces.first - faces.last) == (face2 - face1) and faces.last >= face1)
         | 
| 397 | 
            +
                    end
         | 
| 398 | 
            +
                  else
         | 
| 399 | 
            +
                    expression_faces = [face1, face2].sort.reverse
         | 
| 400 | 
            +
                    face_match = (expression_faces == faces)
         | 
| 401 | 
            +
                  end
         | 
| 402 | 
            +
                  case suit_match
         | 
| 403 | 
            +
                  when ''
         | 
| 404 | 
            +
                    face_match
         | 
| 405 | 
            +
                  when 's'
         | 
| 406 | 
            +
                    face_match and suited
         | 
| 407 | 
            +
                  when 'o'
         | 
| 408 | 
            +
                    face_match and !suited
         | 
| 409 | 
            +
                  end
         | 
| 410 | 
            +
                else
         | 
| 411 | 
            +
                  raise ArgumentError, "Invalid expression: #{expression.inspect}"
         | 
| 412 | 
            +
                end
         | 
| 413 | 
            +
              end
         | 
| 414 | 
            +
             | 
| 415 | 
            +
              def +(other)
         | 
| 416 | 
            +
                cards = @hand.map { |card| Card.new(card) }
         | 
| 417 | 
            +
                case other
         | 
| 418 | 
            +
                when String
         | 
| 419 | 
            +
                  cards << Card.new(other)
         | 
| 420 | 
            +
                when Card
         | 
| 421 | 
            +
                  cards << other
         | 
| 422 | 
            +
                when PokerHand
         | 
| 423 | 
            +
                  cards += other.hand
         | 
| 424 | 
            +
                else
         | 
| 425 | 
            +
                  raise ArgumentError, "Invalid argument: #{other.inspect}"
         | 
| 426 | 
            +
                end
         | 
| 427 | 
            +
                PokerHand.new(cards)
         | 
| 428 | 
            +
              end
         | 
| 429 | 
            +
             | 
| 341 430 | 
             
              private
         | 
| 342 | 
            -
             | 
| 431 | 
            +
             | 
| 343 432 | 
             
              def check_for_duplicates
         | 
| 344 | 
            -
                if @hand.size != @hand.uniq.size &&  | 
| 433 | 
            +
                if @hand.size != @hand.uniq.size && !allow_duplicates
         | 
| 345 434 | 
             
                  raise "Attempting to create a hand that contains duplicate cards. Set PokerHand.allow_duplicates to true if you do not want to ignore this error."
         | 
| 346 435 | 
             
                end
         | 
| 347 436 | 
             
              end
         | 
| 348 | 
            -
             | 
| 437 | 
            +
             | 
| 349 438 | 
             
              # if md is a string, arrange_hand will remove extra white space
         | 
| 350 439 | 
             
              # if md is a MatchData, arrange_hand returns the matched segment
         | 
| 351 440 | 
             
              # followed by the pre_match and the post_match
         | 
| 352 441 | 
             
              def arrange_hand(md)
         | 
| 353 | 
            -
                  hand = if  | 
| 442 | 
            +
                  hand = if md.respond_to?(:to_str)
         | 
| 354 443 | 
             
                    md
         | 
| 355 444 | 
             
                  else
         | 
| 356 445 | 
             
                    md[0] + ' ' + md.pre_match + md.post_match
         | 
| @@ -358,12 +447,23 @@ class PokerHand | |
| 358 447 | 
             
                  hand.strip.squeeze(" ")   # remove extra whitespace
         | 
| 359 448 | 
             
              end
         | 
| 360 449 |  | 
| 361 | 
            -
              # delta transform  | 
| 362 | 
            -
              # between card values is in the string | 
| 363 | 
            -
              # straight and/or straight flush
         | 
| 450 | 
            +
              # delta transform returns a string representation of the cards where the
         | 
| 451 | 
            +
              # delta between card values is in the string. This is necessary so a regexp
         | 
| 452 | 
            +
              # can then match a straight and/or straight flush
         | 
| 453 | 
            +
              #
         | 
| 454 | 
            +
              # Examples
         | 
| 455 | 
            +
              #
         | 
| 456 | 
            +
              #   PokerHand.new("As Qc Jh Ts 9d 8h")
         | 
| 457 | 
            +
              #   # => '0As 2Qc 1Jh 1Ts 19d 18h'
         | 
| 458 | 
            +
              #
         | 
| 459 | 
            +
              #   PokerHand.new("Ah Qd Td 5d 4d")
         | 
| 460 | 
            +
              #   # => '0Ah 2Qd 2Td 55d 14d'
         | 
| 461 | 
            +
              #
         | 
| 364 462 | 
             
              def delta_transform(use_suit = false)
         | 
| 463 | 
            +
                # In order to check for both ace high and ace low straights we create low
         | 
| 464 | 
            +
                # ace duplicates of all of the high aces.
         | 
| 365 465 | 
             
                aces = @hand.select { |c| c.face == Card::face_value('A') }
         | 
| 366 | 
            -
                aces.map! { |c| Card.new( | 
| 466 | 
            +
                aces.map! { |c| Card.new(0, c.suit) }  # hack to give the appearance of a low ace
         | 
| 367 467 |  | 
| 368 468 | 
             
                base = if (use_suit)
         | 
| 369 469 | 
             
                  (@hand + aces).sort_by { |c| [c.suit, c.face] }.reverse
         | 
| @@ -371,6 +471,7 @@ class PokerHand | |
| 371 471 | 
             
                  (@hand + aces).sort_by { |c| [c.face, c.suit] }.reverse
         | 
| 372 472 | 
             
                end
         | 
| 373 473 |  | 
| 474 | 
            +
                # Insert delta in front of each card
         | 
| 374 475 | 
             
                result = base.inject(['',nil]) do |(delta_hand, prev_card), card|
         | 
| 375 476 | 
             
                  if (prev_card)
         | 
| 376 477 | 
             
                    delta = prev_card - card.face
         | 
| @@ -403,5 +504,5 @@ class PokerHand | |
| 403 504 | 
             
                # careful to use gsub as gsub! can return nil here
         | 
| 404 505 | 
             
                arranged_hand.gsub(/\s+$/, '')
         | 
| 405 506 | 
             
              end
         | 
| 406 | 
            -
             | 
| 407 | 
            -
            end
         | 
| 507 | 
            +
             | 
| 508 | 
            +
            end
         |