card_dealer 0.3.0 → 0.3.1
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/.rspec +0 -1
- data/CHANGELOG.md +8 -1
- data/CLAUDE.md +57 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +18 -17
- data/README.md +2 -0
- data/docs/ideation/2026-11-06-next-feature.md +131 -0
- data/lib/card_dealer/binary_deck.rb +7 -0
- data/lib/card_dealer/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 740499d4aa92d740445805a9e78e4c2ff72e0fdd79a44c1abd34d25aeed17b1d
|
|
4
|
+
data.tar.gz: 482ee0d77fcc5f171b220f601c99ce3beebfab94e60f989d2cf395ecd836178c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 67fe4b4516e2c4dd8857270330c7300a65abc6d038f9a0b8634cd63a003d6c96ccb4243ec9dbe86c5348dab9e783d9f2110d8c87443418a9bd11e5dc0aca5f29
|
|
7
|
+
data.tar.gz: 408fcce45791e37cc982665cf3432b104a21f15b937ecefed14c8dc360b9ed22ce695f4051a4e5a3a425d15eb58e2ff93e42effbb4cde84ba21e5f7abccf8f92
|
data/.rspec
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,12 @@ removed.
|
|
|
16
16
|
|
|
17
17
|
## [Unreleased]
|
|
18
18
|
|
|
19
|
+
## [0.3.1] - 2026-11-06
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Binary decoding rejects payload lengths that do not match the declared card count with `CardDealer::BinaryDeck::InvalidEncodingError`, including truncated decks.
|
|
24
|
+
|
|
19
25
|
## [0.3.0] - 2026-09-07
|
|
20
26
|
|
|
21
27
|
### Changed
|
|
@@ -71,7 +77,8 @@ Initial release!
|
|
|
71
77
|
- Ability to shuffle a deck
|
|
72
78
|
- Ability to reset a deck
|
|
73
79
|
|
|
74
|
-
[unreleased]: https://github.com/svyatov/card_dealer/compare/v0.3.
|
|
80
|
+
[unreleased]: https://github.com/svyatov/card_dealer/compare/v0.3.1...HEAD
|
|
81
|
+
[0.3.1]: https://github.com/svyatov/card_dealer/compare/v0.3.0...v0.3.1
|
|
75
82
|
[0.3.0]: https://github.com/svyatov/card_dealer/compare/v0.2.0...v0.3.0
|
|
76
83
|
[0.2.0]: https://github.com/svyatov/card_dealer/compare/v0.1.0...v0.2.0
|
|
77
84
|
[0.1.0]: https://github.com/svyatov/card_dealer/releases/tag/v0.1.0
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
CardDealer is a Ruby gem for creating, shuffling, and dealing decks of playing cards. It supports standard 52-card and 36-card decks, custom deck configurations, and binary serialization for storage.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install dependencies
|
|
13
|
+
bin/setup
|
|
14
|
+
|
|
15
|
+
# Run all checks (default task: steep + rubocop + rspec)
|
|
16
|
+
bundle exec rake
|
|
17
|
+
|
|
18
|
+
# Run tests only
|
|
19
|
+
bundle exec rake spec
|
|
20
|
+
|
|
21
|
+
# Run a single test file
|
|
22
|
+
bundle exec rspec spec/card_dealer/card_spec.rb
|
|
23
|
+
|
|
24
|
+
# Run a specific test by line number
|
|
25
|
+
bundle exec rspec spec/card_dealer/deck_spec.rb:42
|
|
26
|
+
|
|
27
|
+
# Run linter
|
|
28
|
+
bundle exec rake rubocop
|
|
29
|
+
|
|
30
|
+
# Run type checker
|
|
31
|
+
bundle exec rake steep
|
|
32
|
+
|
|
33
|
+
# Interactive console
|
|
34
|
+
bin/console
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Architecture
|
|
38
|
+
|
|
39
|
+
The gem has four main classes under the `CardDealer` module:
|
|
40
|
+
|
|
41
|
+
- **Card** (`lib/card_dealer/card.rb`) - Represents a single playing card with rank (2-A, where T=10) and suit (c/d/h/s). Cards are comparable and can be used as hash keys.
|
|
42
|
+
|
|
43
|
+
- **Deck** (`lib/card_dealer/deck.rb`) - A collection of cards supporting shuffle (Fisher-Yates with optional seed), deal, and burn operations. Tracks burned cards separately.
|
|
44
|
+
|
|
45
|
+
- **BuildDeck** (`lib/card_dealer/build_deck.rb`) - Factory class with class methods for creating decks:
|
|
46
|
+
- `standard52` / `standard36` for common configurations
|
|
47
|
+
- `custom` for specifying decks count, cards per suit, ranks (`:highest`, `:lowest`, or array), and suits
|
|
48
|
+
|
|
49
|
+
- **BinaryDeck** (`lib/card_dealer/binary_deck.rb`) - Encodes/decodes decks to/from binary strings using 6-bit card representations. Supports decks up to 4 billion cards with three template sizes (8/16/32-bit headers).
|
|
50
|
+
|
|
51
|
+
## Type Definitions
|
|
52
|
+
|
|
53
|
+
RBS type signatures are in `sig/`. The project uses Steep for type checking with lenient diagnostics configured in `Steepfile`.
|
|
54
|
+
|
|
55
|
+
## Requirements
|
|
56
|
+
|
|
57
|
+
- Ruby >= 3.3.0
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
card_dealer (0.3.
|
|
4
|
+
card_dealer (0.3.1)
|
|
5
5
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: https://rubygems.org/
|
|
8
8
|
specs:
|
|
9
9
|
ast (2.4.3)
|
|
10
10
|
concurrent-ruby (1.3.8)
|
|
11
|
-
csv (3.3.
|
|
11
|
+
csv (3.3.7)
|
|
12
12
|
diff-lcs (1.6.2)
|
|
13
13
|
ffi (1.17.4-arm64-darwin)
|
|
14
14
|
ffi (1.17.4-x86_64-linux-gnu)
|
|
15
|
-
fileutils (1.8.
|
|
16
|
-
json (
|
|
15
|
+
fileutils (1.8.1)
|
|
16
|
+
json (3.1.0)
|
|
17
17
|
language_server-protocol (3.17.0.6)
|
|
18
18
|
lint_roller (1.1.0)
|
|
19
|
-
listen (3.10.
|
|
19
|
+
listen (3.10.1)
|
|
20
20
|
logger
|
|
21
21
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
|
22
22
|
rb-inotify (~> 0.9, >= 0.9.10)
|
|
23
23
|
logger (1.7.0)
|
|
24
|
-
parallel (2.
|
|
25
|
-
parser (3.3.
|
|
24
|
+
parallel (2.3.0)
|
|
25
|
+
parser (3.3.13.0)
|
|
26
26
|
ast (~> 2.4.1)
|
|
27
27
|
racc
|
|
28
28
|
prism (1.9.0)
|
|
@@ -36,7 +36,7 @@ GEM
|
|
|
36
36
|
logger
|
|
37
37
|
prism (>= 1.6.0)
|
|
38
38
|
tsort
|
|
39
|
-
regexp_parser (2.
|
|
39
|
+
regexp_parser (2.13.1)
|
|
40
40
|
rspec (3.13.2)
|
|
41
41
|
rspec-core (~> 3.13.0)
|
|
42
42
|
rspec-expectations (~> 3.13.0)
|
|
@@ -50,14 +50,14 @@ GEM
|
|
|
50
50
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
51
51
|
rspec-support (~> 3.13.0)
|
|
52
52
|
rspec-support (3.13.7)
|
|
53
|
-
rubocop (1.
|
|
53
|
+
rubocop (1.92.2)
|
|
54
54
|
json (>= 2.3)
|
|
55
55
|
language_server-protocol (~> 3.17.0.2)
|
|
56
56
|
lint_roller (~> 1.1.0)
|
|
57
57
|
parallel (>= 1.10)
|
|
58
58
|
parser (>= 3.3.0.2)
|
|
59
59
|
rainbow (>= 2.2.2, < 4.0)
|
|
60
|
-
regexp_parser (>= 2.
|
|
60
|
+
regexp_parser (>= 2.11.3, < 3.0)
|
|
61
61
|
rubocop-ast (>= 1.49.0, < 2.0)
|
|
62
62
|
ruby-progressbar (~> 1.7)
|
|
63
63
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
@@ -73,7 +73,7 @@ GEM
|
|
|
73
73
|
rubocop (~> 1.86, >= 1.86.2)
|
|
74
74
|
ruby-progressbar (1.13.0)
|
|
75
75
|
securerandom (0.4.1)
|
|
76
|
-
simplecov (1.
|
|
76
|
+
simplecov (1.3.2)
|
|
77
77
|
steep (2.1.0)
|
|
78
78
|
concurrent-ruby (>= 1.1.10)
|
|
79
79
|
csv (>= 3.0.9)
|
|
@@ -94,26 +94,27 @@ GEM
|
|
|
94
94
|
terminal-table (4.0.0)
|
|
95
95
|
unicode-display_width (>= 1.1.1, < 4)
|
|
96
96
|
tsort (0.2.0)
|
|
97
|
-
unicode-display_width (3.
|
|
98
|
-
unicode-emoji (~> 4.
|
|
99
|
-
unicode-emoji (4.
|
|
100
|
-
uri (1.1.
|
|
97
|
+
unicode-display_width (3.3.0)
|
|
98
|
+
unicode-emoji (~> 4.3)
|
|
99
|
+
unicode-emoji (4.3.0)
|
|
100
|
+
uri (1.1.2)
|
|
101
101
|
|
|
102
102
|
PLATFORMS
|
|
103
103
|
arm64-darwin-22
|
|
104
104
|
arm64-darwin-23
|
|
105
105
|
arm64-darwin-24
|
|
106
|
+
arm64-darwin-25
|
|
106
107
|
x86_64-linux
|
|
107
108
|
|
|
108
109
|
DEPENDENCIES
|
|
109
110
|
card_dealer!
|
|
110
111
|
rake (~> 13.4)
|
|
111
112
|
rspec (~> 3.13)
|
|
112
|
-
rubocop (~> 1.
|
|
113
|
+
rubocop (~> 1.92)
|
|
113
114
|
rubocop-rake (~> 0.7)
|
|
114
115
|
rubocop-rspec (~> 3.10)
|
|
115
116
|
simplecov
|
|
116
117
|
steep
|
|
117
118
|
|
|
118
119
|
BUNDLED WITH
|
|
119
|
-
4.0.
|
|
120
|
+
4.0.23
|
data/README.md
CHANGED
|
@@ -119,6 +119,8 @@ deck = CardDealer::Deck.from_binary(encoded)
|
|
|
119
119
|
deck.to_a # => ["As", "Td"]
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
If the payload length does not match the declared card count, decoding raises `CardDealer::BinaryDeck::InvalidEncodingError`.
|
|
123
|
+
|
|
122
124
|
## Development
|
|
123
125
|
|
|
124
126
|
After checking out the repo, run `bin/setup` to install dependencies. Run `bundle exec rake` to run the type checker, the linter, and the tests. Run `bin/console` for an interactive prompt.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Next feature for CardDealer
|
|
2
|
+
|
|
3
|
+
## Subject
|
|
4
|
+
|
|
5
|
+
Identify the next obvious missing feature in this Ruby gem. Rank one feature, as requested.
|
|
6
|
+
|
|
7
|
+
The best feature candidate is atomic round-robin dealing to multiple hands. There is no evidence that any feature is mandatory.
|
|
8
|
+
|
|
9
|
+
Repair the confirmed binary-decoder truncation defect before adding features. The validation section records its reproduction.
|
|
10
|
+
|
|
11
|
+
Follow-up: commit `4376fae` implements the length check and documents `CardDealer::BinaryDeck::InvalidEncodingError`. The findings below describe the checkout examined before that repair.
|
|
12
|
+
|
|
13
|
+
### Grounding
|
|
14
|
+
|
|
15
|
+
- README.md:3 defines the purpose: "CardDealer builds, shuffles, and deals decks of playing cards for Ruby card game applications."
|
|
16
|
+
- `lib/card_dealer/deck.rb:56-58` implements one contiguous deal with optional burns. Multi-hand distribution requires caller loops.
|
|
17
|
+
- `spec/card_dealer/deck_spec.rb:97-102` intentionally permits empty results. Exact dealing must use an explicit new contract.
|
|
18
|
+
- `lib/card_dealer/binary_deck.rb:68-98` stores remaining card order. It omits the seed and burned cards.
|
|
19
|
+
- `CHANGELOG.md:54-56` records removal of `Deck#reset` and recommends rebuilding with `BuildDeck`.
|
|
20
|
+
- Recent commits `83513ab`, `bb3d2ce`, and `afa9a3d` concern dependency maintenance. They supply no feature-demand evidence.
|
|
21
|
+
- GitHub returned no open issues for `svyatov/card_dealer` on 2026-11-06. No glossary, ADRs, or tracker guide were found.
|
|
22
|
+
|
|
23
|
+
### Method
|
|
24
|
+
|
|
25
|
+
One read-only explorer supplied grounding. Four generation tasks covered all six ideation frames. Their results merged into 18 distinct candidates and three combinations.
|
|
26
|
+
|
|
27
|
+
An independent verifier checked all 21 candidates against the source and arguments. No verifier verdict was overruled.
|
|
28
|
+
|
|
29
|
+
Utility is inferred from the API. Source evidence establishes capability gaps, not consumer demand. Sizes include implementation, signatures, documentation, and meaningful tests.
|
|
30
|
+
|
|
31
|
+
## Ranked ideas
|
|
32
|
+
|
|
33
|
+
### 1. Atomic round-robin dealing to multiple hands
|
|
34
|
+
|
|
35
|
+
Distribute one card to each hand per round, returning ordinary arrays of cards. Check the entire request before removing cards.
|
|
36
|
+
|
|
37
|
+
If capacity is insufficient, raise a documented error and leave the deck unchanged. Preserve the permissive behavior of `deal`.
|
|
38
|
+
|
|
39
|
+
**Basis, direct:** `lib/card_dealer/deck.rb:56` defines `def deal(num = 1, burn: 0)`. Line 57 executes `@burned_cards += cards.shift(burn) if burn.positive?`. Line 58 executes `cards.shift(num)`. This returns a single contiguous group and performs no capacity preflight. `spec/card_dealer/deck_spec.rb:97-102` explicitly tests empty-array results for empty decks.
|
|
40
|
+
|
|
41
|
+
**Why it matters:** Games that distribute alternating cards need a caller loop. Games that require complete hands also need a capacity check.
|
|
42
|
+
|
|
43
|
+
Combining those operations gives one bounded dealing capability. It fits the stated purpose without introducing game rules, players, or hand classes.
|
|
44
|
+
|
|
45
|
+
**Size:** M. The algorithm is small, but public error semantics, signatures, compatibility, and examples need care.
|
|
46
|
+
|
|
47
|
+
**Proposed shape, subject to design:**
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
hands = deck.deal_hands(players: 3, cards_each: 2)
|
|
51
|
+
# From an unshuffled standard deck:
|
|
52
|
+
# [[2c, 5c], [3c, 6c], [4c, 7c]] as Card objects
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The smallest scope has positive player counts, nonnegative card counts, capacity validation, and no burns. Names and zero-count behavior require design review.
|
|
56
|
+
|
|
57
|
+
Atomic means capacity validation before mutation. It does not imply thread safety or protection against external mutation through `cards`.
|
|
58
|
+
|
|
59
|
+
**Main objection:** A short Ruby loop already performs distribution. The feature earns its maintenance cost only if consumers need the shared ordering and failure contract.
|
|
60
|
+
|
|
61
|
+
**Take it further**
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
$grill-with-docs Atomic round-robin dealing to multiple hands in CardDealer.
|
|
65
|
+
|
|
66
|
+
Basis, direct: lib/card_dealer/deck.rb:56 defines `def deal(num = 1, burn: 0)`. Line 57 executes `@burned_cards += cards.shift(burn) if burn.positive?`. Line 58 executes `cards.shift(num)`. This returns a single contiguous group and performs no capacity preflight. spec/card_dealer/deck_spec.rb:97-102 explicitly tests empty-array results for empty decks.
|
|
67
|
+
|
|
68
|
+
Why it matters: Games that distribute alternating cards need a caller loop. Games that require complete hands also need a capacity check. Combining those operations gives one bounded dealing capability. It fits the stated purpose without introducing game rules, players, or hand classes.
|
|
69
|
+
|
|
70
|
+
Consider a separate API such as deal_hands(players:, cards_each:), returning arrays of Card objects. Distribute one card per player per round. Validate the complete request before mutation. Insufficient capacity must leave the deck unchanged. Preserve existing deal behavior. Start without burns.
|
|
71
|
+
|
|
72
|
+
Challenge whether a documented loop is sufficient before accepting a permanent API. There is no open-issue demand evidence. Resolve names, zero counts, error types, size limits, and compatibility before implementation.
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Cut
|
|
76
|
+
|
|
77
|
+
These cuts rank one next feature. They do not declare every alternative useless. IDs refer to the merged candidate list.
|
|
78
|
+
|
|
79
|
+
| ID | Idea | Basis | Reason |
|
|
80
|
+
| --- | --- | --- | --- |
|
|
81
|
+
| I1 | Round-robin dealing alone | direct: `deck.rb:58`, `cards.shift(num)`, returns one group. | Duplicate of the stronger ranked combination, I20. |
|
|
82
|
+
| I2 | Exact dealing alone | direct: `deck.rb:57-58` burns before dealing, without preflight. | Combined into I20; a standalone strict API remains a separate design choice. |
|
|
83
|
+
| I3 | Peek | direct: `deck.rb:12`, `attr_reader :cards`, already exposes inspection. | Cost over value: `deck.cards.first(n)` already expresses the operation. |
|
|
84
|
+
| I4 | Enumerable iteration | direct: `deck.rb:93`, `cards.map(&:to_s)`, defines string-valued `to_a`. | Cost over value: collection methods exist through `cards`; iteration would need a careful `to_a` contract. |
|
|
85
|
+
| I5 | Full state snapshots | direct: `binary_deck.rb:70-71` encodes cards and size; `:98`, `Deck.new cards`, reconstructs fresh state. | Cost over value: no demonstrated consumer needs seed and burned cards restored. |
|
|
86
|
+
| I6 | Independent deck copies | direct: `deck.rb:23`, `@cards = cards`; `:35` and `:58` mutate the collection. | Cost over value for the next feature: branching demand is unproven and card mutability needs a defined policy. |
|
|
87
|
+
| I7 | Exclude known cards by multiplicity | direct: `build_deck.rb:112-115` repeats decks; `card.rb:91` compares rank and suit values. | Cost over value: useful for simulations, but the scenario is not evidenced. |
|
|
88
|
+
| I8 | Validated binary decoding | direct: `binary_deck.rb:94-95` uses `scan(/.{6}/).take(deck_size)` and map `fetch`; a truncated payload silently decoded one of two declared cards. | Classify as a confirmed repair, not a speculative feature. Prioritize the repair before the ranked feature. |
|
|
89
|
+
| I9 | Ordered import from card notation | direct: `README.md:103` constructs individual cards inside `Deck.new`. | Cost over value: mapping card strings through `Card.new` is already small. |
|
|
90
|
+
| I10 | Generic arbitrary-card decks | reasoned: dealing requires ordered removal, not ranks or suits; separating those concerns enables more games. | Cost over value: expands the domain and conflicts with assumptions in card encoding. |
|
|
91
|
+
| I11 | Caller-supplied random generator | direct: `deck.rb:35`, `Random.new(@seed.to_i)`, constructs the generator internally. | Cost over value: coordinating random streams is unproven demand and complicates replay semantics. |
|
|
92
|
+
| I12 | Replayable operation transcript | direct: `deck.rb:33-35` records a seed; `:56-58` does not log deal operations. | Cost over value: operation history adds persistent session responsibilities. |
|
|
93
|
+
| I13 | Conservation checks | reasoned: comparing initial multiplicities with remaining, burned, and supplied dealt cards detects missing or extra values. | Cost over value: the caller must supply dealt inventory; equal physical copies remain indistinguishable. |
|
|
94
|
+
| I14 | Scenario builder | reasoned: subtracting known cards from the starting multiset reconstructs a remaining population, with multiplicity preserved. | Duplicate of I7 plus existing shuffle composition. |
|
|
95
|
+
| I15 | Streaming binary encoding | direct: `binary_deck.rb:70` builds a complete mapped binary-text string with `join`. | Cost over value: no measured memory or throughput problem justifies the added interface. |
|
|
96
|
+
| I16 | Batch independent simulations | reasoned: each independent trial requires a fresh population; a helper can enforce reconstruction and seed handling. | Outside the focus: adds a simulation workflow above the deck primitives. |
|
|
97
|
+
| I17 | Jokers | reasoned: a distinct joker needs an identity outside ordinary rank/suit pairs; built-in support enables joker games. | Cost over value: no demand evidence justifies changes to Card and binary-format compatibility. |
|
|
98
|
+
| I18 | Documentation recipe only | reasoned: a recipe can teach composition without adding public compatibility obligations. | Outside the requested missing-feature result; retain as the main alternative during design. |
|
|
99
|
+
| I19 | Safe inspection, peek plus iteration | direct: `deck.rb:12` exposes mutable cards; `:93` converts them to strings. | Duplicate of I3 and I4; a combined inspection interface adds scope without stronger demand. |
|
|
100
|
+
| I21 | Reproducible simulation branches | direct: I5, I6, and I7 establish snapshot, copying, and exclusion gaps. | Cost over value: combining three speculative needs creates a larger simulation feature set. |
|
|
101
|
+
|
|
102
|
+
Paths abbreviated in this table refer to `lib/card_dealer/`, except `README.md`.
|
|
103
|
+
|
|
104
|
+
## Validation
|
|
105
|
+
|
|
106
|
+
Read-only Ruby checks confirmed contiguous repeated deals and round-robin distribution using a caller loop.
|
|
107
|
+
|
|
108
|
+
Three two-card contiguous deals produced `[["2c", "3c"], ["4c", "5c"], ["6c", "7c"]]`.
|
|
109
|
+
|
|
110
|
+
The equivalent round-robin loop produced `[["2c", "5c"], ["3c", "6c"], ["4c", "7c"]]` and left 46 cards.
|
|
111
|
+
|
|
112
|
+
A one-card deck given `deal(5, burn: 1)` returned `[]`, left zero remaining cards, and recorded the card as burned.
|
|
113
|
+
|
|
114
|
+
These checks validate the observed primitives. The proposed API has not been implemented.
|
|
115
|
+
|
|
116
|
+
### Confirmed maintenance priority
|
|
117
|
+
|
|
118
|
+
The independent verifier found that a truncated payload can decode successfully with fewer cards than its header declares. A separate local reproduction confirmed it:
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
payload = [2, 0].pack("CC")
|
|
122
|
+
CardDealer::Deck.from_binary(payload).to_a # => ["2c"]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Location: `lib/card_dealer/binary_deck.rb:93-98`. The header declares two cards, but the returned deck contains one.
|
|
126
|
+
|
|
127
|
+
Cause: scanning produces available six-bit groups, and `take(deck_size)` accepts fewer groups without validating the declared count.
|
|
128
|
+
|
|
129
|
+
Proposed repair: reject incomplete payloads before returning a deck. Define a documented format error and preserve valid binary encodings.
|
|
130
|
+
|
|
131
|
+
The repair is implemented in `4376fae`. Broader malformed-input cases require a separate bounded investigation.
|
|
@@ -12,6 +12,7 @@ module CardDealer
|
|
|
12
12
|
#
|
|
13
13
|
class BinaryDeck
|
|
14
14
|
class DeckTooLargeError < Error; end
|
|
15
|
+
class InvalidEncodingError < Error; end
|
|
15
16
|
|
|
16
17
|
SUIT_MAP = Card::SUIT_MAP.transform_values { |value| value * 13 }
|
|
17
18
|
RANK_MAP = Card::RANK_MAP
|
|
@@ -83,6 +84,8 @@ module CardDealer
|
|
|
83
84
|
#
|
|
84
85
|
# @return [Deck] The decoded deck of cards.
|
|
85
86
|
#
|
|
87
|
+
# @raise [InvalidEncodingError] If the payload length does not match the declared card count.
|
|
88
|
+
#
|
|
86
89
|
# @example
|
|
87
90
|
# encoded_deck = "\x02\xCDP" # (binary string)
|
|
88
91
|
# decoded_deck = CardDealer::BinaryDeck.decode(encoded_deck)
|
|
@@ -91,6 +94,10 @@ module CardDealer
|
|
|
91
94
|
def decode(encoded_deck)
|
|
92
95
|
template = decoding_template_for encoded_deck.bytesize
|
|
93
96
|
deck_size, binary_numbers = encoded_deck.unpack(template)
|
|
97
|
+
if deck_size.nil? || binary_numbers.size != (((deck_size * 6) + 7) / 8) * 8
|
|
98
|
+
raise InvalidEncodingError, "Encoded deck length does not match its card count"
|
|
99
|
+
end
|
|
100
|
+
|
|
94
101
|
cards = binary_numbers.scan(/.{6}/).take(deck_size).map do |binary_number|
|
|
95
102
|
REVERSE_CARD_BINARY_NUMBER_MAP.fetch(binary_number)
|
|
96
103
|
end
|
data/lib/card_dealer/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: card_dealer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Leonid Svyatov
|
|
@@ -23,6 +23,7 @@ files:
|
|
|
23
23
|
- ".rspec"
|
|
24
24
|
- ".rubocop.yml"
|
|
25
25
|
- CHANGELOG.md
|
|
26
|
+
- CLAUDE.md
|
|
26
27
|
- CODE_OF_CONDUCT.md
|
|
27
28
|
- CONTRIBUTING.md
|
|
28
29
|
- Gemfile
|
|
@@ -33,6 +34,7 @@ files:
|
|
|
33
34
|
- SECURITY.md
|
|
34
35
|
- Steepfile
|
|
35
36
|
- card_dealer.gemspec
|
|
37
|
+
- docs/ideation/2026-11-06-next-feature.md
|
|
36
38
|
- lib/card_dealer.rb
|
|
37
39
|
- lib/card_dealer/binary_deck.rb
|
|
38
40
|
- lib/card_dealer/build_deck.rb
|