sashite-cell 2.0.2 → 4.0.0
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/LICENSE +201 -0
- data/README.md +142 -265
- data/lib/sashite/cell/constants.rb +19 -0
- data/lib/sashite/cell/coordinate.rb +113 -0
- data/lib/sashite/cell/errors/argument/messages.rb +22 -0
- data/lib/sashite/cell/errors/argument.rb +30 -0
- data/lib/sashite/cell/errors.rb +3 -0
- data/lib/sashite/cell/formatter.rb +102 -0
- data/lib/sashite/cell/parser.rb +234 -0
- data/lib/sashite/cell.rb +57 -237
- data/lib/sashite-cell.rb +0 -11
- metadata +13 -6
- data/LICENSE.md +0 -22
data/README.md
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
|
-
#
|
|
1
|
+
# cell.rb
|
|
2
2
|
|
|
3
3
|
[](https://github.com/sashite/cell.rb/tags)
|
|
4
4
|
[](https://rubydoc.info/github/sashite/cell.rb/main)
|
|
5
|
-
](https://github.com/sashite/cell.rb/actions)
|
|
6
|
+
[](https://github.com/sashite/cell.rb/blob/main/LICENSE)
|
|
7
7
|
|
|
8
|
-
> **CELL** (Coordinate Encoding for Layered Locations)
|
|
8
|
+
> **CELL** (Coordinate Encoding for Layered Locations) implementation for Ruby.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Overview
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
This library implements the [CELL Specification v1.0.0](https://sashite.dev/specs/cell/1.0.0/).
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
### Implementation Constraints
|
|
15
|
+
|
|
16
|
+
| Constraint | Value | Rationale |
|
|
17
|
+
|------------|-------|-----------|
|
|
18
|
+
| Max dimensions | 3 | Sufficient for 1D, 2D, 3D boards |
|
|
19
|
+
| Max index value | 255 | Fits in 8-bit integer, covers 256×256×256 boards |
|
|
20
|
+
| Max string length | 7 | `"iv256IV"` (max for all dimensions at 255) |
|
|
21
|
+
|
|
22
|
+
These constraints enable bounded memory usage and safe parsing.
|
|
15
23
|
|
|
16
24
|
## Installation
|
|
17
25
|
|
|
@@ -26,322 +34,191 @@ Or install manually:
|
|
|
26
34
|
gem install sashite-cell
|
|
27
35
|
```
|
|
28
36
|
|
|
29
|
-
##
|
|
30
|
-
|
|
31
|
-
CELL uses a cyclical three-character-set system that repeats indefinitely based on dimensional position:
|
|
32
|
-
|
|
33
|
-
**Dimension (n % 3 = 1)**: Latin Lowercase Letters
|
|
34
|
-
- `a`, `b`, `c`, ..., `z`, `aa`, `ab`, ..., `zz`, `aaa`, ...
|
|
37
|
+
## Usage
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
- `1`, `2`, `3`, ..., `25`, `26`, ...
|
|
38
|
-
|
|
39
|
-
**Dimension (n % 3 = 0)**: Latin Uppercase Letters
|
|
40
|
-
- `A`, `B`, `C`, ..., `Z`, `AA`, `AB`, ..., `ZZ`, `AAA`, ...
|
|
41
|
-
|
|
42
|
-
## Basic Usage
|
|
43
|
-
|
|
44
|
-
### Validation
|
|
39
|
+
### Parsing (String → Coordinate)
|
|
45
40
|
|
|
46
|
-
|
|
41
|
+
Convert a CELL string into a `Coordinate` object.
|
|
47
42
|
|
|
48
43
|
```ruby
|
|
49
44
|
require "sashite/cell"
|
|
50
45
|
|
|
51
|
-
#
|
|
52
|
-
Sashite::Cell.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
Sashite::Cell.valid?("h8Hh8") # => true (5D coordinate)
|
|
56
|
-
Sashite::Cell.valid?("*") # => false (not a CELL coordinate)
|
|
57
|
-
Sashite::Cell.valid?("a0") # => false (invalid numeral)
|
|
58
|
-
Sashite::Cell.valid?("") # => false (empty string)
|
|
59
|
-
|
|
60
|
-
# Alias for convenience
|
|
61
|
-
Cell = Sashite::Cell
|
|
62
|
-
Cell.valid?("a1") # => true
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### Dimensional Analysis
|
|
66
|
-
|
|
67
|
-
```ruby
|
|
68
|
-
# Get the number of dimensions in a coordinate
|
|
69
|
-
Sashite::Cell.dimensions("a1") # => 2
|
|
70
|
-
Sashite::Cell.dimensions("a1A") # => 3
|
|
71
|
-
Sashite::Cell.dimensions("h8Hh8") # => 5
|
|
72
|
-
Sashite::Cell.dimensions("foobar") # => 1
|
|
73
|
-
|
|
74
|
-
# Parse coordinate into dimensional components
|
|
75
|
-
Sashite::Cell.parse("a1A")
|
|
76
|
-
# => ["a", "1", "A"]
|
|
77
|
-
|
|
78
|
-
Sashite::Cell.parse("h8Hh8")
|
|
79
|
-
# => ["h", "8", "H", "h", "8"]
|
|
80
|
-
|
|
81
|
-
Sashite::Cell.parse("foobar")
|
|
82
|
-
# => ["foobar"]
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Coordinate Conversion
|
|
86
|
-
|
|
87
|
-
```ruby
|
|
88
|
-
# Convert coordinates to arrays of integers (0-indexed)
|
|
89
|
-
Sashite::Cell.to_indices("a1")
|
|
90
|
-
# => [0, 0]
|
|
91
|
-
|
|
92
|
-
Sashite::Cell.to_indices("e4")
|
|
93
|
-
# => [4, 3]
|
|
46
|
+
# Standard parsing (returns Coordinate or raises)
|
|
47
|
+
coord = Sashite::Cell.parse("e4")
|
|
48
|
+
coord.indices # => [4, 3]
|
|
49
|
+
coord.dimensions # => 2
|
|
94
50
|
|
|
95
|
-
|
|
96
|
-
|
|
51
|
+
# 3D coordinate
|
|
52
|
+
coord = Sashite::Cell.parse("a1A")
|
|
53
|
+
coord.indices # => [0, 0, 0]
|
|
97
54
|
|
|
98
|
-
#
|
|
99
|
-
Sashite::Cell.
|
|
100
|
-
# => "a1"
|
|
101
|
-
|
|
102
|
-
Sashite::Cell.from_indices(4, 3)
|
|
103
|
-
# => "e4"
|
|
104
|
-
|
|
105
|
-
Sashite::Cell.from_indices(0, 0, 0)
|
|
106
|
-
# => "a1A"
|
|
55
|
+
# Invalid input raises Sashite::Cell::Errors::Argument
|
|
56
|
+
Sashite::Cell.parse("a0") # => raises Sashite::Cell::Errors::Argument
|
|
107
57
|
```
|
|
108
58
|
|
|
109
|
-
|
|
59
|
+
### Formatting (Coordinate → String)
|
|
110
60
|
|
|
111
|
-
|
|
61
|
+
Convert a `Coordinate` back to a CELL string.
|
|
112
62
|
|
|
113
63
|
```ruby
|
|
114
|
-
#
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
a6 b6 c6 d6 e6 f6 g6 h6
|
|
121
|
-
a7 b7 c7 d7 e7 f7 g7 h7
|
|
122
|
-
a8 b8 c8 d8 e8 f8 g8 h8]
|
|
123
|
-
|
|
124
|
-
chess_squares.all? { |square| Sashite::Cell.valid?(square) }
|
|
125
|
-
# => true
|
|
64
|
+
# From Coordinate object
|
|
65
|
+
coord = Sashite::Cell::Coordinate.new(4, 3)
|
|
66
|
+
coord.to_s # => "e4"
|
|
67
|
+
|
|
68
|
+
# Direct formatting (convenience)
|
|
69
|
+
Sashite::Cell.format(2, 2, 2) # => "c3C"
|
|
126
70
|
```
|
|
127
71
|
|
|
128
|
-
###
|
|
72
|
+
### Validation
|
|
129
73
|
|
|
130
74
|
```ruby
|
|
131
|
-
#
|
|
132
|
-
|
|
133
|
-
shogi_positions.all? { |pos| Sashite::Cell.valid?(pos) }
|
|
134
|
-
# => true
|
|
75
|
+
# Boolean check
|
|
76
|
+
Sashite::Cell.valid?("e4") # => true
|
|
135
77
|
|
|
136
|
-
#
|
|
137
|
-
Sashite::Cell.
|
|
78
|
+
# Detailed error
|
|
79
|
+
Sashite::Cell.validate("a0") # => raises Sashite::Cell::Errors::Argument, "leading zero"
|
|
138
80
|
```
|
|
139
81
|
|
|
140
|
-
###
|
|
82
|
+
### Accessing Coordinate Data
|
|
141
83
|
|
|
142
84
|
```ruby
|
|
143
|
-
|
|
144
|
-
positions_3d = %w[a1A b2B c3C a2B b3C c1A]
|
|
145
|
-
positions_3d.all? { |pos| Sashite::Cell.valid?(pos) && Sashite::Cell.dimensions(pos) == 3 }
|
|
146
|
-
# => true
|
|
147
|
-
|
|
148
|
-
# Winning diagonal across all three dimensions
|
|
149
|
-
diagonal_win = %w[a1A b2B c3C]
|
|
150
|
-
diagonal_win.map { |pos| Sashite::Cell.to_indices(pos) }
|
|
151
|
-
# => [[0,0,0], [1,1,1], [2,2,2]]
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### Multi-dimensional Coordinates
|
|
85
|
+
coord = Sashite::Cell.parse("e4")
|
|
155
86
|
|
|
156
|
-
|
|
157
|
-
#
|
|
158
|
-
coord_4d = "a1Aa"
|
|
159
|
-
coord_5d = "b2Bb2"
|
|
87
|
+
# Get dimensions count
|
|
88
|
+
coord.dimensions # => 2
|
|
160
89
|
|
|
161
|
-
|
|
162
|
-
|
|
90
|
+
# Get indices as array
|
|
91
|
+
coord.indices # => [4, 3]
|
|
163
92
|
|
|
164
|
-
#
|
|
165
|
-
|
|
166
|
-
|
|
93
|
+
# Access individual index
|
|
94
|
+
coord.indices[0] # => 4
|
|
95
|
+
coord.indices[1] # => 3
|
|
167
96
|
```
|
|
168
97
|
|
|
169
98
|
## API Reference
|
|
170
99
|
|
|
171
|
-
###
|
|
172
|
-
|
|
173
|
-
#### Validation
|
|
174
|
-
- `Sashite::Cell.valid?(string)` - Check if string represents a valid CELL coordinate
|
|
175
|
-
|
|
176
|
-
#### Analysis
|
|
177
|
-
- `Sashite::Cell.dimensions(string)` - Get number of dimensions
|
|
178
|
-
- `Sashite::Cell.parse(string)` - Parse coordinate into dimensional components array
|
|
179
|
-
|
|
180
|
-
#### Conversion
|
|
181
|
-
- `Sashite::Cell.to_indices(string)` - Convert CELL coordinate to 0-indexed integer array
|
|
182
|
-
- `Sashite::Cell.from_indices(*indices)` - Convert splat indices to CELL coordinate
|
|
183
|
-
|
|
184
|
-
#### Utilities
|
|
185
|
-
- `Sashite::Cell.regex` - Get the validation regular expression
|
|
186
|
-
|
|
187
|
-
### Constants
|
|
188
|
-
|
|
189
|
-
- `Sashite::Cell::REGEX` - Regular expression for CELL validation per specification v1.0.0
|
|
190
|
-
|
|
191
|
-
## Properties of CELL
|
|
192
|
-
|
|
193
|
-
* **Multi-dimensional**: Supports unlimited dimensional coordinate systems
|
|
194
|
-
* **Cyclical**: Uses systematic three-character-set repetition
|
|
195
|
-
* **ASCII-based**: Pure ASCII characters for universal compatibility
|
|
196
|
-
* **Unambiguous**: Each coordinate maps to exactly one location
|
|
197
|
-
* **Scalable**: Extends naturally from 1D to unlimited dimensions
|
|
198
|
-
* **Rule-agnostic**: Independent of specific game mechanics
|
|
199
|
-
|
|
200
|
-
## Character Set Details
|
|
201
|
-
|
|
202
|
-
### Latin Lowercase (Dimensions 1, 4, 7, ...)
|
|
203
|
-
Single letters: `a` through `z` (positions 0-25)
|
|
204
|
-
Double letters: `aa` through `zz` (positions 26-701)
|
|
205
|
-
Triple letters: `aaa` through `zzz` (positions 702-18277)
|
|
206
|
-
And so on...
|
|
207
|
-
|
|
208
|
-
### Arabic Numerals (Dimensions 2, 5, 8, ...)
|
|
209
|
-
Standard decimal notation: `1`, `2`, `3`, ... (1-indexed)
|
|
210
|
-
No leading zeros, unlimited range
|
|
211
|
-
|
|
212
|
-
### Latin Uppercase (Dimensions 3, 6, 9, ...)
|
|
213
|
-
Single letters: `A` through `Z` (positions 0-25)
|
|
214
|
-
Double letters: `AA` through `ZZ` (positions 26-701)
|
|
215
|
-
Triple letters: `AAA` through `ZZZ` (positions 702-18277)
|
|
216
|
-
And so on...
|
|
217
|
-
|
|
218
|
-
## Integration with DROP
|
|
219
|
-
|
|
220
|
-
CELL complements the DROP specification for complete location coverage:
|
|
100
|
+
### Types
|
|
221
101
|
|
|
222
102
|
```ruby
|
|
223
|
-
#
|
|
224
|
-
|
|
225
|
-
|
|
103
|
+
# Coordinate represents a parsed CELL coordinate with up to 3 dimensions.
|
|
104
|
+
class Sashite::Cell::Coordinate
|
|
105
|
+
# Creates a Coordinate from 1 to 3 indices.
|
|
106
|
+
# Raises Sashite::Cell::Errors::Argument if no indices provided or more than 3.
|
|
107
|
+
#
|
|
108
|
+
# @param indices [Array<Integer>] 0-indexed coordinate values (0-255)
|
|
109
|
+
# @return [Coordinate]
|
|
110
|
+
def initialize(*indices)
|
|
111
|
+
|
|
112
|
+
# Returns the number of dimensions (1, 2, or 3).
|
|
113
|
+
#
|
|
114
|
+
# @return [Integer]
|
|
115
|
+
def dimensions
|
|
116
|
+
|
|
117
|
+
# Returns the coordinate indices as a frozen array.
|
|
118
|
+
#
|
|
119
|
+
# @return [Array<Integer>]
|
|
120
|
+
def indices
|
|
121
|
+
|
|
122
|
+
# Returns the CELL string representation.
|
|
123
|
+
#
|
|
124
|
+
# @return [String]
|
|
125
|
+
def to_s
|
|
226
126
|
end
|
|
227
|
-
|
|
228
|
-
valid_game_location?("a1") # => true (board position)
|
|
229
|
-
valid_game_location?("*") # => true (reserve position)
|
|
230
|
-
valid_game_location?("$") # => false (invalid)
|
|
231
127
|
```
|
|
232
128
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
### Chess
|
|
129
|
+
### Constants
|
|
236
130
|
|
|
237
131
|
```ruby
|
|
238
|
-
#
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
Sashite::Cell.valid?(start_position) # => true
|
|
243
|
-
Sashite::Cell.valid?(end_position) # => true
|
|
244
|
-
|
|
245
|
-
# Convert to array indices for board representation
|
|
246
|
-
Sashite::Cell.to_indices("e4") # => [4, 3]
|
|
132
|
+
Sashite::Cell::Constants::MAX_DIMENSIONS # => 3
|
|
133
|
+
Sashite::Cell::Constants::MAX_INDEX_VALUE # => 255
|
|
134
|
+
Sashite::Cell::Constants::MAX_STRING_LENGTH # => 7
|
|
247
135
|
```
|
|
248
136
|
|
|
249
|
-
###
|
|
137
|
+
### Parsing
|
|
250
138
|
|
|
251
139
|
```ruby
|
|
252
|
-
#
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
140
|
+
# Parses a CELL string into a Coordinate.
|
|
141
|
+
# Raises Sashite::Cell::Errors::Argument if the string is not valid.
|
|
142
|
+
#
|
|
143
|
+
# @param string [String] CELL coordinate string
|
|
144
|
+
# @return [Coordinate]
|
|
145
|
+
# @raise [Sashite::Cell::Errors::Argument] if invalid
|
|
146
|
+
def Sashite::Cell.parse(string)
|
|
259
147
|
```
|
|
260
148
|
|
|
261
|
-
###
|
|
149
|
+
### Formatting
|
|
262
150
|
|
|
263
151
|
```ruby
|
|
264
|
-
#
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
#
|
|
269
|
-
Sashite::Cell.
|
|
270
|
-
Sashite::Cell.dimensions(tesseract_pos) # => 5
|
|
271
|
-
|
|
272
|
-
# Convert for mathematical operations
|
|
273
|
-
Sashite::Cell.to_indices(hypercube_4d) # => [0, 0, 0, 0]
|
|
152
|
+
# Formats indices into a CELL string.
|
|
153
|
+
# Convenience method equivalent to Coordinate.new(*indices).to_s.
|
|
154
|
+
#
|
|
155
|
+
# @param indices [Array<Integer>] 0-indexed coordinate values
|
|
156
|
+
# @return [String]
|
|
157
|
+
def Sashite::Cell.format(*indices)
|
|
274
158
|
```
|
|
275
159
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
This implementation strictly follows the [CELL Specification v1.0.0](https://sashite.dev/specs/cell/1.0.0/) and includes:
|
|
279
|
-
|
|
280
|
-
- **Exact regex**: Uses the official validation pattern from the specification
|
|
281
|
-
- **Complete API**: All methods and behaviors defined in the specification
|
|
282
|
-
- **Full test coverage**: Validates against all specification examples
|
|
283
|
-
- **Round-trip safety**: Guaranteed coordinate ↔ indices conversion integrity
|
|
284
|
-
|
|
285
|
-
### Specification Examples
|
|
286
|
-
|
|
287
|
-
All examples from the CELL specification work correctly:
|
|
160
|
+
### Validation
|
|
288
161
|
|
|
289
162
|
```ruby
|
|
290
|
-
#
|
|
291
|
-
Sashite::Cell
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
#
|
|
302
|
-
Sashite::Cell.valid?(
|
|
303
|
-
Sashite::Cell.valid?("a0") # => false
|
|
304
|
-
Sashite::Cell.valid?("1a") # => false
|
|
305
|
-
Sashite::Cell.valid?("a1a") # => false
|
|
163
|
+
# Validates a CELL string.
|
|
164
|
+
# Raises Sashite::Cell::Errors::Argument with descriptive message if invalid.
|
|
165
|
+
#
|
|
166
|
+
# @param string [String] CELL coordinate string
|
|
167
|
+
# @return [nil]
|
|
168
|
+
# @raise [Sashite::Cell::Errors::Argument] if invalid
|
|
169
|
+
def Sashite::Cell.validate(string)
|
|
170
|
+
|
|
171
|
+
# Reports whether string is a valid CELL coordinate.
|
|
172
|
+
#
|
|
173
|
+
# @param string [String] CELL coordinate string
|
|
174
|
+
# @return [Boolean]
|
|
175
|
+
def Sashite::Cell.valid?(string)
|
|
306
176
|
```
|
|
307
177
|
|
|
308
|
-
|
|
178
|
+
### Errors
|
|
309
179
|
|
|
310
|
-
|
|
311
|
-
- [API Documentation](https://rubydoc.info/github/sashite/cell.rb/main)
|
|
312
|
-
- [CELL Examples](https://sashite.dev/specs/cell/1.0.0/examples/)
|
|
180
|
+
All parsing and validation errors raise `Sashite::Cell::Errors::Argument` (a subclass of `ArgumentError`) with descriptive messages:
|
|
313
181
|
|
|
314
|
-
|
|
182
|
+
| Message | Cause |
|
|
183
|
+
|---------|-------|
|
|
184
|
+
| `"empty input"` | String length is 0 |
|
|
185
|
+
| `"input exceeds 7 characters"` | String too long |
|
|
186
|
+
| `"must start with lowercase letter"` | Invalid first character |
|
|
187
|
+
| `"unexpected character"` | Character violates the cyclic sequence |
|
|
188
|
+
| `"leading zero"` | Numeric part starts with '0' |
|
|
189
|
+
| `"exceeds 3 dimensions"` | More than 3 dimensions |
|
|
190
|
+
| `"index exceeds 255"` | Decoded value out of range |
|
|
315
191
|
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
bundle install
|
|
323
|
-
|
|
324
|
-
# Run tests
|
|
325
|
-
ruby test.rb
|
|
192
|
+
```ruby
|
|
193
|
+
begin
|
|
194
|
+
Sashite::Cell.parse("a0")
|
|
195
|
+
rescue Sashite::Cell::Errors::Argument => e
|
|
196
|
+
puts e.message # => "leading zero"
|
|
197
|
+
end
|
|
326
198
|
|
|
327
|
-
#
|
|
328
|
-
|
|
199
|
+
# Also catchable as ArgumentError for compatibility
|
|
200
|
+
begin
|
|
201
|
+
Sashite::Cell.parse("a0")
|
|
202
|
+
rescue ArgumentError => e
|
|
203
|
+
puts e.message # => "leading zero"
|
|
204
|
+
end
|
|
329
205
|
```
|
|
330
206
|
|
|
331
|
-
##
|
|
207
|
+
## Design Principles
|
|
332
208
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
7. Create a Pull Request
|
|
209
|
+
- **Bounded values**: Index validation prevents overflow
|
|
210
|
+
- **Object-oriented**: `Coordinate` class enables methods and encapsulation
|
|
211
|
+
- **Ruby idioms**: `valid?` predicate, `to_s` conversion
|
|
212
|
+
- **Custom error class**: `Errors::Argument` inherits from `ArgumentError` for precise error handling
|
|
213
|
+
- **Immutable coordinates**: Frozen indices array prevents mutation
|
|
214
|
+
- **No dependencies**: Pure Ruby standard library only
|
|
340
215
|
|
|
341
|
-
##
|
|
216
|
+
## Related Specifications
|
|
342
217
|
|
|
343
|
-
|
|
218
|
+
- [Game Protocol](https://sashite.dev/game-protocol/) — Conceptual foundation
|
|
219
|
+
- [CELL Specification](https://sashite.dev/specs/cell/1.0.0/) — Official specification
|
|
220
|
+
- [CELL Examples](https://sashite.dev/specs/cell/1.0.0/examples/) — Usage examples
|
|
344
221
|
|
|
345
|
-
##
|
|
222
|
+
## License
|
|
346
223
|
|
|
347
|
-
|
|
224
|
+
Available as open source under the [Apache License 2.0](https://opensource.org/licenses/Apache-2.0).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sashite
|
|
4
|
+
module Cell
|
|
5
|
+
module Constants
|
|
6
|
+
# Maximum number of dimensions supported by a CELL coordinate.
|
|
7
|
+
# Sufficient for 1D, 2D, and 3D game boards.
|
|
8
|
+
MAX_DIMENSIONS = 3
|
|
9
|
+
|
|
10
|
+
# Maximum value for a single coordinate index.
|
|
11
|
+
# Fits in an 8-bit unsigned integer (0-255).
|
|
12
|
+
MAX_INDEX_VALUE = 255
|
|
13
|
+
|
|
14
|
+
# Maximum length of a CELL string representation.
|
|
15
|
+
# Corresponds to "iv256IV" (worst case for all dimensions at 255).
|
|
16
|
+
MAX_STRING_LENGTH = 7
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "constants"
|
|
4
|
+
require_relative "errors"
|
|
5
|
+
require_relative "formatter"
|
|
6
|
+
|
|
7
|
+
module Sashite
|
|
8
|
+
module Cell
|
|
9
|
+
# Represents a parsed CELL coordinate with up to 3 dimensions.
|
|
10
|
+
#
|
|
11
|
+
# A Coordinate holds 0-indexed integer values for each dimension
|
|
12
|
+
# and provides conversion to/from CELL string format.
|
|
13
|
+
#
|
|
14
|
+
# @example Creating a coordinate
|
|
15
|
+
# coord = Sashite::Cell::Coordinate.new(4, 3)
|
|
16
|
+
# coord.indices # => [4, 3]
|
|
17
|
+
# coord.dimensions # => 2
|
|
18
|
+
# coord.to_s # => "e4"
|
|
19
|
+
#
|
|
20
|
+
# @example 3D coordinate
|
|
21
|
+
# coord = Sashite::Cell::Coordinate.new(0, 0, 0)
|
|
22
|
+
# coord.to_s # => "a1A"
|
|
23
|
+
class Coordinate
|
|
24
|
+
# Returns the coordinate indices as a frozen array.
|
|
25
|
+
#
|
|
26
|
+
# @return [Array<Integer>] 0-indexed coordinate values
|
|
27
|
+
#
|
|
28
|
+
# @example
|
|
29
|
+
# Sashite::Cell::Coordinate.new(4, 3).indices # => [4, 3]
|
|
30
|
+
attr_reader :indices
|
|
31
|
+
|
|
32
|
+
# Creates a Coordinate from 1 to 3 indices.
|
|
33
|
+
#
|
|
34
|
+
# @param indices [Array<Integer>] 0-indexed coordinate values (0-255 each)
|
|
35
|
+
# @raise [Sashite::Cell::Errors::Argument] if no indices provided, more than 3, or values out of range
|
|
36
|
+
#
|
|
37
|
+
# @example
|
|
38
|
+
# Sashite::Cell::Coordinate.new(4, 3) # 2D coordinate
|
|
39
|
+
# Sashite::Cell::Coordinate.new(0, 0, 0) # 3D coordinate
|
|
40
|
+
def initialize(*indices)
|
|
41
|
+
if indices.empty?
|
|
42
|
+
raise Errors::Argument, Errors::Argument::Messages::NO_INDICES
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if indices.size > Constants::MAX_DIMENSIONS
|
|
46
|
+
raise Errors::Argument, Errors::Argument::Messages::TOO_MANY_DIMENSIONS
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
indices.each do |index|
|
|
50
|
+
unless index.is_a?(::Integer) && index >= 0 && index <= Constants::MAX_INDEX_VALUE
|
|
51
|
+
raise Errors::Argument, Errors::Argument::Messages::INDEX_OUT_OF_RANGE
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
@indices = indices.freeze
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns the number of dimensions (1, 2, or 3).
|
|
59
|
+
#
|
|
60
|
+
# @return [Integer] dimension count
|
|
61
|
+
#
|
|
62
|
+
# @example
|
|
63
|
+
# Sashite::Cell::Coordinate.new(4, 3).dimensions # => 2
|
|
64
|
+
def dimensions
|
|
65
|
+
@indices.size
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Returns the CELL string representation.
|
|
69
|
+
#
|
|
70
|
+
# @return [String] CELL coordinate string
|
|
71
|
+
#
|
|
72
|
+
# @example
|
|
73
|
+
# Sashite::Cell::Coordinate.new(4, 3).to_s # => "e4"
|
|
74
|
+
def to_s
|
|
75
|
+
Formatter.indices_to_string(@indices)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Checks equality with another Coordinate.
|
|
79
|
+
#
|
|
80
|
+
# @param other [Object] object to compare
|
|
81
|
+
# @return [Boolean] true if equal, false otherwise
|
|
82
|
+
#
|
|
83
|
+
# @example
|
|
84
|
+
# Sashite::Cell::Coordinate.new(4, 3) == Sashite::Cell::Coordinate.new(4, 3) # => true
|
|
85
|
+
def ==(other)
|
|
86
|
+
other.is_a?(Coordinate) && @indices == other.indices
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
alias eql? ==
|
|
90
|
+
|
|
91
|
+
# Returns hash code for use in Hash keys.
|
|
92
|
+
#
|
|
93
|
+
# @return [Integer] hash code
|
|
94
|
+
#
|
|
95
|
+
# @example
|
|
96
|
+
# coord = Sashite::Cell::Coordinate.new(4, 3)
|
|
97
|
+
# hash = { coord => "value" }
|
|
98
|
+
def hash
|
|
99
|
+
@indices.hash
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Returns a human-readable representation.
|
|
103
|
+
#
|
|
104
|
+
# @return [String] inspection string
|
|
105
|
+
#
|
|
106
|
+
# @example
|
|
107
|
+
# Sashite::Cell::Coordinate.new(4, 3).inspect # => "#<Sashite::Cell::Coordinate e4>"
|
|
108
|
+
def inspect
|
|
109
|
+
"#<#{self.class} #{self}>"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sashite
|
|
4
|
+
module Cell
|
|
5
|
+
module Errors
|
|
6
|
+
class Argument < ::ArgumentError
|
|
7
|
+
# Error messages for validation failures.
|
|
8
|
+
# Kept as constants to ensure consistency across the library.
|
|
9
|
+
module Messages
|
|
10
|
+
EMPTY_INPUT = "empty input"
|
|
11
|
+
INPUT_TOO_LONG = "input exceeds 7 characters"
|
|
12
|
+
INVALID_START = "must start with lowercase letter"
|
|
13
|
+
UNEXPECTED_CHARACTER = "unexpected character"
|
|
14
|
+
LEADING_ZERO = "leading zero"
|
|
15
|
+
TOO_MANY_DIMENSIONS = "exceeds 3 dimensions"
|
|
16
|
+
INDEX_OUT_OF_RANGE = "index exceeds 255"
|
|
17
|
+
NO_INDICES = "at least one index required"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "argument/messages"
|
|
4
|
+
|
|
5
|
+
module Sashite
|
|
6
|
+
module Cell
|
|
7
|
+
module Errors
|
|
8
|
+
# Custom error class for CELL parsing and validation failures.
|
|
9
|
+
#
|
|
10
|
+
# Inherits from ArgumentError to maintain semantic meaning
|
|
11
|
+
# while allowing specific rescue of CELL-related errors.
|
|
12
|
+
#
|
|
13
|
+
# @example Rescuing specific CELL errors
|
|
14
|
+
# begin
|
|
15
|
+
# Sashite::Cell.parse("invalid")
|
|
16
|
+
# rescue Sashite::Cell::Errors::Argument => e
|
|
17
|
+
# puts "CELL error: #{e.message}"
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# @example Rescuing as ArgumentError
|
|
21
|
+
# begin
|
|
22
|
+
# Sashite::Cell.parse("invalid")
|
|
23
|
+
# rescue ArgumentError => e
|
|
24
|
+
# puts "Argument error: #{e.message}"
|
|
25
|
+
# end
|
|
26
|
+
class Argument < ::ArgumentError
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|