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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4f9b59efbde62a455eb8cc518b88e10819824ec42723a97947093ebda55ca88
4
- data.tar.gz: 8be49f812cbde111460d824e308110dd2dcc5bda31248ab8d41fe8cbe6bf963f
3
+ metadata.gz: 10507b7c372be3c958141e1e06c399eaead316c8578bcbca4a988ece49e76af5
4
+ data.tar.gz: b0dfddf4d95907e61862efa01e0c8c8ae55a33a72d004c4557cf687e83dfaf96
5
5
  SHA512:
6
- metadata.gz: 271397f599f5d7e05c5cb86795506e5c97afb853d77eb575fa1d8ab612bd8641c3f4d4eea63aed3e4493c9742f01be98d6f2778e8b19c6483552f9ae78d549c2
7
- data.tar.gz: abbd762746b5b86b1ee16b983888072e396648fa7fd9632c02c9234d1f7be145c9a2b95ad949f2231a1fd289ed539482d6346bae24ef612db5912d3b0d10071d
6
+ metadata.gz: a0407ea05a850c8b2bac7be37c1b128589225d1122bd26ba4e01a4e510565d2b7ca89c187a27c362cd3d7fc564e6e7cc2a7b9d9bbdaf0c8462be512dcf79c72b
7
+ data.tar.gz: 1eb8477fd9c2f64e83133060a347a7b9a3e3f1f106d01612ec0e5ce72c21975974a9a0b1089c708e8403811063149ae93e22672e5f5eb762c78b86701e2f09e7
data/.rubocop.yml CHANGED
@@ -23,5 +23,15 @@ Style/Documentation:
23
23
  Layout/LineLength:
24
24
  Max: 120
25
25
 
26
+ Metrics/BlockLength:
27
+ AllowedMethods:
28
+ - 'namespace'
29
+
26
30
  RSpec/NestedGroups:
27
31
  Max: 4
32
+
33
+ RSpec/ExampleLength:
34
+ Max: 15
35
+
36
+ RSpec/MultipleMemoizedHelpers:
37
+ Max: 10
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, group: :test
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.1.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
- faker (3.1.1)
14
- i18n (>= 1.8.11, < 2)
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
- This gem allows you to generate a deck of cards, shuffle them, and deal them to players.
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 UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
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 UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
21
+ $ gem install card_dealer
14
22
 
15
23
  ## Usage
16
24
 
17
- TBD
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 `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
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 release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
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
- task default: %i[spec rubocop]
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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ target :lib do
4
+ signature "sig"
5
+ check "lib"
6
+ configure_code_diagnostics(Steep::Diagnostic::Ruby.lenient)
7
+ end
@@ -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
@@ -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
- # For faster lookup
15
- RANKS_SET = RANKS.to_set.freeze
16
- SUITS_SET = SUITS.to_set.freeze
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
- attr_reader :rank, :suit
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
- # Initializes a new card.
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
- # @param rank [String] the card's rank (2-9, T, J, Q, K, A)
23
- # @param suit [String] the card's suit (c, d, h, s)
24
- def initialize(rank, suit)
25
- validate_arguments rank, suit
69
+ def to_s
70
+ @to_s ||= "#{rank}#{suit}"
71
+ end
26
72
 
27
- @rank = rank
28
- @suit = suit
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
- # @return [String] the card's rank and suit
32
- def to_s
33
- "#{rank}#{suit}"
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
- # @return [String] the card's object string representation
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
- %(#<CardDealer::Card "#{self}">)
160
+ %(#<#{self.class} "#{self}">)
39
161
  end
40
162
 
41
163
  private
42
164
 
43
- def validate_arguments(rank, suit)
44
- raise Error, "Invalid rank: #{rank}" unless RANKS_SET.include?(rank)
45
- raise Error, "Invalid suit: #{suit}" unless SUITS_SET.include?(suit)
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
@@ -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
- attr_reader :cards, :seed
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 decks [Integer] the number of decks to use
10
- # @param cards_per_suit [Integer] the number of cards per suit to use
11
- # @param ranks [Array<String>, :highest, :lowest] the ranks to use
12
- # @option :highest will use the highest cards_per_suit ranks (A, K, Q, J, T, ...)
13
- # :lowest will use the lowest cards_per_suit ranks (2, 3, 4, 5, 6, ...)
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
- # Resets the deck to its original state.
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
- # @return [Deck]
29
- def reset
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
- @decks.times do
33
- @suits.each do |suit|
34
- @ranks.each do |rank|
35
- @cards << Card.new(rank, suit)
36
- end
37
- end
38
- end
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
- self
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
- # Shuffles the deck using the Fisher-Yates algorithm.
69
+ # Compares two deck instances for equality based on their cards.
44
70
  #
45
- # @param seed [Integer] the seed to use for shuffling
46
- # @return [Deck]
47
- def shuffle(seed = Random.new_seed)
48
- @seed = seed
49
- @cards.shuffle! random: Random.new(seed)
50
- self
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
- # @return [Array<String>] the array of cards in the deck as strings
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
- # @return [String] the cards in the deck as a string
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
- private
64
-
65
- def build_suits(suits)
66
- case suits
67
- when :all
68
- Card::SUITS
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
- def build_ranks(ranks, cards_per_suit)
77
- case ranks
78
- when :highest
79
- Card::RANKS.last(cards_per_suit)
80
- when :lowest
81
- Card::RANKS.first(cards_per_suit)
82
- when Array
83
- ranks
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CardDealer
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
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
@@ -8,18 +8,20 @@ module CardDealer
8
8
  RANKS: Array[String]
9
9
  SUITS: Array[String]
10
10
 
11
- RANKS_SET: Set[String]
12
- SUITS_SET: Set[String]
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: String, suit: String) -> void
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 validate_arguments: (rank: String, suit: String) -> void
25
+ def validate_card!: -> void
24
26
  end
25
27
  end
@@ -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
- @decks: Integer
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 reset: -> self
11
+ def shuffle: (?Integer seed) -> self
17
12
 
18
- def shuffle: (?seed: Integer) -> self
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
- private
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
@@ -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.1.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-03-26 00:00:00.000000000 Z
11
+ date: 2023-04-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Card dealer allows to generate a deck of cards, shuffle them, and deal
14
- them to players.
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: It deals with cards. It's a card dealer.
72
+ summary: A delightful card dealing companion for your digital table.
63
73
  test_files: []