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 +4 -4
- data/README.md +229 -233
- data/lib/sashite/cell.rb +231 -40
- data/lib/sashite-cell.rb +9 -10
- metadata +10 -10
- data/lib/sashite/cell/location/hand_char.rb +0 -16
- data/lib/sashite/cell/location.rb +0 -136
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 84228cab7c7155caaec3a2adad475150eb8b272c5c8e67d2dfb1e626b6120e7e
|
4
|
+
data.tar.gz: 318c49ae54afbfec1edaeca7a03c23c7cc5a65cec0658d14973f54a203c15914
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: afe40400d123291deed966e92943ac01b18195cb1a1d191473ff0c719721288c4bae5c1032b6a524f194c722779818c81d02c4709e115bb7f29916eb3ed1828f
|
7
|
+
data.tar.gz: be17bfa93a1db05ddaecc938db407a3788ab565d1254e3f61ff9cdd714a09298c5137d3131e11d374d0c9c2d51725a0720f8e8dac7375b614ad3909e26e8687c
|
data/README.md
CHANGED
@@ -5,13 +5,13 @@
|
|
5
5
|

|
6
6
|
[](https://github.com/sashite/cell.rb/raw/main/LICENSE.md)
|
7
7
|
|
8
|
-
> **CELL** (Coordinate
|
8
|
+
> **CELL** (Coordinate Encoding for Layered Locations) support for the Ruby language.
|
9
9
|
|
10
10
|
## What is CELL?
|
11
11
|
|
12
|
-
CELL (Coordinate
|
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/
|
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
|
-
|
31
|
+
CELL uses a cyclical three-character-set system that repeats indefinitely based on dimensional position:
|
32
32
|
|
33
|
-
|
33
|
+
**Dimension (n % 3 = 1)**: Latin Lowercase Letters
|
34
|
+
- `a`, `b`, `c`, ..., `z`, `aa`, `ab`, ..., `zz`, `aaa`, ...
|
34
35
|
|
35
|
-
|
36
|
+
**Dimension (n % 3 = 2)**: Arabic Numerals
|
37
|
+
- `1`, `2`, `3`, ..., `25`, `26`, ...
|
36
38
|
|
37
|
-
|
38
|
-
|
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
|
-
###
|
44
|
+
### Validation
|
54
45
|
|
55
|
-
The primary
|
46
|
+
The primary functionality is validating CELL coordinates:
|
56
47
|
|
57
48
|
```ruby
|
58
49
|
require "sashite/cell"
|
59
50
|
|
60
|
-
#
|
61
|
-
|
62
|
-
# =>
|
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
|
-
|
65
|
-
|
74
|
+
# Parse coordinate into dimensional components
|
75
|
+
Sashite::Cell.parse("a1A")
|
76
|
+
# => ["a", "1", "A"]
|
66
77
|
|
67
|
-
|
68
|
-
|
69
|
-
hand = Sashite::Cell::Location.new("*")
|
78
|
+
Sashite::Cell.parse("h8Hh8")
|
79
|
+
# => ["h", "8", "H", "h", "8"]
|
70
80
|
|
71
|
-
|
72
|
-
|
81
|
+
Sashite::Cell.parse("foobar")
|
82
|
+
# => ["foobar"]
|
73
83
|
```
|
74
84
|
|
75
|
-
###
|
76
|
-
|
77
|
-
Convert a location object back to its CELL string representation:
|
85
|
+
### Coordinate Conversion
|
78
86
|
|
79
87
|
```ruby
|
80
|
-
|
81
|
-
|
82
|
-
# =>
|
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
|
-
|
92
|
+
Sashite::Cell.to_indices("e4")
|
93
|
+
# => [4, 3]
|
90
94
|
|
91
|
-
|
95
|
+
Sashite::Cell.to_indices("a1A")
|
96
|
+
# => [0, 0, 0]
|
92
97
|
|
93
|
-
|
94
|
-
|
95
|
-
|
98
|
+
# Convert arrays of integers back to CELL coordinates
|
99
|
+
Sashite::Cell.from_indices(0, 0)
|
100
|
+
# => "a1"
|
96
101
|
|
97
|
-
|
98
|
-
|
102
|
+
Sashite::Cell.from_indices(4, 3)
|
103
|
+
# => "e4"
|
99
104
|
|
100
|
-
|
101
|
-
|
105
|
+
Sashite::Cell.from_indices(0, 0, 0)
|
106
|
+
# => "a1A"
|
102
107
|
```
|
103
108
|
|
104
|
-
##
|
109
|
+
## Usage Examples
|
105
110
|
|
106
|
-
### Chess
|
111
|
+
### Chess Board (8x8)
|
107
112
|
|
108
113
|
```ruby
|
109
|
-
# Standard chess
|
110
|
-
|
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
|
-
|
113
|
-
def valid_chess_square?(location)
|
114
|
-
return false unless location.board?
|
128
|
+
### Shōgi Board (9x9)
|
115
129
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
-
|
123
|
-
|
136
|
+
# Convert to indices for board representation
|
137
|
+
Sashite::Cell.to_indices("e5") # => [4, 4] (center of 9x9 board)
|
124
138
|
```
|
125
139
|
|
126
|
-
###
|
140
|
+
### 3D Tic-Tac-Toe (3x3x3)
|
127
141
|
|
128
142
|
```ruby
|
129
|
-
#
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
-
###
|
154
|
+
### Multi-dimensional Coordinates
|
140
155
|
|
141
156
|
```ruby
|
142
|
-
#
|
143
|
-
|
157
|
+
# Higher dimensional coordinates
|
158
|
+
coord_4d = "a1Aa"
|
159
|
+
coord_5d = "b2Bb2"
|
144
160
|
|
145
|
-
#
|
146
|
-
|
147
|
-
return false unless location.board?
|
161
|
+
Sashite::Cell.dimensions(coord_4d) # => 4
|
162
|
+
Sashite::Cell.dimensions(coord_5d) # => 5
|
148
163
|
|
149
|
-
|
150
|
-
|
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
|
-
|
153
|
-
number = coord[1..].to_i
|
169
|
+
## API Reference
|
154
170
|
|
155
|
-
|
156
|
-
number.between?(1, board_size)
|
157
|
-
end
|
158
|
-
```
|
171
|
+
### Module Methods
|
159
172
|
|
160
|
-
|
173
|
+
#### Validation
|
174
|
+
- `Sashite::Cell.valid?(string)` - Check if string represents a valid CELL coordinate
|
161
175
|
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
171
|
-
|
172
|
-
```
|
184
|
+
#### Utilities
|
185
|
+
- `Sashite::Cell.regex` - Get the validation regular expression
|
173
186
|
|
174
|
-
|
187
|
+
### Constants
|
175
188
|
|
176
|
-
|
189
|
+
- `Sashite::Cell::REGEX` - Regular expression for CELL validation per specification v1.0.0
|
177
190
|
|
178
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
###
|
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
|
-
#
|
221
|
-
|
222
|
-
Sashite::Cell.valid?(
|
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
|
-
#
|
237
|
-
|
238
|
-
|
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
|
-
|
233
|
+
## Examples in Different Games
|
234
|
+
|
235
|
+
### Chess
|
245
236
|
|
246
237
|
```ruby
|
247
|
-
#
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
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
|
-
#
|
276
|
-
|
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
|
-
|
249
|
+
### Go (19x19)
|
281
250
|
|
282
|
-
|
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
|
-
|
285
|
-
|
257
|
+
[corner, edge, tengen].all? { |pos| Sashite::Cell.valid?(pos) }
|
258
|
+
# => true
|
259
|
+
```
|
286
260
|
|
287
|
-
###
|
261
|
+
### Abstract Strategy Games
|
288
262
|
|
289
|
-
|
290
|
-
|
263
|
+
```ruby
|
264
|
+
# Multi-dimensional abstract games
|
265
|
+
hypercube_4d = "a1Aa"
|
266
|
+
tesseract_pos = "h8Hh8"
|
291
267
|
|
292
|
-
|
268
|
+
# Validate high-dimensional coordinates
|
269
|
+
Sashite::Cell.valid?(hypercube_4d) # => true
|
270
|
+
Sashite::Cell.dimensions(tesseract_pos) # => 5
|
293
271
|
|
294
|
-
|
295
|
-
|
296
|
-
|
272
|
+
# Convert for mathematical operations
|
273
|
+
Sashite::Cell.to_indices(hypercube_4d) # => [0, 0, 0, 0]
|
274
|
+
```
|
297
275
|
|
298
|
-
|
299
|
-
- `#to_s` - Convert to CELL string representation
|
300
|
-
- `#inspect` - Detailed string representation for debugging
|
276
|
+
## Specification Compliance
|
301
277
|
|
302
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
287
|
+
All examples from the CELL specification work correctly:
|
316
288
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
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
|
-
##
|
308
|
+
## Documentation
|
323
309
|
|
324
|
-
|
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
|
-
|
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
|
-
|
316
|
+
```sh
|
317
|
+
# Clone the repository
|
318
|
+
git clone https://github.com/sashite/cell.rb.git
|
319
|
+
cd cell.rb
|
334
320
|
|
335
|
-
|
321
|
+
# Install dependencies
|
322
|
+
bundle install
|
336
323
|
|
337
|
-
|
324
|
+
# Run tests
|
325
|
+
ruby test.rb
|
338
326
|
|
339
|
-
|
327
|
+
# Generate documentation
|
328
|
+
yard doc
|
329
|
+
```
|
340
330
|
|
341
|
-
##
|
331
|
+
## Contributing
|
342
332
|
|
343
|
-
|
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
|
-
|
343
|
+
Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
|
348
344
|
|
349
|
-
## About
|
345
|
+
## About
|
350
346
|
|
351
|
-
|
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
|
4
|
+
# CELL (Coordinate Encoding for Layered Locations) implementation for Ruby
|
10
5
|
#
|
11
|
-
#
|
12
|
-
#
|
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
|
+
# 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
|
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
|
-
#
|
20
|
-
#
|
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
|
-
# @
|
24
|
-
|
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
|
-
|
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
|
-
# @
|
29
|
-
#
|
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
|
-
# @
|
32
|
-
#
|
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
|
38
|
-
# Sashite::Cell.
|
39
|
-
# Sashite::Cell.
|
40
|
-
# Sashite::Cell.
|
41
|
-
|
42
|
-
|
43
|
-
return
|
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
|
-
|
63
|
+
parse_recursive(string, 1)
|
46
64
|
end
|
47
65
|
|
48
|
-
#
|
66
|
+
# Convert a CELL coordinate to an array of 0-indexed integers
|
49
67
|
#
|
50
|
-
# @param
|
51
|
-
# @return [
|
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
|
-
#
|
56
|
-
#
|
57
|
-
|
58
|
-
|
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:
|
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
|
13
|
-
|
14
|
-
|
15
|
-
|
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/
|
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.
|
51
|
+
rubygems_version: 3.6.9
|
52
52
|
specification_version: 4
|
53
|
-
summary: CELL (Coordinate
|
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
|