sashite-snn 3.1.0 → 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.
data/README.md CHANGED
@@ -1,21 +1,23 @@
1
- # Snn.rb
1
+ # snn.rb
2
2
 
3
3
  [![Version](https://img.shields.io/github/v/tag/sashite/snn.rb?label=Version&logo=github)](https://github.com/sashite/snn.rb/tags)
4
4
  [![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/sashite/snn.rb/main)
5
- ![Ruby](https://github.com/sashite/snn.rb/actions/workflows/main.yml/badge.svg?branch=main)
6
- [![License](https://img.shields.io/github/license/sashite/snn.rb?label=License&logo=github)](https://github.com/sashite/snn.rb/raw/main/LICENSE.md)
5
+ [![CI](https://github.com/sashite/snn.rb/actions/workflows/ruby.yml/badge.svg?branch=main)](https://github.com/sashite/snn.rb/actions)
6
+ [![License](https://img.shields.io/github/license/sashite/snn.rb)](https://github.com/sashite/snn.rb/blob/main/LICENSE)
7
7
 
8
- > **SNN** (Style Name Notation) implementation for the Ruby language.
8
+ > **SNN** (Style Name Notation) implementation for Ruby.
9
9
 
10
- ## What is SNN?
10
+ ## Overview
11
11
 
12
- SNN (Style Name Notation) is a **foundational**, human-readable naming system for identifying game styles in abstract strategy board games. SNN serves as a primitive building block using descriptive alphabetic names with case encoding to represent style identity and player assignment.
12
+ This library implements the [SNN Specification v1.0.0](https://sashite.dev/specs/snn/1.0.0/).
13
13
 
14
- Each SNN name is a case-consistent alphabetic identifier where:
15
- - **Uppercase names** (e.g., `CHESS`, `SHOGI`) represent the **first player's** style
16
- - **Lowercase names** (e.g., `chess`, `shogi`) represent the **second player's** style
14
+ ### Implementation Constraints
17
15
 
18
- This gem implements the [SNN Specification v1.0.0](https://sashite.dev/specs/snn/1.0.0/) as a foundational primitive with no dependencies.
16
+ | Constraint | Value | Rationale |
17
+ |------------|-------|-----------|
18
+ | Max string length | 32 | Sufficient for realistic style names |
19
+
20
+ These constraints enable bounded memory usage and safe parsing.
19
21
 
20
22
  ## Installation
21
23
 
@@ -30,352 +32,138 @@ Or install manually:
30
32
  gem install sashite-snn
31
33
  ```
32
34
 
33
- ## Quick Start
34
-
35
- ```ruby
36
- require "sashite/snn"
37
-
38
- # Parse SNN strings into style name objects
39
- name = Sashite::Snn.parse("SHOGI") # => #<Snn::Name value="SHOGI">
40
- name.to_s # => "SHOGI"
41
- name.value # => "SHOGI"
42
-
43
- # Create from string or symbol
44
- name = Sashite::Snn.name("CHESS") # => #<Snn::Name value="CHESS">
45
- name = Sashite::Snn::Name.new(:xiangqi) # => #<Snn::Name value="xiangqi">
46
-
47
- # Validate SNN strings
48
- Sashite::Snn.valid?("MAKRUK") # => true
49
- Sashite::Snn.valid?("shogi") # => true
50
- Sashite::Snn.valid?("Chess") # => false (mixed case)
51
- Sashite::Snn.valid?("Chess960") # => false (contains digits)
52
- ```
53
-
54
- ## SNN Format
55
-
56
- An SNN string consists of alphabetic characters only, all in the same case:
57
-
58
- ```
59
- <alphabetic-name>
60
- ```
61
-
62
- **Examples:**
63
- - `CHESS` — Chess style for first player
64
- - `chess` — Chess style for second player
65
- - `SHOGI` — Shōgi style for first player
66
- - `shogi` — Shōgi style for second player
67
-
68
- ## Format Specification
69
-
70
- ### Structure
71
-
72
- ```
73
- <alphabetic-name>
74
- ```
75
-
76
- Where the name directly represents the style identity and player assignment through case.
77
-
78
- ### Grammar (BNF)
35
+ ## Usage
79
36
 
80
- ```bnf
81
- <snn> ::= <uppercase-name> | <lowercase-name>
37
+ ### Parsing (String → StyleName)
82
38
 
83
- <uppercase-name> ::= <uppercase-letter>+
84
- <lowercase-name> ::= <lowercase-letter>+
85
-
86
- <uppercase-letter> ::= "A" | "B" | "C" | ... | "Z"
87
- <lowercase-letter> ::= "a" | "b" | "c" | ... | "z"
88
- ```
89
-
90
- ### Regular Expression
39
+ Convert an SNN string into a `StyleName` object.
91
40
 
92
41
  ```ruby
93
- /\A([A-Z]+|[a-z]+)\z/
94
- ```
95
-
96
- ### Constraints
97
-
98
- 1. **Case consistency**: All letters must be either uppercase OR lowercase
99
- 2. **Alphabetic only**: Only ASCII letters allowed (no digits, no special characters)
100
- 3. **Direct assignment**: Names represent styles through explicit association
101
-
102
- ## API Reference
103
-
104
- ### Module Methods
105
-
106
- #### `Sashite::Snn.valid?(string)`
42
+ require "sashite/snn"
107
43
 
108
- Returns `true` if the string is valid SNN notation.
44
+ # Standard parsing (raises on error)
45
+ snn = Sashite::Snn.parse("Chess")
46
+ snn.name # => "Chess"
109
47
 
110
- - **Parameter**: `string` (String) - String to validate
111
- - **Returns**: `Boolean` - `true` if valid, `false` otherwise
48
+ # With numeric suffix
49
+ snn = Sashite::Snn.parse("Chess960")
50
+ snn.name # => "Chess960"
112
51
 
113
- ```ruby
114
- Sashite::Snn.valid?("CHESS") # => true
115
- Sashite::Snn.valid?("shogi") # => true
116
- Sashite::Snn.valid?("Chess") # => false (mixed case)
117
- Sashite::Snn.valid?("CHESS960") # => false (contains digits)
118
- Sashite::Snn.valid?("3DChess") # => false (starts with digit)
52
+ # Invalid input raises ArgumentError
53
+ Sashite::Snn.parse("chess") # => raises ArgumentError, "invalid format"
54
+ Sashite::Snn.parse("") # => raises ArgumentError, "empty input"
119
55
  ```
120
56
 
121
- #### `Sashite::Snn.parse(string)`
122
-
123
- Parses an SNN string into a `Name` object.
57
+ ### Formatting (StyleName → String)
124
58
 
125
- - **Parameter**: `string` (String) - SNN notation string
126
- - **Returns**: `Name` - Immutable name object
127
- - **Raises**: `ArgumentError` if the string is invalid
59
+ Convert a `StyleName` back to an SNN string.
128
60
 
129
61
  ```ruby
130
- name = Sashite::Snn.parse("SHOGI") # => #<Snn::Name value="SHOGI">
131
- name = Sashite::Snn.parse("chess") # => #<Snn::Name value="chess">
132
- ```
133
-
134
- #### `Sashite::Snn.name(value)`
62
+ # From StyleName object
63
+ snn = Sashite::Snn::StyleName.new("Chess")
64
+ snn.to_s # => "Chess"
135
65
 
136
- Creates a new `Name` instance directly.
137
-
138
- - **Parameter**: `value` (String, Symbol) - Style name to construct
139
- - **Returns**: `Name` - New name instance
140
- - **Raises**: `ArgumentError` if name format is invalid
141
-
142
- ```ruby
143
- Sashite::Snn.name("XIANGQI") # => #<Snn::Name value="XIANGQI">
144
- Sashite::Snn.name(:makruk) # => #<Snn::Name value="makruk">
66
+ # String interpolation
67
+ "Playing #{snn}" # => "Playing Chess"
145
68
  ```
146
69
 
147
- ### Name Object
148
-
149
- The `Name` object is immutable and provides read-only access to the style name:
70
+ ### Validation
150
71
 
151
72
  ```ruby
152
- name = Sashite::Snn.parse("SHOGI")
153
-
154
- name.value # => "SHOGI"
155
- name.to_s # => "SHOGI"
156
- name.frozen? # => true
73
+ # Boolean check
74
+ Sashite::Snn.valid?("Chess") # => true
75
+ Sashite::Snn.valid?("Chess960") # => true
76
+ Sashite::Snn.valid?("chess") # => false (lowercase start)
77
+ Sashite::Snn.valid?("") # => false (empty)
157
78
  ```
158
79
 
159
- **Equality and hashing:**
160
- ```ruby
161
- name1 = Sashite::Snn.parse("CHESS")
162
- name2 = Sashite::Snn.parse("CHESS")
163
- name3 = Sashite::Snn.parse("chess")
164
-
165
- name1 == name2 # => true
166
- name1.hash == name2.hash # => true
167
- name1 == name3 # => false (different case = different player)
168
- ```
169
-
170
- ## Examples
171
-
172
- ### Traditional Chess Family
80
+ ### Accessing Data
173
81
 
174
82
  ```ruby
175
- # First player styles (uppercase)
176
- chess = Sashite::Snn.parse("CHESS") # Western Chess
177
- shogi = Sashite::Snn.parse("SHOGI") # Japanese Chess
178
- xiangqi = Sashite::Snn.parse("XIANGQI") # Chinese Chess
179
- makruk = Sashite::Snn.parse("MAKRUK") # Thai Chess
180
-
181
- # Second player styles (lowercase)
182
- chess_p2 = Sashite::Snn.parse("chess")
183
- shogi_p2 = Sashite::Snn.parse("shogi")
184
- ```
185
-
186
- ### Historical Games
83
+ snn = Sashite::Snn.parse("Chess960")
187
84
 
188
- ```ruby
189
- chaturanga = Sashite::Snn.parse("CHATURANGA") # Ancient Indian Chess
190
- shatranj = Sashite::Snn.parse("SHATRANJ") # Medieval Islamic Chess
85
+ # Get the name (attribute)
86
+ snn.name # => "Chess960"
191
87
  ```
192
88
 
193
- ### Modern Variants
194
-
195
- ```ruby
196
- raumschach = Sashite::Snn.parse("RAUMSCHACH") # 3D Chess
197
- omega = Sashite::Snn.parse("OMEGA") # Omega Chess
198
- ```
89
+ ## API Reference
199
90
 
200
- ### Case Consistency
91
+ ### Types
201
92
 
202
93
  ```ruby
203
- # Valid - all uppercase
204
- Sashite::Snn.valid?("CHESS") # => true
205
- Sashite::Snn.valid?("XIANGQI") # => true
206
-
207
- # Valid - all lowercase
208
- Sashite::Snn.valid?("shogi") # => true
209
- Sashite::Snn.valid?("makruk") # => true
210
-
211
- # Invalid - mixed case
212
- Sashite::Snn.valid?("Chess") # => false
213
- Sashite::Snn.valid?("Shogi") # => false
214
- Sashite::Snn.valid?("XiangQi") # => false
215
-
216
- # Invalid - contains non-alphabetic characters
217
- Sashite::Snn.valid?("CHESS960") # => false
218
- Sashite::Snn.valid?("GO9X9") # => false
219
- Sashite::Snn.valid?("MINI_SHOGI") # => false
220
- ```
94
+ # StyleName represents a validated SNN style name.
95
+ class Sashite::Snn::StyleName
96
+ # Creates a StyleName from a valid name string.
97
+ # Raises ArgumentError if the name is invalid.
98
+ #
99
+ # @param name [String] SNN style name
100
+ # @return [StyleName]
101
+ def initialize(name)
221
102
 
222
- ### Working with Names
103
+ # Returns the style name.
104
+ #
105
+ # @return [String]
106
+ def name
223
107
 
224
- ```ruby
225
- # Create and compare
226
- name1 = Sashite::Snn.parse("SHOGI")
227
- name2 = Sashite::Snn.parse("SHOGI")
228
- name1 == name2 # => true
229
-
230
- # String and symbol inputs
231
- name1 = Sashite::Snn.name("XIANGQI")
232
- name2 = Sashite::Snn.name(:XIANGQI)
233
- name1 == name2 # => true
234
-
235
- # Immutability
236
- name = Sashite::Snn.parse("CHESS")
237
- name.frozen? # => true
238
- name.value.frozen? # => true
108
+ # Returns the SNN string representation.
109
+ #
110
+ # @return [String]
111
+ def to_s
112
+ end
239
113
  ```
240
114
 
241
- ### Collections
115
+ ### Constants
242
116
 
243
117
  ```ruby
244
- # Create a set of styles
245
- styles = %w[CHESS SHOGI XIANGQI MAKRUK].map { |n| Sashite::Snn.parse(n) }
246
-
247
- # Filter by prefix
248
- styles.select { |s| s.value.start_with?("X") }.map(&:to_s)
249
- # => ["XIANGQI"]
250
-
251
- # Use in hash
252
- style_map = {
253
- Sashite::Snn.parse("CHESS") => "Western Chess",
254
- Sashite::Snn.parse("SHOGI") => "Japanese Chess"
255
- }
118
+ Sashite::Snn::StyleName::MAX_STRING_LENGTH # => 32
256
119
  ```
257
120
 
258
- ## Relationship with SIN
259
-
260
- **SNN and SIN are independent primitives** that serve complementary roles:
261
-
262
- - **SNN**: Human-readable, descriptive names (`CHESS`, `SHOGI`)
263
- - **SIN**: Compact, single-character identification (`C`, `S`)
264
-
265
- ### Optional Correspondence
266
-
267
- While both specifications can be used independently, they may be related through:
268
-
269
- - **Mapping tables**: External context defining SNN ↔ SIN relationships
270
- - **Case consistency**: When mapped, case must be preserved (`CHESS` ↔ `C`, `chess` ↔ `c`)
121
+ ### Parsing
271
122
 
272
123
  ```ruby
273
- # Example mapping (defined externally, not part of SNN)
274
- SNN_TO_SIN = {
275
- "CHESS" => "C",
276
- "chess" => "c",
277
- "SHOGI" => "S",
278
- "shogi" => "s"
279
- }
280
-
281
- # Multiple SNN names may map to the same SIN
282
- SNN_TO_SIN["CAPABLANCA"] = "C" # Also maps to "C"
283
- SNN_TO_SIN["COURIER"] = "C" # Also maps to "C"
124
+ # Parses an SNN string into a StyleName.
125
+ # Raises ArgumentError if the string is not valid.
126
+ #
127
+ # @param string [String] SNN style name string
128
+ # @return [StyleName]
129
+ # @raise [ArgumentError] if invalid
130
+ def Sashite::Snn.parse(string)
284
131
  ```
285
132
 
286
- ### Important Notes
287
-
288
- 1. **No dependency**: SNN does not depend on SIN, nor SIN on SNN
289
- 2. **Bidirectional mapping requires context**: Converting between SNN and SIN requires external mapping information
290
- 3. **Independent usage**: Systems may use SNN alone, SIN alone, or both with defined mappings
291
- 4. **Multiple mappings**: One SNN name may correspond to multiple SIN characters in different contexts, and vice versa
292
-
293
- ## Error Handling
133
+ ### Validation
294
134
 
295
135
  ```ruby
296
- begin
297
- name = Sashite::Snn.parse("Chess960")
298
- rescue ArgumentError => e
299
- warn "Invalid SNN: #{e.message}"
300
- # => "Invalid SNN string: \"Chess960\""
301
- end
136
+ # Reports whether string is a valid SNN style name.
137
+ #
138
+ # @param string [String] SNN style name string
139
+ # @return [Boolean]
140
+ def Sashite::Snn.valid?(string)
302
141
  ```
303
142
 
304
- ### Common Errors
305
-
306
- ```ruby
307
- # Mixed case
308
- Sashite::Snn.parse("Chess")
309
- # => ArgumentError: Invalid SNN string: "Chess"
310
-
311
- # Contains digits
312
- Sashite::Snn.parse("CHESS960")
313
- # => ArgumentError: Invalid SNN string: "CHESS960"
143
+ ### Errors
314
144
 
315
- # Contains special characters
316
- Sashite::Snn.parse("MINI_SHOGI")
317
- # => ArgumentError: Invalid SNN string: "MINI_SHOGI"
145
+ All parsing and validation errors raise `ArgumentError` with descriptive messages:
318
146
 
319
- # Empty string
320
- Sashite::Snn.parse("")
321
- # => ArgumentError: Invalid SNN string: ""
322
- ```
147
+ | Message | Cause |
148
+ |---------|-------|
149
+ | `"empty input"` | String length is 0 |
150
+ | `"input too long"` | String exceeds 32 characters |
151
+ | `"invalid format"` | Does not match SNN format |
323
152
 
324
153
  ## Design Principles
325
154
 
326
- - **Human-readable**: Descriptive names for better usability
327
- - **Case-consistent**: Visual distinction between players through case
328
- - **Foundational primitive**: Serves as building block for formal style identification
329
- - **Rule-agnostic**: Independent of specific game mechanics
330
- - **Self-contained**: No external dependencies
331
- - **Immutable**: All objects are frozen and thread-safe
332
- - **Canonical**: Each style has one valid representation per context
333
-
334
- ## Properties
335
-
336
- - **Purely functional**: Immutable data structures, no side effects
337
- - **Specification compliant**: Strict adherence to [SNN v1.0.0](https://sashite.dev/specs/snn/1.0.0/)
338
- - **Minimal API**: Simple validation, parsing, and comparison
339
- - **Universal**: Supports any abstract strategy board game style
340
- - **No dependencies**: Foundational primitive requiring no external gems
341
-
342
- ## Documentation
155
+ - **Bounded values**: Maximum string length prevents resource exhaustion
156
+ - **Object-oriented**: `StyleName` class enables methods and encapsulation
157
+ - **Ruby idioms**: `valid?` predicate, `to_s` conversion, `ArgumentError` for invalid input
158
+ - **Immutable style names**: `freeze` after construction
159
+ - **No dependencies**: Pure Ruby standard library only
343
160
 
344
- - [SNN Specification v1.0.0](https://sashite.dev/specs/snn/1.0.0/) — Complete technical specification
345
- - [SNN Examples](https://sashite.dev/specs/snn/1.0.0/examples/) — Comprehensive examples
346
- - [API Documentation](https://rubydoc.info/github/sashite/snn.rb/main) — Full API reference
161
+ ## Related Specifications
347
162
 
348
- ## Development
349
-
350
- ```sh
351
- # Clone the repository
352
- git clone https://github.com/sashite/snn.rb.git
353
- cd snn.rb
354
-
355
- # Install dependencies
356
- bundle install
357
-
358
- # Run tests
359
- ruby test.rb
360
-
361
- # Generate documentation
362
- yard doc
363
- ```
364
-
365
- ## Contributing
366
-
367
- 1. Fork the repository
368
- 2. Create a feature branch (`git checkout -b feature/new-feature`)
369
- 3. Add tests for your changes
370
- 4. Ensure all tests pass (`ruby test.rb`)
371
- 5. Commit your changes (`git commit -am 'Add new feature'`)
372
- 6. Push to the branch (`git push origin feature/new-feature`)
373
- 7. Create a Pull Request
163
+ - [Game Protocol](https://sashite.dev/game-protocol/) — Conceptual foundation
164
+ - [SNN Specification](https://sashite.dev/specs/snn/1.0.0/) — Official specification
165
+ - [SNN Examples](https://sashite.dev/specs/snn/1.0.0/examples/) — Usage examples
374
166
 
375
167
  ## License
376
168
 
377
- Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
378
-
379
- ## About
380
-
381
- Maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of board game cultures.
169
+ Available as open source under the [Apache License 2.0](https://opensource.org/licenses/Apache-2.0).
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sashite
4
+ module Snn
5
+ # Constants for the SNN (Style Name Notation) specification.
6
+ #
7
+ # Defines validation constraints for SNN tokens.
8
+ #
9
+ # @example
10
+ # Sashite::Snn::Constants::MAX_STRING_LENGTH # => 32
11
+ #
12
+ # @see https://sashite.dev/specs/snn/1.0.0/
13
+ module Constants
14
+ # Maximum length of a valid SNN string.
15
+ #
16
+ # @return [Integer] 32
17
+ MAX_STRING_LENGTH = 32
18
+
19
+ # Empty string constant for internal use.
20
+ #
21
+ # @return [String] ""
22
+ EMPTY_STRING = ""
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sashite
4
+ module Snn
5
+ module Errors
6
+ class Argument < ::ArgumentError
7
+ # Error messages for SNN parsing and validation.
8
+ #
9
+ # @example
10
+ # Messages::EMPTY_INPUT # => "empty input"
11
+ # Messages::INPUT_TOO_LONG # => "input too long"
12
+ # Messages::INVALID_FORMAT # => "invalid format"
13
+ module Messages
14
+ # Parsing error messages.
15
+
16
+ # Error message for empty input string.
17
+ EMPTY_INPUT = "empty input"
18
+
19
+ # Error message for input exceeding maximum length.
20
+ INPUT_TOO_LONG = "input too long"
21
+
22
+ # Error message for invalid SNN format.
23
+ INVALID_FORMAT = "invalid format"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "argument/messages"
4
+
5
+ module Sashite
6
+ module Sin
7
+ module Errors
8
+ # Namespace for ArgumentError-related constants and messages.
9
+ #
10
+ # Provides structured access to error messages used when raising
11
+ # ArgumentError exceptions throughout the library.
12
+ #
13
+ # @example Raising an error with a message
14
+ # raise ArgumentError, Argument::Messages::EMPTY_INPUT
15
+ #
16
+ # @see Argument::Messages
17
+ class Argument < ::ArgumentError
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "errors/argument"
4
+
5
+ module Sashite
6
+ module Snn
7
+ # Namespace for SNN error classes.
8
+ module Errors
9
+ end
10
+ end
11
+ end