sashite-cell 1.0.0 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1071f1a4bf4e1d9f6893185fe61fc25a361ca97ba8e9d74e3e72f2fba1300362
4
- data.tar.gz: 2b723da1c614caed08b87bbb8eb382d484a5cc0e915ab6ed7c87949196322ded
3
+ metadata.gz: 84228cab7c7155caaec3a2adad475150eb8b272c5c8e67d2dfb1e626b6120e7e
4
+ data.tar.gz: 318c49ae54afbfec1edaeca7a03c23c7cc5a65cec0658d14973f54a203c15914
5
5
  SHA512:
6
- metadata.gz: 86c6f48a5c77440ddfe886b6c7da0e8871149af862164b7fc060dd07002afa18e69bffc0f897f1de392ecbf7bcc2507786903b326513b6241251f3be20638c81
7
- data.tar.gz: 37dd7540c541a6aaee7d8a29ff8e6eb03119dabdc6c0c0f21cd63fdcb8ed59ff2d80d74b479a9b9a8e6cbb84cb8dbd981ed2492cd48c2c07e3fe383a917a5a8f
6
+ metadata.gz: afe40400d123291deed966e92943ac01b18195cb1a1d191473ff0c719721288c4bae5c1032b6a524f194c722779818c81d02c4709e115bb7f29916eb3ed1828f
7
+ data.tar.gz: be17bfa93a1db05ddaecc938db407a3788ab565d1254e3f61ff9cdd714a09298c5137d3131e11d374d0c9c2d51725a0720f8e8dac7375b614ad3909e26e8687c
data/README.md CHANGED
@@ -5,13 +5,13 @@
5
5
  ![Ruby](https://github.com/sashite/cell.rb/actions/workflows/main.yml/badge.svg?branch=main)
6
6
  [![License](https://img.shields.io/github/license/sashite/cell.rb?label=License&logo=github)](https://github.com/sashite/cell.rb/raw/main/LICENSE.md)
7
7
 
8
- > **CELL** (Coordinate Expression Location Label) support for the Ruby language.
8
+ > **CELL** (Coordinate Encoding for Layered Locations) support for the Ruby language.
9
9
 
10
10
  ## What is CELL?
11
11
 
12
- CELL (Coordinate Expression Location Label) defines a consistent and rule-agnostic format for representing locations in abstract strategy board games. CELL provides a standardized way to identify positions on game boards and pieces held in hand/reserve, establishing a common foundation for location reference across the Sashité notation ecosystem.
12
+ CELL (Coordinate Encoding for Layered Locations) is a standardized format for representing coordinates on multi-dimensional game boards using a cyclical ASCII character system. CELL supports unlimited dimensional coordinate systems through the systematic repetition of three distinct character sets.
13
13
 
14
- This gem implements the [CELL Specification v1.0.0](https://sashite.dev/documents/cell/1.0.0/), providing a Ruby interface for working with game locations through a clean and simple API that serves as a foundational building block for other Sashité specifications.
14
+ This gem implements the [CELL Specification v1.0.0](https://sashite.dev/specs/cell/1.0.0/), providing a Ruby interface for working with multi-dimensional game coordinates through a clean, functional API.
15
15
 
16
16
  ## Installation
17
17
 
@@ -28,324 +28,320 @@ gem install sashite-cell
28
28
 
29
29
  ## CELL Format
30
30
 
31
- A CELL location is represented by a single string following one of two patterns:
31
+ CELL uses a cyclical three-character-set system that repeats indefinitely based on dimensional position:
32
32
 
33
- ### Board Coordinates
33
+ **Dimension (n % 3 = 1)**: Latin Lowercase Letters
34
+ - `a`, `b`, `c`, ..., `z`, `aa`, `ab`, ..., `zz`, `aaa`, ...
34
35
 
35
- Any non-empty string containing only alphanumeric characters (`a-z`, `A-Z`, `0-9`):
36
+ **Dimension (n % 3 = 2)**: Arabic Numerals
37
+ - `1`, `2`, `3`, ..., `25`, `26`, ...
36
38
 
37
- ```
38
- e4 # Chess notation
39
- 5c # Shōgi notation
40
- A3a # 3D coordinate
41
- center # Custom coordinate
42
- ```
43
-
44
- ### Hand/Reserve Location
45
-
46
- The reserved character `*` represents pieces held off-board:
47
- ```
48
- * # Hand/reserve location
49
- ```
39
+ **Dimension (n % 3 = 0)**: Latin Uppercase Letters
40
+ - `A`, `B`, `C`, ..., `Z`, `AA`, `AB`, ..., `ZZ`, `AAA`, ...
50
41
 
51
42
  ## Basic Usage
52
43
 
53
- ### Creating Location Objects
44
+ ### Validation
54
45
 
55
- The primary interface is the `Sashite::Cell::Location` class, which represents a game location in CELL format:
46
+ The primary functionality is validating CELL coordinates:
56
47
 
57
48
  ```ruby
58
49
  require "sashite/cell"
59
50
 
60
- # Parse CELL strings into location objects
61
- board_pos = Sashite::Cell::Location.parse("e4")
62
- # => #<Sashite::Cell::Location:0x... @coordinate="e4">
51
+ # Check if a string represents a valid CELL coordinate
52
+ Sashite::Cell.valid?("a1") # => true (2D coordinate)
53
+ Sashite::Cell.valid?("a1A") # => true (3D coordinate)
54
+ Sashite::Cell.valid?("e4") # => true (2D coordinate)
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
63
73
 
64
- hand_pos = Sashite::Cell::Location.parse("*")
65
- # => #<Sashite::Cell::Location:0x... @coordinate="*">
74
+ # Parse coordinate into dimensional components
75
+ Sashite::Cell.parse("a1A")
76
+ # => ["a", "1", "A"]
66
77
 
67
- # Create directly with constructor
68
- location = Sashite::Cell::Location.new("e4")
69
- hand = Sashite::Cell::Location.new("*")
78
+ Sashite::Cell.parse("h8Hh8")
79
+ # => ["h", "8", "H", "h", "8"]
70
80
 
71
- # Convenience method
72
- location = Sashite::Cell.location("e4")
81
+ Sashite::Cell.parse("foobar")
82
+ # => ["foobar"]
73
83
  ```
74
84
 
75
- ### Converting to CELL String
76
-
77
- Convert a location object back to its CELL string representation:
85
+ ### Coordinate Conversion
78
86
 
79
87
  ```ruby
80
- location = Sashite::Cell::Location.parse("e4")
81
- location.to_s
82
- # => "e4"
83
-
84
- hand = Sashite::Cell::Location.parse("*")
85
- hand.to_s
86
- # => "*"
87
- ```
88
+ # Convert coordinates to arrays of integers (0-indexed)
89
+ Sashite::Cell.to_indices("a1")
90
+ # => [0, 0]
88
91
 
89
- ### Checking Location Types
92
+ Sashite::Cell.to_indices("e4")
93
+ # => [4, 3]
90
94
 
91
- Distinguish between board coordinates and hand/reserve locations:
95
+ Sashite::Cell.to_indices("a1A")
96
+ # => [0, 0, 0]
92
97
 
93
- ```ruby
94
- board_loc = Sashite::Cell::Location.parse("e4")
95
- hand_loc = Sashite::Cell::Location.parse("*")
98
+ # Convert arrays of integers back to CELL coordinates
99
+ Sashite::Cell.from_indices(0, 0)
100
+ # => "a1"
96
101
 
97
- board_loc.board? # => true
98
- board_loc.hand? # => false
102
+ Sashite::Cell.from_indices(4, 3)
103
+ # => "e4"
99
104
 
100
- hand_loc.board? # => false
101
- hand_loc.hand? # => true
105
+ Sashite::Cell.from_indices(0, 0, 0)
106
+ # => "a1A"
102
107
  ```
103
108
 
104
- ## Game-Specific Examples
109
+ ## Usage Examples
105
110
 
106
- ### Chess
111
+ ### Chess Board (8x8)
107
112
 
108
113
  ```ruby
109
- # Standard chess coordinates
110
- locations = %w[a1 e4 h8].map { |coord| Sashite::Cell::Location.parse(coord) }
114
+ # Standard chess notation mapping
115
+ chess_squares = %w[a1 b1 c1 d1 e1 f1 g1 h1
116
+ a2 b2 c2 d2 e2 f2 g2 h2
117
+ a3 b3 c3 d3 e3 f3 g3 h3
118
+ a4 b4 c4 d4 e4 f4 g4 h4
119
+ a5 b5 c5 d5 e5 f5 g5 h5
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
126
+ ```
111
127
 
112
- # Check if valid chess square
113
- def valid_chess_square?(location)
114
- return false unless location.board?
128
+ ### Shōgi Board (9x9)
115
129
 
116
- coord = location.to_s
117
- coord.length == 2 &&
118
- coord[0].between?("a", "h") &&
119
- coord[1].between?("1", "8")
120
- end
130
+ ```ruby
131
+ # CELL coordinates for shōgi positions
132
+ shogi_positions = %w[a1 e5 i9] # Left corner, center, right corner
133
+ shogi_positions.all? { |pos| Sashite::Cell.valid?(pos) }
134
+ # => true
121
135
 
122
- valid_chess_square?(Sashite::Cell::Location.parse("e4")) # => true
123
- valid_chess_square?(Sashite::Cell::Location.parse("z9")) # => false
136
+ # Convert to indices for board representation
137
+ Sashite::Cell.to_indices("e5") # => [4, 4] (center of 9x9 board)
124
138
  ```
125
139
 
126
- ### Shōgi
140
+ ### 3D Tic-Tac-Toe (3x3x3)
127
141
 
128
142
  ```ruby
129
- # Shōgi board coordinates and hand
130
- board_positions = %w[9a 5e 1i].map { |coord| Sashite::Cell::Location.parse(coord) }
131
- hand_position = Sashite::Cell::Location.parse("*")
132
-
133
- # Group by location type
134
- positions = board_positions + [hand_position]
135
- grouped = positions.group_by(&:hand?)
136
- # => {false => [board positions], true => [hand position]}
143
+ # Three-dimensional game coordinates
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]]
137
152
  ```
138
153
 
139
- ### Go
154
+ ### Multi-dimensional Coordinates
140
155
 
141
156
  ```ruby
142
- # Go coordinates (traditional notation)
143
- go_positions = %w[A1 T19 K10].map { |coord| Sashite::Cell::Location.parse(coord) }
157
+ # Higher dimensional coordinates
158
+ coord_4d = "a1Aa"
159
+ coord_5d = "b2Bb2"
144
160
 
145
- # Custom validation for Go board
146
- def valid_go_position?(location, board_size = 19)
147
- return false unless location.board?
161
+ Sashite::Cell.dimensions(coord_4d) # => 4
162
+ Sashite::Cell.dimensions(coord_5d) # => 5
148
163
 
149
- coord = location.to_s
150
- return false unless [2, 3].include?(coord.length)
164
+ # Parse into components
165
+ Sashite::Cell.parse(coord_4d) # => ["a", "1", "A", "a"]
166
+ Sashite::Cell.parse(coord_5d) # => ["b", "2", "B", "b", "2"]
167
+ ```
151
168
 
152
- letter = coord[0]
153
- number = coord[1..].to_i
169
+ ## API Reference
154
170
 
155
- letter.between?("A", ("A".ord + board_size - 1).chr) &&
156
- number.between?(1, board_size)
157
- end
158
- ```
171
+ ### Module Methods
159
172
 
160
- ### Custom Coordinate Systems
173
+ #### Validation
174
+ - `Sashite::Cell.valid?(string)` - Check if string represents a valid CELL coordinate
161
175
 
162
- ```ruby
163
- # 3D chess coordinates
164
- location_3d = Sashite::Cell::Location.parse("A3a")
176
+ #### Analysis
177
+ - `Sashite::Cell.dimensions(string)` - Get number of dimensions
178
+ - `Sashite::Cell.parse(string)` - Parse coordinate into dimensional components array
165
179
 
166
- # Named locations
167
- center = Sashite::Cell::Location.parse("center")
168
- corner = Sashite::Cell::Location.parse("NE")
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
169
183
 
170
- # Hexagonal coordinates
171
- hex_coord = Sashite::Cell::Location.parse("Q3R7")
172
- ```
184
+ #### Utilities
185
+ - `Sashite::Cell.regex` - Get the validation regular expression
173
186
 
174
- ## Advanced Usage
187
+ ### Constants
175
188
 
176
- ### Working with Collections
189
+ - `Sashite::Cell::REGEX` - Regular expression for CELL validation per specification v1.0.0
177
190
 
178
- ```ruby
179
- # Mix of board and hand locations
180
- locations = [
181
- Sashite::Cell::Location.parse("e4"),
182
- Sashite::Cell::Location.parse("d5"),
183
- Sashite::Cell::Location.parse("*"),
184
- Sashite::Cell::Location.parse("a1")
185
- ]
186
-
187
- # Separate board from hand locations
188
- board_locations = locations.select(&:board?)
189
- hand_locations = locations.select(&:hand?)
190
-
191
- # Convert collection to strings
192
- coordinates = locations.map(&:to_s)
193
- # => ["e4", "d5", "*", "a1"]
194
- ```
191
+ ## Properties of CELL
195
192
 
196
- ### Game State Representation
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
197
199
 
198
- ```ruby
199
- # Represent piece positions
200
- piece_locations = {
201
- "white_king" => Sashite::Cell::Location.parse("e1"),
202
- "black_king" => Sashite::Cell::Location.parse("e8"),
203
- "white_rook" => Sashite::Cell::Location.parse("a1"),
204
- "captured_pieces" => Sashite::Cell::Location.parse("*")
205
- }
206
-
207
- # Find pieces on specific ranks/files
208
- def pieces_on_file(locations, file)
209
- locations.select do |piece, location|
210
- location.board? && location.to_s.start_with?(file)
211
- end
212
- end
200
+ ## Character Set Details
213
201
 
214
- e_file_pieces = pieces_on_file(piece_locations, "e")
215
- ```
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
216
211
 
217
- ### Validation and Error Handling
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:
218
221
 
219
222
  ```ruby
220
- # Check validity before parsing
221
- Sashite::Cell.valid?("e4") # => true
222
- Sashite::Cell.valid?("*") # => true
223
- Sashite::Cell.valid?("") # => false
224
- Sashite::Cell.valid?("e-4") # => false
225
- Sashite::Cell.valid?("@") # => false
226
-
227
- # Safe parsing
228
- def safe_parse(coord_string)
229
- return nil unless Sashite::Cell.valid?(coord_string)
230
-
231
- Sashite::Cell::Location.parse(coord_string)
232
- rescue ArgumentError
233
- nil
223
+ # Combined location validation
224
+ def valid_game_location?(location)
225
+ Sashite::Cell.valid?(location) || Sashite::Drop.reserve?(location)
234
226
  end
235
227
 
236
- # Invalid coordinates raise ArgumentError
237
- begin
238
- Sashite::Cell::Location.parse("")
239
- rescue ArgumentError => e
240
- puts "Invalid coordinate: #{e.message}"
241
- end
228
+ valid_game_location?("a1") # => true (board position)
229
+ valid_game_location?("*") # => true (reserve position)
230
+ valid_game_location?("$") # => false (invalid)
242
231
  ```
243
232
 
244
- ### Integration with Other Notations
233
+ ## Examples in Different Games
234
+
235
+ ### Chess
245
236
 
246
237
  ```ruby
247
- # CELL serves as foundation for move notation
248
- class SimpleMove
249
- def initialize(from, to)
250
- @from = Sashite::Cell::Location.parse(from)
251
- @to = Sashite::Cell::Location.parse(to)
252
- end
253
-
254
- def from_board?
255
- @from.board?
256
- end
257
-
258
- def to_board?
259
- @to.board?
260
- end
261
-
262
- def drop_move?
263
- @from.hand? && @to.board?
264
- end
265
-
266
- def capture_move?
267
- @from.board? && @to.board?
268
- end
269
-
270
- def to_s
271
- "#{@from}→#{@to}"
272
- end
273
- end
238
+ # Standard algebraic notation positions
239
+ start_position = "e2"
240
+ end_position = "e4"
241
+
242
+ Sashite::Cell.valid?(start_position) # => true
243
+ Sashite::Cell.valid?(end_position) # => true
274
244
 
275
- # Usage
276
- move = SimpleMove.new("e4", "e5") # Normal move
277
- drop = SimpleMove.new("*", "e4") # Drop from hand
245
+ # Convert to array indices for board representation
246
+ Sashite::Cell.to_indices("e4") # => [4, 3]
278
247
  ```
279
248
 
280
- ## API Reference
249
+ ### Go (19x19)
281
250
 
282
- ### Module Methods
251
+ ```ruby
252
+ # Go board positions
253
+ corner = "a1" # Corner position
254
+ edge = "j1" # Edge position
255
+ tengen = "j10" # Center point (tengen) on 19x19 board
283
256
 
284
- - `Sashite::Cell.valid?(cell_string)` - Check if a string is valid CELL notation
285
- - `Sashite::Cell.location(coordinate)` - Convenience method to create locations
257
+ [corner, edge, tengen].all? { |pos| Sashite::Cell.valid?(pos) }
258
+ # => true
259
+ ```
286
260
 
287
- ### Sashite::Cell::Location Class Methods
261
+ ### Abstract Strategy Games
288
262
 
289
- - `Sashite::Cell::Location.parse(cell_string)` - Parse a CELL string into a location object
290
- - `Sashite::Cell::Location.new(coordinate)` - Create a new location instance
263
+ ```ruby
264
+ # Multi-dimensional abstract games
265
+ hypercube_4d = "a1Aa"
266
+ tesseract_pos = "h8Hh8"
291
267
 
292
- ### Instance Methods
268
+ # Validate high-dimensional coordinates
269
+ Sashite::Cell.valid?(hypercube_4d) # => true
270
+ Sashite::Cell.dimensions(tesseract_pos) # => 5
293
271
 
294
- #### Type Checking
295
- - `#board?` - Check if location represents a board coordinate
296
- - `#hand?` - Check if location represents hand/reserve
272
+ # Convert for mathematical operations
273
+ Sashite::Cell.to_indices(hypercube_4d) # => [0, 0, 0, 0]
274
+ ```
297
275
 
298
- #### Conversion
299
- - `#to_s` - Convert to CELL string representation
300
- - `#inspect` - Detailed string representation for debugging
276
+ ## Specification Compliance
301
277
 
302
- #### Comparison
303
- - `#==` - Compare locations for equality
304
- - `#eql?` - Strict equality comparison
305
- - `#hash` - Hash value for use in collections
278
+ This implementation strictly follows the [CELL Specification v1.0.0](https://sashite.dev/specs/cell/1.0.0/) and includes:
306
279
 
307
- ## Properties of CELL
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
308
284
 
309
- * **Rule-agnostic**: CELL does not encode game states, movement rules, or game-specific conditions
310
- * **Universal location identification**: Supports both board positions and hand/reserve areas
311
- * **Canonical representation**: Equivalent locations yield identical strings
312
- * **Arbitrary coordinate systems**: Flexible format supports any alphanumeric coordinate system
313
- * **Foundational specification**: Serves as building block for other Sashité notations
285
+ ### Specification Examples
314
286
 
315
- ## Constraints
287
+ All examples from the CELL specification work correctly:
316
288
 
317
- * Board coordinates must contain only alphanumeric characters (`a-z`, `A-Z`, `0-9`)
318
- * Board coordinates must be non-empty strings
319
- * Hand/reserve locations must be exactly the character `*`
320
- * No other characters or formats are permitted
289
+ ```ruby
290
+ # Basic Examples from spec
291
+ Sashite::Cell.valid?("a") # => true (1D)
292
+ Sashite::Cell.valid?("a1") # => true (2D)
293
+ Sashite::Cell.valid?("a1A") # => true (3D)
294
+ Sashite::Cell.valid?("a1Aa1A") # => true (6D)
295
+
296
+ # Extended Alphabet Examples from spec
297
+ Sashite::Cell.valid?("aa1AA") # => true
298
+ Sashite::Cell.valid?("z26Z") # => true
299
+ Sashite::Cell.valid?("abc123XYZ") # => true
300
+
301
+ # Invalid Examples from spec
302
+ Sashite::Cell.valid?("") # => false
303
+ Sashite::Cell.valid?("a0") # => false
304
+ Sashite::Cell.valid?("1a") # => false
305
+ Sashite::Cell.valid?("a1a") # => false
306
+ ```
321
307
 
322
- ## Use Cases
308
+ ## Documentation
323
309
 
324
- CELL is particularly useful in the following scenarios:
310
+ - [Official CELL Specification v1.0.0](https://sashite.dev/specs/cell/1.0.0/)
311
+ - [API Documentation](https://rubydoc.info/github/sashite/cell.rb/main)
312
+ - [CELL Examples](https://sashite.dev/specs/cell/1.0.0/examples/)
325
313
 
326
- 1. **Move notation systems**: As coordinate foundation for MIN, PMN, and GGN specifications
327
- 2. **Game engine development**: When implementing position tracking across different board layouts
328
- 3. **Board representation**: When storing piece positions in databases or memory structures
329
- 4. **Cross-game compatibility**: When building systems that work with multiple game types
330
- 5. **Position analysis**: When comparing locations across different coordinate systems
331
- 6. **User interface development**: When translating between display coordinates and logical positions
314
+ ## Development
332
315
 
333
- ## Dependencies
316
+ ```sh
317
+ # Clone the repository
318
+ git clone https://github.com/sashite/cell.rb.git
319
+ cd cell.rb
334
320
 
335
- This gem has no external dependencies beyond Ruby standard library.
321
+ # Install dependencies
322
+ bundle install
336
323
 
337
- ## Specification
324
+ # Run tests
325
+ ruby test.rb
338
326
 
339
- - [CELL Specification](https://sashite.dev/documents/cell/1.0.0/)
327
+ # Generate documentation
328
+ yard doc
329
+ ```
340
330
 
341
- ## Documentation
331
+ ## Contributing
342
332
 
343
- - [CELL Documentation](https://rubydoc.info/github/sashite/cell.rb/main)
333
+ 1. Fork the repository
334
+ 2. Create a feature branch (`git checkout -b feature/new-feature`)
335
+ 3. Add tests for your changes
336
+ 4. Ensure all tests pass (`ruby test.rb`)
337
+ 5. Commit your changes (`git commit -am 'Add new feature'`)
338
+ 6. Push to the branch (`git push origin feature/new-feature`)
339
+ 7. Create a Pull Request
344
340
 
345
341
  ## License
346
342
 
347
- The [gem](https://rubygems.org/gems/sashite-cell) is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
343
+ Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
348
344
 
349
- ## About Sashité
345
+ ## About
350
346
 
351
- This project is maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of Chinese, Japanese, and Western chess cultures.
347
+ Maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of board game cultures.
data/lib/sashite/cell.rb CHANGED
@@ -1,61 +1,252 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "cell/location"
4
-
5
- # Sashité module providing implementations of various game notation specifications
6
- #
7
- # @see https://sashite.com/ Sashité
8
3
  module Sashite
9
- # CELL (Coordinate Expression Location Label) implementation
4
+ # CELL (Coordinate Encoding for Layered Locations) implementation for Ruby
10
5
  #
11
- # CELL defines a consistent and rule-agnostic format for representing locations
12
- # in abstract strategy board games. CELL provides a standardized way to identify
13
- # positions on game boards and pieces held in hand/reserve.
6
+ # Provides functionality for working with multi-dimensional game board coordinates
7
+ # using a cyclical ASCII character system.
14
8
  #
15
- # @see https://sashite.dev/documents/cell/1.0.0/ CELL Specification v1.0.0
9
+ # This implementation is strictly compliant with CELL Specification v1.0.0
10
+ # @see https://sashite.dev/specs/cell/1.0.0/ CELL Specification v1.0.0
16
11
  module Cell
17
- # Regular expression for validating CELL notation
12
+ # Regular expression for validating CELL coordinates according to specification v1.0.0
13
+ # Optimized version with redundant nested repeat operator removed for clean Ruby execution
14
+ REGEX = /\A[a-z]+(?:[1-9]\d*[A-Z]+[a-z]+)*(?:[1-9]\d*[A-Z]*)?\z/
15
+
16
+ # Check if a string represents a valid CELL coordinate
18
17
  #
19
- # Matches either:
20
- # - Hand/reserve location: exactly "*"
21
- # - Board coordinate: one or more alphanumeric characters [a-zA-Z0-9]
18
+ # @param string [String] the string to validate
19
+ # @return [Boolean] true if the string is a valid CELL coordinate
22
20
  #
23
- # @return [Regexp] the validation pattern
24
- CELL_PATTERN = /\A(#{::Regexp.escape(Location::HAND_CHAR)}|[a-zA-Z0-9]+)\z/
21
+ # @example
22
+ # Sashite::Cell.valid?("a1") # => true
23
+ # Sashite::Cell.valid?("a1A") # => true
24
+ # Sashite::Cell.valid?("*") # => false
25
+ # Sashite::Cell.valid?("a0") # => false
26
+ def self.valid?(string)
27
+ return false unless string.is_a?(String)
28
+ return false if string.empty?
25
29
 
26
- # Check if a string is valid CELL notation
30
+ # Use the optimized CELL v1.0.0 regex for validation
31
+ string.match?(REGEX)
32
+ end
33
+
34
+ # Get the number of dimensions in a coordinate
35
+ #
36
+ # @param string [String] the coordinate string
37
+ # @return [Integer] the number of dimensions
27
38
  #
28
- # @param cell_string [String] the string to validate
29
- # @return [Boolean] true if valid CELL notation, false otherwise
39
+ # @example
40
+ # Sashite::Cell.dimensions("a1") # => 2
41
+ # Sashite::Cell.dimensions("a1A") # => 3
42
+ # Sashite::Cell.dimensions("foobar") # => 1
43
+ def self.dimensions(string)
44
+ return 0 unless valid?(string)
45
+
46
+ parse(string).length
47
+ end
48
+
49
+ # Parse a coordinate string into dimensional components
30
50
  #
31
- # @example Valid CELL strings
32
- # Sashite::Cell.valid?("e4") # => true
33
- # Sashite::Cell.valid?("*") # => true
34
- # Sashite::Cell.valid?("A3a") # => true
35
- # Sashite::Cell.valid?("center") # => true
51
+ # @param string [String] the coordinate string to parse
52
+ # @return [Array<String>] array of dimensional components
36
53
  #
37
- # @example Invalid CELL strings
38
- # Sashite::Cell.valid?("") # => false
39
- # Sashite::Cell.valid?("e-4") # => false
40
- # Sashite::Cell.valid?("@") # => false
41
- # Sashite::Cell.valid?("e4!") # => false
42
- def self.valid?(cell_string)
43
- return false unless cell_string.is_a?(::String)
54
+ # @example
55
+ # Sashite::Cell.parse("a1A") # => ["a", "1", "A"]
56
+ # Sashite::Cell.parse("h8Hh8") # => ["h", "8", "H", "h", "8"]
57
+ # Sashite::Cell.parse("foobar") # => ["foobar"] (if valid single dimension)
58
+ def self.parse(string)
59
+ return [] unless string.is_a?(::String)
60
+ return [] if string.empty?
61
+ return [] unless valid?(string)
44
62
 
45
- CELL_PATTERN.match?(cell_string)
63
+ parse_recursive(string, 1)
46
64
  end
47
65
 
48
- # Convenience method to create a Location object
66
+ # Convert a CELL coordinate to an array of 0-indexed integers
49
67
  #
50
- # @param coordinate [String] the coordinate string in CELL format
51
- # @return [Location] a new Location object
52
- # @raise [ArgumentError] if the coordinate is invalid
68
+ # @param string [String] the CELL coordinate
69
+ # @return [Array<Integer>] array of 0-indexed positions
53
70
  #
54
71
  # @example
55
- # location = Sashite::Cell.location("e4")
56
- # hand = Sashite::Cell.location("*")
57
- def self.location(coordinate)
58
- Location.new(coordinate)
72
+ # Sashite::Cell.to_indices("a1") # => [0, 0]
73
+ # Sashite::Cell.to_indices("e4") # => [4, 3]
74
+ # Sashite::Cell.to_indices("a1A") # => [0, 0, 0]
75
+ def self.to_indices(string)
76
+ return [] unless valid?(string)
77
+
78
+ parse(string).map.with_index do |component, index|
79
+ dimension_type = dimension_type(index + 1)
80
+ component_to_index(component, dimension_type)
81
+ end
82
+ end
83
+
84
+ # Convert an array of 0-indexed integers to a CELL coordinate
85
+ #
86
+ # @param indices [Array<Integer>] splat arguments of 0-indexed positions
87
+ # @return [String] the CELL coordinate
88
+ #
89
+ # @example
90
+ # Sashite::Cell.from_indices(0, 0) # => "a1"
91
+ # Sashite::Cell.from_indices(4, 3) # => "e4"
92
+ # Sashite::Cell.from_indices(0, 0, 0) # => "a1A"
93
+ def self.from_indices(*indices)
94
+ return "" if indices.empty?
95
+
96
+ result = indices.map.with_index do |index, dimension|
97
+ dimension_type = dimension_type(dimension + 1)
98
+ index_to_component(index, dimension_type)
99
+ end.join
100
+
101
+ # Verify the result is valid according to CELL specification
102
+ valid?(result) ? result : ""
103
+ end
104
+
105
+ # Get the validation regular expression
106
+ #
107
+ # @return [Regexp] the CELL validation regex from specification v1.0.0
108
+ def self.regex
109
+ REGEX
110
+ end
111
+
112
+ # Recursively parse a coordinate string into components
113
+ # following the strict CELL specification cyclical pattern
114
+ #
115
+ # @param string [String] the remaining string to parse
116
+ # @param dimension [Integer] the current dimension (1-indexed)
117
+ # @return [Array<String>] array of dimensional components
118
+ def self.parse_recursive(string, dimension)
119
+ return [] if string.empty?
120
+
121
+ expected_type = dimension_type(dimension)
122
+ component = extract_component(string, expected_type)
123
+
124
+ return [] if component.nil?
125
+
126
+ # Extract component and recursively parse the rest
127
+ remaining = string[component.length..]
128
+ [component] + parse_recursive(remaining, dimension + 1)
129
+ end
130
+
131
+ # Determine the character set type for a given dimension
132
+ # Following CELL specification cyclical system: dimension n % 3 determines character set
133
+ #
134
+ # @param dimension [Integer] the dimension number (1-indexed)
135
+ # @return [Symbol] :lowercase, :numeric, or :uppercase
136
+ def self.dimension_type(dimension)
137
+ case dimension % 3
138
+ when 1 then :lowercase # n % 3 = 1: Latin lowercase letters
139
+ when 2 then :numeric # n % 3 = 2: Arabic numerals
140
+ when 0 then :uppercase # n % 3 = 0: Latin uppercase letters
141
+ end
142
+ end
143
+
144
+ # Extract the next component from a string based on expected type
145
+ # Strictly follows CELL specification patterns
146
+ #
147
+ # @param string [String] the string to extract from
148
+ # @param type [Symbol] the expected component type
149
+ # @return [String, nil] the extracted component or nil if invalid
150
+ def self.extract_component(string, type)
151
+ case type
152
+ when :lowercase
153
+ # Latin lowercase letters: [a-z]+
154
+ match = string.match(/\A([a-z]+)/)
155
+ match ? match[1] : nil
156
+ when :numeric
157
+ # Arabic numerals: [1-9]\d* (CELL specification requires positive integers only)
158
+ match = string.match(/\A([1-9]\d*)/)
159
+ match ? match[1] : nil
160
+ when :uppercase
161
+ # Latin uppercase letters: [A-Z]+
162
+ match = string.match(/\A([A-Z]+)/)
163
+ match ? match[1] : nil
164
+ end
165
+ end
166
+
167
+ # Convert a component to its 0-indexed position
168
+ #
169
+ # @param component [String] the component
170
+ # @param type [Symbol] the component type
171
+ # @return [Integer] the 0-indexed position
172
+ def self.component_to_index(component, type)
173
+ case type
174
+ when :lowercase
175
+ letters_to_index(component)
176
+ when :numeric
177
+ component.to_i - 1
178
+ when :uppercase
179
+ letters_to_index(component.downcase)
180
+ end
181
+ end
182
+
183
+ # Convert a 0-indexed position to a component
184
+ #
185
+ # @param index [Integer] the 0-indexed position
186
+ # @param type [Symbol] the component type
187
+ # @return [String] the component
188
+ def self.index_to_component(index, type)
189
+ case type
190
+ when :lowercase
191
+ index_to_letters(index)
192
+ when :numeric
193
+ (index + 1).to_s
194
+ when :uppercase
195
+ index_to_letters(index).upcase
196
+ end
197
+ end
198
+
199
+ # Convert letter sequence to 0-indexed position
200
+ # Extended alphabet per CELL specification: a=0, b=1, ..., z=25, aa=26, ab=27, ..., zz=701, aaa=702, etc.
201
+ #
202
+ # @param letters [String] the letter sequence
203
+ # @return [Integer] the 0-indexed position
204
+ def self.letters_to_index(letters)
205
+ length = letters.length
206
+ index = 0
207
+
208
+ # Add positions from shorter sequences
209
+ (1...length).each do |len|
210
+ index += 26**len
211
+ end
212
+
213
+ # Add position within current length
214
+ letters.each_char.with_index do |char, pos|
215
+ index += (char.ord - 97) * (26**(length - pos - 1))
216
+ end
217
+
218
+ index
219
+ end
220
+
221
+ # Convert 0-indexed position to letter sequence
222
+ # Extended alphabet per CELL specification: 0=a, 1=b, ..., 25=z, 26=aa, 27=ab, ..., 701=zz, 702=aaa, etc.
223
+ #
224
+ # @param index [Integer] the 0-indexed position
225
+ # @return [String] the letter sequence
226
+ def self.index_to_letters(index)
227
+ # Find the length of the result
228
+ length = 1
229
+ base = 0
230
+
231
+ loop do
232
+ range_size = 26**length
233
+ break if index < base + range_size
234
+
235
+ base += range_size
236
+ length += 1
237
+ end
238
+
239
+ # Convert within the found length
240
+ adjusted_index = index - base
241
+ result = ""
242
+
243
+ length.times do |pos|
244
+ char_index = adjusted_index / (26**(length - pos - 1))
245
+ result += (char_index + 97).chr
246
+ adjusted_index %= (26**(length - pos - 1))
247
+ end
248
+
249
+ result
59
250
  end
60
251
  end
61
252
  end
data/lib/sashite-cell.rb CHANGED
@@ -1,15 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "sashite/cell"
4
+
3
5
  # Sashité namespace for board game notation libraries
6
+ #
7
+ # Sashité provides a collection of libraries for representing and manipulating
8
+ # board game concepts according to the Sashité Protocol specifications.
9
+ #
10
+ # @see https://sashite.dev/protocol/ Sashité Protocol
11
+ # @see https://sashite.dev/specs/ Sashité Specifications
12
+ # @author Sashité
4
13
  module Sashite
5
- # Coordinate Expression Location Label (CELL) implementation for Ruby
6
- #
7
- # CELL defines a consistent and rule-agnostic format for representing locations
8
- # in abstract strategy board games. CELL provides a standardized way to identify
9
- # positions on game boards and pieces held in hand/reserve, establishing a
10
- # common foundation for location reference across the Sashité notation ecosystem.
11
- #
12
- # @see https://sashite.dev/documents/cell/1.0.0/ CELL Specification v1.0.0
13
14
  end
14
-
15
- require_relative "sashite/cell"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sashite-cell
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
@@ -9,10 +9,10 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
- description: CELL defines a consistent and rule-agnostic format for representing locations
13
- in abstract strategy board games. This gem provides a Ruby interface for working
14
- with game locations, serving as a foundational building block for the Sashité notation
15
- ecosystem.
12
+ description: CELL defines a standardized format for representing coordinates on multi-dimensional
13
+ game boards using a cyclical ASCII character system. This gem provides a Ruby interface
14
+ for working with unlimited dimensional game coordinates through a clean, functional
15
+ API that strictly follows the CELL Specification v1.0.0.
16
16
  email: contact@cyril.email
17
17
  executables: []
18
18
  extensions: []
@@ -22,8 +22,6 @@ files:
22
22
  - README.md
23
23
  - lib/sashite-cell.rb
24
24
  - lib/sashite/cell.rb
25
- - lib/sashite/cell/location.rb
26
- - lib/sashite/cell/location/hand_char.rb
27
25
  homepage: https://github.com/sashite/cell.rb
28
26
  licenses:
29
27
  - MIT
@@ -32,7 +30,9 @@ metadata:
32
30
  documentation_uri: https://rubydoc.info/github/sashite/cell.rb/main
33
31
  homepage_uri: https://github.com/sashite/cell.rb
34
32
  source_code_uri: https://github.com/sashite/cell.rb
35
- specification_uri: https://sashite.dev/documents/cell/1.0.0/
33
+ specification_uri: https://sashite.dev/specs/cell/1.0.0/
34
+ wiki_uri: https://sashite.dev/specs/cell/1.0.0/examples/
35
+ funding_uri: https://github.com/sponsors/sashite
36
36
  rubygems_mfa_required: 'true'
37
37
  rdoc_options: []
38
38
  require_paths:
@@ -48,7 +48,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
48
48
  - !ruby/object:Gem::Version
49
49
  version: '0'
50
50
  requirements: []
51
- rubygems_version: 3.6.7
51
+ rubygems_version: 3.6.9
52
52
  specification_version: 4
53
- summary: CELL (Coordinate Expression Location Label) implementation for Ruby
53
+ summary: CELL (Coordinate Encoding for Layered Locations) implementation for Ruby
54
54
  test_files: []
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Sashite
4
- module Cell
5
- class Location
6
- # Character representing hand/reserve location in CELL notation
7
- #
8
- # This character is used to identify pieces held off-board in a player's
9
- # hand or reserve, as opposed to pieces positioned on the game board.
10
- #
11
- # @return [String] the hand/reserve character
12
- # @see https://sashite.dev/documents/cell/1.0.0/ CELL Specification v1.0.0
13
- HAND_CHAR = "*"
14
- end
15
- end
16
- end
@@ -1,136 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "location/hand_char"
4
-
5
- module Sashite
6
- module Cell
7
- # Represents a game location in CELL format
8
- #
9
- # A Location object encapsulates either a board coordinate (alphanumeric string)
10
- # or a hand/reserve location (the "*" character). This class provides methods
11
- # to distinguish between location types and convert to string representation.
12
- #
13
- # @see https://sashite.dev/documents/cell/1.0.0/ CELL Specification v1.0.0
14
- class Location
15
- # The coordinate string for this location
16
- # @return [String] the coordinate in CELL format
17
- attr_reader :coordinate
18
-
19
- # Create a new Location object
20
- #
21
- # @param coordinate [String] the coordinate string in CELL format
22
- # @raise [ArgumentError] if the coordinate is not valid CELL notation
23
- #
24
- # @example Create board locations
25
- # Location.new("e4") # Chess square
26
- # Location.new("5c") # Shōgi square
27
- # Location.new("A3a") # 3D coordinate
28
- # Location.new("center") # Custom coordinate
29
- #
30
- # @example Create hand/reserve location
31
- # Location.new("*") # Hand/reserve
32
- def initialize(coordinate)
33
- raise ::ArgumentError, "Invalid CELL coordinate: #{coordinate.inspect}" unless Sashite::Cell.valid?(coordinate)
34
-
35
- @coordinate = coordinate.freeze
36
-
37
- freeze
38
- end
39
-
40
- # Parse a CELL string into a Location object
41
- #
42
- # @param cell_string [String] the CELL string to parse
43
- # @return [Location] a new Location object
44
- # @raise [ArgumentError] if the string is not valid CELL notation
45
- #
46
- # @example
47
- # location = Location.parse("e4")
48
- # hand = Location.parse("*")
49
- def self.parse(cell_string)
50
- new(cell_string)
51
- end
52
-
53
- # Check if this location represents a board coordinate
54
- #
55
- # @return [Boolean] true if this is a board coordinate, false if hand/reserve
56
- #
57
- # @example
58
- # Location.new("e4").board? # => true
59
- # Location.new("*").board? # => false
60
- def board?
61
- @coordinate != HAND_CHAR
62
- end
63
-
64
- # Check if this location represents a hand/reserve location
65
- #
66
- # @return [Boolean] true if this is hand/reserve, false if board coordinate
67
- #
68
- # @example
69
- # Location.new("e4").hand? # => false
70
- # Location.new("*").hand? # => true
71
- def hand?
72
- @coordinate == HAND_CHAR
73
- end
74
-
75
- # Convert to CELL string representation
76
- #
77
- # @return [String] the coordinate in CELL format
78
- #
79
- # @example
80
- # Location.new("e4").to_s # => "e4"
81
- # Location.new("*").to_s # => "*"
82
- def to_s
83
- @coordinate
84
- end
85
-
86
- # Detailed string representation for debugging
87
- #
88
- # @return [String] detailed representation showing class and coordinate
89
- #
90
- # @example
91
- # Location.new("e4").inspect
92
- # # => "#<Sashite::Cell::Location:0x... @coordinate=\"e4\">"
93
- def inspect
94
- "#<#{self.class}:0x#{object_id.to_s(16)} @coordinate=#{@coordinate.inspect}>"
95
- end
96
-
97
- # Compare locations for equality
98
- #
99
- # Two locations are equal if they have the same coordinate string.
100
- #
101
- # @param other [Object] the object to compare with
102
- # @return [Boolean] true if locations are equal
103
- #
104
- # @example
105
- # loc1 = Location.new("e4")
106
- # loc2 = Location.new("e4")
107
- # loc1 == loc2 # => true
108
- def ==(other)
109
- other.is_a?(Location) && @coordinate == other.coordinate
110
- end
111
-
112
- # Strict equality comparison
113
- #
114
- # @param other [Object] the object to compare with
115
- # @return [Boolean] true if objects are equal
116
- def eql?(other)
117
- self == other
118
- end
119
-
120
- # Hash value for use in collections
121
- #
122
- # Ensures that equal locations have the same hash value.
123
- #
124
- # @return [Integer] hash value based on coordinate
125
- #
126
- # @example
127
- # locations = Set.new
128
- # locations << Location.new("e4")
129
- # locations << Location.new("e4") # Won't be added (same hash)
130
- # locations.size # => 1
131
- def hash
132
- [self.class, @coordinate].hash
133
- end
134
- end
135
- end
136
- end