sashite-cell 2.0.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sashite
4
+ module Cell
5
+ # Formats index arrays into CELL coordinate strings.
6
+ #
7
+ # This module handles the conversion from numeric indices to their
8
+ # CELL string representation following the cyclic pattern:
9
+ # - Dimension 1, 4, 7...: lowercase letters (a-z, aa-iv)
10
+ # - Dimension 2, 5, 8...: positive integers (1-256)
11
+ # - Dimension 3, 6, 9...: uppercase letters (A-Z, AA-IV)
12
+ #
13
+ # @example
14
+ # Formatter.indices_to_string([4, 3]) # => "e4"
15
+ # Formatter.indices_to_string([0, 0, 0]) # => "a1A"
16
+ #
17
+ # @api private
18
+ module Formatter
19
+ # Formats an indices array to a CELL string.
20
+ #
21
+ # @param indices [Array<Integer>] 0-indexed coordinate values
22
+ # @return [String] CELL coordinate string
23
+ #
24
+ # @example
25
+ # Formatter.indices_to_string([4, 3]) # => "e4"
26
+ # Formatter.indices_to_string([255, 255, 255]) # => "iv256IV"
27
+ def self.indices_to_string(indices)
28
+ result = +""
29
+
30
+ indices.each_with_index do |index, i|
31
+ dimension_type = i % 3
32
+
33
+ result << case dimension_type
34
+ when 0 then encode_to_lower(index)
35
+ when 1 then encode_to_number(index)
36
+ when 2 then encode_to_upper(index)
37
+ end
38
+ end
39
+
40
+ result.freeze
41
+ end
42
+
43
+ # Encodes an index (0-255) as lowercase letters (a-z, aa-iv).
44
+ #
45
+ # @param index [Integer] 0-indexed value (0-255)
46
+ # @return [String] lowercase letter sequence
47
+ #
48
+ # @example
49
+ # encode_to_lower(0) # => "a"
50
+ # encode_to_lower(25) # => "z"
51
+ # encode_to_lower(26) # => "aa"
52
+ # encode_to_lower(255) # => "iv"
53
+ private_class_method def self.encode_to_lower(index)
54
+ encode_to_letters(index, base: "a")
55
+ end
56
+
57
+ # Encodes an index (0-255) as uppercase letters (A-Z, AA-IV).
58
+ #
59
+ # @param index [Integer] 0-indexed value (0-255)
60
+ # @return [String] uppercase letter sequence
61
+ #
62
+ # @example
63
+ # encode_to_upper(0) # => "A"
64
+ # encode_to_upper(25) # => "Z"
65
+ # encode_to_upper(26) # => "AA"
66
+ # encode_to_upper(255) # => "IV"
67
+ private_class_method def self.encode_to_upper(index)
68
+ encode_to_letters(index, base: "A")
69
+ end
70
+
71
+ # Encodes an index to a letter sequence.
72
+ #
73
+ # @param index [Integer] 0-indexed value (0-255)
74
+ # @param base [String] base character ("a" or "A")
75
+ # @return [String] letter sequence
76
+ private_class_method def self.encode_to_letters(index, base:)
77
+ base_ord = base.ord
78
+
79
+ if index < 26
80
+ (base_ord + index).chr
81
+ else
82
+ adjusted = index - 26
83
+ first = adjusted / 26
84
+ second = adjusted % 26
85
+ (base_ord + first).chr + (base_ord + second).chr
86
+ end
87
+ end
88
+
89
+ # Encodes an index (0-255) as a 1-based positive integer string.
90
+ #
91
+ # @param index [Integer] 0-indexed value (0-255)
92
+ # @return [String] number string (1-indexed)
93
+ #
94
+ # @example
95
+ # encode_to_number(0) # => "1"
96
+ # encode_to_number(255) # => "256"
97
+ private_class_method def self.encode_to_number(index)
98
+ (index + 1).to_s
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,234 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "constants"
4
+ require_relative "errors"
5
+
6
+ module Sashite
7
+ module Cell
8
+ # Parses CELL coordinate strings into index arrays.
9
+ #
10
+ # This module handles the conversion from CELL string representation
11
+ # to numeric indices, following the cyclic pattern:
12
+ # - Dimension 1, 4, 7...: lowercase letters (a-z, aa-iv)
13
+ # - Dimension 2, 5, 8...: positive integers (1-256)
14
+ # - Dimension 3, 6, 9...: uppercase letters (A-Z, AA-IV)
15
+ #
16
+ # Security considerations:
17
+ # - Character-by-character parsing (no regex, no ReDoS risk)
18
+ # - Fail-fast on invalid input
19
+ # - Bounded iteration (max 7 characters)
20
+ # - Explicit ASCII validation
21
+ #
22
+ # @example
23
+ # Parser.parse_to_indices("e4") # => [4, 3]
24
+ # Parser.parse_to_indices("a1A") # => [0, 0, 0]
25
+ #
26
+ # @api private
27
+ module Parser
28
+ # Parses a CELL string into an array of indices.
29
+ #
30
+ # @param string [String] CELL coordinate string
31
+ # @return [Array<Integer>] 0-indexed coordinate values
32
+ # @raise [Sashite::Cell::Errors::Argument] if parsing fails
33
+ #
34
+ # @example
35
+ # Parser.parse_to_indices("e4") # => [4, 3]
36
+ # Parser.parse_to_indices("iv256IV") # => [255, 255, 255]
37
+ def self.parse_to_indices(string)
38
+ raise Errors::Argument, Errors::Argument::Messages::EMPTY_INPUT if string.empty?
39
+
40
+ if string.length > Constants::MAX_STRING_LENGTH
41
+ raise Errors::Argument, Errors::Argument::Messages::INPUT_TOO_LONG
42
+ end
43
+
44
+ first_byte = string.getbyte(0)
45
+ unless lowercase?(first_byte)
46
+ raise Errors::Argument, Errors::Argument::Messages::INVALID_START
47
+ end
48
+
49
+ indices = []
50
+ pos = 0
51
+ dimension_type = 0 # 0: lowercase, 1: integer, 2: uppercase
52
+
53
+ while pos < string.length
54
+ if indices.size >= Constants::MAX_DIMENSIONS
55
+ raise Errors::Argument, Errors::Argument::Messages::TOO_MANY_DIMENSIONS
56
+ end
57
+
58
+ case dimension_type
59
+ when 0
60
+ value, consumed = parse_lowercase(string, pos)
61
+ indices << value
62
+ pos += consumed
63
+ dimension_type = 1
64
+ when 1
65
+ value, consumed = parse_integer(string, pos)
66
+ indices << value
67
+ pos += consumed
68
+ dimension_type = 2
69
+ when 2
70
+ value, consumed = parse_uppercase(string, pos)
71
+ indices << value
72
+ pos += consumed
73
+ dimension_type = 0
74
+ end
75
+ end
76
+
77
+ indices
78
+ end
79
+
80
+ # Checks if a byte is a lowercase ASCII letter (a-z).
81
+ #
82
+ # @param byte [Integer, nil] byte value
83
+ # @return [Boolean] true if lowercase letter
84
+ private_class_method def self.lowercase?(byte)
85
+ byte && byte >= 97 && byte <= 122 # 'a' = 97, 'z' = 122
86
+ end
87
+
88
+ # Checks if a byte is an uppercase ASCII letter (A-Z).
89
+ #
90
+ # @param byte [Integer, nil] byte value
91
+ # @return [Boolean] true if uppercase letter
92
+ private_class_method def self.uppercase?(byte)
93
+ byte && byte >= 65 && byte <= 90 # 'A' = 65, 'Z' = 90
94
+ end
95
+
96
+ # Checks if a byte is an ASCII digit (0-9).
97
+ #
98
+ # @param byte [Integer, nil] byte value
99
+ # @return [Boolean] true if digit
100
+ private_class_method def self.digit?(byte)
101
+ byte && byte >= 48 && byte <= 57 # '0' = 48, '9' = 57
102
+ end
103
+
104
+ # Parses lowercase letters starting at position.
105
+ #
106
+ # @param string [String] input string
107
+ # @param pos [Integer] starting position
108
+ # @return [Array(Integer, Integer)] decoded value and characters consumed
109
+ # @raise [Sashite::Cell::Errors::Argument] if parsing fails
110
+ private_class_method def self.parse_lowercase(string, pos)
111
+ byte = string.getbyte(pos)
112
+ unless lowercase?(byte)
113
+ raise Errors::Argument, Errors::Argument::Messages::UNEXPECTED_CHARACTER
114
+ end
115
+
116
+ chars = [byte]
117
+ pos += 1
118
+
119
+ while pos < string.length && lowercase?(string.getbyte(pos))
120
+ chars << string.getbyte(pos)
121
+ pos += 1
122
+ end
123
+
124
+ value = decode_lowercase(chars)
125
+ if value > Constants::MAX_INDEX_VALUE
126
+ raise Errors::Argument, Errors::Argument::Messages::INDEX_OUT_OF_RANGE
127
+ end
128
+
129
+ [value, chars.size]
130
+ end
131
+
132
+ # Parses a positive integer starting at position.
133
+ #
134
+ # @param string [String] input string
135
+ # @param pos [Integer] starting position
136
+ # @return [Array(Integer, Integer)] decoded value and characters consumed
137
+ # @raise [Sashite::Cell::Errors::Argument] if parsing fails
138
+ private_class_method def self.parse_integer(string, pos)
139
+ byte = string.getbyte(pos)
140
+ unless digit?(byte)
141
+ raise Errors::Argument, Errors::Argument::Messages::UNEXPECTED_CHARACTER
142
+ end
143
+
144
+ # Check for leading zero
145
+ if byte == 48 # '0'
146
+ raise Errors::Argument, Errors::Argument::Messages::LEADING_ZERO
147
+ end
148
+
149
+ chars = [byte]
150
+ pos += 1
151
+
152
+ while pos < string.length && digit?(string.getbyte(pos))
153
+ chars << string.getbyte(pos)
154
+ pos += 1
155
+ end
156
+
157
+ value = decode_integer(chars)
158
+ if value < 0 || value > Constants::MAX_INDEX_VALUE
159
+ raise Errors::Argument, Errors::Argument::Messages::INDEX_OUT_OF_RANGE
160
+ end
161
+
162
+ [value, chars.size]
163
+ end
164
+
165
+ # Parses uppercase letters starting at position.
166
+ #
167
+ # @param string [String] input string
168
+ # @param pos [Integer] starting position
169
+ # @return [Array(Integer, Integer)] decoded value and characters consumed
170
+ # @raise [Sashite::Cell::Errors::Argument] if parsing fails
171
+ private_class_method def self.parse_uppercase(string, pos)
172
+ byte = string.getbyte(pos)
173
+ unless uppercase?(byte)
174
+ raise Errors::Argument, Errors::Argument::Messages::UNEXPECTED_CHARACTER
175
+ end
176
+
177
+ chars = [byte]
178
+ pos += 1
179
+
180
+ while pos < string.length && uppercase?(string.getbyte(pos))
181
+ chars << string.getbyte(pos)
182
+ pos += 1
183
+ end
184
+
185
+ value = decode_uppercase(chars)
186
+ if value > Constants::MAX_INDEX_VALUE
187
+ raise Errors::Argument, Errors::Argument::Messages::INDEX_OUT_OF_RANGE
188
+ end
189
+
190
+ [value, chars.size]
191
+ end
192
+
193
+ # Decodes lowercase letter bytes to an index.
194
+ #
195
+ # @param bytes [Array<Integer>] byte values
196
+ # @return [Integer] decoded index (0-255)
197
+ private_class_method def self.decode_lowercase(bytes)
198
+ if bytes.size == 1
199
+ bytes[0] - 97 # 'a' = 97
200
+ else
201
+ first = bytes[0] - 97
202
+ second = bytes[1] - 97
203
+ 26 + (first * 26) + second
204
+ end
205
+ end
206
+
207
+ # Decodes uppercase letter bytes to an index.
208
+ #
209
+ # @param bytes [Array<Integer>] byte values
210
+ # @return [Integer] decoded index (0-255)
211
+ private_class_method def self.decode_uppercase(bytes)
212
+ if bytes.size == 1
213
+ bytes[0] - 65 # 'A' = 65
214
+ else
215
+ first = bytes[0] - 65
216
+ second = bytes[1] - 65
217
+ 26 + (first * 26) + second
218
+ end
219
+ end
220
+
221
+ # Decodes digit bytes to an index (1-based to 0-based).
222
+ #
223
+ # @param bytes [Array<Integer>] byte values
224
+ # @return [Integer] decoded index (0-255)
225
+ private_class_method def self.decode_integer(bytes)
226
+ value = 0
227
+ bytes.each do |byte|
228
+ value = (value * 10) + (byte - 48) # '0' = 48
229
+ end
230
+ value - 1 # Convert from 1-based to 0-based
231
+ end
232
+ end
233
+ end
234
+ end
data/lib/sashite/cell.rb CHANGED
@@ -1,264 +1,84 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "cell/errors"
4
+ require_relative "cell/formatter"
5
+ require_relative "cell/coordinate"
6
+ require_relative "cell/parser"
7
+
3
8
  module Sashite
4
- # CELL (Coordinate Encoding for Layered Locations) implementation for Ruby
9
+ # CELL (Coordinate Encoding for Layered Locations) implementation.
10
+ #
11
+ # Provides parsing, formatting, and validation of CELL coordinates
12
+ # for multi-dimensional game boards (up to 3 dimensions).
13
+ #
14
+ # @example Parsing a coordinate
15
+ # coord = Sashite::Cell.parse("e4")
16
+ # coord.indices # => [4, 3]
17
+ # coord.dimensions # => 2
18
+ #
19
+ # @example Formatting indices
20
+ # Sashite::Cell.format(4, 3) # => "e4"
5
21
  #
6
- # Provides functionality for working with multi-dimensional game board coordinates
7
- # using a cyclical ASCII character system.
22
+ # @example Validation
23
+ # Sashite::Cell.valid?("e4") # => true
24
+ # Sashite::Cell.valid?("a0") # => false
8
25
  #
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
26
+ # @see https://sashite.dev/specs/cell/1.0.0/
11
27
  module Cell
12
- # Regular expression from CELL Specification v1.0.0
13
- # Note: Line breaks must be rejected separately (see valid?)
14
- REGEX = /^[a-z]+(?:[1-9][0-9]*[A-Z]+[a-z]+)*(?:[1-9][0-9]*[A-Z]*)?$/
15
-
16
- # Check if a string represents a valid CELL coordinate
17
- #
18
- # Implements full-string matching as required by the CELL specification.
19
- # Rejects any input containing line breaks (\r or \n).
28
+ # Parses a CELL string into a Coordinate.
20
29
  #
21
- # @param string [String] the string to validate
22
- # @return [Boolean] true if the string is a valid CELL coordinate
30
+ # @param string [String] CELL coordinate string
31
+ # @return [Coordinate] parsed coordinate
32
+ # @raise [Sashite::Cell::Errors::Argument] if the string is not a valid CELL coordinate
23
33
  #
24
34
  # @example
25
- # Sashite::Cell.valid?("a1") # => true
26
- # Sashite::Cell.valid?("a1A") # => true
27
- # Sashite::Cell.valid?("*") # => false
28
- # Sashite::Cell.valid?("a0") # => false
29
- # Sashite::Cell.valid?("a1\n") # => false
30
- def self.valid?(string)
31
- return false unless string.is_a?(String)
32
- return false if string.empty?
33
- return false if string.include?("\r") || string.include?("\n")
34
-
35
- string.match?(REGEX)
36
- end
37
-
38
- # Get the number of dimensions in a coordinate
39
- #
40
- # @param string [String] the coordinate string
41
- # @return [Integer] the number of dimensions
42
- #
43
- # @example
44
- # Sashite::Cell.dimensions("a1") # => 2
45
- # Sashite::Cell.dimensions("a1A") # => 3
46
- # Sashite::Cell.dimensions("foobar") # => 1
47
- def self.dimensions(string)
48
- return 0 unless valid?(string)
49
-
50
- parse(string).length
51
- end
52
-
53
- # Parse a coordinate string into dimensional components
54
- #
55
- # @param string [String] the coordinate string to parse
56
- # @return [Array<String>] array of dimensional components
57
- #
58
- # @example
59
- # Sashite::Cell.parse("a1A") # => ["a", "1", "A"]
60
- # Sashite::Cell.parse("h8Hh8") # => ["h", "8", "H", "h", "8"]
61
- # Sashite::Cell.parse("foobar") # => ["foobar"] (if valid single dimension)
35
+ # Sashite::Cell.parse("e4") # => #<Sashite::Cell::Coordinate e4>
36
+ # Sashite::Cell.parse("a1A") # => #<Sashite::Cell::Coordinate a1A>
37
+ # Sashite::Cell.parse("a0") # => raises Sashite::Cell::Errors::Argument
62
38
  def self.parse(string)
63
- return [] unless string.is_a?(::String)
64
- return [] if string.empty?
65
- return [] unless valid?(string)
66
-
67
- parse_recursive(string, 1)
39
+ Coordinate.new(*Parser.parse_to_indices(string))
68
40
  end
69
41
 
70
- # Convert a CELL coordinate to an array of 0-indexed integers
42
+ # Formats indices into a CELL string.
71
43
  #
72
- # @param string [String] the CELL coordinate
73
- # @return [Array<Integer>] array of 0-indexed positions
44
+ # @param indices [Array<Integer>] 0-indexed coordinate values (0-255)
45
+ # @return [String] CELL coordinate string
46
+ # @raise [Sashite::Cell::Errors::Argument] if indices are invalid
74
47
  #
75
48
  # @example
76
- # Sashite::Cell.to_indices("a1") # => [0, 0]
77
- # Sashite::Cell.to_indices("e4") # => [4, 3]
78
- # Sashite::Cell.to_indices("a1A") # => [0, 0, 0]
79
- def self.to_indices(string)
80
- return [] unless valid?(string)
81
-
82
- parse(string).map.with_index do |component, index|
83
- dimension_type = dimension_type(index + 1)
84
- component_to_index(component, dimension_type)
85
- end
49
+ # Sashite::Cell.format(4, 3) # => "e4"
50
+ # Sashite::Cell.format(0, 0, 0) # => "a1A"
51
+ def self.format(*indices)
52
+ Coordinate.new(*indices).to_s
86
53
  end
87
54
 
88
- # Convert an array of 0-indexed integers to a CELL coordinate
55
+ # Validates a CELL string.
89
56
  #
90
- # @param indices [Array<Integer>] splat arguments of 0-indexed positions
91
- # @return [String] the CELL coordinate
57
+ # @param string [String] CELL coordinate string
58
+ # @return [nil]
59
+ # @raise [Sashite::Cell::Errors::Argument] if the string is not a valid CELL coordinate
92
60
  #
93
61
  # @example
94
- # Sashite::Cell.from_indices(0, 0) # => "a1"
95
- # Sashite::Cell.from_indices(4, 3) # => "e4"
96
- # Sashite::Cell.from_indices(0, 0, 0) # => "a1A"
97
- def self.from_indices(*indices)
98
- return "" if indices.empty?
99
-
100
- result = indices.map.with_index do |index, dimension|
101
- dimension_type = dimension_type(dimension + 1)
102
- index_to_component(index, dimension_type)
103
- end.join
104
-
105
- # Verify the result is valid according to CELL specification
106
- valid?(result) ? result : ""
107
- end
108
-
109
- # Get the validation regular expression
110
- #
111
- # Note: This regex alone does not guarantee full compliance. The valid?
112
- # method additionally rejects strings containing line breaks, as required
113
- # by the specification's anchoring requirements.
114
- #
115
- # @return [Regexp] the CELL validation regex from specification v1.0.0
116
- def self.regex
117
- REGEX
118
- end
119
-
120
- # Recursively parse a coordinate string into components
121
- # following the strict CELL specification cyclical pattern
122
- #
123
- # @param string [String] the remaining string to parse
124
- # @param dimension [Integer] the current dimension (1-indexed)
125
- # @return [Array<String>] array of dimensional components
126
- def self.parse_recursive(string, dimension)
127
- return [] if string.empty?
128
-
129
- expected_type = dimension_type(dimension)
130
- component = extract_component(string, expected_type)
131
-
132
- return [] if component.nil?
133
-
134
- # Extract component and recursively parse the rest
135
- remaining = string[component.length..]
136
- [component] + parse_recursive(remaining, dimension + 1)
62
+ # Sashite::Cell.validate("e4") # => nil
63
+ # Sashite::Cell.validate("a0") # => raises Sashite::Cell::Errors::Argument
64
+ def self.validate(string)
65
+ Parser.parse_to_indices(string)
66
+ nil
137
67
  end
138
68
 
139
- # Determine the character set type for a given dimension
140
- # Following CELL specification cyclical system: dimension n % 3 determines character set
69
+ # Reports whether string is a valid CELL coordinate.
141
70
  #
142
- # @param dimension [Integer] the dimension number (1-indexed)
143
- # @return [Symbol] :lowercase, :numeric, or :uppercase
144
- def self.dimension_type(dimension)
145
- case dimension % 3
146
- when 1 then :lowercase # n % 3 = 1: Latin lowercase letters
147
- when 2 then :numeric # n % 3 = 2: Arabic numerals
148
- when 0 then :uppercase # n % 3 = 0: Latin uppercase letters
149
- end
150
- end
151
-
152
- # Extract the next component from a string based on expected type
153
- # Strictly follows CELL specification patterns
154
- #
155
- # @param string [String] the string to extract from
156
- # @param type [Symbol] the expected component type
157
- # @return [String, nil] the extracted component or nil if invalid
158
- def self.extract_component(string, type)
159
- case type
160
- when :lowercase
161
- # Latin lowercase letters: [a-z]+
162
- match = string.match(/^([a-z]+)/)
163
- match ? match[1] : nil
164
- when :numeric
165
- # Arabic numerals: [1-9][0-9]* (CELL specification requires positive integers only)
166
- match = string.match(/^([1-9][0-9]*)/)
167
- match ? match[1] : nil
168
- when :uppercase
169
- # Latin uppercase letters: [A-Z]+
170
- match = string.match(/^([A-Z]+)/)
171
- match ? match[1] : nil
172
- end
173
- end
174
-
175
- # Convert a component to its 0-indexed position
176
- #
177
- # @param component [String] the component
178
- # @param type [Symbol] the component type
179
- # @return [Integer] the 0-indexed position
180
- def self.component_to_index(component, type)
181
- case type
182
- when :lowercase
183
- letters_to_index(component)
184
- when :numeric
185
- component.to_i - 1
186
- when :uppercase
187
- letters_to_index(component.downcase)
188
- end
189
- end
190
-
191
- # Convert a 0-indexed position to a component
192
- #
193
- # @param index [Integer] the 0-indexed position
194
- # @param type [Symbol] the component type
195
- # @return [String] the component
196
- def self.index_to_component(index, type)
197
- case type
198
- when :lowercase
199
- index_to_letters(index)
200
- when :numeric
201
- (index + 1).to_s
202
- when :uppercase
203
- index_to_letters(index).upcase
204
- end
205
- end
206
-
207
- # Convert letter sequence to 0-indexed position
208
- # Extended alphabet per CELL specification: a=0, b=1, ..., z=25, aa=26, ab=27, ..., zz=701, aaa=702, etc.
71
+ # @param string [String] CELL coordinate string
72
+ # @return [Boolean] true if valid, false otherwise
209
73
  #
210
- # @param letters [String] the letter sequence
211
- # @return [Integer] the 0-indexed position
212
- def self.letters_to_index(letters)
213
- length = letters.length
214
- index = 0
215
-
216
- # Add positions from shorter sequences
217
- (1...length).each do |len|
218
- index += 26**len
219
- end
220
-
221
- # Add position within current length
222
- letters.each_char.with_index do |char, pos|
223
- index += (char.ord - 97) * (26**(length - pos - 1))
224
- end
225
-
226
- index
227
- end
228
-
229
- # Convert 0-indexed position to letter sequence
230
- # Extended alphabet per CELL specification: 0=a, 1=b, ..., 25=z, 26=aa, 27=ab, ..., 701=zz, 702=aaa, etc.
231
- #
232
- # @param index [Integer] the 0-indexed position
233
- # @return [String] the letter sequence
234
- def self.index_to_letters(index)
235
- # Find the length of the result
236
- length = 1
237
- base = 0
238
-
239
- loop do
240
- range_size = 26**length
241
- break if index < base + range_size
242
-
243
- base += range_size
244
- length += 1
245
- end
246
-
247
- # Convert within the found length
248
- adjusted_index = index - base
249
- result = ""
250
-
251
- length.times do |pos|
252
- char_index = adjusted_index / (26**(length - pos - 1))
253
- result += (char_index + 97).chr
254
- adjusted_index %= (26**(length - pos - 1))
255
- end
256
-
257
- result
74
+ # @example
75
+ # Sashite::Cell.valid?("e4") # => true
76
+ # Sashite::Cell.valid?("a0") # => false
77
+ def self.valid?(string)
78
+ validate(string)
79
+ true
80
+ rescue Errors::Argument
81
+ false
258
82
  end
259
-
260
- private_class_method :parse_recursive, :dimension_type, :extract_component
261
- private_class_method :component_to_index, :index_to_component
262
- private_class_method :letters_to_index, :index_to_letters
263
83
  end
264
84
  end
data/lib/sashite-cell.rb CHANGED
@@ -1,14 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "sashite/cell"
4
-
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é
13
- module Sashite
14
- end