hands 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ --markup markdown
2
+ 'lib/**/*.rb'
3
+ -
4
+ Readme.markdown
5
+ Todo.markdown
6
+ LICENSE
data/Gemfile CHANGED
@@ -2,6 +2,15 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
+ # Utility
5
6
  gem 'rake'
6
- gem 'rspec', '~>2.7.0'
7
- gem 'simplecov', :require => false
7
+
8
+ # Documentation
9
+ gem 'yard'
10
+ gem 'redcarpet'
11
+
12
+ group :test do
13
+ gem 'minitest'
14
+ gem 'minitest-wscolor'
15
+ gem 'simplecov'
16
+ end
data/LICENSE CHANGED
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
19
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
20
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
21
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile CHANGED
@@ -1,8 +1,12 @@
1
- #!/usr/bin/env rake
2
1
  require 'bundler/gem_tasks'
3
- require 'rspec/core/rake_task'
2
+ require 'rake/testtask'
4
3
 
5
- desc 'Run specs'
6
- RSpec::Core::RakeTask.new do |t|
7
- t.rspec_opts = ['--color']
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.pattern = 'test/**/*_test.rb'
7
+ end
8
+ task default: :test
9
+
10
+ task :coverage do
11
+ `open coverage/index.html`
8
12
  end
@@ -4,6 +4,8 @@ Simple library for calculating poker hands.
4
4
 
5
5
  Currently this gem is very limited. I plan on adding outs, odds, and other actually useful stuff. I started writing this on a plane as a personal challenge. It's current state is crude, although tested and works.
6
6
 
7
+ [![Dependency Status](https://gemnasium.com/soffes/hands.png)](https://gemnasium.com/soffes/hands)
8
+
7
9
  ## Installation
8
10
 
9
11
  Add this line to your application's Gemfile:
@@ -22,6 +24,8 @@ Or install it yourself as:
22
24
 
23
25
  ## Usage
24
26
 
27
+ Read the [documentation](http://rubydoc.info/github/samsoffes/hands/master/frames).
28
+
25
29
  ``` ruby
26
30
  # Best hand detection
27
31
  straight = Hands::Hand.new
@@ -49,8 +53,8 @@ flush << Hands::Card[4, :hearts]
49
53
  flush > pair # true
50
54
 
51
55
  # Card comparison
52
- card1 = Hands::Card.new(:value => 2, :suite => :hearts)
53
- card2 = Hands::Card.new(:value => 3, :suite => :clubs)
56
+ card1 = Hands::Card.new(:value => 2, :suit => :hearts)
57
+ card2 = Hands::Card.new(:value => 3, :suit => :clubs)
54
58
  card2 > card1 # true
55
59
  ```
56
60
 
@@ -59,15 +63,15 @@ card2 > card1 # true
59
63
  Running and reading the tests is (for now) the best way to see the functionality of this gem.
60
64
 
61
65
  ```
62
- $ bundle
63
- $ bundle exec rake spec
66
+ $ bundle exec rake
64
67
  ```
65
68
 
66
69
  ## Contributing
67
70
 
68
71
  1. Fork it
69
72
  2. Create your feature branch (`git checkout -b my-new-feature`)
70
- 3. Write a passing spec
71
- 4. Commit your changes (`git commit -am 'Added some feature'`)
72
- 5. Push to the branch (`git push origin my-new-feature`)
73
- 6. Create new Pull Request
73
+ 3. Write passing specs
74
+ 4. Write documentation
75
+ 5. Commit your changes (`git commit -am 'Added some feature'`)
76
+ 6. Push to the branch (`git push origin my-new-feature`)
77
+ 7. Create new Pull Request
@@ -0,0 +1,19 @@
1
+ # Hands Todo
2
+
3
+ ## Features
4
+
5
+ * Best 5 card hand in hold'em
6
+ * Blinds
7
+ * Betting
8
+ * Missed blinds
9
+ * Outs
10
+ * Odds for completing each hand
11
+ * Pot odds
12
+ * Games other than hold'em
13
+ * AI
14
+
15
+ ## To Document and Test
16
+
17
+ * Player
18
+ * Table
19
+ * Deck
@@ -3,10 +3,10 @@ require File.expand_path('../lib/hands/version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = ['Sam Soffes']
6
- gem.email = ['sam@samsoff.es']
6
+ gem.email = ['sam@soff.es']
7
7
  gem.description = 'Simple library for various poker hands calculations.'
8
8
  gem.summary = 'Simple library for various poker hands calculations.'
9
- gem.homepage = 'http://github.com/samsoffes/hands'
9
+ gem.homepage = 'http://github.com/soffes/hands'
10
10
 
11
11
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
12
  gem.files = `git ls-files`.split("\n")
@@ -1,12 +1,21 @@
1
1
  require 'hands/version'
2
2
  require 'hands/card'
3
3
  require 'hands/hand'
4
+ require 'hands/player'
5
+ require 'hands/deck'
6
+ require 'hands/table'
4
7
 
8
+ # See <http://www.pagat.com/poker/rules/ranking.html> for ranking references.
5
9
  module Hands
6
- VALUES = %w{ 2 3 4 5 6 7 8 9 10 j q k a }
10
+ # All card values
11
+ VALUES = %w{ 2 3 4 5 6 7 8 9 10 j q k a }.freeze
12
+
13
+ # All card value descriptions
7
14
  VALUE_DESCRIPTIONS = %w{ two three four five six seven eight nine ten jack queen king ace }
8
15
 
9
- # Reference: http://www.pagat.com/poker/rules/ranking.html
10
- SUITES = %w{ clubs diamonds hearts spades } # Reverse alphabetical
11
- HAND_ORDER = %w{ high_card pair two_pair three_of_a_kind straight flush full_house four_of_a_kind straight_flush }
16
+ # Reverse alphabetically ordered suits
17
+ SUITES = [:clubs, :diamonds, :hearts, :spades].freeze
18
+
19
+ # Ranks of poker hands
20
+ HAND_ORDER = %w{ high_card pair two_pair three_of_a_kind straight flush full_house four_of_a_kind straight_flush }.freeze
12
21
  end
@@ -1,27 +1,46 @@
1
1
  module Hands
2
+ # Represents a poker playing card.
3
+ #
4
+ # You can use `Card[]` as a quick initializer. For example,
5
+ # `Card[2, :clubs].description` results in `"Two of Clubs"`.
2
6
  class Card
3
7
  include Comparable
4
8
 
5
- attr_accessor :suite
9
+ # @return [Symbol] Card's suit
10
+ # @see SUITES
11
+ attr_accessor :suit
12
+
13
+ # Card's value
14
+ #
15
+ # If an invalid value is set, the value will be set to `nil`.
16
+ # @return [String] Card's value
17
+ # @see VALUES
6
18
  attr_accessor :value
7
19
 
8
- def self.[](first = nil, second = nil)
9
- self.new(first, second)
20
+ # (see #initialize)
21
+ def self.[](value = nil, suit = nil)
22
+ self.new(value, suit)
10
23
  end
11
24
 
12
- def initialize(first = nil, suite = nil)
25
+ # Initialize a Card
26
+ #
27
+ # @param [String, Integer, Hash] value If an `Integer` or `String` are provided, this will be set to the value. If a `Hash` is provided, its value for `:value` will be set to the `Card`'s value and its `:suit` value will be set to the `Card`'s suit.
28
+ # @param [Symbol] suit Sets the `Card`'s suit.
29
+ # @return [Card] A new instance of Card
30
+ # @see Card.[]
31
+ def initialize(value = nil, suit = nil)
13
32
  # Value provided
14
- if first.is_a?(Integer) or first.is_a?(String)
15
- self.value = first
33
+ if value.is_a?(Integer) or value.is_a?(String)
34
+ self.value = value
16
35
 
17
36
  # Hash provided
18
- elsif first.is_a?(Hash)
19
- self.value = first[:value] if first[:value]
20
- self.suite = first[:suite] if first[:suite]
37
+ elsif value.is_a?(Hash)
38
+ self.value = value[:value] if value[:value]
39
+ self.suit = value[:suit] if value[:suit]
21
40
  end
22
41
 
23
- # Set suite
24
- self.suite = suite if suite
42
+ # Set suit
43
+ self.suit = suit if suit
25
44
  end
26
45
 
27
46
  def value=(val)
@@ -29,7 +48,7 @@ module Hands
29
48
  if val.is_a?(Integer)
30
49
  # Number range
31
50
  if val > 0 and val <= 10
32
- @value = val
51
+ @value = val.to_s
33
52
  return
34
53
 
35
54
  # Face card or ace range
@@ -48,6 +67,17 @@ module Hands
48
67
  @value = nil
49
68
  end
50
69
 
70
+ def suit=(suit)
71
+ if (suit.is_a?(String) or suit.is_a?(Symbol)) and SUITES.include?(suit.to_sym)
72
+ @suit = suit.to_sym
73
+ else
74
+ @suit = nil
75
+ end
76
+ end
77
+
78
+ # Standard inspect
79
+ #
80
+ # @return [String] `super`'s implementation and the receiver's `description` if it `is_valid?`
51
81
  def inspect
52
82
  if self.is_valid?
53
83
  "#{super} #{self.description}"
@@ -56,35 +86,60 @@ module Hands
56
86
  end
57
87
  end
58
88
 
89
+ # @return [Boolean] Does the receiver contain a valid value and suit combination
59
90
  def is_valid?
60
- SUITES.include?(self.suite.to_s) and VALUES.include?(self.value.to_s.downcase)
91
+ SUITES.include?(@suit) and VALUES.include?(@value.to_s.downcase)
61
92
  end
62
93
 
94
+ # @return [Boolean] Does the receiver contain an invalid value and suit combination
63
95
  def is_invalid?
64
96
  !self.is_valid?
65
97
  end
66
98
 
99
+ # Get a string representation of the card
100
+ #
101
+ # @return [String] string representation of the card
67
102
  def description
68
103
  if self.is_valid?
69
- "#{VALUE_DESCRIPTIONS[self.value_index].capitalize} of #{self.suite.to_s.capitalize}"
104
+ "#{VALUE_DESCRIPTIONS[self.value_index].capitalize} of #{self.suit.to_s.capitalize}"
70
105
  else
71
106
  'invalid'
72
107
  end
73
108
  end
74
109
 
75
- def <=>(other_card, check_suite = false)
110
+ # Compares the card with another card
111
+ #
112
+ # By default `check_suit` is `false`. If set to `true`, it will order cards that have the same value by their suit.
113
+ #
114
+ # @param [Card] other_card the card to compare the receiver to
115
+ # @param [Boolean] check_suit a boolean indicating if the suit should be accounted for
116
+ # @return [Integer] `-1` if `other_card` is less than the receiver, `0` for equal to, and `1` for greater than
117
+ # @see SUITES
118
+ def <=>(other_card, check_suit = false)
76
119
  # TODO: Handle invalid cards
77
120
  result = self.value_index <=> other_card.value_index
78
- return self.suite_index <=> other_card.suite_index if result == 0 and check_suite
121
+ return self.suit_index <=> other_card.suit_index if result == 0 and check_suit
79
122
  result
80
123
  end
81
124
 
82
- def suite_index
83
- SUITES.index(self.suite.to_s.downcase)
125
+ # Suite's index
126
+ #
127
+ # Mainly used for internal reasons when comparing cards.
128
+ #
129
+ # @return [Integer] index of the card's suit
130
+ # @see SUITES
131
+ def suit_index
132
+ SUITES.index(self.suit.downcase)
84
133
  end
85
134
 
135
+ # Values's index
136
+ #
137
+ # Mainly used for internal reasons when comparing cards.
138
+ #
139
+ # @return [Integer] index of the card's value
140
+ # @see VALUES
86
141
  def value_index
87
- VALUES.index(self.value.to_s.downcase)
142
+ VALUES.index(self.value.downcase)
88
143
  end
89
144
  end
90
145
  end
@@ -0,0 +1,27 @@
1
+ module Hands
2
+ class Deck
3
+ # @return [Array] {Card}s in the {Deck}
4
+ attr_reader :cards
5
+
6
+ # Initialize the deck with 52 {Card}s
7
+ def initialize
8
+ @cards = []
9
+ VALUES.each do |value|
10
+ SUITES.each do |suit|
11
+ @cards << Card[value, suit]
12
+ end
13
+ end
14
+ end
15
+
16
+ # Shuffle the {Deck}
17
+ def shuffle!
18
+ @cards.shuffle!
19
+ end
20
+
21
+ # Pop a card off the {Deck}
22
+ # @return [Card] the {Card}(s) popped off the {Deck}
23
+ def pop(number_of_cards = 1)
24
+ @cards.pop(number_of_cards)
25
+ end
26
+ end
27
+ end
@@ -1,164 +1,50 @@
1
+ require 'hands/hand_detection'
2
+
1
3
  module Hands
4
+ # Represents a poker hand.
2
5
  class Hand
3
6
  include Comparable
4
7
 
8
+ # @return [Array] {Card}s in the {Hand}
5
9
  attr_accessor :cards
6
10
 
7
- def cards
8
- @cards ||= []
11
+ def initialize
12
+ @cards = []
9
13
  end
10
14
 
15
+ # Compares the {Hand} with another {Hand}.
16
+ #
17
+ # @param [Hand] other_hand the {Hand} to compare the receiver to
18
+ # @return [Integer] `-1` if `other_hand` is less than the receiver, `0` for equal to, and `1` for greater than
19
+ # @see HAND_ORDER
20
+ # @see Card#<=>
11
21
  def <=>(other_hand)
12
22
  response = (self.hand_index <=> other_hand.hand_index)
13
23
 
14
- # If the hands tie, see which is higher (i.e. higher pair)
24
+ # If the {Hand}s tie, see which is higher (i.e. higher pair)
15
25
  if response == 0
16
- self.cards <=> other_hand.cards
26
+ @cards <=> other_hand.cards
17
27
  else
18
28
  response
19
29
  end
20
30
  end
21
31
 
32
+ # Add a {Card}
33
+ #
34
+ # @return [Card] the {Card} added
22
35
  def <<(card)
23
- self.cards << card
24
- end
25
-
26
- def suites
27
- self.cards.collect(&:suite).uniq
28
- end
29
-
30
- def best_hand
31
- response = {}
32
- HAND_ORDER.reverse.each do |type|
33
- cards = self.send(type.to_sym)
34
- next unless cards
35
- response[:type] = type
36
- response[:cards] = cards
37
- break
38
- end
39
- response
40
- end
41
-
42
- def high_card
43
- self.cards.sort.reverse
44
- end
45
-
46
- def pair
47
- self.pairs(1)
48
- end
49
-
50
- def two_pair
51
- self.pairs(2)
52
- end
53
-
54
- def three_of_a_kind
55
- self.kinds(3)
56
- end
57
-
58
- def straight
59
- return nil unless self.cards.length == 5
60
- cs = self.cards.sort.reverse
61
-
62
- # Ace's low
63
- if cs.first.value == 'a' and cs[1].value == 5
64
- # Move ace to end
65
- ace = cs.first
66
- cs = cs[1..4]
67
- cs << ace
68
-
69
- # Check succession
70
- csr = cs.reverse
71
- 4.times do |i|
72
- next if i == 0
73
- return nil unless csr[i].value_index == i - 1
74
- end
75
-
76
- # Normal
77
- else
78
- # Check range
79
- return nil unless cs.first.value_index - cs.last.value_index == 4
80
-
81
- # Check succession
82
- 4.times do |i|
83
- return nil unless cs[i].value_index == cs[i + 1].value_index + 1
84
- end
85
- end
86
- cs
87
- end
88
-
89
- def flush
90
- # If all of the cards are the same suite, we have a flush
91
- return nil unless self.suites.length == 1
92
- self.cards.sort.reverse
93
- end
94
-
95
- def full_house
96
- dupes = self.duplicates
97
- return nil unless dupes.length == 2
98
-
99
- a = []
100
- b = []
101
-
102
- hand = self.cards.select do |card|
103
- if dupes.first == card.value
104
- a << card
105
- elsif dupes.last == card.value
106
- b << card
107
- end
108
- end
109
-
110
- return nil unless a.length + b.length == 5
111
- self.cards.sort.reverse
112
- end
113
-
114
- def four_of_a_kind
115
- self.kinds(4)
36
+ @cards << card
37
+ @cards.flatten! # TODO: Figure out why this is necessary
116
38
  end
117
39
 
118
- def straight_flush
119
- return nil unless self.flush
120
- self.straight
121
- end
122
-
123
- protected
124
-
125
- def hand_index
126
- best = self.best_hand
127
- return -1 if best.nil?
128
- HAND_ORDER.index(best[:type].to_s)
40
+ # @return [Array] All of the suits contained in the {Hand}
41
+ def suits
42
+ @cards.collect(&:suit).uniq
129
43
  end
130
44
 
131
- def duplicates
132
- pairs = self.cards.collect(&:value)
133
- pairs.uniq.select{ |e| (pairs - [e]).size < pairs.size - 1 }
134
- end
135
-
136
- def pairs(min)
137
- dupes = self.duplicates
138
- return nil if dupes.length < min
139
-
140
- hand = self.cards.select do |card|
141
- dupes.include?(card.value)
142
- end
143
-
144
- hand = hand.sort.reverse
145
- hand << (self.cards - hand).sort.reverse
146
- hand.flatten
147
- end
148
-
149
- def kinds(num)
150
- dupes = self.duplicates
151
- return nil unless dupes.length == 1
152
-
153
- hand = self.cards.select do |card|
154
- dupes.include?(card.value)
155
- end
156
-
157
- return nil unless hand.length == num
158
-
159
- hand = hand.sort.reverse
160
- hand << (self.cards - hand).sort.reverse
161
- hand.flatten
45
+ # Empties the hand
46
+ def empty!
47
+ @cards = []
162
48
  end
163
49
  end
164
50
  end