sashite-cell 1.0.0 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1071f1a4bf4e1d9f6893185fe61fc25a361ca97ba8e9d74e3e72f2fba1300362
4
- data.tar.gz: 2b723da1c614caed08b87bbb8eb382d484a5cc0e915ab6ed7c87949196322ded
3
+ metadata.gz: c4fefcdbade2adc694035b6fbff032fb8e03466e35406a67f2a9d1c61b9fd600
4
+ data.tar.gz: 42c3150a201310805948eaa2e9e6bf05fc9c198925024d7a203f0093d6ee969a
5
5
  SHA512:
6
- metadata.gz: 86c6f48a5c77440ddfe886b6c7da0e8871149af862164b7fc060dd07002afa18e69bffc0f897f1de392ecbf7bcc2507786903b326513b6241251f3be20638c81
7
- data.tar.gz: 37dd7540c541a6aaee7d8a29ff8e6eb03119dabdc6c0c0f21cd63fdcb8ed59ff2d80d74b479a9b9a8e6cbb84cb8dbd981ed2492cd48c2c07e3fe383a917a5a8f
6
+ metadata.gz: 185910faaa10494b20d39b58ac3f25007d432d63d230de76cf666762a215ab8177283c0fe167efc28fdc24b4abcc55bdb25211fc9d216856b62d889005e57c8c
7
+ data.tar.gz: 43fe0766f8fe932e2a57dec85c89a07db4f30a543319f035b7e893a8122cfaf30ed90a635515bb9867a1509dc9e374ecf3c0b16c6efb39b419413a4811aebfe2
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** (Cell Encoding Location Label) 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 (Cell Encoding Location Label) 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/documents/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,319 +28,242 @@ 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">
63
-
64
- hand_pos = Sashite::Cell::Location.parse("*")
65
- # => #<Sashite::Cell::Location:0x... @coordinate="*">
66
-
67
- # Create directly with constructor
68
- location = Sashite::Cell::Location.new("e4")
69
- hand = Sashite::Cell::Location.new("*")
70
-
71
- # Convenience method
72
- location = Sashite::Cell.location("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
73
63
  ```
74
64
 
75
- ### Converting to CELL String
76
-
77
- Convert a location object back to its CELL string representation:
65
+ ### Dimensional Analysis
78
66
 
79
67
  ```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
-
89
- ### Checking Location Types
90
-
91
- Distinguish between board coordinates and hand/reserve locations:
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
92
73
 
93
- ```ruby
94
- board_loc = Sashite::Cell::Location.parse("e4")
95
- hand_loc = Sashite::Cell::Location.parse("*")
74
+ # Parse coordinate into dimensional components
75
+ Sashite::Cell.parse("a1A")
76
+ # => ["a", "1", "A"]
96
77
 
97
- board_loc.board? # => true
98
- board_loc.hand? # => false
78
+ Sashite::Cell.parse("h8Hh8")
79
+ # => ["h", "8", "H", "h", "8"]
99
80
 
100
- hand_loc.board? # => false
101
- hand_loc.hand? # => true
81
+ Sashite::Cell.parse("foobar")
82
+ # => ["foobar"]
102
83
  ```
103
84
 
104
- ## Game-Specific Examples
105
-
106
- ### Chess
85
+ ### Coordinate Conversion
107
86
 
108
87
  ```ruby
109
- # Standard chess coordinates
110
- locations = %w[a1 e4 h8].map { |coord| Sashite::Cell::Location.parse(coord) }
88
+ # Convert coordinates to arrays of integers (0-indexed)
89
+ Sashite::Cell.to_indices("a1")
90
+ # => [0, 0]
111
91
 
112
- # Check if valid chess square
113
- def valid_chess_square?(location)
114
- return false unless location.board?
92
+ Sashite::Cell.to_indices("e4")
93
+ # => [4, 3]
115
94
 
116
- coord = location.to_s
117
- coord.length == 2 &&
118
- coord[0].between?("a", "h") &&
119
- coord[1].between?("1", "8")
120
- end
95
+ Sashite::Cell.to_indices("a1A")
96
+ # => [0, 0, 0]
121
97
 
122
- valid_chess_square?(Sashite::Cell::Location.parse("e4")) # => true
123
- valid_chess_square?(Sashite::Cell::Location.parse("z9")) # => false
124
- ```
98
+ # Convert arrays of integers back to CELL coordinates
99
+ Sashite::Cell.from_indices(0, 0)
100
+ # => "a1"
125
101
 
126
- ### Shōgi
102
+ Sashite::Cell.from_indices(4, 3)
103
+ # => "e4"
127
104
 
128
- ```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]}
105
+ Sashite::Cell.from_indices(0, 0, 0)
106
+ # => "a1A"
137
107
  ```
138
108
 
139
- ### Go
109
+ ## Usage Examples
140
110
 
141
- ```ruby
142
- # Go coordinates (traditional notation)
143
- go_positions = %w[A1 T19 K10].map { |coord| Sashite::Cell::Location.parse(coord) }
111
+ ### Chess Board (8x8)
144
112
 
145
- # Custom validation for Go board
146
- def valid_go_position?(location, board_size = 19)
147
- return false unless location.board?
148
-
149
- coord = location.to_s
150
- return false unless [2, 3].include?(coord.length)
151
-
152
- letter = coord[0]
153
- number = coord[1..].to_i
154
-
155
- letter.between?("A", ("A".ord + board_size - 1).chr) &&
156
- number.between?(1, board_size)
157
- end
113
+ ```ruby
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
158
126
  ```
159
127
 
160
- ### Custom Coordinate Systems
128
+ ### Shogi Board (9x9)
161
129
 
162
130
  ```ruby
163
- # 3D chess coordinates
164
- location_3d = Sashite::Cell::Location.parse("A3a")
165
-
166
- # Named locations
167
- center = Sashite::Cell::Location.parse("center")
168
- corner = Sashite::Cell::Location.parse("NE")
169
-
170
- # Hexagonal coordinates
171
- hex_coord = Sashite::Cell::Location.parse("Q3R7")
131
+ # Japanese shogi uses 9x9 board
132
+ shogi_position = "5e" # 5th file, e rank
133
+ Sashite::Cell.valid?(shogi_position) # => true
134
+ Sashite::Cell.dimensions(shogi_position) # => 2
135
+ Sashite::Cell.to_indices(shogi_position) # => [4, 4]
172
136
  ```
173
137
 
174
- ## Advanced Usage
175
-
176
- ### Working with Collections
138
+ ### 3D Tic-Tac-Toe (3x3x3)
177
139
 
178
140
  ```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"]
141
+ # Three-dimensional game coordinates
142
+ positions_3d = %w[a1A b2B c3C a2B b3C c1A]
143
+ positions_3d.all? { |pos| Sashite::Cell.valid?(pos) && Sashite::Cell.dimensions(pos) == 3 }
144
+ # => true
194
145
  ```
195
146
 
196
- ### Game State Representation
147
+ ### Multi-dimensional Coordinates
197
148
 
198
149
  ```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
150
+ # Higher dimensional coordinates
151
+ coord_4d = "a1Aa"
152
+ coord_5d = "b2Bb2"
213
153
 
214
- e_file_pieces = pieces_on_file(piece_locations, "e")
154
+ Sashite::Cell.dimensions(coord_4d) # => 4
155
+ Sashite::Cell.dimensions(coord_5d) # => 5
156
+
157
+ # Parse into components
158
+ Sashite::Cell.parse(coord_4d) # => ["a", "1", "A", "a"]
159
+ Sashite::Cell.parse(coord_5d) # => ["b", "2", "B", "b", "2"]
215
160
  ```
216
161
 
217
- ### Validation and Error Handling
162
+ ## API Reference
218
163
 
219
- ```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
234
- end
164
+ ### Module Methods
235
165
 
236
- # Invalid coordinates raise ArgumentError
237
- begin
238
- Sashite::Cell::Location.parse("")
239
- rescue ArgumentError => e
240
- puts "Invalid coordinate: #{e.message}"
241
- end
242
- ```
166
+ #### Validation
167
+ - `Sashite::Cell.valid?(string)` - Check if string represents a valid CELL coordinate
243
168
 
244
- ### Integration with Other Notations
169
+ #### Analysis
170
+ - `Sashite::Cell.dimensions(string)` - Get number of dimensions
171
+ - `Sashite::Cell.parse(string)` - Parse coordinate into dimensional components array
245
172
 
246
- ```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
173
+ #### Conversion
174
+ - `Sashite::Cell.to_indices(string)` - Convert CELL coordinate to 0-indexed integer array
175
+ - `Sashite::Cell.from_indices(*indices)` - Convert splat indices to CELL coordinate
274
176
 
275
- # Usage
276
- move = SimpleMove.new("e4", "e5") # Normal move
277
- drop = SimpleMove.new("*", "e4") # Drop from hand
278
- ```
177
+ #### Utilities
178
+ - `Sashite::Cell.regex` - Get the validation regular expression
279
179
 
280
- ## API Reference
180
+ ### Constants
281
181
 
282
- ### Module Methods
182
+ - `Sashite::Cell::REGEX` - Regular expression for CELL validation: `/\A(?:[a-z]+|[1-9]\d*|[A-Z]+)+\z/`
283
183
 
284
- - `Sashite::Cell.valid?(cell_string)` - Check if a string is valid CELL notation
285
- - `Sashite::Cell.location(coordinate)` - Convenience method to create locations
184
+ ## Properties of CELL
286
185
 
287
- ### Sashite::Cell::Location Class Methods
186
+ * **Multi-dimensional**: Supports unlimited dimensional coordinate systems
187
+ * **Cyclical**: Uses systematic three-character-set repetition
188
+ * **ASCII-based**: Pure ASCII characters for universal compatibility
189
+ * **Unambiguous**: Each coordinate maps to exactly one location
190
+ * **Scalable**: Extends naturally from 1D to unlimited dimensions
191
+ * **Functional**: Provides a clean, stateless API for coordinate operations
288
192
 
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
193
+ ## Character Set Details
291
194
 
292
- ### Instance Methods
195
+ ### Latin Lowercase (Dimensions 1, 4, 7, ...)
196
+ Single letters: `a` through `z` (positions 0-25)
197
+ Double letters: `aa` through `zz` (positions 26-701)
198
+ Triple letters: `aaa` through `zzz` (positions 702-18277)
199
+ And so on...
293
200
 
294
- #### Type Checking
295
- - `#board?` - Check if location represents a board coordinate
296
- - `#hand?` - Check if location represents hand/reserve
201
+ ### Arabic Numerals (Dimensions 2, 5, 8, ...)
202
+ Standard decimal notation: `1`, `2`, `3`, ... (1-indexed)
203
+ No leading zeros, unlimited range
297
204
 
298
- #### Conversion
299
- - `#to_s` - Convert to CELL string representation
300
- - `#inspect` - Detailed string representation for debugging
205
+ ### Latin Uppercase (Dimensions 3, 6, 9, ...)
206
+ Single letters: `A` through `Z` (positions 0-25)
207
+ Double letters: `AA` through `ZZ` (positions 26-701)
208
+ Triple letters: `AAA` through `ZZZ` (positions 702-18277)
209
+ And so on...
301
210
 
302
- #### Comparison
303
- - `#==` - Compare locations for equality
304
- - `#eql?` - Strict equality comparison
305
- - `#hash` - Hash value for use in collections
211
+ ## Integration with DROP
306
212
 
307
- ## Properties of CELL
213
+ CELL complements the DROP specification for complete location coverage:
308
214
 
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
215
+ ```ruby
216
+ # Combined location validation
217
+ def valid_game_location?(location)
218
+ Sashite::Cell.valid?(location) || Sashite::Drop.reserve?(location)
219
+ end
314
220
 
315
- ## Constraints
221
+ valid_game_location?("a1") # => true (board position)
222
+ valid_game_location?("*") # => true (reserve position)
223
+ valid_game_location?("$") # => false (invalid)
224
+ ```
316
225
 
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
226
+ ## Examples in Different Games
321
227
 
322
- ## Use Cases
228
+ ### Chess
323
229
 
324
- CELL is particularly useful in the following scenarios:
230
+ ```ruby
231
+ # Standard algebraic notation positions
232
+ start_position = "e2"
233
+ end_position = "e4"
325
234
 
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
235
+ Sashite::Cell.valid?(start_position) # => true
236
+ Sashite::Cell.valid?(end_position) # => true
237
+ ```
238
+
239
+ ### Go (19x19)
332
240
 
333
- ## Dependencies
241
+ ```ruby
242
+ # Go board positions
243
+ corner = "a1" # Corner position
244
+ edge = "j1" # Edge position
245
+ tengen = "j10" # Center point (tengen) on 19x19 board
334
246
 
335
- This gem has no external dependencies beyond Ruby standard library.
247
+ [corner, edge, tengen].all? { |pos| Sashite::Cell.valid?(pos) }
248
+ # => true
249
+ ```
336
250
 
337
- ## Specification
251
+ ### Abstract Strategy Games
338
252
 
339
- - [CELL Specification](https://sashite.dev/documents/cell/1.0.0/)
253
+ ```ruby
254
+ # Multi-dimensional abstract games
255
+ hypercube_4d = "a1Aa"
256
+ tesseract_pos = "h8Hh8"
257
+
258
+ # Validate high-dimensional coordinates
259
+ Sashite::Cell.valid?(hypercube_4d) # => true
260
+ Sashite::Cell.dimensions(tesseract_pos) # => 5
261
+ ```
340
262
 
341
263
  ## Documentation
342
264
 
343
- - [CELL Documentation](https://rubydoc.info/github/sashite/cell.rb/main)
265
+ - [Official CELL Specification](https://sashite.dev/documents/cell/1.0.0/)
266
+ - [API Documentation](https://rubydoc.info/github/sashite/cell.rb/main)
344
267
 
345
268
  ## License
346
269
 
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 (Cell Encoding Location Label) 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
9
  # @see https://sashite.dev/documents/cell/1.0.0/ CELL Specification v1.0.0
16
10
  module Cell
17
- # Regular expression for validating CELL notation
11
+ # Regular expression for validating CELL coordinates according to specification
12
+ # This is the exact regex from the CELL specification v1.0.0
13
+ # Using non-capturing groups to avoid Ruby's nested quantifier warning
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
17
+ #
18
+ # @param string [String] the string to validate
19
+ # @return [Boolean] true if the string is a valid CELL coordinate
20
+ #
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?
29
+
30
+ # Use the formal regex for validation
31
+ string.match?(REGEX)
32
+ end
33
+
34
+ # Get the number of dimensions in a coordinate
18
35
  #
19
- # Matches either:
20
- # - Hand/reserve location: exactly "*"
21
- # - Board coordinate: one or more alphanumeric characters [a-zA-Z0-9]
36
+ # @param string [String] the coordinate string
37
+ # @return [Integer] the number of dimensions
22
38
  #
23
- # @return [Regexp] the validation pattern
24
- CELL_PATTERN = /\A(#{::Regexp.escape(Location::HAND_CHAR)}|[a-zA-Z0-9]+)\z/
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
25
48
 
26
- # Check if a string is valid CELL notation
49
+ # Parse a coordinate string into dimensional components
27
50
  #
28
- # @param cell_string [String] the string to validate
29
- # @return [Boolean] true if valid CELL notation, false otherwise
51
+ # @param string [String] the coordinate string to parse
52
+ # @return [Array<String>] array of dimensional components
53
+ #
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)
62
+
63
+ parse_recursive(string, 1)
64
+ end
65
+
66
+ # Convert a CELL coordinate to an array of 0-indexed integers
30
67
  #
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
68
+ # @param string [String] the CELL coordinate
69
+ # @return [Array<Integer>] array of 0-indexed positions
36
70
  #
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)
71
+ # @example
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)
44
77
 
45
- CELL_PATTERN.match?(cell_string)
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
46
82
  end
47
83
 
48
- # Convenience method to create a Location object
84
+ # Convert an array of 0-indexed integers to a CELL coordinate
49
85
  #
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
86
+ # @param indices [Array<Integer>] splat arguments of 0-indexed positions
87
+ # @return [String] the CELL coordinate
53
88
  #
54
89
  # @example
55
- # location = Sashite::Cell.location("e4")
56
- # hand = Sashite::Cell.location("*")
57
- def self.location(coordinate)
58
- Location.new(coordinate)
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
108
+ def self.regex
109
+ REGEX
110
+ end
111
+
112
+ # Recursively parse a coordinate string into components
113
+ # following the strict CELL specification 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
+ # Invalid format according to CELL specification
127
+
128
+ # Extract component and recursively parse the rest
129
+ remaining = string[component.length..]
130
+ [component] + parse_recursive(remaining, dimension + 1)
131
+ end
132
+
133
+ # Determine the character set type for a given dimension
134
+ # Following CELL specification: dimension n % 3 determines character set
135
+ #
136
+ # @param dimension [Integer] the dimension number (1-indexed)
137
+ # @return [Symbol] :lowercase, :numeric, or :uppercase
138
+ def self.dimension_type(dimension)
139
+ case dimension % 3
140
+ when 1 then :lowercase
141
+ when 2 then :numeric
142
+ when 0 then :uppercase
143
+ end
144
+ end
145
+
146
+ # Extract the next component from a string based on expected type
147
+ # Strictly follows CELL specification patterns
148
+ #
149
+ # @param string [String] the string to extract from
150
+ # @param type [Symbol] the expected component type
151
+ # @return [String, nil] the extracted component or nil if invalid
152
+ def self.extract_component(string, type)
153
+ case type
154
+ when :lowercase
155
+ match = string.match(/\A([a-z]+)/)
156
+ match ? match[1] : nil
157
+ when :numeric
158
+ # CELL specification requires positive integers only (no zero)
159
+ match = string.match(/\A([1-9]\d*)/)
160
+ match ? match[1] : nil
161
+ when :uppercase
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: 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: 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
@@ -2,13 +2,6 @@
2
2
 
3
3
  # Sashité namespace for board game notation libraries
4
4
  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
5
  # @see https://sashite.dev/documents/cell/1.0.0/ CELL Specification v1.0.0
13
6
  end
14
7
 
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.0
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 diverse writing systems from around the world. This gem provides
14
+ a Ruby interface for working with multi-dimensional game coordinates through a clean,
15
+ functional API.
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
@@ -48,7 +46,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
48
46
  - !ruby/object:Gem::Version
49
47
  version: '0'
50
48
  requirements: []
51
- rubygems_version: 3.6.7
49
+ rubygems_version: 3.6.9
52
50
  specification_version: 4
53
- summary: CELL (Coordinate Expression Location Label) implementation for Ruby
51
+ summary: CELL (Cell Encoding Location Label) implementation for Ruby
54
52
  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