card_dealer 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -0
- data/CHANGELOG.md +34 -2
- data/Gemfile +4 -2
- data/Gemfile.lock +43 -4
- data/README.md +113 -6
- data/Rakefile +8 -1
- data/Steepfile +7 -0
- data/lib/card_dealer/binary_deck.rb +154 -0
- data/lib/card_dealer/build_deck.rb +124 -0
- data/lib/card_dealer/card.rb +156 -19
- data/lib/card_dealer/deck.rb +94 -61
- data/lib/card_dealer/version.rb +1 -1
- data/lib/card_dealer.rb +6 -4
- data/sig/card_dealer/binary_deck.rbs +30 -0
- data/sig/card_dealer/build_deck.rbs +21 -0
- data/sig/card_dealer/card.rbs +6 -4
- data/sig/card_dealer/deck.rbs +9 -16
- data/tasks/fixtures.rake +39 -0
- metadata +15 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10507b7c372be3c958141e1e06c399eaead316c8578bcbca4a988ece49e76af5
|
4
|
+
data.tar.gz: b0dfddf4d95907e61862efa01e0c8c8ae55a33a72d004c4557cf687e83dfaf96
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a0407ea05a850c8b2bac7be37c1b128589225d1122bd26ba4e01a4e510565d2b7ca89c187a27c362cd3d7fc564e6e7cc2a7b9d9bbdaf0c8462be512dcf79c72b
|
7
|
+
data.tar.gz: 1eb8477fd9c2f64e83133060a347a7b9a3e3f1f106d01612ec0e5ce72c21975974a9a0b1089c708e8403811063149ae93e22672e5f5eb762c78b86701e2f09e7
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,13 +1,45 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.2.0] - 2023-04-01
|
4
|
+
|
5
|
+
### BREAKING CHANGES
|
6
|
+
|
7
|
+
- Deck building in extracted from the `Deck` class to the `BuildDeck` class:
|
8
|
+
- `Deck#new` now takes an array of cards instead of a hash of options
|
9
|
+
- `Deck#reset` removed
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- `BinaryDeck` class to convert decks to binary format and back
|
14
|
+
(to save space when storing decks in a database, for example)
|
15
|
+
- `BuildDeck` class to build decks
|
16
|
+
- `Card` instances are now safe to use as hash keys
|
17
|
+
- `Card#eql?`, `Card#hash` and `Card#==` methods to compare cards and safely use them as hash keys
|
18
|
+
- `Card#<=>` method to make cards sortable
|
19
|
+
- `Deck#size` method to get the number of cards in the deck
|
20
|
+
- `Deck#deal` method to deal cards from the deck
|
21
|
+
- `Deck#to_binary_s` helper method to convert the deck to a binary string
|
22
|
+
- `Deck#from_binary` helper method to convert the binary string to a deck
|
23
|
+
- `Deck#==` method to compare decks for equality
|
24
|
+
- Lots of documentation
|
25
|
+
- [Steep](https://github.com/soutaro/steep) gem to check types correctness
|
26
|
+
|
27
|
+
### Changed
|
28
|
+
|
29
|
+
- It's possible to initialize a card with a string now, e.g. `Card.new('9c')`
|
30
|
+
|
31
|
+
### Fixed
|
32
|
+
|
33
|
+
- All the type declarations are now correct
|
34
|
+
|
3
35
|
## [0.1.0] - 2023-03-26
|
4
36
|
|
5
37
|
Initial release!
|
6
38
|
|
7
39
|
### Added
|
8
40
|
|
9
|
-
- Card class
|
10
|
-
- Deck class
|
41
|
+
- `Card` class
|
42
|
+
- `Deck` class
|
11
43
|
- Ability to create a decks of various size, ranks and suits
|
12
44
|
- Ability to shuffle a deck
|
13
45
|
- Ability to reset a deck
|
data/Gemfile
CHANGED
@@ -7,10 +7,12 @@ gemspec
|
|
7
7
|
|
8
8
|
gem "rake", "~> 13.0"
|
9
9
|
|
10
|
-
gem "faker", "~> 3.1"
|
11
10
|
gem "rspec", "~> 3.12"
|
12
|
-
gem "simplecov", require: false
|
11
|
+
gem "simplecov", require: false
|
12
|
+
gem "simplecov_json_formatter", require: false
|
13
13
|
|
14
14
|
gem "rubocop", "~> 1.28"
|
15
15
|
gem "rubocop-rake", "~> 0.6"
|
16
16
|
gem "rubocop-rspec", "~> 2.19"
|
17
|
+
|
18
|
+
gem "steep", require: false
|
data/Gemfile.lock
CHANGED
@@ -1,25 +1,41 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
card_dealer (0.
|
4
|
+
card_dealer (0.2.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
+
activesupport (7.0.4.3)
|
10
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
11
|
+
i18n (>= 1.6, < 2)
|
12
|
+
minitest (>= 5.1)
|
13
|
+
tzinfo (~> 2.0)
|
9
14
|
ast (2.4.2)
|
10
15
|
concurrent-ruby (1.2.2)
|
16
|
+
csv (3.2.6)
|
11
17
|
diff-lcs (1.5.0)
|
12
18
|
docile (1.4.0)
|
13
|
-
|
14
|
-
|
19
|
+
ffi (1.15.5)
|
20
|
+
fileutils (1.7.0)
|
15
21
|
i18n (1.12.0)
|
16
22
|
concurrent-ruby (~> 1.0)
|
17
23
|
json (2.6.3)
|
24
|
+
language_server-protocol (3.17.0.3)
|
25
|
+
listen (3.8.0)
|
26
|
+
rb-fsevent (~> 0.10, >= 0.10.3)
|
27
|
+
rb-inotify (~> 0.9, >= 0.9.10)
|
28
|
+
logger (1.5.3)
|
29
|
+
minitest (5.18.0)
|
18
30
|
parallel (1.22.1)
|
19
31
|
parser (3.2.1.1)
|
20
32
|
ast (~> 2.4.1)
|
21
33
|
rainbow (3.1.1)
|
22
34
|
rake (13.0.6)
|
35
|
+
rb-fsevent (0.11.2)
|
36
|
+
rb-inotify (0.10.1)
|
37
|
+
ffi (~> 1.0)
|
38
|
+
rbs (2.8.4)
|
23
39
|
regexp_parser (2.7.0)
|
24
40
|
rexml (3.2.5)
|
25
41
|
rspec (3.12.0)
|
@@ -55,26 +71,49 @@ GEM
|
|
55
71
|
rubocop (~> 1.33)
|
56
72
|
rubocop-capybara (~> 2.17)
|
57
73
|
ruby-progressbar (1.13.0)
|
74
|
+
securerandom (0.2.2)
|
58
75
|
simplecov (0.22.0)
|
59
76
|
docile (~> 1.1)
|
60
77
|
simplecov-html (~> 0.11)
|
61
78
|
simplecov_json_formatter (~> 0.1)
|
62
79
|
simplecov-html (0.12.3)
|
63
80
|
simplecov_json_formatter (0.1.4)
|
81
|
+
steep (1.3.2)
|
82
|
+
activesupport (>= 5.1)
|
83
|
+
csv (>= 3.0.9)
|
84
|
+
fileutils (>= 1.1.0)
|
85
|
+
json (>= 2.1.0)
|
86
|
+
language_server-protocol (>= 3.15, < 4.0)
|
87
|
+
listen (~> 3.0)
|
88
|
+
logger (>= 1.3.0)
|
89
|
+
parallel (>= 1.0.0)
|
90
|
+
parser (>= 3.1)
|
91
|
+
rainbow (>= 2.2.2, < 4.0)
|
92
|
+
rbs (~> 2.8.0)
|
93
|
+
securerandom (>= 0.1)
|
94
|
+
strscan (>= 1.0.0)
|
95
|
+
terminal-table (>= 2, < 4)
|
96
|
+
strscan (3.0.6)
|
97
|
+
terminal-table (3.0.2)
|
98
|
+
unicode-display_width (>= 1.1.1, < 3)
|
99
|
+
tzinfo (2.0.6)
|
100
|
+
concurrent-ruby (~> 1.0)
|
64
101
|
unicode-display_width (2.4.2)
|
65
102
|
|
66
103
|
PLATFORMS
|
67
104
|
arm64-darwin-22
|
105
|
+
x86_64-linux
|
68
106
|
|
69
107
|
DEPENDENCIES
|
70
108
|
card_dealer!
|
71
|
-
faker (~> 3.1)
|
72
109
|
rake (~> 13.0)
|
73
110
|
rspec (~> 3.12)
|
74
111
|
rubocop (~> 1.28)
|
75
112
|
rubocop-rake (~> 0.6)
|
76
113
|
rubocop-rspec (~> 2.19)
|
77
114
|
simplecov
|
115
|
+
simplecov_json_formatter
|
116
|
+
steep
|
78
117
|
|
79
118
|
BUNDLED WITH
|
80
119
|
2.4.9
|
data/README.md
CHANGED
@@ -1,26 +1,133 @@
|
|
1
1
|
# CardDealer
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/card_dealer.svg)](https://badge.fury.io/rb/card_dealer)
|
3
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/a5266ef126fbbe754ff8/maintainability)](https://codeclimate.com/github/svyatov/card_dealer/maintainability)
|
4
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/a5266ef126fbbe754ff8/test_coverage)](https://codeclimate.com/github/svyatov/card_dealer/test_coverage)
|
5
|
+
[![MIT License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.txt)
|
2
6
|
|
3
|
-
|
7
|
+
CardDealer is your go-to gem for creating, shuffling, and dealing decks of cards
|
8
|
+
with ease. Whether you're building a poker night app or a virtual bridge club,
|
9
|
+
CardDealer has got you covered. Enjoy customizable deck options, smooth
|
10
|
+
shuffling algorithms, and simple yet powerful deck manipulation tools that bring
|
11
|
+
the classic card game experience to life.
|
4
12
|
|
5
13
|
## Installation
|
6
14
|
|
7
15
|
Install the gem and add to the application's Gemfile by executing:
|
8
16
|
|
9
|
-
$ bundle add
|
17
|
+
$ bundle add card_dealer
|
10
18
|
|
11
19
|
If bundler is not being used to manage dependencies, install the gem by executing:
|
12
20
|
|
13
|
-
$ gem install
|
21
|
+
$ gem install card_dealer
|
14
22
|
|
15
23
|
## Usage
|
16
24
|
|
17
|
-
|
25
|
+
### Creating a standard 52-card deck
|
26
|
+
|
27
|
+
To create a standard 52-card deck, use the `CardDealer::BuildDeck.standard52` method:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
deck = CardDealer::BuildDeck.standard52
|
31
|
+
puts deck.cards
|
32
|
+
```
|
33
|
+
|
34
|
+
### Creating a standard 36-card deck
|
35
|
+
|
36
|
+
To create a standard 36-card deck, use the `CardDealer::BuildDeck.standard36` method:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
deck = CardDealer::BuildDeck.standard36
|
40
|
+
puts deck.cards
|
41
|
+
```
|
42
|
+
|
43
|
+
### Creating a custom deck of cards
|
44
|
+
|
45
|
+
To create a custom deck of cards, use the `CardDealer::BuildDeck.custom` method.
|
46
|
+
You can specify the number of decks, cards per suit, ranks, and suits:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
deck = CardDealer::BuildDeck.custom(
|
50
|
+
decks: 2,
|
51
|
+
cards_per_suit: 5,
|
52
|
+
ranks: :highest,
|
53
|
+
suits: %w[c d]
|
54
|
+
)
|
55
|
+
puts deck.cards
|
56
|
+
```
|
57
|
+
|
58
|
+
### Shuffling and dealing cards
|
59
|
+
|
60
|
+
The `CardDealer::Deck` class provides methods for shuffling and dealing cards:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
deck = CardDealer::BuildDeck.standard52
|
64
|
+
deck.shuffle
|
65
|
+
hand = deck.deal(5)
|
66
|
+
puts hand
|
67
|
+
```
|
68
|
+
|
69
|
+
You can also burn cards before dealing:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
deck = CardDealer::BuildDeck.standard52
|
73
|
+
deck.shuffle
|
74
|
+
hand = deck.deal(3, burn: 1)
|
75
|
+
puts hand
|
76
|
+
```
|
77
|
+
|
78
|
+
To burn cards without dealing, just pass `0` as the number of cards to deal:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
deck = CardDealer::BuildDeck.standard52
|
82
|
+
deck.shuffle
|
83
|
+
hand = deck.deal(0, burn: 1)
|
84
|
+
puts hand
|
85
|
+
```
|
86
|
+
|
87
|
+
Burned cards are stored within the deck and can be accessed via `burned_cards` method:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
deck = CardDealer::BuildDeck.standard52
|
91
|
+
deck.shuffle
|
92
|
+
deck.deal(0, burn: 10)
|
93
|
+
puts deck.burned_cards
|
94
|
+
```
|
95
|
+
|
96
|
+
### Encoding a deck of cards as a binary string
|
97
|
+
|
98
|
+
To encode a deck of cards as a binary string, use the `CardDealer::BinaryDeck.encode` method.
|
99
|
+
This is useful if you'd like to store a deck of cards in a database, cache, or a file:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
deck = CardDealer::BuildDeck.standard52
|
103
|
+
encoded_deck = CardDealer::BinaryDeck.encode(deck)
|
104
|
+
# - or -
|
105
|
+
encoded_deck = deck.to_binary_s
|
106
|
+
puts encoded_deck
|
107
|
+
```
|
108
|
+
|
109
|
+
### Decoding a binary string into a deck of cards
|
110
|
+
|
111
|
+
To decode a binary string into a deck of cards, use the `CardDealer::BinaryDeck.decode` method:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
encoded_deck = "\x02\xCDP" # binary string
|
115
|
+
decoded_deck = CardDealer::BinaryDeck.decode(encoded_deck)
|
116
|
+
# - or -
|
117
|
+
decoded_deck = CardDealer::Deck.from_binary(encoded_deck)
|
118
|
+
puts decoded_deck.cards
|
119
|
+
```
|
18
120
|
|
19
121
|
## Development
|
20
122
|
|
21
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
123
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
124
|
+
`rake spec` to run the tests. You can also run `bin/console` for an interactive
|
125
|
+
prompt that will allow you to experiment.
|
22
126
|
|
23
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To
|
127
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
128
|
+
release a new version, update the version number in `version.rb`, and then run
|
129
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
130
|
+
git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
24
131
|
|
25
132
|
## Contributing
|
26
133
|
|
data/Rakefile
CHANGED
@@ -9,4 +9,11 @@ require "rubocop/rake_task"
|
|
9
9
|
|
10
10
|
RuboCop::RakeTask.new
|
11
11
|
|
12
|
-
|
12
|
+
desc "Run Steep typeckecker"
|
13
|
+
task :steep do
|
14
|
+
exit(1) unless system("bundle exec steep check")
|
15
|
+
end
|
16
|
+
|
17
|
+
Dir["tasks/**/*.rake"].each { |t| load t }
|
18
|
+
|
19
|
+
task default: %i[steep rubocop spec]
|
data/Steepfile
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CardDealer
|
4
|
+
# The BinaryDeck class provides a way to efficiently store a deck of cards as binary data.
|
5
|
+
# It can encode a deck of cards into a binary string and decode it back into a deck.
|
6
|
+
# This class can handle encoding and decoding of decks with up to 4'294'967'295 cards.
|
7
|
+
#
|
8
|
+
# The binary format consists of an initial byte(s) representing the number of cards in the deck,
|
9
|
+
# followed by a series of 6-bit card representations packed into bytes.
|
10
|
+
#
|
11
|
+
# An encoded deck with 52 or fewer cards takes up no more than 40 bytes.
|
12
|
+
#
|
13
|
+
class BinaryDeck
|
14
|
+
class DeckTooLargeError < Error; end
|
15
|
+
|
16
|
+
SUIT_MAP = Card::SUIT_MAP.transform_values { |value| value * 13 }
|
17
|
+
RANK_MAP = Card::RANK_MAP
|
18
|
+
|
19
|
+
# A hash mapping cards to their corresponding numbers
|
20
|
+
CARD_NUMBER_MAP = SUIT_MAP.each_with_object({}) do |(suit, suit_number), hash|
|
21
|
+
RANK_MAP.each do |rank, rank_number|
|
22
|
+
hash[Card.new(rank, suit)] = suit_number + rank_number
|
23
|
+
end
|
24
|
+
end.freeze
|
25
|
+
|
26
|
+
# A hash mapping cards to their corresponding binary strings
|
27
|
+
CARD_BINARY_NUMBER_MAP = CARD_NUMBER_MAP.transform_values { |number| format "%06b", number }.freeze
|
28
|
+
# A hash mapping binary strings to their corresponding cards
|
29
|
+
REVERSE_CARD_BINARY_NUMBER_MAP = CARD_BINARY_NUMBER_MAP.invert
|
30
|
+
|
31
|
+
# The binary format string for encoding/decoding decks with size <= 255
|
32
|
+
BIN_TEMPLATE_8 = "CB*"
|
33
|
+
# The binary format string for encoding/decoding decks with size > 255 and <= 65535
|
34
|
+
BIN_TEMPLATE_16 = "SB*"
|
35
|
+
# The binary format string for encoding/decoding decks with size > 65535
|
36
|
+
BIN_TEMPLATE_32 = "LB*"
|
37
|
+
|
38
|
+
# The maximum deck size supported by the BIN_TEMPLATE_8 format
|
39
|
+
BIN_8_ENCODE_LIMIT = 255
|
40
|
+
# The maximum encoded deck bytesize supported by the BIN_TEMPLATE_8 format
|
41
|
+
BIN_8_DECODE_LIMIT = 193
|
42
|
+
# The maximum deck size supported by the BIN_TEMPLATE_16 format
|
43
|
+
BIN_16_ENCODE_LIMIT = 65_535
|
44
|
+
# The maximum encoded deck bytesize supported by the BIN_TEMPLATE_16 format
|
45
|
+
BIN_16_DECODE_LIMIT = 49_154
|
46
|
+
# The maximum deck size supported by the BIN_TEMPLATE_32 format
|
47
|
+
BIN_32_ENCODE_LIMIT = 4_294_967_295
|
48
|
+
# The maximum encoded deck bytesize supported by the BIN_TEMPLATE_32 format
|
49
|
+
BIN_32_DECODE_LIMIT = 3_221_225_472
|
50
|
+
|
51
|
+
class << self
|
52
|
+
# Encodes a deck of cards into a binary string using a suitable template.
|
53
|
+
#
|
54
|
+
# The method takes a Deck object, maps each card to its corresponding binary number,
|
55
|
+
# and concatenates the binary numbers to create a single binary string. Then, it chooses
|
56
|
+
# the appropriate binary encoding template based on the deck size and packs the deck size
|
57
|
+
# and binary numbers into a single encoded binary string.
|
58
|
+
#
|
59
|
+
# @param deck [Deck] The deck of cards to encode.
|
60
|
+
#
|
61
|
+
# @return [String] The encoded binary string representing the deck of cards.
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# deck = CardDealer::Deck.new([CardDealer::Card.new("As"), CardDealer::Card.new("Td")])
|
65
|
+
# encoded_deck = CardDealer::BinaryDeck.encode(deck)
|
66
|
+
# encoded_deck #=> "\x02\xCDP" (binary string)
|
67
|
+
#
|
68
|
+
def encode(deck)
|
69
|
+
template = encoding_template_for deck.size
|
70
|
+
binary_numbers = deck.cards.map { |card| CARD_BINARY_NUMBER_MAP.fetch(card) }.join
|
71
|
+
[deck.size, binary_numbers].pack(template)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Decodes a binary string into a deck of cards using a suitable template.
|
75
|
+
#
|
76
|
+
# The method takes an encoded binary string, and based on its size, selects the
|
77
|
+
# appropriate decoding template. It unpacks the binary string into a deck size
|
78
|
+
# and binary numbers. Then, it scans the binary numbers, takes the required number
|
79
|
+
# of cards based on the deck size, and maps each binary number back to its
|
80
|
+
# corresponding card. Finally, it returns a new Deck object containing the decoded cards.
|
81
|
+
#
|
82
|
+
# @param encoded_deck [String] The encoded binary string to decode.
|
83
|
+
#
|
84
|
+
# @return [Deck] The decoded deck of cards.
|
85
|
+
#
|
86
|
+
# @example
|
87
|
+
# encoded_deck = "\x02\xCDP" # (binary string)
|
88
|
+
# decoded_deck = CardDealer::BinaryDeck.decode(encoded_deck)
|
89
|
+
# decoded_deck.cards #=> [#<CardDealer::Card "As">, #<CardDealer::Card "Td">]
|
90
|
+
#
|
91
|
+
def decode(encoded_deck)
|
92
|
+
template = decoding_template_for encoded_deck.bytesize
|
93
|
+
deck_size, binary_numbers = encoded_deck.unpack(template)
|
94
|
+
cards = binary_numbers.scan(/.{6}/).take(deck_size).map do |binary_number|
|
95
|
+
REVERSE_CARD_BINARY_NUMBER_MAP.fetch(binary_number)
|
96
|
+
end
|
97
|
+
|
98
|
+
Deck.new cards
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
# Determines the appropriate encoding template based on the deck size.
|
104
|
+
#
|
105
|
+
# The method checks the deck size and returns the corresponding binary template
|
106
|
+
# for encoding the deck. If the deck size is too large to be supported, it raises
|
107
|
+
# a DeckTooLargeError.
|
108
|
+
#
|
109
|
+
# @param deck_size [Integer] The number of cards in the deck to be encoded.
|
110
|
+
#
|
111
|
+
# @return [String] The appropriate encoding template based on the deck size.
|
112
|
+
#
|
113
|
+
# @raise [DeckTooLargeError] If the deck size is too large to be supported.
|
114
|
+
#
|
115
|
+
# @example
|
116
|
+
# encoding_template_for(52) #=> "CB*"
|
117
|
+
# encoding_template_for(520) #=> "SB*"
|
118
|
+
# encoding_template_for(5_000_000_000) #=> raises DeckTooLargeError
|
119
|
+
#
|
120
|
+
def encoding_template_for(deck_size)
|
121
|
+
return BIN_TEMPLATE_8 if deck_size <= BIN_8_ENCODE_LIMIT
|
122
|
+
return BIN_TEMPLATE_16 if deck_size <= BIN_16_ENCODE_LIMIT
|
123
|
+
return BIN_TEMPLATE_32 if deck_size <= BIN_32_ENCODE_LIMIT
|
124
|
+
|
125
|
+
raise DeckTooLargeError, "Deck size #{deck_size} is too large to encode!"
|
126
|
+
end
|
127
|
+
|
128
|
+
# Determines the appropriate decoding template based on the size of the encoded deck.
|
129
|
+
#
|
130
|
+
# The method checks the encoded deck size and returns the corresponding binary template
|
131
|
+
# for decoding the deck. If the encoded deck size is too large to be supported, it raises
|
132
|
+
# a DeckTooLargeError.
|
133
|
+
#
|
134
|
+
# @param bytesize [Integer] The size of the encoded deck to be decoded.
|
135
|
+
#
|
136
|
+
# @return [String] The appropriate decoding template based on the encoded deck size.
|
137
|
+
#
|
138
|
+
# @raise [DeckTooLargeError] If the encoded deck size is too large to be supported.
|
139
|
+
#
|
140
|
+
# @example
|
141
|
+
# decoding_template_for(40) #=> "CB*"
|
142
|
+
# decoding_template_for(392) #=> "SB*"
|
143
|
+
# decoding_template_for(4_000_000_000) #=> raises DeckTooLargeError
|
144
|
+
#
|
145
|
+
def decoding_template_for(bytesize)
|
146
|
+
return BIN_TEMPLATE_8 if bytesize <= BIN_8_DECODE_LIMIT
|
147
|
+
return BIN_TEMPLATE_16 if bytesize <= BIN_16_DECODE_LIMIT
|
148
|
+
return BIN_TEMPLATE_32 if bytesize <= BIN_32_DECODE_LIMIT
|
149
|
+
|
150
|
+
raise DeckTooLargeError, "Encoded deck size #{bytesize} is too large to decode!"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CardDealer
|
4
|
+
# The BuildDeck class is responsible for creating a deck of cards with various configurations.
|
5
|
+
#
|
6
|
+
# It allows specifying the number of decks, cards per suit, ranks, and suits. It also provides
|
7
|
+
# methods for creating standard 52-card and 36-card decks. The resulting deck can be used with
|
8
|
+
# the {Deck} class for shuffling and other operations.
|
9
|
+
#
|
10
|
+
class BuildDeck
|
11
|
+
class InvalidRanksError < Error; end
|
12
|
+
class InvalidSuitsError < Error; end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Builds a new deck with the specified configuration.
|
16
|
+
#
|
17
|
+
# @param decks [Integer] The number of decks to use.
|
18
|
+
# @param cards_per_suit [Integer] The number of cards per suit to use.
|
19
|
+
# @param ranks [Array<String>, :highest, :lowest] The ranks to use.
|
20
|
+
# - :highest will use the highest cards_per_suit ranks (A, K, Q, J, T, ...).
|
21
|
+
# - :lowest will use the lowest cards_per_suit ranks (2, 3, 4, 5, 6, ...).
|
22
|
+
# - Array will use the specified ranks (e.g., %w[2 4 6 8 T]).
|
23
|
+
# @param suits [Array<String>, :all] The suits to use.
|
24
|
+
# - :all will use all suits (c, d, h, s).
|
25
|
+
# - Array will use the specified suits (e.g., %w[d h]).
|
26
|
+
#
|
27
|
+
# @return [Deck] A new deck with the specified configuration of cards.
|
28
|
+
#
|
29
|
+
# @raise [InvalidRanksError] If ranks are invalid.
|
30
|
+
# @raise [InvalidSuitsError] If suits are invalid.
|
31
|
+
#
|
32
|
+
def custom(decks: 1, cards_per_suit: 13, ranks: :highest, suits: :all)
|
33
|
+
ranks = build_ranks ranks, cards_per_suit
|
34
|
+
suits = build_suits suits
|
35
|
+
build_deck decks, ranks, suits
|
36
|
+
end
|
37
|
+
|
38
|
+
# Builds a standard 52-card deck.
|
39
|
+
#
|
40
|
+
# @param decks [Integer] The number of decks to use.
|
41
|
+
#
|
42
|
+
# @return [Deck] A new deck with the standard 52-card configuration.
|
43
|
+
#
|
44
|
+
def standard52(decks: 1)
|
45
|
+
custom decks:
|
46
|
+
end
|
47
|
+
|
48
|
+
# Builds a standard 36-card deck.
|
49
|
+
#
|
50
|
+
# @param decks [Integer] The number of decks to use.
|
51
|
+
#
|
52
|
+
# @return [Deck] A new deck with the standard 36-card configuration.
|
53
|
+
#
|
54
|
+
def standard36(decks: 1)
|
55
|
+
custom decks:, cards_per_suit: 9
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Builds the ranks for the deck based on the provided configuration.
|
61
|
+
#
|
62
|
+
# @param ranks [:highest, :lowest, Array<String>] The ranks configuration.
|
63
|
+
# @param cards_per_suit [Integer] The number of cards per suit.
|
64
|
+
#
|
65
|
+
# @return [Array<String>] The resulting ranks for the deck.
|
66
|
+
#
|
67
|
+
# @raise [InvalidRanksError] If the ranks configuration is invalid.
|
68
|
+
#
|
69
|
+
def build_ranks(ranks, cards_per_suit)
|
70
|
+
case ranks
|
71
|
+
when :highest
|
72
|
+
Card::RANKS.last(cards_per_suit)
|
73
|
+
when :lowest
|
74
|
+
Card::RANKS.first(cards_per_suit)
|
75
|
+
when Array
|
76
|
+
ranks
|
77
|
+
else
|
78
|
+
raise InvalidRanksError, "Invalid ranks: #{ranks}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Builds the suits for the deck based on the provided configuration.
|
83
|
+
#
|
84
|
+
# @param suits [:all, Array<String>] The suits configuration.
|
85
|
+
#
|
86
|
+
# @return [Array<String>] The resulting suits for the deck.
|
87
|
+
#
|
88
|
+
# @raise [InvalidSuitsError] If the suits configuration is invalid.
|
89
|
+
#
|
90
|
+
def build_suits(suits)
|
91
|
+
case suits
|
92
|
+
when :all
|
93
|
+
Card::SUITS
|
94
|
+
when Array
|
95
|
+
suits
|
96
|
+
else
|
97
|
+
raise InvalidSuitsError, "Invalid suits: #{suits}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Builds the deck using the specified configuration of decks, ranks, and suits.
|
102
|
+
#
|
103
|
+
# @param decks [Integer] The number of decks to use.
|
104
|
+
# @param ranks [Array<String>] The ranks to use in the deck.
|
105
|
+
# @param suits [Array<String>] The suits to use in the deck.
|
106
|
+
#
|
107
|
+
# @return [Deck] A new deck object with the specified configuration.
|
108
|
+
#
|
109
|
+
def build_deck(decks, ranks, suits)
|
110
|
+
cards = []
|
111
|
+
|
112
|
+
decks.times do
|
113
|
+
suits.each do |suit|
|
114
|
+
ranks.each do |rank|
|
115
|
+
cards << Card.new(rank, suit)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
Deck.new cards
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/card_dealer/card.rb
CHANGED
@@ -1,7 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CardDealer
|
4
|
+
# The Card class represents a standard playing card with a rank and suit.
|
5
|
+
#
|
6
|
+
# A card has a rank (2-9, T, J, Q, K, A) and a suit (c, d, h, s).
|
7
|
+
# The class provides methods to initialize a card, compare cards for equality,
|
8
|
+
# and generate string representations of card instances.
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
#
|
12
|
+
# card1 = CardDealer::Card.new("A", "s")
|
13
|
+
# card2 = CardDealer::Card.new("K", "h")
|
14
|
+
# card3 = CardDealer::Card.new("As")
|
15
|
+
#
|
16
|
+
# card1 == card3 # => true
|
17
|
+
# card1.to_s # => "As"
|
18
|
+
# card2.inspect # => '#<CardDealer::Card "Kh">'
|
19
|
+
#
|
4
20
|
class Card
|
21
|
+
class InvalidRankError < Error; end
|
22
|
+
class InvalidSuitError < Error; end
|
23
|
+
|
5
24
|
CLUBS = "c"
|
6
25
|
DIAMONDS = "d"
|
7
26
|
HEARTS = "h"
|
@@ -10,39 +29,157 @@ module CardDealer
|
|
10
29
|
# T = 10, J = Jack, Q = Queen, K = King, A = Ace
|
11
30
|
RANKS = %w[2 3 4 5 6 7 8 9 T J Q K A].freeze
|
12
31
|
SUITS = [CLUBS, DIAMONDS, HEARTS, SPADES].freeze
|
32
|
+
RANK_MAP = RANKS.zip((0..12).to_a).to_h.freeze
|
33
|
+
SUIT_MAP = SUITS.zip((0..3).to_a).to_h.freeze
|
13
34
|
|
14
|
-
#
|
15
|
-
|
16
|
-
|
35
|
+
# @return [String] The rank of the card
|
36
|
+
attr_reader :rank
|
37
|
+
# @return [String] The suit of the card
|
38
|
+
attr_reader :suit
|
17
39
|
|
18
|
-
|
40
|
+
# Initializes a new card instance with the specified rank and suit.
|
41
|
+
#
|
42
|
+
# The method accepts either a combination of rank and suit as separate arguments
|
43
|
+
# or a single string containing both rank and suit (e.g., "9c").
|
44
|
+
#
|
45
|
+
# @param rank [String] The card's rank (2-9, T, J, Q, K, A) or the card's combined rank and suit (e.g., "9c")
|
46
|
+
# @param suit [String, nil] The card's suit (c, d, h, s). Omit if the rank parameter contains both rank and suit.
|
47
|
+
#
|
48
|
+
# @example Creating a card with separate rank and suit arguments
|
49
|
+
# card = CardDealer::Card.new("9", "c")
|
50
|
+
#
|
51
|
+
# @example Creating a card with a combined rank and suit string
|
52
|
+
# card = CardDealer::Card.new("9c")
|
53
|
+
#
|
54
|
+
def initialize(rank, suit = nil)
|
55
|
+
rank, suit = rank.chars if suit.nil?
|
56
|
+
@rank = rank.to_s
|
57
|
+
@suit = suit.to_s
|
58
|
+
validate_card!
|
59
|
+
end
|
19
60
|
|
20
|
-
#
|
61
|
+
# Returns the string representation of the card, combining its rank and suit (e.g., "As", "Td", "2c").
|
62
|
+
#
|
63
|
+
# @return [String] The card's combined rank and suit as a string.
|
64
|
+
#
|
65
|
+
# @example
|
66
|
+
# card = CardDealer::Card.new("A", "s")
|
67
|
+
# card.to_s #=> "As"
|
21
68
|
#
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
validate_arguments rank, suit
|
69
|
+
def to_s
|
70
|
+
@to_s ||= "#{rank}#{suit}"
|
71
|
+
end
|
26
72
|
|
27
|
-
|
28
|
-
|
73
|
+
# Determines if two card instances are identical based on their rank and suit.
|
74
|
+
#
|
75
|
+
# This method compares the rank and suit of the current card instance with those
|
76
|
+
# of another card instance. It returns true if both rank and suit are equal,
|
77
|
+
# and false otherwise.
|
78
|
+
#
|
79
|
+
# @param other [Card] The card instance to compare with.
|
80
|
+
#
|
81
|
+
# @return [Boolean] True if the rank and suit of both card instances are equal, false otherwise.
|
82
|
+
#
|
83
|
+
# @example
|
84
|
+
# card1 = CardDealer::Card.new("A", "s")
|
85
|
+
# card2 = CardDealer::Card.new("A", "s")
|
86
|
+
# card1.eql?(card2) #=> true
|
87
|
+
#
|
88
|
+
def eql?(other)
|
89
|
+
return false unless other.is_a?(Card)
|
90
|
+
|
91
|
+
rank == other.rank && suit == other.suit
|
29
92
|
end
|
30
93
|
|
31
|
-
#
|
32
|
-
|
33
|
-
|
94
|
+
# Calculates a hash value for the card instance based on its string representation.
|
95
|
+
#
|
96
|
+
# This method computes a hash value for the card using the combined rank and suit string.
|
97
|
+
# This hash value is required when using card instances as hash keys.
|
98
|
+
#
|
99
|
+
# @return [Integer] The hash value of the card instance.
|
100
|
+
#
|
101
|
+
# @example
|
102
|
+
# card = CardDealer::Card.new("A", "s")
|
103
|
+
# card_hash = card.hash
|
104
|
+
# cards_hash = { card => "Ace of Spades" }
|
105
|
+
#
|
106
|
+
def hash
|
107
|
+
to_s.hash
|
34
108
|
end
|
35
109
|
|
36
|
-
#
|
110
|
+
# Compares two card instances for equality based on their rank and suit.
|
111
|
+
#
|
112
|
+
# This method utilizes the `eql?` method to compare the current card instance
|
113
|
+
# with another card instance. It returns true if both rank and suit are equal,
|
114
|
+
# and false otherwise.
|
115
|
+
#
|
116
|
+
# @param other [Card] The card instance to compare with.
|
117
|
+
#
|
118
|
+
# @return [Boolean] True if the rank and suit of both card instances are equal, false otherwise.
|
119
|
+
#
|
120
|
+
# @example
|
121
|
+
# card1 = CardDealer::Card.new("A", "s")
|
122
|
+
# card2 = CardDealer::Card.new("A", "s")
|
123
|
+
# card1 == card2 #=> true
|
124
|
+
#
|
125
|
+
def ==(other)
|
126
|
+
eql?(other)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Compares two card instances based on their rank and suit.
|
130
|
+
#
|
131
|
+
# @param other [Card] The card instance to compare with.
|
132
|
+
#
|
133
|
+
# @return [Integer, nil] Returns -1 if the current card is less than the
|
134
|
+
# other card, 0 if both cards are equal, and 1 if the current card is
|
135
|
+
# greater than the other card. Returns nil if the comparison is not possible
|
136
|
+
# (e.g., other is not a Card object).
|
137
|
+
#
|
138
|
+
def <=>(other)
|
139
|
+
return unless other.is_a?(Card)
|
140
|
+
return 0 if self == other
|
141
|
+
|
142
|
+
rank_comparison = RANK_MAP[rank] <=> RANK_MAP[other.rank]
|
143
|
+
return rank_comparison unless rank_comparison.zero?
|
144
|
+
|
145
|
+
SUIT_MAP[suit] <=> SUIT_MAP[other.suit]
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns a string representation of the card instance's object state for debugging purposes.
|
149
|
+
#
|
150
|
+
# This method generates a human-readable string that includes the card's class and string representation.
|
151
|
+
# It is useful for inspecting the object state when debugging.
|
152
|
+
#
|
153
|
+
# @return [String] A string representation of the card instance's object state.
|
154
|
+
#
|
155
|
+
# @example
|
156
|
+
# card = CardDealer::Card.new("A", "s")
|
157
|
+
# card.inspect #=> '#<CardDealer::Card "As">'
|
158
|
+
#
|
37
159
|
def inspect
|
38
|
-
%(
|
160
|
+
%(#<#{self.class} "#{self}">)
|
39
161
|
end
|
40
162
|
|
41
163
|
private
|
42
164
|
|
43
|
-
|
44
|
-
|
45
|
-
|
165
|
+
# Validates the rank and suit arguments provided when initializing a card instance.
|
166
|
+
#
|
167
|
+
# @raise [InvalidRankError] If the rank is invalid.
|
168
|
+
# @raise [InvalidSuitError] If the suit is invalid.
|
169
|
+
#
|
170
|
+
# @example
|
171
|
+
# # Valid rank and suit
|
172
|
+
# card = CardDealer::Card.new("A", "s")
|
173
|
+
#
|
174
|
+
# # Invalid rank
|
175
|
+
# card = CardDealer::Card.new("X", "s") #=> raises InvalidRankError, "Invalid rank: X"
|
176
|
+
#
|
177
|
+
# # Invalid suit
|
178
|
+
# card = CardDealer::Card.new("A", "x") #=> raises InvalidSuitError, "Invalid suit: x"
|
179
|
+
#
|
180
|
+
def validate_card!
|
181
|
+
raise InvalidRankError, "Invalid rank: #{rank}" unless RANK_MAP.key?(rank)
|
182
|
+
raise InvalidSuitError, "Invalid suit: #{suit}" unless SUIT_MAP.key?(suit)
|
46
183
|
end
|
47
184
|
end
|
48
185
|
end
|
data/lib/card_dealer/deck.rb
CHANGED
@@ -1,89 +1,122 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CardDealer
|
4
|
+
# The Deck class represents a collection of cards.
|
5
|
+
#
|
6
|
+
# It supports operations like shuffling, getting the size of the deck, and converting
|
7
|
+
# the deck to an array or string. The deck can be initialized with an array of {Card}
|
8
|
+
# objects, which can be created using the {BuildDeck} class.
|
9
|
+
#
|
4
10
|
class Deck
|
5
|
-
|
11
|
+
# @return [Array<Card>] The array of cards in the deck.
|
12
|
+
attr_reader :cards
|
13
|
+
# @return [Integer, nil] The seed used for shuffling the deck.
|
14
|
+
attr_reader :seed
|
15
|
+
# @return [Array<Card>] The array of cards that have been burned.
|
16
|
+
attr_reader :burned_cards
|
6
17
|
|
7
|
-
# Initializes a new deck.
|
8
|
-
#
|
9
|
-
# @param
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
# array will use the specified ranks (e.g. %w[2 4 6 8 T])
|
15
|
-
# @param suits [Array<String>, :all] the suits to use
|
16
|
-
# :all will use all suits (c, d, h, s)
|
17
|
-
# array will use the specified suits (e.g. %w[d h])
|
18
|
-
# @raise [Error] if ranks or suits are invalid
|
19
|
-
def initialize(decks: 1, cards_per_suit: 13, ranks: :highest, suits: :all)
|
20
|
-
@decks = decks
|
21
|
-
@ranks = build_ranks ranks, cards_per_suit
|
22
|
-
@suits = build_suits suits
|
23
|
-
reset
|
18
|
+
# Initializes a new deck with the specified cards.
|
19
|
+
#
|
20
|
+
# @param cards [Array<Card>] The array of cards to use in the deck (see {BuildDeck}).
|
21
|
+
#
|
22
|
+
def initialize(cards)
|
23
|
+
@cards = cards
|
24
|
+
@burned_cards = []
|
24
25
|
end
|
25
26
|
|
26
|
-
#
|
27
|
+
# Shuffles the deck using the Fisher-Yates algorithm.
|
28
|
+
#
|
29
|
+
# @param seed [Integer] The seed to use for shuffling. If not provided, a new seed is generated.
|
30
|
+
#
|
31
|
+
# @return [Deck] The shuffled deck.
|
27
32
|
#
|
28
|
-
|
29
|
-
|
30
|
-
@cards
|
33
|
+
def shuffle(seed = Random.new_seed)
|
34
|
+
@seed ||= seed
|
35
|
+
@cards.shuffle! random: Random.new(@seed.to_i)
|
36
|
+
self
|
37
|
+
end
|
31
38
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
+
# Deals a specified number of cards from the top of the deck, optionally burning cards before the deal.
|
40
|
+
#
|
41
|
+
# The method removes the specified number of cards from the top of the deck and returns them.
|
42
|
+
# If the burn parameter is specified, it will remove (burn) the indicated number of cards
|
43
|
+
# from the top of the deck before dealing.
|
44
|
+
#
|
45
|
+
# @param num [Integer] The number of cards to deal (default: 1).
|
46
|
+
# @param burn [Integer] The number of cards to burn before dealing (default: 0).
|
47
|
+
#
|
48
|
+
# @return [Array<Card>] The dealt cards.
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# deck = CardDealer::Deck.new([CardDealer::Card.new("As"), CardDealer::Card.new("Td")])
|
52
|
+
# dealt_cards = deck.deal(burn: 1)
|
53
|
+
# dealt_cards #=> [#<CardDealer::Card "Td">]
|
54
|
+
# deck.burned_cards #=> [CardDealer::Card.new("As")]
|
55
|
+
#
|
56
|
+
def deal(num = 1, burn: 0)
|
57
|
+
@burned_cards += cards.shift(burn) if burn.positive?
|
58
|
+
cards.shift(num)
|
59
|
+
end
|
39
60
|
|
40
|
-
|
61
|
+
# Returns the number of cards in the deck.
|
62
|
+
#
|
63
|
+
# @return [Integer] The number of cards in the deck.
|
64
|
+
#
|
65
|
+
def size
|
66
|
+
cards.size
|
41
67
|
end
|
42
68
|
|
43
|
-
#
|
69
|
+
# Compares two deck instances for equality based on their cards.
|
44
70
|
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
71
|
+
# Two decks are considered equal if they have the same cards in the same order.
|
72
|
+
#
|
73
|
+
# @param other [Deck] The other deck instance to compare with.
|
74
|
+
#
|
75
|
+
# @return [Boolean] True if the decks are equal, false otherwise.
|
76
|
+
#
|
77
|
+
# @example
|
78
|
+
# deck1 = CardDealer::BuildDeck.standard52
|
79
|
+
# deck2 = CardDealer::BuildDeck.standard52
|
80
|
+
# deck1 == deck2 #=> true
|
81
|
+
#
|
82
|
+
def ==(other)
|
83
|
+
return false unless other.is_a?(Deck)
|
84
|
+
|
85
|
+
cards == other.cards
|
51
86
|
end
|
52
87
|
|
53
|
-
#
|
88
|
+
# Returns an array of cards as strings in the deck.
|
89
|
+
#
|
90
|
+
# @return [Array<String>] The array of cards in the deck as strings (see {Card#to_s})
|
91
|
+
#
|
54
92
|
def to_a
|
55
93
|
cards.map(&:to_s)
|
56
94
|
end
|
57
95
|
|
58
|
-
#
|
96
|
+
# Returns the cards in the deck as a single string.
|
97
|
+
#
|
98
|
+
# @return [String] The cards in the deck as a single string.
|
99
|
+
#
|
59
100
|
def to_s
|
60
101
|
to_a.to_s
|
61
102
|
end
|
62
103
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
when Array
|
70
|
-
suits
|
71
|
-
else
|
72
|
-
raise Error, "Invalid suits: #{suits}"
|
73
|
-
end
|
104
|
+
# Encodes the deck as a binary string using the {BinaryDeck} class.
|
105
|
+
#
|
106
|
+
# @return [String] The binary representation of the deck.
|
107
|
+
#
|
108
|
+
def to_binary_s
|
109
|
+
BinaryDeck.encode(self)
|
74
110
|
end
|
75
111
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
else
|
85
|
-
raise Error, "Invalid ranks: #{ranks}"
|
86
|
-
end
|
112
|
+
# Decodes a binary string into a Deck instance using the {BinaryDeck} class.
|
113
|
+
#
|
114
|
+
# @param encoded_deck [String] The binary string representation of a deck.
|
115
|
+
#
|
116
|
+
# @return [Deck] The decoded Deck object.
|
117
|
+
#
|
118
|
+
def self.from_binary(encoded_deck)
|
119
|
+
BinaryDeck.decode(encoded_deck)
|
87
120
|
end
|
88
121
|
end
|
89
122
|
end
|
data/lib/card_dealer/version.rb
CHANGED
data/lib/card_dealer.rb
CHANGED
@@ -2,10 +2,12 @@
|
|
2
2
|
|
3
3
|
require "set"
|
4
4
|
|
5
|
-
require_relative "card_dealer/version"
|
6
|
-
require_relative "card_dealer/card"
|
7
|
-
require_relative "card_dealer/deck"
|
8
|
-
|
9
5
|
module CardDealer
|
10
6
|
class Error < StandardError; end
|
11
7
|
end
|
8
|
+
|
9
|
+
require_relative "card_dealer/version"
|
10
|
+
require_relative "card_dealer/card"
|
11
|
+
require_relative "card_dealer/deck"
|
12
|
+
require_relative "card_dealer/build_deck"
|
13
|
+
require_relative "card_dealer/binary_deck"
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module CardDealer
|
2
|
+
class BinaryDeck
|
3
|
+
SUIT_MAP: Hash[String, Integer]
|
4
|
+
RANK_MAP: Hash[String, Integer]
|
5
|
+
CARD_NUMBER_MAP: Hash[Card, Integer]
|
6
|
+
CARD_BINARY_NUMBER_MAP: Hash[Card, String]
|
7
|
+
REVERSE_CARD_BINARY_NUMBER_MAP: Hash[String, Card]
|
8
|
+
|
9
|
+
BIN_TEMPLATE_8: String
|
10
|
+
BIN_TEMPLATE_16: String
|
11
|
+
BIN_TEMPLATE_32: String
|
12
|
+
|
13
|
+
BIN_8_ENCODE_LIMIT: Integer
|
14
|
+
BIN_8_DECODE_LIMIT: Integer
|
15
|
+
BIN_16_ENCODE_LIMIT: Integer
|
16
|
+
BIN_16_DECODE_LIMIT: Integer
|
17
|
+
BIN_32_ENCODE_LIMIT: Integer
|
18
|
+
BIN_32_DECODE_LIMIT: Integer
|
19
|
+
|
20
|
+
def self.encode: (Deck) -> String
|
21
|
+
|
22
|
+
def self.decode: (String) -> Deck
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def self.encoding_template_for: (Integer) -> String
|
27
|
+
|
28
|
+
def self.decoding_template_for: (Integer) -> String
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module CardDealer
|
2
|
+
class BuildDeck
|
3
|
+
def self.custom: (
|
4
|
+
?decks: Integer,
|
5
|
+
?cards_per_suit: Integer,
|
6
|
+
?ranks: :highest | :lowest | Array[String],
|
7
|
+
?suits: :all | Array[String]) -> Deck
|
8
|
+
|
9
|
+
def self.standard52: (?decks: Integer) -> Deck
|
10
|
+
|
11
|
+
def self.standard36: (?decks: Integer) -> Deck
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def self.build_ranks: (:highest | :lowest | Array[String] ranks, Integer cards_per_rank) -> Array[String]
|
16
|
+
|
17
|
+
def self.build_suits: (:all | Array[String] suits) -> Array[String]
|
18
|
+
|
19
|
+
def self.build_deck: (Integer decks, Array[String] ranks, Array[String] suits) -> Deck
|
20
|
+
end
|
21
|
+
end
|
data/sig/card_dealer/card.rbs
CHANGED
@@ -8,18 +8,20 @@ module CardDealer
|
|
8
8
|
RANKS: Array[String]
|
9
9
|
SUITS: Array[String]
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
RANK_MAP: Hash[String, Integer]
|
12
|
+
SUIT_MAP: Hash[String, Integer]
|
13
|
+
|
14
|
+
@to_s: String
|
13
15
|
|
14
16
|
attr_reader rank: String
|
15
17
|
attr_reader suit: String
|
16
18
|
|
17
|
-
def initialize: (rank
|
19
|
+
def initialize: (String rank, ?String? suit) -> void
|
18
20
|
|
19
21
|
def to_s: -> String
|
20
22
|
|
21
23
|
private
|
22
24
|
|
23
|
-
def
|
25
|
+
def validate_card!: -> void
|
24
26
|
end
|
25
27
|
end
|
data/sig/card_dealer/deck.rbs
CHANGED
@@ -1,30 +1,23 @@
|
|
1
1
|
module CardDealer
|
2
2
|
class Deck
|
3
3
|
attr_reader cards: Array[Card]
|
4
|
-
attr_reader seed: Integer
|
4
|
+
attr_reader seed: Integer?
|
5
|
+
attr_reader burned_cards: Array[Card]?
|
5
6
|
|
6
|
-
|
7
|
-
@ranks: :highest | :lowest | Array[String]
|
8
|
-
@suits: :all | Array[String]
|
7
|
+
def self.from_binary: (String) -> Deck
|
9
8
|
|
10
|
-
def initialize: (
|
11
|
-
?decks: Integer,
|
12
|
-
?cards_per_suit: Integer,
|
13
|
-
?ranks: :highest | :lowest | Array[String],
|
14
|
-
?suits: :all | Array[String]) -> void
|
9
|
+
def initialize: (Array[Card] cards) -> void
|
15
10
|
|
16
|
-
def
|
11
|
+
def shuffle: (?Integer seed) -> self
|
17
12
|
|
18
|
-
def
|
13
|
+
def deal: (?Integer num, ?burn: Integer) -> Array[Card]
|
14
|
+
|
15
|
+
def size: -> Integer
|
19
16
|
|
20
17
|
def to_a: -> Array[String]
|
21
18
|
|
22
19
|
def to_s: -> String
|
23
20
|
|
24
|
-
|
25
|
-
|
26
|
-
def build_suits: (suits: :all | Array[String]) -> Array[String]
|
27
|
-
|
28
|
-
def build_ranks: (ranks: :highest | :lowest | Array[String]) -> Array[String]
|
21
|
+
def to_binary_s: -> String
|
29
22
|
end
|
30
23
|
end
|
data/tasks/fixtures.rake
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :fixtures do
|
4
|
+
desc "Generates deck fixtures for specs"
|
5
|
+
task :generate do
|
6
|
+
require "json"
|
7
|
+
require_relative "../lib/card_dealer"
|
8
|
+
|
9
|
+
# Less then 255 cards
|
10
|
+
deck = CardDealer::BuildDeck.standard52.shuffle
|
11
|
+
deck_partial = CardDealer::Deck.new(deck.cards.take(11)).shuffle
|
12
|
+
deck_single_card = CardDealer::Deck.new([deck.cards.first])
|
13
|
+
|
14
|
+
File.write("spec/fixtures/deck_standard52.json", JSON.pretty_generate(seed: deck.seed, cards: deck.to_a))
|
15
|
+
File.write("spec/fixtures/deck_standard52.bin", CardDealer::BinaryDeck.encode(deck))
|
16
|
+
File.write("spec/fixtures/deck_partial.json", JSON.pretty_generate(cards: deck_partial.to_a))
|
17
|
+
File.write("spec/fixtures/deck_partial.bin", CardDealer::BinaryDeck.encode(deck_partial))
|
18
|
+
File.write("spec/fixtures/deck_single_card.json", JSON.pretty_generate(cards: deck_single_card.to_a))
|
19
|
+
File.write("spec/fixtures/deck_single_card.bin", CardDealer::BinaryDeck.encode(deck_single_card))
|
20
|
+
|
21
|
+
# Between 255 and 65535 cards
|
22
|
+
encodable_cards_limit_large = CardDealer::BinaryDeck::BIN_16_ENCODE_LIMIT
|
23
|
+
max_encodable_decks_large = (encodable_cards_limit_large / deck.size).floor
|
24
|
+
max_encodable_cards_large = (deck.cards * (max_encodable_decks_large + 1)).take(encodable_cards_limit_large)
|
25
|
+
deck_large = CardDealer::Deck.new(max_encodable_cards_large).shuffle
|
26
|
+
|
27
|
+
File.write("spec/fixtures/deck_large.json", JSON.pretty_generate(cards: deck_large.to_a))
|
28
|
+
File.write("spec/fixtures/deck_large.bin", CardDealer::BinaryDeck.encode(deck_large))
|
29
|
+
|
30
|
+
# More than 65535 cards
|
31
|
+
deck_large_plus_one = CardDealer::Deck.new(max_encodable_cards_large + [deck.cards.sample]).shuffle
|
32
|
+
deck_huge = CardDealer::Deck.new(max_encodable_cards_large * 2).shuffle
|
33
|
+
|
34
|
+
File.write("spec/fixtures/deck_large_plus_one.json", JSON.pretty_generate(cards: deck_large_plus_one.to_a))
|
35
|
+
File.write("spec/fixtures/deck_large_plus_one.bin", CardDealer::BinaryDeck.encode(deck_large_plus_one))
|
36
|
+
File.write("spec/fixtures/deck_huge.json", JSON.pretty_generate(cards: deck_huge.to_a))
|
37
|
+
File.write("spec/fixtures/deck_huge.bin", CardDealer::BinaryDeck.encode(deck_huge))
|
38
|
+
end
|
39
|
+
end
|
metadata
CHANGED
@@ -1,17 +1,21 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: card_dealer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Leonid Svyatov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-01 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
14
|
-
|
13
|
+
description: |
|
14
|
+
CardDealer is your go-to gem for creating, shuffling, and dealing decks of
|
15
|
+
cards with ease. Whether you're building a poker night app or a virtual
|
16
|
+
bridge club, CardDealer has got you covered. Enjoy customizable deck
|
17
|
+
options, smooth shuffling algorithms, and simple yet powerful deck
|
18
|
+
manipulation tools that bring the classic card game experience to life.
|
15
19
|
email:
|
16
20
|
- leonid@svyatov.com
|
17
21
|
executables: []
|
@@ -26,13 +30,19 @@ files:
|
|
26
30
|
- LICENSE.txt
|
27
31
|
- README.md
|
28
32
|
- Rakefile
|
33
|
+
- Steepfile
|
29
34
|
- lib/card_dealer.rb
|
35
|
+
- lib/card_dealer/binary_deck.rb
|
36
|
+
- lib/card_dealer/build_deck.rb
|
30
37
|
- lib/card_dealer/card.rb
|
31
38
|
- lib/card_dealer/deck.rb
|
32
39
|
- lib/card_dealer/version.rb
|
33
40
|
- sig/card_dealer.rbs
|
41
|
+
- sig/card_dealer/binary_deck.rbs
|
42
|
+
- sig/card_dealer/build_deck.rbs
|
34
43
|
- sig/card_dealer/card.rbs
|
35
44
|
- sig/card_dealer/deck.rbs
|
45
|
+
- tasks/fixtures.rake
|
36
46
|
homepage: https://github.com/svyatov/card_dealer
|
37
47
|
licenses:
|
38
48
|
- MIT
|
@@ -59,5 +69,5 @@ requirements: []
|
|
59
69
|
rubygems_version: 3.4.9
|
60
70
|
signing_key:
|
61
71
|
specification_version: 4
|
62
|
-
summary:
|
72
|
+
summary: A delightful card dealing companion for your digital table.
|
63
73
|
test_files: []
|