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,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