deck_of_cards_handler 0.1.4 → 0.1.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9bb4b9ad9eeda85ab0251a2d2a0d1030ad1713956582d204726402254d1b423b
4
- data.tar.gz: 61b9a15a482800bc77b55f3e81402314ad1668fdfb0d8d1076bec4388ab05cb6
3
+ metadata.gz: c7de6705b81dcdbf0b0a3401bb40a0a343fd6794ff909994c658c8a9f3d2304c
4
+ data.tar.gz: 8a4e77d32f0bf80d9fb5aab2b0fb79ce88c4a00715cc0ee40a13b019e1278ba4
5
5
  SHA512:
6
- metadata.gz: f10d2d29dabdd04098c987d992e06c303cb3a142097973aceb4f0e11cbfaf52feeea8d23418f488167fa645742accfb44ac6841efc71b84cada89b5362275865
7
- data.tar.gz: aa06d832d4e713cade2a60cd8a06fc7d7da776e3f86b9b1ce62c6101448c91c930f4248efd7a82c84587add52de9d3d08c05205a623eca53dde2d7c5f4fcb95a
6
+ metadata.gz: 2493bd9f55e74662de828a3a4260bb4a0d4f638fb4e1d26326091921e654cf1f8313243dccecd0e06f152229a68f9d331429ff4a25621ea35b419d84506f3d73
7
+ data.tar.gz: 18f233fa7fd9626e39b49b599d39381a3a69cf2c7650e85d1b2f9b0c345a8d9be6a521fce8d05a84526c556630973b849033d62a9c64285977b63bf3a508287d
data/CHANGELOG.md CHANGED
@@ -3,3 +3,7 @@
3
3
  ## [0.1.0] - 2025-09-26
4
4
 
5
5
  - Initial release
6
+
7
+ ## [0.1.6] - 2025-10-20
8
+
9
+ - Added poker hand comparison
data/README.md CHANGED
@@ -46,6 +46,62 @@ gem install deck_of_cards_handler
46
46
  # assign a position value to the cards
47
47
  deck.set_cards_positions
48
48
 
49
+ deck
50
+ # =>
51
+ # <Packet:0x000000012ae2b848
52
+ # @cards=
53
+ [#<Card:0x000000012ae2bd20 @position=1, @suit="S", @value="4">,
54
+ #<Card:0x000000012ae2cae0 @position=2, @suit="H", @value="2">,
55
+ #<Card:0x000000012ae2c220 @position=3, @suit="D", @value="7">,
56
+ #<Card:0x000000012ae2bd98 @position=4, @suit="S", @value="3">,
57
+ #<Card:0x000000012ae2c9f0 @position=5, @suit="H", @value="4">,
58
+ #<Card:0x000000012ae2c298 @position=6, @suit="D", @value="6">,
59
+ #<Card:0x000000012ae2df08 @position=7, @suit="C", @value="A">,
60
+ #<Card:0x000000012ae2c978 @position=8, @suit="H", @value="5">,
61
+ #<Card:0x000000012ae2ce00 @position=9, @suit="C", @value="9">,
62
+ #<Card:0x000000012ae2d260 @position=10, @suit="C", @value="2">,
63
+ #<Card:0x000000012ae2c630 @position=11, @suit="H", @value="Q">,
64
+ #<Card:0x000000012ae2c400 @position=12, @suit="D", @value="3">,
65
+ #<Card:0x000000012ae2b960 @position=13, @suit="S", @value="Q">,
66
+ #<Card:0x000000012ae2c810 @position=14, @suit="H", @value="8">,
67
+ #<Card:0x000000012ae2cf68 @position=15, @suit="C", @value="6">,
68
+ #<Card:0x000000012ae2cfe0 @position=16, @suit="C", @value="5">,
69
+ #<Card:0x000000012ae2c798 @position=17, @suit="H", @value="9">,
70
+ #<Card:0x000000012ae2b8e8 @position=18, @suit="S", @value="K">,
71
+ #<Card:0x000000012ae2c478 @position=19, @suit="D", @value="2">,
72
+ #<Card:0x000000012ae2c6a8 @position=20, @suit="H", @value="J">,
73
+ #<Card:0x000000012ae2d0d0 @position=21, @suit="C", @value="3">,
74
+ #<Card:0x000000012ae2ce78 @position=22, @suit="C", @value="8">,
75
+ #<Card:0x000000012ae2c900 @position=23, @suit="H", @value="6">,
76
+ #<Card:0x000000012ae2ba50 @position=24, @suit="S", @value="10">,
77
+ #<Card:0x000000012ae2c310 @position=25, @suit="D", @value="5">,
78
+ #<Card:0x000000012ae2bf50 @position=26, @suit="D", @value="K">,
79
+ #<Card:0x000000012ae2be10 @position=27, @suit="S", @value="2">,
80
+ #<Card:0x000000012ae2ca68 @position=28, @suit="H", @value="3">,
81
+ #<Card:0x000000012ae2c1a8 @position=29, @suit="D", @value="8">,
82
+ #<Card:0x000000012ae2bca8 @position=30, @suit="S", @value="5">,
83
+ #<Card:0x000000012ae2cc20 @position=31, @suit="C", @value="K">,
84
+ #<Card:0x000000012ae2c040 @position=32, @suit="D", @value="J">,
85
+ #<Card:0x000000012ae2bb40 @position=33, @suit="S", @value="8">,
86
+ #<Card:0x000000012ae2cd88 @position=34, @suit="C", @value="10">,
87
+ #<Card:0x000000012ae2c5b8 @position=35, @suit="H", @value="K">,
88
+ #<Card:0x000000012ae2b9d8 @position=36, @suit="S", @value="J">,
89
+ #<Card:0x000000012ae2cef0 @position=37, @suit="C", @value="7">,
90
+ #<Card:0x000000012ae2c720 @position=38, @suit="H", @value="10">,
91
+ #<Card:0x000000012ae2c4f0 @position=39, @suit="D", @value="A">,
92
+ #<Card:0x000000012ae2d058 @position=40, @suit="C", @value="4">,
93
+ #<Card:0x000000012ae2c888 @position=41, @suit="H", @value="7">,
94
+ #<Card:0x000000012ae2c388 @position=42, @suit="D", @value="4">,
95
+ #<Card:0x000000012ae2be88 @position=43, @suit="S", @value="A">,
96
+ #<Card:0x000000012ae2bac8 @position=44, @suit="S", @value="9">,
97
+ #<Card:0x000000012ae2cd10 @position=45, @suit="C", @value="J">,
98
+ #<Card:0x000000012ae2bfc8 @position=46, @suit="D", @value="Q">,
99
+ #<Card:0x000000012ae2bbb8 @position=47, @suit="S", @value="7">,
100
+ #<Card:0x000000012ae2cc98 @position=48, @suit="C", @value="Q">,
101
+ #<Card:0x000000012ae2c0b8 @position=49, @suit="D", @value="10">,
102
+ #<Card:0x000000012ae2bc30 @position=50, @suit="S", @value="6">,
103
+ #<Card:0x000000012ae2cb58 @position=51, @suit="H", @value="A">,
104
+ #<Card:0x000000012ae2c130 @position=52, @suit="D", @value="9">]>
49
105
  ```
50
106
 
51
107
  </details>
@@ -68,6 +124,9 @@ gem install deck_of_cards_handler
68
124
  deck.shuffle
69
125
 
70
126
  hands = deck.deal_into_piles(number_of_piles: 5, number_of_cards: 5)
127
+
128
+ hands.map(&:to_s)
129
+ # => [["5 of D", "8 of C", "6 of S", "10 of D", "5 of C"], ["7 of C", "5 of S", "4 of C", "2 of D", "Q of D"], ["3 of S", "8 of D", "A of D", "2 of C", "7 of D"], ["Q of H", "4 of S", "3 of D", "J of S", "9 of S"], ["6 of C", "6 of H", "10 of C", "4 of D", "A of H"]]
71
130
  ```
72
131
 
73
132
  </details>
@@ -50,28 +50,20 @@ class Card
50
50
  other.suit == suit && other.value == value
51
51
  end
52
52
 
53
- sig { returns(T::Array[Integer]) }
54
- def rank
53
+ sig { params(low_ace: T::Boolean).returns(Integer) }
54
+ def rank(low_ace: false)
55
55
  case value
56
- when "A" then [1, 14]
57
- when "J" then [11]
58
- when "Q" then [12]
59
- when "K" then [13]
60
- else [value.to_i]
56
+ when "A" then low_ace ? 1 : 14
57
+ when "J" then 11
58
+ when "Q" then 12
59
+ when "K" then 13
60
+ else value.to_i
61
61
  end
62
62
  end
63
63
 
64
64
  sig { params(other: Card).returns(Integer) }
65
65
  def <=>(other)
66
- # compare by all possible ranks (Ace flexible)
67
- my_ranks = rank
68
- other_ranks = other.rank
69
-
70
- # check if any ranks overlap (Ace == 1 or 14)
71
- return 0 if (my_ranks & other_ranks).any?
72
-
73
- # otherwise compare by highest possible
74
- T.must(my_ranks.max) <=> T.must(other_ranks.max)
66
+ rank <=> other.rank
75
67
  end
76
68
 
77
69
  class << self
@@ -52,9 +52,9 @@ class Packet
52
52
  packet
53
53
  end
54
54
 
55
- sig { params(string: String).returns(Packet) }
55
+ sig { overridable.params(string: String).returns(Packet) }
56
56
  def build_from_string(string:) # rubocop:disable Metrics
57
- content = string.split(",")
57
+ content = string.split(",").map(&:strip)
58
58
  cards = []
59
59
  cards_set = Set.new
60
60
 
@@ -70,10 +70,7 @@ class Packet
70
70
  cards << card
71
71
  end
72
72
 
73
- packet = Packet.new(cards:)
74
- packet.set_cards_positions
75
-
76
- packet
73
+ Packet.new(cards:)
77
74
  end
78
75
  end
79
76
 
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "poker_hands/poker_hand"
4
+
5
+ require_relative "poker_hands/high_card"
6
+ require_relative "poker_hands/one_pair"
7
+ require_relative "poker_hands/two_pairs"
8
+ require_relative "poker_hands/three_of_a_kind"
9
+ require_relative "poker_hands/straight"
10
+ require_relative "poker_hands/flush"
11
+ require_relative "poker_hands/full_house"
12
+ require_relative "poker_hands/four_of_a_kind"
13
+ require_relative "poker_hands/straight_flush"
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module PokerHands
5
+ class Flush < PokerHand
6
+ sig { override.returns(Integer) }
7
+ def rank
8
+ 6
9
+ end
10
+
11
+ sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
12
+ def <=>(other)
13
+ return rank <=> other.rank unless instance_of?(other.class)
14
+
15
+ 0
16
+ end
17
+
18
+ class << self
19
+ sig { params(cards: T::Array[Card]).returns(T::Boolean) }
20
+ def is?(cards)
21
+ suit = T.must(cards.first).suit
22
+ cards.all? { _1.suit == suit }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module PokerHands
5
+ class FourOfAKind < PokerHand
6
+ class << self
7
+ sig { params(cards: T::Array[Card]).returns(T::Boolean) }
8
+ def is?(cards)
9
+ counts = cards.map(&:rank).flatten.tally
10
+ counts.values.count(4) == 1
11
+ end
12
+ end
13
+
14
+ sig { returns(T::Array[Card]) }
15
+ attr_reader :four_of_a_kind
16
+
17
+ sig { returns(T::Array[Card]) }
18
+ attr_reader :kicker
19
+
20
+ sig { params(cards: T::Array[Card]).void }
21
+ def initialize(cards:)
22
+ super
23
+ @four_of_a_kind = T.let(extract_four_of_a_kind, T::Array[Card])
24
+ @kicker = T.let(extract_kicker, T::Array[Card])
25
+ end
26
+
27
+ sig { override.returns(Integer) }
28
+ def rank
29
+ 8
30
+ end
31
+
32
+ sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
33
+ def <=>(other)
34
+ return rank <=> other.rank unless instance_of?(other.class)
35
+
36
+ comparison = four_of_a_kind_value <=> other.four_of_a_kind_value
37
+ return comparison unless comparison.zero?
38
+
39
+ kicker_value <=> other.kicker_value
40
+ end
41
+
42
+ protected
43
+
44
+ sig { returns(Integer) }
45
+ def four_of_a_kind_value
46
+ T.must(four_of_a_kind.first).rank
47
+ end
48
+
49
+ sig { returns(Integer) }
50
+ def kicker_value
51
+ T.must(kicker.first).rank
52
+ end
53
+
54
+ private
55
+
56
+ sig { returns(T::Array[Card]) }
57
+ def extract_four_of_a_kind
58
+ cards.group_by(&:rank).values.select { _1.size == 4 }.flatten
59
+ end
60
+
61
+ sig { returns(T::Array[Card]) }
62
+ def extract_kicker
63
+ cards.group_by(&:rank).values.select { _1.size == 1 }.flatten
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module PokerHands
5
+ class FullHouse < PokerHand
6
+ class << self
7
+ sig { params(cards: T::Array[Card]).returns(T::Boolean) }
8
+ def is?(cards)
9
+ PokerHands::ThreeOfAKind.is?(cards) && PokerHands::OnePair.is?(cards)
10
+ end
11
+ end
12
+
13
+ sig { returns(T::Array[Card]) }
14
+ attr_reader :three_of_a_kind
15
+
16
+ sig { returns(T::Array[Card]) }
17
+ attr_reader :pair
18
+
19
+ sig { params(cards: T::Array[Card]).void }
20
+ def initialize(cards:)
21
+ super
22
+ @three_of_a_kind = T.let(extract_three_of_a_kind, T::Array[Card])
23
+ @pair = T.let(extract_pair, T::Array[Card])
24
+ end
25
+
26
+ sig { override.returns(Integer) }
27
+ def rank
28
+ 7
29
+ end
30
+
31
+ sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
32
+ def <=>(other)
33
+ return rank <=> other.rank unless instance_of?(other.class)
34
+
35
+ comparison = three_of_a_kind_value <=> other.three_of_a_kind_value
36
+ return comparison unless comparison.zero?
37
+
38
+ pair_value <=> other.pair_value
39
+ end
40
+
41
+ protected
42
+
43
+ sig { returns(Integer) }
44
+ def three_of_a_kind_value
45
+ T.must(three_of_a_kind.first).rank
46
+ end
47
+
48
+ sig { returns(Integer) }
49
+ def pair_value
50
+ T.must(pair.first).rank
51
+ end
52
+
53
+ private
54
+
55
+ sig { returns(T::Array[Card]) }
56
+ def extract_three_of_a_kind
57
+ cards.group_by(&:rank).values.select { _1.size == 3 }.flatten
58
+ end
59
+
60
+ sig { returns(T::Array[Card]) }
61
+ def extract_pair
62
+ cards.group_by(&:rank).values.select { _1.size == 2 }.flatten
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module PokerHands
5
+ class HighCard < PokerHand
6
+ sig { override.returns(Integer) }
7
+ def rank
8
+ 1
9
+ end
10
+
11
+ sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
12
+ def <=>(other)
13
+ return rank <=> other.rank if self.class != other.class
14
+
15
+ sum_cards_ranks(cards) <=> sum_cards_ranks(other.cards)
16
+ end
17
+
18
+ private
19
+
20
+ sig { params(cards: T::Array[Card]).returns(Integer) }
21
+ def sum_cards_ranks(cards)
22
+ T.must(cards.map(&:rank).reduce(&:+))
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module PokerHands
5
+ class OnePair < PokerHand
6
+ class << self
7
+ sig { params(cards: T::Array[Card]).returns(T::Boolean) }
8
+ def is?(cards)
9
+ counts = cards.map(&:rank).flatten.tally
10
+ counts.values.count(2) == 1
11
+ end
12
+ end
13
+
14
+ sig { returns(T::Array[Card]) }
15
+ attr_reader :pair
16
+
17
+ sig { returns(T::Array[Card]) }
18
+ attr_reader :kickers
19
+
20
+ sig { params(cards: T::Array[Card]).void }
21
+ def initialize(cards:)
22
+ super
23
+ @pair = T.let(extract_pair, T::Array[Card])
24
+ @kickers = T.let(extract_kickers, T::Array[Card])
25
+ end
26
+
27
+ sig { override.returns(Integer) }
28
+ def rank
29
+ 2
30
+ end
31
+
32
+ sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
33
+ def <=>(other)
34
+ return rank <=> other.rank unless instance_of?(other.class)
35
+
36
+ pair_comparison = pair_value <=> other.pair_value
37
+ return pair_comparison unless pair_comparison.zero?
38
+
39
+ kickers_value <=> other.kickers_value
40
+ end
41
+
42
+ protected
43
+
44
+ sig { returns(Integer) }
45
+ def pair_value
46
+ T.must(pair.first).rank
47
+ end
48
+
49
+ sig { returns(Integer) }
50
+ def kickers_value
51
+ T.must(kickers.map(&:rank).reduce(&:+))
52
+ end
53
+
54
+ private
55
+
56
+ sig { returns(T::Array[Card]) }
57
+ def extract_pair
58
+ cards.group_by(&:rank).values.select { _1.size == 2 }.flatten
59
+ end
60
+
61
+ sig { returns(T::Array[Card]) }
62
+ def extract_kickers
63
+ cards.group_by(&:rank).values.select { _1.size == 1 }.flatten
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,61 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module PokerHands
5
+ # This represents a poker hands raking
6
+ class PokerHand
7
+ extend T::Sig
8
+ extend T::Helpers
9
+ include Comparable
10
+ abstract!
11
+
12
+ sig { returns(T::Array[Card]) }
13
+ attr_reader :cards
14
+
15
+ sig { params(cards: T::Array[Card]).void }
16
+ def initialize(cards:)
17
+ raise ArgumentError if cards.size != 5
18
+
19
+ @cards = cards
20
+ end
21
+
22
+ HANDS = %i[OnePair TwoPairs ThreeOfAKind Straight Flush FullHouse FourOfAKind StraightFlush].freeze
23
+
24
+ sig { abstract.params(other: T.untyped).returns(T.nilable(Integer)) }
25
+ def <=>(other); end
26
+
27
+ sig { abstract.returns(Integer) }
28
+ def rank; end
29
+
30
+ class << self
31
+ extend T::Sig
32
+ sig { params(string: String).returns(PokerHand) }
33
+ def build_from_string(string:)
34
+ cards = Packet.build_from_string(string:).cards
35
+ PokerHand.create(cards:)
36
+ end
37
+
38
+ sig do
39
+ params(cards: T::Array[Card]).returns(
40
+ T.any(
41
+ HighCard,
42
+ OnePair,
43
+ TwoPairs,
44
+ ThreeOfAKind,
45
+ Straight,
46
+ Flush,
47
+ FullHouse,
48
+ FourOfAKind,
49
+ StraightFlush
50
+ )
51
+ )
52
+ end
53
+ def create(cards:)
54
+ HANDS.reverse_each do |hand|
55
+ return PokerHands.const_get(hand).new(cards:) if PokerHands.const_get(hand).is?(cards)
56
+ end
57
+ HighCard.new(cards:)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module PokerHands
5
+ class Straight < PokerHand
6
+ class << self
7
+ sig { params(cards: T::Array[Card]).returns(T::Boolean) }
8
+ def is?(cards)
9
+ ranks = cards.map(&:rank).sort
10
+ return true if ranks.each_cons(2).all? { |a, b| b == T.must(a) + 1 }
11
+
12
+ ranks = cards.map { _1.rank(low_ace: true) }.sort
13
+ ranks.each_cons(2).all? { |a, b| b == T.must(a) + 1 }
14
+ end
15
+ end
16
+
17
+ sig { override.returns(Integer) }
18
+ def rank
19
+ 5
20
+ end
21
+
22
+ sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
23
+ def <=>(other)
24
+ return rank <=> other.rank unless instance_of?(other.class)
25
+
26
+ c1 = cards.map { card_value(_1) }
27
+ c2 = other.cards.map { other.card_value(_1) }
28
+
29
+ c1 <=> c2
30
+ end
31
+
32
+ protected
33
+
34
+ sig { params(card: Card).returns(Integer) }
35
+ def card_value(card)
36
+ return card.rank unless card.value == "A"
37
+ return 1 if low_ace?
38
+
39
+ 14
40
+ end
41
+
42
+ private
43
+
44
+ sig { returns(T::Boolean) }
45
+ def low_ace?
46
+ ranks = cards.map { _1.rank(low_ace: true) }.sort
47
+ ranks.each_cons(2).all? { |a, b| b == T.must(a) + 1 }
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module PokerHands
5
+ class StraightFlush < PokerHand
6
+ class << self
7
+ sig { params(cards: T::Array[Card]).returns(T::Boolean) }
8
+ def is?(cards)
9
+ Flush.is?(cards) && Straight.is?(cards)
10
+ end
11
+ end
12
+
13
+ sig { override.returns(Integer) }
14
+ def rank
15
+ 9
16
+ end
17
+
18
+ sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
19
+ def <=>(other)
20
+ return rank <=> other.rank unless instance_of?(other.class)
21
+
22
+ c1 = cards.map { card_value(_1) }
23
+ c2 = other.cards.map { other.card_value(_1) }
24
+
25
+ c1 <=> c2
26
+ end
27
+
28
+ protected
29
+
30
+ sig { params(card: Card).returns(Integer) }
31
+ def card_value(card)
32
+ return card.rank unless card.value == "A"
33
+ return 1 if low_ace?
34
+
35
+ 14
36
+ end
37
+
38
+ private
39
+
40
+ sig { returns(T::Boolean) }
41
+ def low_ace?
42
+ ranks = cards.map { _1.rank(low_ace: true) }.sort
43
+ ranks.each_cons(2).all? { |a, b| b == T.must(a) + 1 }
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module PokerHands
5
+ class ThreeOfAKind < PokerHand
6
+ class << self
7
+ sig { params(cards: T::Array[Card]).returns(T::Boolean) }
8
+ def is?(cards)
9
+ counts = cards.map(&:rank).flatten.tally
10
+ counts.values.count(3) == 1
11
+ end
12
+ end
13
+
14
+ sig { returns(T::Array[Card]) }
15
+ attr_reader :three_of_a_kind
16
+
17
+ sig { returns(T::Array[Card]) }
18
+ attr_reader :kickers
19
+
20
+ sig { params(cards: T::Array[Card]).void }
21
+ def initialize(cards:)
22
+ super
23
+ @three_of_a_kind = T.let(extract_three_of_a_kind, T::Array[Card])
24
+ @kickers = T.let(extract_kickers, T::Array[Card])
25
+ end
26
+
27
+ sig { override.returns(Integer) }
28
+ def rank
29
+ 4
30
+ end
31
+
32
+ sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
33
+ def <=>(other)
34
+ return rank <=> other.rank unless instance_of?(other.class)
35
+
36
+ comparison = three_of_a_kind_value <=> other.three_of_a_kind_value
37
+ return comparison unless comparison.zero?
38
+
39
+ kickers_value <=> other.kickers_value
40
+ end
41
+
42
+ protected
43
+
44
+ sig { returns(Integer) }
45
+ def three_of_a_kind_value
46
+ T.must(three_of_a_kind.first).rank
47
+ end
48
+
49
+ sig { returns(Integer) }
50
+ def kickers_value
51
+ T.must(kickers.map(&:rank).reduce(&:+))
52
+ end
53
+
54
+ private
55
+
56
+ sig { returns(T::Array[Card]) }
57
+ def extract_three_of_a_kind
58
+ cards.group_by(&:rank).values.select { _1.size == 3 }.flatten
59
+ end
60
+
61
+ sig { returns(T::Array[Card]) }
62
+ def extract_kickers
63
+ cards.group_by(&:rank).values.select { _1.size == 1 }.flatten
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module PokerHands
5
+ class TwoPairs < PokerHand
6
+ sig { returns(T::Array[Card]) }
7
+ attr_reader :pairs
8
+
9
+ sig { returns(T::Array[Card]) }
10
+ attr_reader :kicker
11
+
12
+ sig { params(cards: T::Array[Card]).void }
13
+ def initialize(cards:)
14
+ super
15
+ @pairs = T.let(extract_pairs, T::Array[Card])
16
+ @kicker = T.let(extract_kicker, T::Array[Card])
17
+ end
18
+
19
+ sig { override.returns(Integer) }
20
+ def rank
21
+ 3
22
+ end
23
+
24
+ sig { override.params(other: T.untyped).returns(T.nilable(Integer)) }
25
+ def <=>(other) # rubocop:disable Metrics/AbcSize
26
+ return rank <=> other.rank unless instance_of?(other.class)
27
+
28
+ first_pair_comparison = pair_values.first <=> other.pair_values.first
29
+ return first_pair_comparison unless T.must(first_pair_comparison).zero?
30
+
31
+ second_pair_comparison = pair_values.last <=> other.pair_values.last
32
+ return second_pair_comparison unless T.must(second_pair_comparison).zero?
33
+
34
+ kickers_value <=> other.kickers_value
35
+ end
36
+
37
+ class << self
38
+ sig { params(cards: T::Array[Card]).returns(T::Boolean) }
39
+ def is?(cards)
40
+ counts = cards.map(&:rank).flatten.tally
41
+ counts.values.count(2) == 2
42
+ end
43
+ end
44
+
45
+ protected
46
+
47
+ sig { returns(T::Array[Integer]) }
48
+ def pair_values
49
+ pairs.map(&:rank).sort.reverse
50
+ end
51
+
52
+ sig { returns(Integer) }
53
+ def kickers_value
54
+ T.must(kicker.map(&:rank).reduce(&:+))
55
+ end
56
+
57
+ private
58
+
59
+ sig { returns(T::Array[Card]) }
60
+ def extract_pairs
61
+ cards.group_by(&:rank).values.select { _1.size == 2 }.flatten
62
+ end
63
+
64
+ sig { returns(T::Array[Card]) }
65
+ def extract_kicker
66
+ cards.group_by(&:rank).values.select { _1.size == 1 }.flatten
67
+ end
68
+ end
69
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeckOfCardsHandler
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.6"
5
5
  end
@@ -4,3 +4,4 @@ require "sorbet-runtime"
4
4
  require_relative "deck_of_cards_handler/version"
5
5
  require_relative "deck_of_cards_handler/card"
6
6
  require_relative "deck_of_cards_handler/packet"
7
+ require_relative "deck_of_cards_handler/poker_hand"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deck_of_cards_handler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Bernard
@@ -75,6 +75,17 @@ files:
75
75
  - lib/deck_of_cards_handler/packet/deals.rb
76
76
  - lib/deck_of_cards_handler/packet/packet.rb
77
77
  - lib/deck_of_cards_handler/packet/shuffles.rb
78
+ - lib/deck_of_cards_handler/poker_hand.rb
79
+ - lib/deck_of_cards_handler/poker_hands/flush.rb
80
+ - lib/deck_of_cards_handler/poker_hands/four_of_a_kind.rb
81
+ - lib/deck_of_cards_handler/poker_hands/full_house.rb
82
+ - lib/deck_of_cards_handler/poker_hands/high_card.rb
83
+ - lib/deck_of_cards_handler/poker_hands/one_pair.rb
84
+ - lib/deck_of_cards_handler/poker_hands/poker_hand.rb
85
+ - lib/deck_of_cards_handler/poker_hands/straight.rb
86
+ - lib/deck_of_cards_handler/poker_hands/straight_flush.rb
87
+ - lib/deck_of_cards_handler/poker_hands/three_of_a_kind.rb
88
+ - lib/deck_of_cards_handler/poker_hands/two_pairs.rb
78
89
  - lib/deck_of_cards_handler/version.rb
79
90
  - sig/deck_of_cards.rbs
80
91
  - sorbet/config