deck_of_cards_handler 0.1.3 → 0.1.5
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 +4 -4
- data/README.md +135 -19
- data/lib/deck_of_cards_handler/card/card.rb +8 -16
- data/lib/deck_of_cards_handler/packet/packet.rb +11 -13
- data/lib/deck_of_cards_handler/poker_hand.rb +123 -0
- data/lib/deck_of_cards_handler/version.rb +1 -1
- data/lib/deck_of_cards_handler.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ada5266ea6fced842e0110cfb42413202aecd0344b6e50d7e03284b148a3de12
|
|
4
|
+
data.tar.gz: 69e43292eb8d3476afd899093ec29391e96dcc97cc52c0378c08830c5b35b47e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b65f41b901b72be9ad9e8978b34cc2ada7f886ed5e6fe704d8fb630cd02cba94bbee162e9e09097b4496663e41cd50d251b653ed77ac9ff92bb7bb5f1458d19f
|
|
7
|
+
data.tar.gz: 62ce89b3c2cf352ad82a8fd3f8c2c86cfbe731cfe4c9f633b53dbd3ff6183d822d48916f6e1b5b66647c85e1e96a75038e1b00916f201119e51dc118e3cfc155
|
data/README.md
CHANGED
|
@@ -1,38 +1,154 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Deck of cards handler
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
A ruby gem for simulating real-world deck handling: shuffle, cut, deal, cull
|
|
4
|
+
and more.
|
|
5
5
|
|
|
6
6
|
## Installation
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Run the following terminal command:
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
```zsh
|
|
11
|
+
gem install deck_of_cards_handler
|
|
12
|
+
```
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
## Usage examples
|
|
15
|
+
|
|
16
|
+
<details>
|
|
17
|
+
<summary>Create the Mnemonica stack from Stay Stack</summary>
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
require "deck_of_cards_handler"
|
|
21
|
+
|
|
22
|
+
# create a deck in stay stack order
|
|
23
|
+
clubs = Card.values.map { Card.new(suit: "C", value: _1) }
|
|
24
|
+
hearts = Card.values.map { Card.new(suit: "H", value: _1) }
|
|
25
|
+
diamonds = Card.values.map { Card.new(suit: "D", value: _1) }
|
|
26
|
+
spades = Card.values.map { Card.new(suit: "S", value: _1) }
|
|
27
|
+
deck = Packet.new(cards: [clubs, hearts, diamonds.reverse, spades.reverse].flatten)
|
|
28
|
+
|
|
29
|
+
# make 4 faro shuffles
|
|
30
|
+
4.times do
|
|
31
|
+
top_half = deck.cut(number: 26)
|
|
32
|
+
deck.faro(other_packet: top_half)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# reverse the first 26 cards
|
|
36
|
+
top_half = deck.cut(number: 26)
|
|
37
|
+
top_half.reverse
|
|
38
|
+
deck.cards = [top_half.cards, deck.cards].flatten
|
|
39
|
+
|
|
40
|
+
# faro the 18 first cards
|
|
41
|
+
top_half = deck.cut(number: 18)
|
|
42
|
+
deck.faro(other_packet: top_half)
|
|
43
|
+
|
|
44
|
+
# cut the 9D to the bottom
|
|
45
|
+
deck.cut_and_complete(number: 9)
|
|
46
|
+
# assign a position value to the cards
|
|
47
|
+
deck.set_cards_positions
|
|
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">]>
|
|
14
105
|
```
|
|
15
106
|
|
|
16
|
-
|
|
107
|
+
</details>
|
|
17
108
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
109
|
+
<details>
|
|
110
|
+
<summary>Distribute 5 hands of poker</summary>
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
require "deck_of_cards_handler"
|
|
21
114
|
|
|
22
|
-
|
|
115
|
+
# create a full deck of cards
|
|
116
|
+
cards = []
|
|
117
|
+
Card.suits.each do |suit|
|
|
118
|
+
Card.values.each do |value|
|
|
119
|
+
cards << Card.new(suit:, value:)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
deck = Packet.new(cards:)
|
|
23
123
|
|
|
24
|
-
|
|
124
|
+
deck.shuffle
|
|
125
|
+
|
|
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"]]
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
</details>
|
|
25
133
|
|
|
26
134
|
## Development
|
|
27
135
|
|
|
28
|
-
After checking out the repo, run
|
|
136
|
+
After checking out the repo, run:
|
|
29
137
|
|
|
30
|
-
|
|
138
|
+
```zsh
|
|
139
|
+
bin/setup
|
|
140
|
+
```
|
|
31
141
|
|
|
32
|
-
|
|
142
|
+
This installs dependencies.
|
|
33
143
|
|
|
34
|
-
|
|
144
|
+
Run the test suite:
|
|
35
145
|
|
|
36
|
-
|
|
146
|
+
```zsh
|
|
147
|
+
rake test
|
|
148
|
+
```
|
|
37
149
|
|
|
38
|
-
|
|
150
|
+
You can also open an interactive console for experimentation:
|
|
151
|
+
|
|
152
|
+
```zsh
|
|
153
|
+
bin/console
|
|
154
|
+
```
|
|
@@ -50,28 +50,20 @@ class Card
|
|
|
50
50
|
other.suit == suit && other.value == value
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
sig {
|
|
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
|
|
57
|
-
when "J" then
|
|
58
|
-
when "Q" then
|
|
59
|
-
when "K" then
|
|
60
|
-
else
|
|
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
|
-
|
|
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
|
|
@@ -47,14 +47,14 @@ class Packet
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
packet = Packet.new(cards:)
|
|
50
|
-
set_cards_positions
|
|
50
|
+
packet.set_cards_positions
|
|
51
51
|
|
|
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
|
|
|
@@ -71,19 +71,10 @@ class Packet
|
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
packet = Packet.new(cards:)
|
|
74
|
-
set_cards_positions
|
|
74
|
+
packet.set_cards_positions
|
|
75
75
|
|
|
76
76
|
packet
|
|
77
77
|
end
|
|
78
|
-
|
|
79
|
-
private
|
|
80
|
-
|
|
81
|
-
sig { params(packet: Packet).void }
|
|
82
|
-
def set_cards_positions(packet:)
|
|
83
|
-
packet.cards.each_with_index do |card, index|
|
|
84
|
-
card.position = index + 1
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
78
|
end
|
|
88
79
|
|
|
89
80
|
sig { params(other_packet: Packet).void }
|
|
@@ -106,6 +97,13 @@ class Packet
|
|
|
106
97
|
self.cards = cards.reverse
|
|
107
98
|
end
|
|
108
99
|
|
|
100
|
+
sig { void }
|
|
101
|
+
def set_cards_positions
|
|
102
|
+
cards.each_with_index do |card, index|
|
|
103
|
+
card.position = index + 1
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
109
107
|
sig { returns(T::Array[String]) }
|
|
110
108
|
def to_s
|
|
111
109
|
cards.map(&:to_s)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# This represents a poker hands raking
|
|
5
|
+
class PokerHand < Packet
|
|
6
|
+
extend T::Sig
|
|
7
|
+
extend T::Helpers
|
|
8
|
+
|
|
9
|
+
sig { params(cards: T::Array[Card]).void }
|
|
10
|
+
def initialize(cards:)
|
|
11
|
+
raise ArgumentError if cards.size != 5
|
|
12
|
+
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
sig { override.params(string: String).returns(PokerHand) }
|
|
18
|
+
def build_from_string(string:)
|
|
19
|
+
result = super
|
|
20
|
+
PokerHand.new(cards: result.cards)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
HAND_RANKS = T.let(
|
|
25
|
+
{
|
|
26
|
+
nothing: 0,
|
|
27
|
+
pair: 1,
|
|
28
|
+
two_pairs: 2,
|
|
29
|
+
three_of_a_kind: 3,
|
|
30
|
+
straight: 4,
|
|
31
|
+
flush: 5,
|
|
32
|
+
full_house: 6,
|
|
33
|
+
four_of_a_kind: 7,
|
|
34
|
+
straight_flush: 8
|
|
35
|
+
}.freeze, T::Hash[Symbol, Integer]
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
sig { returns(Integer) }
|
|
39
|
+
def rank # rubocop:disable Metrics
|
|
40
|
+
return T.must(HAND_RANKS[:straight_flush]) if straight_flush?
|
|
41
|
+
return T.must(HAND_RANKS[:four_of_a_kind]) if four_of_a_kind?
|
|
42
|
+
return T.must(HAND_RANKS[:full_house]) if full_house?
|
|
43
|
+
return T.must(HAND_RANKS[:flush]) if flush?
|
|
44
|
+
return T.must(HAND_RANKS[:straight]) if straight?
|
|
45
|
+
return T.must(HAND_RANKS[:three_of_a_kind]) if three_of_a_kind?
|
|
46
|
+
return T.must(HAND_RANKS[:two_pairs]) if two_pairs?
|
|
47
|
+
return T.must(HAND_RANKS[:pair]) if pair?
|
|
48
|
+
|
|
49
|
+
T.must(HAND_RANKS[:nothing])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
sig { returns(T::Boolean) }
|
|
53
|
+
def pair?
|
|
54
|
+
validate_hand_size
|
|
55
|
+
|
|
56
|
+
counts = cards.map(&:rank).flatten.tally
|
|
57
|
+
counts.values.count(2) == 1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
sig { returns(T::Boolean) }
|
|
61
|
+
def two_pairs?
|
|
62
|
+
validate_hand_size
|
|
63
|
+
|
|
64
|
+
counts = cards.map(&:rank).flatten.tally
|
|
65
|
+
counts.values.count(2) == 2
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
sig { returns(T::Boolean) }
|
|
69
|
+
def three_of_a_kind?
|
|
70
|
+
validate_hand_size
|
|
71
|
+
|
|
72
|
+
counts = cards.map(&:rank).flatten.tally
|
|
73
|
+
counts.values.count(3) == 1
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
sig { returns(T::Boolean) }
|
|
77
|
+
def straight? # rubocop:disable Metrics/AbcSize
|
|
78
|
+
validate_hand_size
|
|
79
|
+
|
|
80
|
+
ranks = cards.map(&:rank).sort
|
|
81
|
+
return true if ranks.each_cons(2).all? { |a, b| b == T.must(a) + 1 }
|
|
82
|
+
|
|
83
|
+
ranks = cards.map { _1.rank(low_ace: true) }
|
|
84
|
+
ranks.each_cons(2).all? { |a, b| b == T.must(a) + 1 }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
sig { returns(T::Boolean) }
|
|
88
|
+
def flush?
|
|
89
|
+
validate_hand_size
|
|
90
|
+
|
|
91
|
+
suit = T.must(cards.first).suit
|
|
92
|
+
cards.all? { _1.suit == suit }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
sig { returns(T::Boolean) }
|
|
96
|
+
def full_house?
|
|
97
|
+
validate_hand_size
|
|
98
|
+
|
|
99
|
+
three_of_a_kind? && pair?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
sig { returns(T::Boolean) }
|
|
103
|
+
def four_of_a_kind?
|
|
104
|
+
validate_hand_size
|
|
105
|
+
|
|
106
|
+
counts = cards.map(&:rank).flatten.tally
|
|
107
|
+
counts.values.count(4) == 1
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
sig { returns(T::Boolean) }
|
|
111
|
+
def straight_flush?
|
|
112
|
+
validate_hand_size
|
|
113
|
+
|
|
114
|
+
straight? && flush?
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
sig { void }
|
|
120
|
+
def validate_hand_size
|
|
121
|
+
raise StandardError unless size == 5
|
|
122
|
+
end
|
|
123
|
+
end
|
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
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Simon Bernard
|
|
@@ -75,6 +75,7 @@ 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
|
|
78
79
|
- lib/deck_of_cards_handler/version.rb
|
|
79
80
|
- sig/deck_of_cards.rbs
|
|
80
81
|
- sorbet/config
|