hands 0.0.2 → 0.1.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.
@@ -0,0 +1,153 @@
1
+ module Hands
2
+ class Hand
3
+ # @return [Hash] A hash with `:type` and `:cards` keys.
4
+ def best_hand
5
+ response = {}
6
+ HAND_ORDER.reverse.each do |type|
7
+ cards = self.send(type.to_sym)
8
+ next unless cards
9
+ response[:type] = type
10
+ response[:cards] = cards
11
+ break
12
+ end
13
+ response
14
+ end
15
+
16
+ # @return [Array] Array of {Card} objects with the highest {Card} first
17
+ def high_card
18
+ self.cards.sort.reverse
19
+ end
20
+
21
+ # @return [Array, Nil] Array of {Card} objects with the pair first and kickers in decending order or `nil` if there isn't a pair in the {Hand}
22
+ def pair
23
+ self.pairs(1)
24
+ end
25
+
26
+ # @return [Array, Nil] Array of {Card} objects with the pairs first and kickers in decending order or `nil` if there isn't two pair in the {Hand}
27
+ def two_pair
28
+ self.pairs(2)
29
+ end
30
+
31
+ # @return [Array, Nil] Array of {Card} objects with the three of a kind first and kickers in decending order or `nil` if there isn't three of a kind in the {Hand}
32
+ def three_of_a_kind
33
+ self.kinds(3)
34
+ end
35
+
36
+ # @return [Array, Nil] Array of {Card} objects with the straight in decending order or `nil` if there isn't a straight in the {Hand}
37
+ def straight
38
+ return nil unless self.cards.length == 5
39
+ cs = self.cards.sort.reverse
40
+
41
+ # Ace's low
42
+ if cs.first.value == 'a' and cs[1].value == '5'
43
+ # Move ace to end
44
+ ace = cs.first
45
+ cs = cs[1..4]
46
+ cs << ace
47
+
48
+ # Check succession
49
+ csr = cs.reverse
50
+ 4.times do |i|
51
+ next if i == 0
52
+ return nil unless csr[i].value_index == i - 1
53
+ end
54
+
55
+ # Normal
56
+ else
57
+ # Check range
58
+ return nil unless cs.first.value_index - cs.last.value_index == 4
59
+
60
+ # Check succession
61
+ 4.times do |i|
62
+ return nil unless cs[i].value_index == cs[i + 1].value_index + 1
63
+ end
64
+ end
65
+ cs
66
+ end
67
+
68
+ # @return [Array, Nil] Array of {Card} objects with the flush in decending order or `nil` if there isn't a flush in the {Hand}
69
+ def flush
70
+ # If all of the {Card}s are the same suit, we have a flush
71
+ return nil unless self.suits.length == 1
72
+ self.cards.sort.reverse
73
+ end
74
+
75
+ # @return [Array, Nil] Array of {Card} objects with the full house in decending order or `nil` if there isn't a full house in the {Hand}
76
+ def full_house
77
+ dupes = self.duplicates
78
+ return nil unless dupes.length == 2
79
+
80
+ a = []
81
+ b = []
82
+
83
+ hand = self.cards.select do |card|
84
+ if dupes.first == card.value
85
+ a << card
86
+ elsif dupes.last == card.value
87
+ b << card
88
+ end
89
+ end
90
+
91
+ return nil unless a.length + b.length == 5
92
+ self.cards.sort.reverse
93
+ end
94
+
95
+ # @return [Array, Nil] Array of {Card} objects with the four of a kind first the kicker in decending order or `nil` if there isn't four of a kind in the {Hand}
96
+ def four_of_a_kind
97
+ self.kinds(4)
98
+ end
99
+
100
+ # @return [Array, Nil] Array of {Card} objects with the straight flush in decending order or `nil` if there isn't a straight flush in the {Hand}
101
+ def straight_flush
102
+ return nil unless self.flush
103
+ self.straight
104
+ end
105
+
106
+ # Hand's index
107
+ #
108
+ # Mainly used for internal reasons when comparing {Hand}.
109
+ #
110
+ # @return [Integer] index of the {Hand}'s rank
111
+ # @see HAND_ORDER
112
+ def hand_index
113
+ best = self.best_hand
114
+ return -1 if best.nil?
115
+ HAND_ORDER.index(best[:type].to_s)
116
+ end
117
+
118
+ protected
119
+
120
+ def duplicates
121
+ pairs = self.cards.collect(&:value)
122
+ pairs.uniq.select{ |e| (pairs - [e]).size < pairs.size - 1 }
123
+ end
124
+
125
+ def pairs(min)
126
+ dupes = self.duplicates
127
+ return nil if dupes.length < min
128
+
129
+ hand = self.cards.select do |card|
130
+ dupes.include?(card.value)
131
+ end
132
+
133
+ hand = hand.sort.reverse
134
+ hand << (self.cards - hand).sort.reverse
135
+ hand.flatten
136
+ end
137
+
138
+ def kinds(num)
139
+ dupes = self.duplicates
140
+ return nil unless dupes.length == 1
141
+
142
+ hand = self.cards.select do |card|
143
+ dupes.include?(card.value)
144
+ end
145
+
146
+ return nil unless hand.length == num
147
+
148
+ hand = hand.sort.reverse
149
+ hand << (self.cards - hand).sort.reverse
150
+ hand.flatten
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,19 @@
1
+ module Hands
2
+ class Player
3
+ # @return [String] Their name
4
+ attr_accessor :name
5
+
6
+ # @return [Hand] Their hand
7
+ attr_accessor :hand
8
+
9
+ # @return [Table] The {Table} they are sitting at
10
+ attr_accessor :table
11
+
12
+ # Initialize a new {Player}
13
+ # @param [String] name {Player}'s name
14
+ def initialize(name = nil)
15
+ @hand = Hand.new
16
+ self.name = name if name
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,78 @@
1
+ module Hands
2
+ # Represents a poker table.
3
+ class Table
4
+ # @return [Deck] the {Deck}
5
+ attr_reader :deck
6
+
7
+ # @return [Array] {Card}s on the table
8
+ attr_reader :community
9
+
10
+ # @return [Array] {Card}s in the muck
11
+ attr_reader :muck
12
+
13
+ # @return [Array] {Player}s at the {Table}
14
+ attr_accessor :players
15
+
16
+ # @return [Integer] index of the {Player} that has the dealer button
17
+ attr_accessor :dealer_position
18
+
19
+ # Initializes the table with a {Deck}
20
+ def initialize
21
+ @deck = Deck.new
22
+ @community = []
23
+ @muck = []
24
+ @dealer_position = 0
25
+ @deck.shuffle!
26
+ end
27
+
28
+ # @return [Array] {Player}s ordered by position. Dealer is last.
29
+ def ordered_players
30
+ [@players.slice(@dealer_position + 1, @players.length - @dealer_position - 1) + @players.slice(0, @dealer_position + 1)]
31
+ end
32
+
33
+ def deal_player_cards!(number_of_cards = 2)
34
+ number_of_cards.times do
35
+ self.ordered_players.flatten.each do |player|
36
+ player.hand << @deck.pop
37
+ end
38
+ end
39
+ end
40
+
41
+ def deal_flop!
42
+ self.burn_card!
43
+ self.turn_cards!(3)
44
+ end
45
+
46
+ def deal_turn!
47
+ self.burn_card!
48
+ self.turn_card!
49
+ end
50
+
51
+ alias_method :deal_river!, :deal_turn!
52
+
53
+ def burn_card!
54
+ @muck << @deck.pop
55
+ end
56
+
57
+ def turn_cards!(number_of_cards = 1)
58
+ @community << @deck.pop(number_of_cards)
59
+ @community.flatten!
60
+ end
61
+
62
+ alias_method :turn_card!, :turn_cards!
63
+
64
+ def new_hand!
65
+ @deck = Deck.new
66
+ @community = []
67
+ @muck = []
68
+ @players.each do |player|
69
+ player.hand.empty!
70
+ end
71
+ @deck.shuffle!
72
+
73
+ # Move the button
74
+ @dealer_position = @dealer_position + 1
75
+ @dealer_position = 0 if @dealer_position == @players.length
76
+ end
77
+ end
78
+ end
@@ -1,3 +1,4 @@
1
1
  module Hands
2
- VERSION = '0.0.2'
2
+ # Gem version
3
+ VERSION = '0.1.0'
3
4
  end
@@ -0,0 +1,11 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ require 'rubygems'
5
+ require 'bundler'
6
+ Bundler.require :test
7
+ require 'minitest/autorun'
8
+ require 'hands'
9
+
10
+ class Hands::TestCase < MiniTest::Unit::TestCase
11
+ end
@@ -0,0 +1,72 @@
1
+ require 'test_helper'
2
+
3
+ class CardTest < Hands::TestCase
4
+ def test_that_it_validates_cards
5
+ card = Hands::Card.new
6
+ refute card.is_valid?
7
+ assert card.is_invalid?
8
+
9
+ card.suit = :hearts
10
+ refute card.is_valid?
11
+
12
+ card.value = 9
13
+ assert card.is_valid?
14
+
15
+ card.suit = 17
16
+ refute card.is_valid?
17
+
18
+ card.suit = :hearts
19
+ card.value = 19
20
+ refute card.is_valid?
21
+ end
22
+
23
+ def test_that_it_allows_integers_for_high_cards
24
+ card1 = Hands::Card.new(value: 11, suit: :clubs)
25
+ assert card1.is_valid?
26
+ assert_equal 'j', card1.value
27
+
28
+ card2 = Hands::Card.new(value: 'j', suit: :clubs)
29
+ assert_equal card1, card2
30
+ end
31
+
32
+ def test_that_its_comparable
33
+ card1 = Hands::Card.new(value: 2, suit: :hearts)
34
+ card2 = Hands::Card.new(value: 3, suit: :clubs)
35
+
36
+ assert card2 > card1
37
+ assert card1 < card2
38
+
39
+ card1.value = 3
40
+ assert card1 == card2
41
+ assert_equal 1, card1.<=>(card2, true)
42
+ assert_equal -1, card2.<=>(card1, true)
43
+ end
44
+
45
+ def test_that_it_is_sortable
46
+ c2 = Hands::Card.new(value: 2, suit: :hearts)
47
+ c3 = Hands::Card.new(value: 3, suit: :hearts)
48
+ c4 = Hands::Card.new(value: 4, suit: :hearts)
49
+ c5 = Hands::Card.new(value: 5, suit: :hearts)
50
+ c6 = Hands::Card.new(value: 6, suit: :hearts)
51
+ c7 = Hands::Card.new(value: 7, suit: :hearts)
52
+ c8 = Hands::Card.new(value: 8, suit: :hearts)
53
+ c9 = Hands::Card.new(value: 9, suit: :hearts)
54
+ c10 = Hands::Card.new(value: 10, suit: :hearts)
55
+ cJ = Hands::Card.new(value: 'j', suit: :hearts)
56
+ cQ = Hands::Card.new(value: 'q', suit: :hearts)
57
+ cK = Hands::Card.new(value: 'k', suit: :hearts)
58
+ cA = Hands::Card.new(value: 'a', suit: :hearts)
59
+
60
+ cards = [c2, c3, c4, c5, c6, c7, c8, c9, c10, cJ, cQ, cK, cA]
61
+ assert_equal cards, cards.sort
62
+ end
63
+
64
+ def test_that_it_includes_the_description_in_inspect
65
+ card = Hands::Card.new(value: 2, suit: :hearts)
66
+ assert_includes card.inspect, 'Two of Hearts'
67
+
68
+ card = Hands::Card.new
69
+ assert_equal 'invalid', card.description
70
+ refute_includes card.inspect, 'invalid'
71
+ end
72
+ end
@@ -0,0 +1,16 @@
1
+ require 'test_helper'
2
+
3
+ class DeckTest < Hands::TestCase
4
+ def test_shuffle
5
+ deck = Hands::Deck.new
6
+ cards = deck.cards.collect(&:description)
7
+ deck.shuffle!
8
+ refute_equal cards, deck.cards.collect(&:description)
9
+ end
10
+
11
+ def test_popping
12
+ deck = Hands::Deck.new
13
+ card = deck.pop
14
+ refute_includes deck.cards, card
15
+ end
16
+ end
@@ -0,0 +1,165 @@
1
+ require 'test_helper'
2
+
3
+ class HandDetectionTest < Hands::TestCase
4
+
5
+ def test_best_hand
6
+ hand = Hands::Hand.new
7
+ hand << Hands::Card[7, :hearts]
8
+ hand << Hands::Card[7, :spades]
9
+ hand << Hands::Card[4, :diamonds]
10
+ hand << Hands::Card[9, :hearts]
11
+ hand << Hands::Card[9, :clubs]
12
+ assert_equal 'two_pair', hand.best_hand[:type]
13
+ end
14
+
15
+
16
+ #### High Card
17
+
18
+ def test_high_card
19
+ hand = Hands::Hand.new
20
+ hand << Hands::Card[2, :hearts]
21
+ hand << Hands::Card[9, :clubs]
22
+ hand << Hands::Card[7, :hearts]
23
+ hand << Hands::Card['a', :spades]
24
+ hand << Hands::Card[4, :diamonds]
25
+ assert_equal %w{a 9 7 4 2}, hand.high_card.collect(&:value)
26
+ refute hand.pair
27
+ refute hand.two_pair
28
+ end
29
+
30
+
31
+ #### Pair
32
+
33
+ def test_pair
34
+ hand = Hands::Hand.new
35
+ hand << Hands::Card[9, :hearts]
36
+ hand << Hands::Card[9, :clubs]
37
+ hand << Hands::Card[7, :hearts]
38
+ hand << Hands::Card[2, :spades]
39
+ hand << Hands::Card[4, :diamonds]
40
+ assert_equal %w{9 9 7 4 2}, hand.pair.collect(&:value)
41
+ refute hand.two_pair
42
+ end
43
+
44
+
45
+ #### Two Pair
46
+
47
+ def test_two_pair
48
+ hand = Hands::Hand.new
49
+ hand << Hands::Card[7, :hearts]
50
+ hand << Hands::Card[7, :spades]
51
+ hand << Hands::Card[4, :diamonds]
52
+ hand << Hands::Card[9, :hearts]
53
+ hand << Hands::Card[9, :clubs]
54
+ assert_equal %w{9 9 7 7 4}, hand.two_pair.collect(&:value)
55
+ assert_equal hand.pair, hand.two_pair
56
+ end
57
+
58
+
59
+ #### Three of a Kind
60
+
61
+ def test_three_of_a_kind
62
+ hand = Hands::Hand.new
63
+ hand << Hands::Card[7, :hearts]
64
+ hand << Hands::Card[7, :spades]
65
+ hand << Hands::Card[7, :diamonds]
66
+ hand << Hands::Card[3, :hearts]
67
+ hand << Hands::Card[9, :clubs]
68
+ assert_equal 'three_of_a_kind', hand.best_hand[:type]
69
+ end
70
+
71
+
72
+ #### Straight
73
+
74
+ def test_straight
75
+ hand = Hands::Hand.new
76
+ hand << Hands::Card[2, :hearts]
77
+ hand << Hands::Card[3, :spades]
78
+ hand << Hands::Card[4, :diamonds]
79
+ hand << Hands::Card[5, :hearts]
80
+ hand << Hands::Card[6, :clubs]
81
+ assert_equal 'straight', hand.best_hand[:type]
82
+ end
83
+
84
+ def test_straight_when_ace_is_low
85
+ hand = Hands::Hand.new
86
+ hand << Hands::Card['A', :hearts]
87
+ hand << Hands::Card[2, :spades]
88
+ hand << Hands::Card[3, :diamonds]
89
+ hand << Hands::Card[4, :hearts]
90
+ hand << Hands::Card[5, :clubs]
91
+ assert_equal 'straight', hand.best_hand[:type]
92
+ end
93
+
94
+ def test_straight_when_ace_is_high
95
+ hand = Hands::Hand.new
96
+ hand << Hands::Card[10, :hearts]
97
+ hand << Hands::Card['J', :spades]
98
+ hand << Hands::Card['Q', :diamonds]
99
+ hand << Hands::Card['K', :hearts]
100
+ hand << Hands::Card['A', :clubs]
101
+ assert_equal 'straight', hand.best_hand[:type]
102
+ end
103
+
104
+ def test_straights_dont_wrap_around
105
+ hand = Hands::Hand.new
106
+ hand << Hands::Card['J', :spades]
107
+ hand << Hands::Card['Q', :diamonds]
108
+ hand << Hands::Card['K', :hearts]
109
+ hand << Hands::Card['A', :clubs]
110
+ hand << Hands::Card[2, :hearts]
111
+ assert_equal 'high_card', hand.best_hand[:type]
112
+ end
113
+
114
+
115
+ #### Flush
116
+
117
+ def test_flush
118
+ hand = Hands::Hand.new
119
+ hand << Hands::Card[6, :hearts]
120
+ hand << Hands::Card[7, :hearts]
121
+ hand << Hands::Card[8, :hearts]
122
+ hand << Hands::Card[2, :hearts]
123
+ hand << Hands::Card[4, :hearts]
124
+ assert_equal 'flush', hand.best_hand[:type]
125
+ end
126
+
127
+
128
+ #### Full House
129
+
130
+ def test_full_house
131
+ hand = Hands::Hand.new
132
+ hand << Hands::Card[7, :hearts]
133
+ hand << Hands::Card[7, :spades]
134
+ hand << Hands::Card[7, :diamonds]
135
+ hand << Hands::Card[9, :spades]
136
+ hand << Hands::Card[9, :clubs]
137
+ assert_equal 'full_house', hand.best_hand[:type]
138
+ end
139
+
140
+
141
+ #### Four of a Kind
142
+
143
+ def test_four_of_a_kind
144
+ hand = Hands::Hand.new
145
+ hand << Hands::Card[7, :hearts]
146
+ hand << Hands::Card[7, :spades]
147
+ hand << Hands::Card[7, :diamonds]
148
+ hand << Hands::Card[7, :clubs]
149
+ hand << Hands::Card[9, :clubs]
150
+ assert_equal 'four_of_a_kind', hand.best_hand[:type]
151
+ end
152
+
153
+
154
+ #### Straight Flush
155
+
156
+ def test_straight_flush
157
+ hand = Hands::Hand.new
158
+ hand << Hands::Card[2, :hearts]
159
+ hand << Hands::Card[3, :hearts]
160
+ hand << Hands::Card[4, :hearts]
161
+ hand << Hands::Card[5, :hearts]
162
+ hand << Hands::Card[6, :hearts]
163
+ assert_equal 'straight_flush', hand.best_hand[:type]
164
+ end
165
+ end