sashite-snn 3.0.0 → 3.1.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 +4 -4
- data/README.md +267 -73
- data/lib/sashite/snn/name.rb +97 -19
- data/lib/sashite/snn.rb +48 -14
- metadata +16 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1528b909fb1159a1b9d3fb06e4b62114efa110d991226945024f442fb08501e9
|
|
4
|
+
data.tar.gz: 3c6c44c3d69d1eacad6141d9309a27e5972dde45067bc8272e9584ed6cfc56ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fc20688ee514ec76125132aba5b121aa4b705230eb716f2abee6c5c1a88978aadfa52bf8cfa1c870315284050808f5fe5903a5e141c760af92f7aaca9bac82d5
|
|
7
|
+
data.tar.gz: b44e7af48ae53d52ca2b1961831b5eca677c2b8182589062139af966d3e960f0638720fba33c7e9099b6a8fe9424124f49386e3c9b025e71f93edfb797e9e558
|
data/README.md
CHANGED
|
@@ -9,16 +9,20 @@
|
|
|
9
9
|
|
|
10
10
|
## What is SNN?
|
|
11
11
|
|
|
12
|
-
SNN (Style Name Notation) is a
|
|
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.
|
|
13
13
|
|
|
14
|
-
|
|
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
|
|
17
|
+
|
|
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.
|
|
15
19
|
|
|
16
20
|
## Installation
|
|
17
21
|
|
|
18
22
|
```ruby
|
|
19
23
|
# In your Gemfile
|
|
20
24
|
gem "sashite-snn"
|
|
21
|
-
|
|
25
|
+
```
|
|
22
26
|
|
|
23
27
|
Or install manually:
|
|
24
28
|
|
|
@@ -26,130 +30,320 @@ Or install manually:
|
|
|
26
30
|
gem install sashite-snn
|
|
27
31
|
```
|
|
28
32
|
|
|
29
|
-
##
|
|
30
|
-
|
|
31
|
-
### Basic Operations
|
|
33
|
+
## Quick Start
|
|
32
34
|
|
|
33
35
|
```ruby
|
|
34
36
|
require "sashite/snn"
|
|
35
37
|
|
|
36
38
|
# Parse SNN strings into style name objects
|
|
37
|
-
name = Sashite::Snn.parse("
|
|
38
|
-
name.to_s # => "
|
|
39
|
-
name.value # => "
|
|
39
|
+
name = Sashite::Snn.parse("SHOGI") # => #<Snn::Name value="SHOGI">
|
|
40
|
+
name.to_s # => "SHOGI"
|
|
41
|
+
name.value # => "SHOGI"
|
|
40
42
|
|
|
41
43
|
# Create from string or symbol
|
|
42
|
-
name = Sashite::Snn.name("
|
|
43
|
-
name = Sashite::Snn::Name.new(:
|
|
44
|
+
name = Sashite::Snn.name("CHESS") # => #<Snn::Name value="CHESS">
|
|
45
|
+
name = Sashite::Snn::Name.new(:xiangqi) # => #<Snn::Name value="xiangqi">
|
|
44
46
|
|
|
45
47
|
# Validate SNN strings
|
|
46
|
-
Sashite::Snn.valid?("
|
|
47
|
-
Sashite::Snn.valid?("
|
|
48
|
-
Sashite::Snn.valid?("
|
|
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)
|
|
79
|
+
|
|
80
|
+
```bnf
|
|
81
|
+
<snn> ::= <uppercase-name> | <lowercase-name>
|
|
82
|
+
|
|
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"
|
|
49
88
|
```
|
|
50
89
|
|
|
51
|
-
###
|
|
90
|
+
### Regular Expression
|
|
52
91
|
|
|
53
92
|
```ruby
|
|
54
|
-
a
|
|
55
|
-
|
|
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
|
|
56
105
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
106
|
+
#### `Sashite::Snn.valid?(string)`
|
|
107
|
+
|
|
108
|
+
Returns `true` if the string is valid SNN notation.
|
|
109
|
+
|
|
110
|
+
- **Parameter**: `string` (String) - String to validate
|
|
111
|
+
- **Returns**: `Boolean` - `true` if valid, `false` otherwise
|
|
112
|
+
|
|
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)
|
|
60
119
|
```
|
|
61
120
|
|
|
62
|
-
|
|
121
|
+
#### `Sashite::Snn.parse(string)`
|
|
122
|
+
|
|
123
|
+
Parses an SNN string into a `Name` object.
|
|
124
|
+
|
|
125
|
+
- **Parameter**: `string` (String) - SNN notation string
|
|
126
|
+
- **Returns**: `Name` - Immutable name object
|
|
127
|
+
- **Raises**: `ArgumentError` if the string is invalid
|
|
63
128
|
|
|
64
129
|
```ruby
|
|
65
|
-
|
|
66
|
-
name = Sashite::Snn.parse("
|
|
67
|
-
name.value # => "Minishogi"
|
|
68
|
-
name.to_s # => "Minishogi"
|
|
130
|
+
name = Sashite::Snn.parse("SHOGI") # => #<Snn::Name value="SHOGI">
|
|
131
|
+
name = Sashite::Snn.parse("chess") # => #<Snn::Name value="chess">
|
|
69
132
|
```
|
|
70
133
|
|
|
71
|
-
|
|
134
|
+
#### `Sashite::Snn.name(value)`
|
|
135
|
+
|
|
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
|
|
72
141
|
|
|
73
142
|
```ruby
|
|
74
|
-
|
|
143
|
+
Sashite::Snn.name("XIANGQI") # => #<Snn::Name value="XIANGQI">
|
|
144
|
+
Sashite::Snn.name(:makruk) # => #<Snn::Name value="makruk">
|
|
145
|
+
```
|
|
75
146
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
147
|
+
### Name Object
|
|
148
|
+
|
|
149
|
+
The `Name` object is immutable and provides read-only access to the style name:
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
name = Sashite::Snn.parse("SHOGI")
|
|
153
|
+
|
|
154
|
+
name.value # => "SHOGI"
|
|
155
|
+
name.to_s # => "SHOGI"
|
|
156
|
+
name.frozen? # => true
|
|
79
157
|
```
|
|
80
158
|
|
|
81
|
-
|
|
159
|
+
**Equality and hashing:**
|
|
160
|
+
```ruby
|
|
161
|
+
name1 = Sashite::Snn.parse("CHESS")
|
|
162
|
+
name2 = Sashite::Snn.parse("CHESS")
|
|
163
|
+
name3 = Sashite::Snn.parse("chess")
|
|
82
164
|
|
|
83
|
-
|
|
165
|
+
name1 == name2 # => true
|
|
166
|
+
name1.hash == name2.hash # => true
|
|
167
|
+
name1 == name3 # => false (different case = different player)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Examples
|
|
84
171
|
|
|
172
|
+
### Traditional Chess Family
|
|
173
|
+
|
|
174
|
+
```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")
|
|
85
184
|
```
|
|
86
|
-
|
|
185
|
+
|
|
186
|
+
### Historical Games
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
chaturanga = Sashite::Snn.parse("CHATURANGA") # Ancient Indian Chess
|
|
190
|
+
shatranj = Sashite::Snn.parse("SHATRANJ") # Medieval Islamic Chess
|
|
87
191
|
```
|
|
88
192
|
|
|
89
|
-
###
|
|
193
|
+
### Modern Variants
|
|
90
194
|
|
|
91
|
-
```
|
|
92
|
-
|
|
195
|
+
```ruby
|
|
196
|
+
raumschach = Sashite::Snn.parse("RAUMSCHACH") # 3D Chess
|
|
197
|
+
omega = Sashite::Snn.parse("OMEGA") # Omega Chess
|
|
198
|
+
```
|
|
93
199
|
|
|
94
|
-
|
|
95
|
-
| <alphanumeric-char> <tail> ; Extended name
|
|
200
|
+
### Case Consistency
|
|
96
201
|
|
|
97
|
-
|
|
202
|
+
```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
|
+
```
|
|
98
221
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
222
|
+
### Working with Names
|
|
223
|
+
|
|
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
|
|
102
239
|
```
|
|
103
240
|
|
|
104
|
-
###
|
|
241
|
+
### Collections
|
|
105
242
|
|
|
106
243
|
```ruby
|
|
107
|
-
|
|
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
|
+
}
|
|
108
256
|
```
|
|
109
257
|
|
|
110
|
-
##
|
|
258
|
+
## Relationship with SIN
|
|
111
259
|
|
|
112
|
-
|
|
113
|
-
* **Canonical**: One valid name per game style within a given context.
|
|
114
|
-
* **ASCII-only**: Compatible with all systems.
|
|
115
|
-
* **Scalable**: Supports unlimited distinct names for current and future game variants.
|
|
260
|
+
**SNN and SIN are independent primitives** that serve complementary roles:
|
|
116
261
|
|
|
117
|
-
|
|
262
|
+
- **SNN**: Human-readable, descriptive names (`CHESS`, `SHOGI`)
|
|
263
|
+
- **SIN**: Compact, single-character identification (`C`, `S`)
|
|
118
264
|
|
|
119
|
-
|
|
265
|
+
### Optional Correspondence
|
|
120
266
|
|
|
121
|
-
|
|
122
|
-
| --------- | ------- |
|
|
123
|
-
| `Chess` | `C`/`c` |
|
|
124
|
-
| `Shogi` | `S`/`s` |
|
|
125
|
-
| `Xiangqi` | `X`/`x` |
|
|
126
|
-
| `Makruk` | `M`/`m` |
|
|
267
|
+
While both specifications can be used independently, they may be related through:
|
|
127
268
|
|
|
128
|
-
|
|
269
|
+
- **Mapping tables**: External context defining SNN ↔ SIN relationships
|
|
270
|
+
- **Case consistency**: When mapped, case must be preserved (`CHESS` ↔ `C`, `chess` ↔ `c`)
|
|
129
271
|
|
|
130
|
-
|
|
272
|
+
```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"
|
|
284
|
+
```
|
|
285
|
+
|
|
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
|
|
131
294
|
|
|
132
295
|
```ruby
|
|
133
|
-
|
|
134
|
-
Sashite::Snn.parse("Chess960")
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
137
302
|
```
|
|
138
303
|
|
|
139
|
-
|
|
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"
|
|
314
|
+
|
|
315
|
+
# Contains special characters
|
|
316
|
+
Sashite::Snn.parse("MINI_SHOGI")
|
|
317
|
+
# => ArgumentError: Invalid SNN string: "MINI_SHOGI"
|
|
318
|
+
|
|
319
|
+
# Empty string
|
|
320
|
+
Sashite::Snn.parse("")
|
|
321
|
+
# => ArgumentError: Invalid SNN string: ""
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Design Principles
|
|
325
|
+
|
|
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
|
|
140
333
|
|
|
141
|
-
|
|
334
|
+
## Properties
|
|
142
335
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
146
341
|
|
|
147
|
-
|
|
342
|
+
## Documentation
|
|
148
343
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
* `#same_base_name?(other)` – Optional helper for SIN mapping equivalence.
|
|
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
|
|
153
347
|
|
|
154
348
|
## Development
|
|
155
349
|
|
data/lib/sashite/snn/name.rb
CHANGED
|
@@ -4,16 +4,36 @@ module Sashite
|
|
|
4
4
|
module Snn
|
|
5
5
|
# Represents a style name in SNN (Style Name Notation) format.
|
|
6
6
|
#
|
|
7
|
-
# SNN provides a
|
|
8
|
-
# Each name must
|
|
9
|
-
#
|
|
7
|
+
# SNN provides a foundational naming system for abstract strategy game styles.
|
|
8
|
+
# Each name must consist of alphabetic characters only, all in the same case
|
|
9
|
+
# (either all uppercase or all lowercase).
|
|
10
|
+
#
|
|
11
|
+
# Case encoding:
|
|
12
|
+
# - UPPERCASE names represent the first player's style
|
|
13
|
+
# - lowercase names represent the second player's style
|
|
14
|
+
#
|
|
15
|
+
# Constraints:
|
|
16
|
+
# - Alphabetic characters only (A-Z or a-z)
|
|
17
|
+
# - Case consistency required (all uppercase OR all lowercase)
|
|
18
|
+
# - No digits, no special characters, no mixed case
|
|
10
19
|
#
|
|
11
20
|
# All instances are immutable.
|
|
21
|
+
#
|
|
22
|
+
# @example Valid names
|
|
23
|
+
# Sashite::Snn::Name.new("CHESS") # => #<Snn::Name value="CHESS">
|
|
24
|
+
# Sashite::Snn::Name.new("shogi") # => #<Snn::Name value="shogi">
|
|
25
|
+
# Sashite::Snn::Name.new("XIANGQI") # => #<Snn::Name value="XIANGQI">
|
|
26
|
+
#
|
|
27
|
+
# @example Invalid names
|
|
28
|
+
# Sashite::Snn::Name.new("Chess") # => ArgumentError (mixed case)
|
|
29
|
+
# Sashite::Snn::Name.new("CHESS960") # => ArgumentError (contains digits)
|
|
30
|
+
# Sashite::Snn::Name.new("GO9X9") # => ArgumentError (contains digits)
|
|
12
31
|
class Name
|
|
13
32
|
# SNN validation pattern matching the specification
|
|
14
|
-
|
|
33
|
+
# Format: All uppercase OR all lowercase alphabetic characters
|
|
34
|
+
SNN_PATTERN = /\A([A-Z]+|[a-z]+)\z/
|
|
15
35
|
|
|
16
|
-
# Error
|
|
36
|
+
# Error message for invalid SNN strings
|
|
17
37
|
ERROR_INVALID_NAME = "Invalid SNN string: %s"
|
|
18
38
|
|
|
19
39
|
# @return [String] the canonical style name
|
|
@@ -21,8 +41,20 @@ module Sashite
|
|
|
21
41
|
|
|
22
42
|
# Create a new style name instance
|
|
23
43
|
#
|
|
24
|
-
#
|
|
44
|
+
# The name must follow SNN format rules: all uppercase or all lowercase
|
|
45
|
+
# alphabetic characters only. No digits, special characters, or mixed case.
|
|
46
|
+
#
|
|
47
|
+
# @param name [String, Symbol] the style name (e.g., "SHOGI", :chess)
|
|
25
48
|
# @raise [ArgumentError] if the name does not match SNN pattern
|
|
49
|
+
#
|
|
50
|
+
# @example Create valid names
|
|
51
|
+
# Sashite::Snn::Name.new("CHESS") # First player Chess
|
|
52
|
+
# Sashite::Snn::Name.new("shogi") # Second player Shōgi
|
|
53
|
+
# Sashite::Snn::Name.new(:XIANGQI) # First player Xiangqi
|
|
54
|
+
#
|
|
55
|
+
# @example Invalid names raise errors
|
|
56
|
+
# Sashite::Snn::Name.new("Chess") # Mixed case
|
|
57
|
+
# Sashite::Snn::Name.new("CHESS960") # Contains digits
|
|
26
58
|
def initialize(name)
|
|
27
59
|
string_value = name.to_s
|
|
28
60
|
self.class.validate_format(string_value)
|
|
@@ -33,57 +65,103 @@ module Sashite
|
|
|
33
65
|
|
|
34
66
|
# Parse an SNN string into a Name object
|
|
35
67
|
#
|
|
68
|
+
# This is an alias for the constructor, provided for consistency
|
|
69
|
+
# with other Sashité specifications.
|
|
70
|
+
#
|
|
36
71
|
# @param string [String] the SNN-formatted style name
|
|
37
72
|
# @return [Name] a new Name instance
|
|
38
73
|
# @raise [ArgumentError] if the string is invalid
|
|
39
74
|
#
|
|
40
|
-
# @example
|
|
41
|
-
# Sashite::Snn::Name.parse("
|
|
75
|
+
# @example Parse valid names
|
|
76
|
+
# Sashite::Snn::Name.parse("SHOGI") # => #<Snn::Name value="SHOGI">
|
|
77
|
+
# Sashite::Snn::Name.parse("chess") # => #<Snn::Name value="chess">
|
|
42
78
|
def self.parse(string)
|
|
43
79
|
new(string)
|
|
44
80
|
end
|
|
45
81
|
|
|
46
82
|
# Check whether the given string is a valid SNN name
|
|
47
83
|
#
|
|
84
|
+
# Valid SNN strings must:
|
|
85
|
+
# - Contain only alphabetic characters (A-Z or a-z)
|
|
86
|
+
# - Have consistent case (all uppercase OR all lowercase)
|
|
87
|
+
# - Contain at least one character
|
|
88
|
+
#
|
|
48
89
|
# @param string [String] input string to validate
|
|
49
90
|
# @return [Boolean] true if valid, false otherwise
|
|
50
91
|
#
|
|
51
|
-
# @example
|
|
52
|
-
# Sashite::Snn::Name.valid?("
|
|
53
|
-
# Sashite::Snn::Name.valid?("
|
|
92
|
+
# @example Valid names
|
|
93
|
+
# Sashite::Snn::Name.valid?("CHESS") # => true
|
|
94
|
+
# Sashite::Snn::Name.valid?("shogi") # => true
|
|
95
|
+
# Sashite::Snn::Name.valid?("XIANGQI") # => true
|
|
96
|
+
#
|
|
97
|
+
# @example Invalid names
|
|
98
|
+
# Sashite::Snn::Name.valid?("Chess") # => false (mixed case)
|
|
99
|
+
# Sashite::Snn::Name.valid?("CHESS960") # => false (contains digits)
|
|
100
|
+
# Sashite::Snn::Name.valid?("GO9X9") # => false (contains digits)
|
|
101
|
+
# Sashite::Snn::Name.valid?("") # => false (empty)
|
|
54
102
|
def self.valid?(string)
|
|
55
103
|
string.is_a?(::String) && string.match?(SNN_PATTERN)
|
|
56
104
|
end
|
|
57
105
|
|
|
58
106
|
# Returns the string representation of the name
|
|
59
107
|
#
|
|
60
|
-
# @return [String]
|
|
108
|
+
# @return [String] the canonical style name
|
|
109
|
+
#
|
|
110
|
+
# @example
|
|
111
|
+
# name = Sashite::Snn::Name.new("SHOGI")
|
|
112
|
+
# name.to_s # => "SHOGI"
|
|
61
113
|
def to_s
|
|
62
114
|
value
|
|
63
115
|
end
|
|
64
116
|
|
|
65
|
-
# Equality based on
|
|
117
|
+
# Equality based on string value
|
|
66
118
|
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
119
|
+
# Two names are equal if they have the same string value. Case matters:
|
|
120
|
+
# "CHESS" (first player) is not equal to "chess" (second player).
|
|
121
|
+
#
|
|
122
|
+
# @param other [Object] object to compare with
|
|
123
|
+
# @return [Boolean] true if equal, false otherwise
|
|
124
|
+
#
|
|
125
|
+
# @example
|
|
126
|
+
# name1 = Sashite::Snn::Name.new("CHESS")
|
|
127
|
+
# name2 = Sashite::Snn::Name.new("CHESS")
|
|
128
|
+
# name3 = Sashite::Snn::Name.new("chess")
|
|
129
|
+
#
|
|
130
|
+
# name1 == name2 # => true
|
|
131
|
+
# name1 == name3 # => false (different case = different player)
|
|
69
132
|
def ==(other)
|
|
70
133
|
other.is_a?(self.class) && value == other.value
|
|
71
134
|
end
|
|
72
135
|
|
|
73
|
-
# Required for correct Set/
|
|
136
|
+
# Required for correct Set/Hash behavior
|
|
74
137
|
alias eql? ==
|
|
75
138
|
|
|
76
139
|
# Hash based on class and value
|
|
77
140
|
#
|
|
78
|
-
#
|
|
141
|
+
# Enables Name objects to be used as hash keys and in sets.
|
|
142
|
+
#
|
|
143
|
+
# @return [Integer] hash code
|
|
144
|
+
#
|
|
145
|
+
# @example Use as hash key
|
|
146
|
+
# styles = {
|
|
147
|
+
# Sashite::Snn::Name.new("CHESS") => "Western Chess",
|
|
148
|
+
# Sashite::Snn::Name.new("SHOGI") => "Japanese Chess"
|
|
149
|
+
# }
|
|
79
150
|
def hash
|
|
80
151
|
[self.class, value].hash
|
|
81
152
|
end
|
|
82
153
|
|
|
83
154
|
# Validate that the string is in proper SNN format
|
|
84
155
|
#
|
|
85
|
-
# @param str [String]
|
|
86
|
-
# @raise [ArgumentError] if
|
|
156
|
+
# @param str [String] string to validate
|
|
157
|
+
# @raise [ArgumentError] if the string does not match SNN pattern
|
|
158
|
+
#
|
|
159
|
+
# @example Valid format
|
|
160
|
+
# Sashite::Snn::Name.validate_format("CHESS") # No error
|
|
161
|
+
# Sashite::Snn::Name.validate_format("shogi") # No error
|
|
162
|
+
#
|
|
163
|
+
# @example Invalid format
|
|
164
|
+
# Sashite::Snn::Name.validate_format("Chess") # Raises ArgumentError
|
|
87
165
|
def self.validate_format(str)
|
|
88
166
|
raise ::ArgumentError, format(ERROR_INVALID_NAME, str.inspect) unless str.match?(SNN_PATTERN)
|
|
89
167
|
end
|
data/lib/sashite/snn.rb
CHANGED
|
@@ -5,53 +5,87 @@ require_relative "snn/name"
|
|
|
5
5
|
module Sashite
|
|
6
6
|
# SNN (Style Name Notation) implementation for Ruby
|
|
7
7
|
#
|
|
8
|
-
# Provides a
|
|
9
|
-
# SNN uses canonical, human-readable
|
|
10
|
-
#
|
|
8
|
+
# Provides a foundational naming system for identifying styles in abstract strategy board games.
|
|
9
|
+
# SNN uses canonical, human-readable alphabetic names with case encoding to represent both
|
|
10
|
+
# style identity and player assignment.
|
|
11
11
|
#
|
|
12
|
-
# Format:
|
|
12
|
+
# Format: All uppercase OR all lowercase alphabetic characters
|
|
13
13
|
#
|
|
14
14
|
# Examples:
|
|
15
|
-
# "
|
|
16
|
-
# "
|
|
17
|
-
# "
|
|
18
|
-
# "
|
|
15
|
+
# "CHESS" - Chess style for first player
|
|
16
|
+
# "chess" - Chess style for second player
|
|
17
|
+
# "SHOGI" - Shōgi style for first player
|
|
18
|
+
# "shogi" - Shōgi style for second player
|
|
19
|
+
# "XIANGQI" - Xiangqi style for first player
|
|
20
|
+
# "xiangqi" - Xiangqi style for second player
|
|
21
|
+
#
|
|
22
|
+
# Case Encoding:
|
|
23
|
+
# - UPPERCASE names represent the first player's style
|
|
24
|
+
# - lowercase names represent the second player's style
|
|
25
|
+
#
|
|
26
|
+
# Constraints:
|
|
27
|
+
# - Alphabetic characters only (A-Z, a-z)
|
|
28
|
+
# - Case consistency required (all uppercase OR all lowercase)
|
|
29
|
+
# - No digits, no special characters, no mixed case
|
|
30
|
+
#
|
|
31
|
+
# As a foundational primitive, SNN has no dependencies and serves as a building block
|
|
32
|
+
# for formal style identification in the Sashité ecosystem.
|
|
19
33
|
#
|
|
20
34
|
# See: https://sashite.dev/specs/snn/1.0.0/
|
|
21
35
|
module Snn
|
|
22
36
|
# Check if a string is valid SNN notation
|
|
23
37
|
#
|
|
38
|
+
# Valid SNN strings must contain only alphabetic characters in consistent case
|
|
39
|
+
# (either all uppercase or all lowercase).
|
|
40
|
+
#
|
|
24
41
|
# @param snn_string [String] the string to validate
|
|
25
42
|
# @return [Boolean] true if valid SNN, false otherwise
|
|
26
43
|
#
|
|
27
44
|
# @example Validate SNN strings
|
|
28
|
-
# Sashite::Snn.valid?("
|
|
29
|
-
# Sashite::Snn.valid?("
|
|
30
|
-
# Sashite::Snn.valid?("
|
|
45
|
+
# Sashite::Snn.valid?("CHESS") # => true
|
|
46
|
+
# Sashite::Snn.valid?("shogi") # => true
|
|
47
|
+
# Sashite::Snn.valid?("Chess") # => false (mixed case)
|
|
48
|
+
# Sashite::Snn.valid?("CHESS960") # => false (contains digits)
|
|
49
|
+
# Sashite::Snn.valid?("GO9X9") # => false (contains digits)
|
|
31
50
|
def self.valid?(snn_string)
|
|
32
51
|
Name.valid?(snn_string)
|
|
33
52
|
end
|
|
34
53
|
|
|
35
54
|
# Parse an SNN string into a Name object
|
|
36
55
|
#
|
|
56
|
+
# Converts a valid SNN string into an immutable Name object. The name must follow
|
|
57
|
+
# SNN format rules: all uppercase or all lowercase alphabetic characters only.
|
|
58
|
+
#
|
|
37
59
|
# @param snn_string [String] the name string
|
|
38
60
|
# @return [Snn::Name] a parsed name object
|
|
39
61
|
# @raise [ArgumentError] if the name is invalid
|
|
40
62
|
#
|
|
41
63
|
# @example Parse valid SNN names
|
|
42
|
-
# Sashite::Snn.parse("
|
|
64
|
+
# Sashite::Snn.parse("SHOGI") # => #<Snn::Name value="SHOGI">
|
|
65
|
+
# Sashite::Snn.parse("chess") # => #<Snn::Name value="chess">
|
|
66
|
+
#
|
|
67
|
+
# @example Invalid names raise errors
|
|
68
|
+
# Sashite::Snn.parse("Chess") # => ArgumentError (mixed case)
|
|
69
|
+
# Sashite::Snn.parse("CHESS960") # => ArgumentError (contains digits)
|
|
43
70
|
def self.parse(snn_string)
|
|
44
71
|
Name.parse(snn_string)
|
|
45
72
|
end
|
|
46
73
|
|
|
47
74
|
# Create a new Name instance directly
|
|
48
75
|
#
|
|
76
|
+
# Constructs a Name object from a string or symbol. The value must follow
|
|
77
|
+
# SNN format rules: all uppercase or all lowercase alphabetic characters only.
|
|
78
|
+
#
|
|
49
79
|
# @param value [String, Symbol] style name to construct
|
|
50
80
|
# @return [Snn::Name] new name instance
|
|
51
81
|
# @raise [ArgumentError] if name format is invalid
|
|
52
82
|
#
|
|
53
|
-
# @example
|
|
54
|
-
# Sashite::Snn.name("
|
|
83
|
+
# @example Create names
|
|
84
|
+
# Sashite::Snn.name("XIANGQI") # => #<Snn::Name value="XIANGQI">
|
|
85
|
+
# Sashite::Snn.name(:makruk) # => #<Snn::Name value="makruk">
|
|
86
|
+
#
|
|
87
|
+
# @example Invalid formats raise errors
|
|
88
|
+
# Sashite::Snn.name("Chess960") # => ArgumentError
|
|
55
89
|
def self.name(value)
|
|
56
90
|
Name.new(value)
|
|
57
91
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sashite-snn
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cyril Kato
|
|
@@ -10,12 +10,18 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
12
|
description: |
|
|
13
|
-
SNN (Style Name Notation) provides a rule-agnostic
|
|
14
|
-
abstract strategy board
|
|
15
|
-
a modern Ruby interface featuring immutable style name objects and functional programming
|
|
16
|
-
principles.
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
SNN (Style Name Notation) provides a foundational, rule-agnostic naming system for identifying
|
|
14
|
+
game styles in abstract strategy board games. This gem implements the SNN Specification v1.0.0
|
|
15
|
+
with a modern Ruby interface featuring immutable style name objects and functional programming
|
|
16
|
+
principles.
|
|
17
|
+
|
|
18
|
+
SNN uses case-consistent alphabetic names (e.g., "CHESS", "SHOGI", "XIANGQI" for first player;
|
|
19
|
+
"chess", "shogi", "xiangqi" for second player) to unambiguously represent both style identity
|
|
20
|
+
and player assignment. As a foundational primitive with no dependencies, SNN serves as a building
|
|
21
|
+
block for formal style identification across the Sashité ecosystem.
|
|
22
|
+
|
|
23
|
+
Format: All uppercase OR all lowercase alphabetic characters only (no digits, no special characters).
|
|
24
|
+
Ideal for game engines, protocols, and tools requiring clear and extensible style identifiers.
|
|
19
25
|
email: contact@cyril.email
|
|
20
26
|
executables: []
|
|
21
27
|
extensions: []
|
|
@@ -50,8 +56,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
50
56
|
- !ruby/object:Gem::Version
|
|
51
57
|
version: '0'
|
|
52
58
|
requirements: []
|
|
53
|
-
rubygems_version: 3.
|
|
59
|
+
rubygems_version: 3.7.1
|
|
54
60
|
specification_version: 4
|
|
55
|
-
summary: SNN (Style Name Notation)
|
|
56
|
-
|
|
61
|
+
summary: SNN (Style Name Notation) - foundational naming system for abstract strategy
|
|
62
|
+
game styles
|
|
57
63
|
test_files: []
|