sashite-snn 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +213 -237
- data/lib/sashite/snn/style.rb +97 -95
- data/lib/sashite/snn.rb +30 -36
- data/lib/sashite-snn.rb +2 -2
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6fb3b57b1f252e95f7e7d3e8484d6729b90ca6f13dfd19705c9ac61d6095cf25
|
4
|
+
data.tar.gz: 296ec73c256baf0ea7999b74b7933b0e95f92b2b7488e3d5efb69b43b7132951
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e7b8270ca459703a6bd41f47424af55452f8c287567abc8bdf0e85acc69914f20a1830361ce10e3f0e157c137e3f31f60149481274b24e6dedb597d109927d6
|
7
|
+
data.tar.gz: 9a684bb46685f7a77fbade6caaf2d642f31a6b31b142e1fb5a47512e8a4133fa50adf0f24c8b551d87c67232c23db6c5436e95d819a38ac72e069f37da63aff5
|
data/README.md
CHANGED
@@ -9,9 +9,9 @@
|
|
9
9
|
|
10
10
|
## What is SNN?
|
11
11
|
|
12
|
-
SNN (Style Name Notation) provides a
|
12
|
+
SNN (Style Name Notation) provides a compact, ASCII-based format for identifying **styles** in abstract strategy board games. SNN uses single-character identifiers with case encoding to represent both style identity and player assignment simultaneously.
|
13
13
|
|
14
|
-
This gem implements the [SNN Specification v1.0.0](https://sashite.dev/specs/snn/1.0.0/), providing a
|
14
|
+
This gem implements the [SNN Specification v1.0.0](https://sashite.dev/specs/snn/1.0.0/), providing a rule-agnostic notation system for style identification in board games.
|
15
15
|
|
16
16
|
## Installation
|
17
17
|
|
@@ -28,143 +28,166 @@ gem install sashite-snn
|
|
28
28
|
|
29
29
|
## Usage
|
30
30
|
|
31
|
+
### Basic Operations
|
32
|
+
|
31
33
|
```ruby
|
32
34
|
require "sashite/snn"
|
33
35
|
|
34
36
|
# Parse SNN strings into style objects
|
35
|
-
style = Sashite::Snn.parse("
|
36
|
-
style.to_s
|
37
|
-
style.
|
38
|
-
style.side
|
37
|
+
style = Sashite::Snn.parse("C") # => #<Snn::Style letter=:C side=:first>
|
38
|
+
style.to_s # => "C"
|
39
|
+
style.letter # => :C
|
40
|
+
style.side # => :first
|
39
41
|
|
40
42
|
# Create styles directly
|
41
|
-
style = Sashite::Snn.style(:
|
42
|
-
style = Sashite::Snn::Style.new(:
|
43
|
+
style = Sashite::Snn.style(:C, :first) # => #<Snn::Style letter=:C side=:first>
|
44
|
+
style = Sashite::Snn::Style.new(:c, :second) # => #<Snn::Style letter=:c side=:second>
|
43
45
|
|
44
46
|
# Validate SNN strings
|
45
|
-
Sashite::Snn.valid?("
|
46
|
-
Sashite::Snn.valid?("
|
47
|
-
Sashite::Snn.valid?("
|
48
|
-
Sashite::Snn.valid?("
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
#
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
#
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
#
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
#
|
72
|
-
|
73
|
-
|
47
|
+
Sashite::Snn.valid?("C") # => true
|
48
|
+
Sashite::Snn.valid?("c") # => true
|
49
|
+
Sashite::Snn.valid?("1") # => false (not a letter)
|
50
|
+
Sashite::Snn.valid?("CC") # => false (not single character)
|
51
|
+
```
|
52
|
+
|
53
|
+
### Style Transformations
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
# All transformations return new immutable instances
|
57
|
+
style = Sashite::Snn.parse("C")
|
58
|
+
|
59
|
+
# Flip player assignment
|
60
|
+
flipped = style.flip # => #<Snn::Style letter=:c side=:second>
|
61
|
+
flipped.to_s # => "c"
|
62
|
+
|
63
|
+
# Change letter
|
64
|
+
changed = style.with_letter(:S) # => #<Snn::Style letter=:S side=:first>
|
65
|
+
changed.to_s # => "S"
|
66
|
+
|
67
|
+
# Change side
|
68
|
+
other_side = style.with_side(:second) # => #<Snn::Style letter=:c side=:second>
|
69
|
+
other_side.to_s # => "c"
|
70
|
+
|
71
|
+
# Chain transformations
|
72
|
+
result = style.flip.with_letter(:M) # => #<Snn::Style letter=:m side=:second>
|
73
|
+
result.to_s # => "m"
|
74
|
+
```
|
75
|
+
|
76
|
+
### Player and Style Queries
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
style = Sashite::Snn.parse("C")
|
80
|
+
opposite = Sashite::Snn.parse("s")
|
81
|
+
|
82
|
+
# Player identification
|
83
|
+
style.first_player? # => true
|
84
|
+
style.second_player? # => false
|
85
|
+
opposite.first_player? # => false
|
86
|
+
opposite.second_player? # => true
|
87
|
+
|
88
|
+
# Letter comparison
|
89
|
+
chess1 = Sashite::Snn.parse("C")
|
90
|
+
chess2 = Sashite::Snn.parse("c")
|
91
|
+
shogi = Sashite::Snn.parse("S")
|
92
|
+
|
93
|
+
chess1.same_letter?(chess2) # => true (both use letter C)
|
94
|
+
chess1.same_side?(shogi) # => true (both first player)
|
95
|
+
chess1.same_letter?(shogi) # => false (different letters)
|
96
|
+
```
|
97
|
+
|
98
|
+
### Style Collections
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
# Working with multiple styles
|
102
|
+
styles = %w[C c S s M m].map { |snn| Sashite::Snn.parse(snn) }
|
103
|
+
|
104
|
+
# Filter by player
|
105
|
+
first_player_styles = styles.select(&:first_player?)
|
106
|
+
first_player_styles.map(&:to_s) # => ["C", "S", "M"]
|
107
|
+
|
108
|
+
# Group by letter family
|
109
|
+
by_letter = styles.group_by { |s| s.letter.to_s.upcase }
|
110
|
+
by_letter["C"].size # => 2 (both C and c)
|
111
|
+
|
112
|
+
# Find specific combinations
|
113
|
+
chess_styles = styles.select { |s| s.letter.to_s.upcase == "C" }
|
114
|
+
chess_styles.map(&:to_s) # => ["C", "c"]
|
74
115
|
```
|
75
116
|
|
76
117
|
## Format Specification
|
77
118
|
|
78
119
|
### Structure
|
79
120
|
```
|
80
|
-
<style-
|
121
|
+
<style-letter>
|
81
122
|
```
|
82
123
|
|
83
|
-
###
|
124
|
+
### Grammar (BNF)
|
125
|
+
```bnf
|
126
|
+
<snn> ::= <uppercase-letter> | <lowercase-letter>
|
84
127
|
|
85
|
-
-
|
86
|
-
|
87
|
-
|
88
|
-
- **Case Consistency**: Entire identifier must be uppercase or lowercase (no mixed case)
|
128
|
+
<uppercase-letter> ::= "A" | "B" | "C" | ... | "Z"
|
129
|
+
<lowercase-letter> ::= "a" | "b" | "c" | ... | "z"
|
130
|
+
```
|
89
131
|
|
90
132
|
### Regular Expression
|
91
133
|
```ruby
|
92
|
-
/\A
|
134
|
+
/\A[A-Za-z]\z/
|
93
135
|
```
|
94
136
|
|
95
|
-
###
|
96
|
-
- `CHESS` - First player chess style
|
97
|
-
- `chess` - Second player chess style
|
98
|
-
- `CHESS960` - First player Fischer Random Chess style
|
99
|
-
- `SHOGI` - First player shōgi style
|
100
|
-
- `shogi` - Second player shōgi style
|
101
|
-
- `XIANGQI` - First player xiangqi style
|
137
|
+
### Style Attribute Mapping
|
102
138
|
|
103
|
-
|
139
|
+
| Style Attribute | SNN Encoding | Examples |
|
140
|
+
|-----------------|--------------|----------|
|
141
|
+
| **Style Family** | Letter choice | `C`/`c` = Chess family |
|
142
|
+
| **Player Assignment** | Letter case | `C` = First player, `c` = Second player |
|
104
143
|
|
105
|
-
|
106
|
-
```ruby
|
107
|
-
# Traditional game styles
|
108
|
-
chess = Sashite::Snn.style(:Chess, :first) # => traditional chess
|
109
|
-
shogi = Sashite::Snn.style(:Shogi, :first) # => traditional shōgi
|
110
|
-
xiangqi = Sashite::Snn.style(:Xiangqi, :first) # => traditional xiangqi
|
111
|
-
makruk = Sashite::Snn.style(:Makruk, :first) # => traditional makruk
|
112
|
-
|
113
|
-
# Player variations
|
114
|
-
white_chess = chess # => first player
|
115
|
-
black_chess = chess.flip # => second player
|
116
|
-
black_chess.to_s # => "chess"
|
117
|
-
```
|
118
|
-
|
119
|
-
### Chess Variants
|
120
|
-
```ruby
|
121
|
-
# Fischer Random Chess
|
122
|
-
chess960_white = Sashite::Snn.style(:Chess960, :first)
|
123
|
-
chess960_black = chess960_white.flip
|
124
|
-
chess960_black.to_s # => "chess960"
|
144
|
+
## Game Examples
|
125
145
|
|
126
|
-
|
127
|
-
koth = Sashite::Snn.style(:Koth, :first)
|
128
|
-
koth.to_s # => "KOTH"
|
146
|
+
The SNN specification is rule-agnostic and does not define specific letter assignments. However, here are common usage patterns:
|
129
147
|
|
130
|
-
|
131
|
-
threecheck = Sashite::Snn.style(:Threecheck, :first)
|
132
|
-
```
|
148
|
+
### Traditional Game Families
|
133
149
|
|
134
|
-
### Shōgi Variants
|
135
150
|
```ruby
|
136
|
-
#
|
137
|
-
|
151
|
+
# Chess family styles
|
152
|
+
chess_white = Sashite::Snn.parse("C") # First player, Chess family
|
153
|
+
chess_black = Sashite::Snn.parse("c") # Second player, Chess family
|
138
154
|
|
139
|
-
#
|
140
|
-
|
155
|
+
# Shōgi family styles
|
156
|
+
shogi_sente = Sashite::Snn.parse("S") # First player, Shōgi family
|
157
|
+
shogi_gote = Sashite::Snn.parse("s") # Second player, Shōgi family
|
141
158
|
|
142
|
-
#
|
143
|
-
|
159
|
+
# Xiangqi family styles
|
160
|
+
xiangqi_red = Sashite::Snn.parse("X") # First player, Xiangqi family
|
161
|
+
xiangqi_black = Sashite::Snn.parse("x") # Second player, Xiangqi family
|
144
162
|
```
|
145
163
|
|
146
|
-
###
|
164
|
+
### Cross-Style Scenarios
|
165
|
+
|
147
166
|
```ruby
|
148
|
-
#
|
167
|
+
# Different families in one match
|
149
168
|
def create_hybrid_match
|
150
|
-
|
151
|
-
Sashite::Snn.
|
152
|
-
Sashite::Snn.
|
169
|
+
[
|
170
|
+
Sashite::Snn.parse("C"), # First player uses Chess family
|
171
|
+
Sashite::Snn.parse("s") # Second player uses Shōgi family
|
153
172
|
]
|
154
|
-
|
155
|
-
# Each player uses their preferred piece style
|
156
|
-
styles
|
157
173
|
end
|
158
174
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
175
|
+
styles = create_hybrid_match
|
176
|
+
styles[0].same_side?(styles[1]) # => false (different players)
|
177
|
+
styles[0].same_letter?(styles[1]) # => false (different families)
|
178
|
+
```
|
179
|
+
|
180
|
+
### Variant Families
|
164
181
|
|
165
|
-
|
166
|
-
|
167
|
-
|
182
|
+
```ruby
|
183
|
+
# Different letters can represent variants within traditions
|
184
|
+
makruk = Sashite::Snn.parse("M") # Makruk (Thai Chess) family
|
185
|
+
janggi = Sashite::Snn.parse("J") # Janggi (Korean Chess) family
|
186
|
+
ogi = Sashite::Snn.parse("O") # Ōgi (王棋) family
|
187
|
+
|
188
|
+
# Each family can have both players
|
189
|
+
makruk_black = makruk.flip # Second player Makruk
|
190
|
+
makruk_black.to_s # => "m"
|
168
191
|
```
|
169
192
|
|
170
193
|
## API Reference
|
@@ -173,139 +196,123 @@ puts compatible_styles?(chess_white, shogi_black) # => true
|
|
173
196
|
|
174
197
|
- `Sashite::Snn.valid?(snn_string)` - Check if string is valid SNN notation
|
175
198
|
- `Sashite::Snn.parse(snn_string)` - Parse SNN string into Style object
|
176
|
-
- `Sashite::Snn.style(
|
199
|
+
- `Sashite::Snn.style(letter, side)` - Create style instance directly
|
177
200
|
|
178
201
|
### Style Class
|
179
202
|
|
180
203
|
#### Creation and Parsing
|
181
|
-
- `Sashite::Snn::Style.new(
|
182
|
-
- `Sashite::Snn::Style.parse(snn_string)` - Parse SNN string
|
204
|
+
- `Sashite::Snn::Style.new(letter, side)` - Create style instance
|
205
|
+
- `Sashite::Snn::Style.parse(snn_string)` - Parse SNN string
|
183
206
|
|
184
207
|
#### Attribute Access
|
185
|
-
- `#
|
208
|
+
- `#letter` - Get style letter (symbol :A through :z)
|
186
209
|
- `#side` - Get player side (:first or :second)
|
187
210
|
- `#to_s` - Convert to SNN string representation
|
188
211
|
|
189
|
-
####
|
190
|
-
|
191
|
-
**Important**: The `name` attribute is always stored with proper capitalization (first letter uppercase, rest lowercase), regardless of the input case when parsing. The display case in `#to_s` is determined by the `side` attribute:
|
192
|
-
|
193
|
-
```ruby
|
194
|
-
# Both create the same internal name representation
|
195
|
-
style1 = Sashite::Snn.parse("CHESS") # name: :Chess, side: :first
|
196
|
-
style2 = Sashite::Snn.parse("chess") # name: :Chess, side: :second
|
197
|
-
|
198
|
-
style1.name # => :Chess (proper capitalization)
|
199
|
-
style2.name # => :Chess (same capitalization)
|
200
|
-
|
201
|
-
style1.to_s # => "CHESS" (uppercase display)
|
202
|
-
style2.to_s # => "chess" (lowercase display)
|
203
|
-
```
|
204
|
-
|
205
|
-
#### Side Queries
|
212
|
+
#### Player Queries
|
206
213
|
- `#first_player?` - Check if first player style
|
207
214
|
- `#second_player?` - Check if second player style
|
208
215
|
|
209
216
|
#### Transformations (immutable - return new instances)
|
210
|
-
- `#flip` - Switch player
|
211
|
-
- `#
|
217
|
+
- `#flip` - Switch player assignment
|
218
|
+
- `#with_letter(new_letter)` - Create style with different letter
|
212
219
|
- `#with_side(new_side)` - Create style with different side
|
213
220
|
|
214
221
|
#### Comparison Methods
|
215
|
-
- `#
|
216
|
-
- `#same_side?(other)` - Check if same side
|
222
|
+
- `#same_letter?(other)` - Check if same style letter (case-insensitive)
|
223
|
+
- `#same_side?(other)` - Check if same player side
|
217
224
|
- `#==(other)` - Full equality comparison
|
218
225
|
|
219
|
-
### Constants
|
220
|
-
|
226
|
+
### Style Class Constants
|
227
|
+
|
228
|
+
- `Sashite::Snn::Style::FIRST_PLAYER` - Symbol for first player (:first)
|
229
|
+
- `Sashite::Snn::Style::SECOND_PLAYER` - Symbol for second player (:second)
|
230
|
+
- `Sashite::Snn::Style::VALID_SIDES` - Array of valid sides
|
231
|
+
- `Sashite::Snn::Style::SNN_PATTERN` - Regular expression for SNN validation
|
221
232
|
|
222
233
|
## Advanced Usage
|
223
234
|
|
224
|
-
###
|
235
|
+
### Letter Case and Side Mapping
|
225
236
|
|
226
237
|
```ruby
|
227
|
-
#
|
228
|
-
|
229
|
-
|
238
|
+
# SNN encodes player assignment through case
|
239
|
+
upper_case_letters = ("A".."Z").map { |letter| Sashite::Snn.parse(letter) }
|
240
|
+
lower_case_letters = ("a".."z").map { |letter| Sashite::Snn.parse(letter) }
|
230
241
|
|
231
|
-
#
|
232
|
-
|
233
|
-
black_chess.name # => :Chess (same name!)
|
242
|
+
# All uppercase letters are first player
|
243
|
+
upper_case_letters.all?(&:first_player?) # => true
|
234
244
|
|
235
|
-
#
|
236
|
-
|
237
|
-
black_chess.side # => :second
|
245
|
+
# All lowercase letters are second player
|
246
|
+
lower_case_letters.all?(&:second_player?) # => true
|
238
247
|
|
239
|
-
#
|
240
|
-
|
241
|
-
|
248
|
+
# Letter families are related by case
|
249
|
+
letter_a_first = Sashite::Snn.parse("A")
|
250
|
+
letter_a_second = Sashite::Snn.parse("a")
|
242
251
|
|
243
|
-
#
|
244
|
-
|
245
|
-
white_chess.same_side?(black_chess) # => false
|
252
|
+
letter_a_first.same_letter?(letter_a_second) # => true
|
253
|
+
letter_a_first.same_side?(letter_a_second) # => false
|
246
254
|
```
|
247
255
|
|
248
256
|
### Immutable Transformations
|
257
|
+
|
249
258
|
```ruby
|
250
259
|
# All transformations return new instances
|
251
|
-
original = Sashite::Snn.style(:
|
260
|
+
original = Sashite::Snn.style(:C, :first)
|
252
261
|
flipped = original.flip
|
253
|
-
|
262
|
+
changed_letter = original.with_letter(:S)
|
254
263
|
|
255
264
|
# Original style is never modified
|
256
|
-
|
257
|
-
|
258
|
-
|
265
|
+
original.to_s # => "C" (unchanged)
|
266
|
+
flipped.to_s # => "c"
|
267
|
+
changed_letter.to_s # => "S"
|
259
268
|
|
260
269
|
# Transformations can be chained
|
261
|
-
result = original.flip.
|
262
|
-
|
270
|
+
result = original.flip.with_letter(:M).flip
|
271
|
+
result.to_s # => "M"
|
263
272
|
```
|
264
273
|
|
265
274
|
### Game Configuration Management
|
275
|
+
|
266
276
|
```ruby
|
267
277
|
class GameConfiguration
|
268
278
|
def initialize
|
269
279
|
@player_styles = {}
|
270
280
|
end
|
271
281
|
|
272
|
-
def set_player_style(player,
|
282
|
+
def set_player_style(player, letter)
|
273
283
|
side = player == :white ? :first : :second
|
274
|
-
@player_styles[player] = Sashite::Snn.style(
|
284
|
+
@player_styles[player] = Sashite::Snn.style(letter, side)
|
275
285
|
end
|
276
286
|
|
277
287
|
def get_player_style(player)
|
278
288
|
@player_styles[player]
|
279
289
|
end
|
280
290
|
|
281
|
-
def
|
291
|
+
def cross_family_match?
|
282
292
|
return false if @player_styles.size < 2
|
283
293
|
|
284
294
|
styles = @player_styles.values
|
285
|
-
!styles.all? { |style| style.
|
295
|
+
!styles.all? { |style| style.same_letter?(styles.first) }
|
286
296
|
end
|
287
297
|
|
288
|
-
def
|
289
|
-
|
290
|
-
|
291
|
-
style_names = @player_styles.values.map(&:name).uniq
|
292
|
-
style_names.size > 1
|
298
|
+
def same_family_match?
|
299
|
+
!cross_family_match?
|
293
300
|
end
|
294
301
|
end
|
295
302
|
|
296
303
|
# Usage
|
297
304
|
config = GameConfiguration.new
|
298
|
-
config.set_player_style(:white, :Chess
|
299
|
-
config.set_player_style(:black, :
|
305
|
+
config.set_player_style(:white, :C) # Chess family, first player
|
306
|
+
config.set_player_style(:black, :S) # Shōgi family, second player
|
300
307
|
|
301
|
-
|
302
|
-
puts config.style_mismatch? # => true
|
308
|
+
config.cross_family_match? # => true
|
303
309
|
|
304
310
|
white_style = config.get_player_style(:white)
|
305
|
-
|
311
|
+
white_style.to_s # => "C"
|
306
312
|
```
|
307
313
|
|
308
314
|
### Style Analysis
|
315
|
+
|
309
316
|
```ruby
|
310
317
|
def analyze_styles(snns)
|
311
318
|
styles = snns.map { |snn| Sashite::Snn.parse(snn) }
|
@@ -313,29 +320,30 @@ def analyze_styles(snns)
|
|
313
320
|
{
|
314
321
|
total: styles.size,
|
315
322
|
by_side: styles.group_by(&:side),
|
316
|
-
|
317
|
-
|
318
|
-
|
323
|
+
by_letter: styles.group_by { |s| s.letter.to_s.upcase },
|
324
|
+
unique_letters: styles.map { |s| s.letter.to_s.upcase }.uniq.size,
|
325
|
+
cross_family: styles.map { |s| s.letter.to_s.upcase }.uniq.size > 1
|
319
326
|
}
|
320
327
|
end
|
321
328
|
|
322
|
-
snns = %w[
|
329
|
+
snns = %w[C c S s X x]
|
323
330
|
analysis = analyze_styles(snns)
|
324
|
-
|
325
|
-
|
326
|
-
|
331
|
+
analysis[:by_side][:first].size # => 3
|
332
|
+
analysis[:unique_letters] # => 3
|
333
|
+
analysis[:cross_family] # => true
|
327
334
|
```
|
328
335
|
|
329
|
-
### Tournament Style
|
336
|
+
### Tournament Style Registry
|
337
|
+
|
330
338
|
```ruby
|
331
339
|
class TournamentStyleRegistry
|
332
340
|
def initialize
|
333
341
|
@registered_styles = Set.new
|
334
342
|
end
|
335
343
|
|
336
|
-
def
|
337
|
-
# Register both sides of a
|
338
|
-
first_player_style = Sashite::Snn.style(
|
344
|
+
def register_letter(letter)
|
345
|
+
# Register both sides of a letter family
|
346
|
+
first_player_style = Sashite::Snn.style(letter.to_s.upcase.to_sym, :first)
|
339
347
|
second_player_style = first_player_style.flip
|
340
348
|
|
341
349
|
@registered_styles.add(first_player_style)
|
@@ -354,94 +362,62 @@ class TournamentStyleRegistry
|
|
354
362
|
@registered_styles.select { |style| style.side == side }
|
355
363
|
end
|
356
364
|
|
357
|
-
def
|
358
|
-
@registered_styles.map
|
365
|
+
def supported_families
|
366
|
+
@registered_styles.map { |s| s.letter.to_s.upcase }.uniq.sort
|
359
367
|
end
|
360
368
|
end
|
361
369
|
|
362
370
|
# Usage
|
363
371
|
registry = TournamentStyleRegistry.new
|
364
|
-
registry.
|
365
|
-
registry.
|
372
|
+
registry.register_letter(:C)
|
373
|
+
registry.register_letter(:S)
|
366
374
|
|
367
|
-
chess_white = Sashite::Snn.parse("
|
368
|
-
shogi_black = Sashite::Snn.parse("
|
375
|
+
chess_white = Sashite::Snn.parse("C")
|
376
|
+
shogi_black = Sashite::Snn.parse("s")
|
369
377
|
|
370
|
-
|
371
|
-
|
378
|
+
registry.valid_pairing?(chess_white, shogi_black) # => true
|
379
|
+
registry.supported_families # => ["C", "S"]
|
372
380
|
```
|
373
381
|
|
374
382
|
## Protocol Mapping
|
375
383
|
|
376
|
-
Following the [
|
384
|
+
Following the [Sashité Protocol](https://sashite.dev/protocol/):
|
377
385
|
|
378
386
|
| Protocol Attribute | SNN Encoding | Examples | Notes |
|
379
387
|
|-------------------|--------------|----------|-------|
|
380
|
-
| **Style** |
|
381
|
-
| **
|
382
|
-
|
383
|
-
**Name Convention**: All style names are internally represented with proper capitalization (first letter uppercase, rest lowercase). The display case is determined by the `side` attribute: first player styles display as uppercase, second player styles as lowercase.
|
384
|
-
|
385
|
-
**Canonical principle**: Identical styles must have identical SNN representations.
|
386
|
-
|
387
|
-
## Properties
|
388
|
-
|
389
|
-
* **Rule-Agnostic**: Independent of specific game mechanics
|
390
|
-
* **Cross-Style Support**: Enables multi-tradition gaming environments
|
391
|
-
* **Canonical Representation**: Consistent naming for equivalent styles
|
392
|
-
* **Name Normalization**: Consistent proper capitalization representation internally
|
393
|
-
* **Immutable**: All style instances are frozen and transformations return new objects
|
394
|
-
* **Functional**: Pure functions with no side effects
|
395
|
-
|
396
|
-
## Implementation Notes
|
397
|
-
|
398
|
-
### Name Normalization Convention
|
399
|
-
|
400
|
-
SNN follows a strict name normalization convention:
|
388
|
+
| **Style Family** | Letter choice | `C`, `S`, `X` | Rule-agnostic letter assignment |
|
389
|
+
| **Player Assignment** | Case encoding | `C` = First player, `c` = Second player | Case determines side |
|
401
390
|
|
402
|
-
|
403
|
-
2. **Input Flexibility**: Both `"CHESS"` and `"chess"` are valid input during parsing
|
404
|
-
3. **Case Semantics**: Input case determines the `side` attribute, not the `name`
|
405
|
-
4. **Display Logic**: Output case is computed from `side` during rendering
|
406
|
-
|
407
|
-
This design ensures:
|
408
|
-
- Consistent internal representation regardless of input format
|
409
|
-
- Clear separation between style identity (name) and ownership (side)
|
410
|
-
- Predictable behavior when comparing styles of the same name
|
411
|
-
|
412
|
-
### Example Flow
|
413
|
-
|
414
|
-
```ruby
|
415
|
-
# Input: "chess" (lowercase)
|
416
|
-
# ↓ Parsing
|
417
|
-
# name: :Chess (normalized with proper capitalization)
|
418
|
-
# side: :second (inferred from lowercase input)
|
419
|
-
# ↓ Display
|
420
|
-
# SNN: "chess" (final representation)
|
421
|
-
```
|
391
|
+
## System Constraints
|
422
392
|
|
423
|
-
|
393
|
+
- **26 possible identifiers** per player using ASCII letters (A-Z, a-z)
|
394
|
+
- **Exactly 2 players** through case distinction
|
395
|
+
- **Single character** per style-player combination
|
396
|
+
- **Rule-agnostic** - no predefined letter meanings
|
424
397
|
|
425
|
-
##
|
398
|
+
## Design Properties
|
426
399
|
|
427
|
-
- **
|
428
|
-
- **
|
429
|
-
- **
|
400
|
+
- **ASCII compatibility**: Maximum portability across systems
|
401
|
+
- **Rule-agnostic**: Independent of specific game mechanics
|
402
|
+
- **Minimal overhead**: Single character per style-player combination
|
403
|
+
- **Canonical representation**: Each style-player combination has exactly one SNN identifier
|
404
|
+
- **Immutable**: All style instances are frozen and transformations return new objects
|
405
|
+
- **Functional**: Pure functions with no side effects
|
430
406
|
|
431
407
|
## Related Specifications
|
432
408
|
|
433
|
-
- [
|
434
|
-
- [
|
409
|
+
- [SNN Specification v1.0.0](https://sashite.dev/specs/snn/1.0.0/) - Complete technical specification
|
410
|
+
- [SNN Examples](https://sashite.dev/specs/snn/1.0.0/examples/) - Practical implementation examples
|
411
|
+
- [Sashité Protocol](https://sashite.dev/protocol/) - Conceptual foundation for abstract strategy board games
|
412
|
+
- [PIN](https://sashite.dev/specs/pin/) - Piece Identifier Notation
|
435
413
|
- [PNN](https://sashite.dev/specs/pnn/) - Piece Name Notation (style-aware piece representation)
|
436
|
-
- [
|
437
|
-
- [HAND](https://sashite.dev/specs/hand/) - Reserve location notation
|
438
|
-
- [PMN](https://sashite.dev/specs/pmn/) - Portable Move Notation
|
414
|
+
- [QPI](https://sashite.dev/specs/qpi/) - Qualified Piece Identifier
|
439
415
|
|
440
416
|
## Documentation
|
441
417
|
|
442
418
|
- [Official SNN Specification v1.0.0](https://sashite.dev/specs/snn/1.0.0/)
|
443
419
|
- [SNN Examples Documentation](https://sashite.dev/specs/snn/1.0.0/examples/)
|
444
|
-
- [
|
420
|
+
- [Sashité Protocol Foundation](https://sashite.dev/protocol/)
|
445
421
|
- [API Documentation](https://rubydoc.info/github/sashite/snn.rb/main)
|
446
422
|
|
447
423
|
## Development
|
data/lib/sashite/snn/style.rb
CHANGED
@@ -4,18 +4,15 @@ module Sashite
|
|
4
4
|
module Snn
|
5
5
|
# Represents a style in SNN (Style Name Notation) format.
|
6
6
|
#
|
7
|
-
# A style consists of
|
8
|
-
# - Uppercase
|
9
|
-
# - Lowercase
|
7
|
+
# A style consists of a single ASCII letter with case-based side encoding:
|
8
|
+
# - Uppercase letter: first player (A, B, C, ..., Z)
|
9
|
+
# - Lowercase letter: second player (a, b, c, ..., z)
|
10
10
|
#
|
11
11
|
# All instances are immutable - transformation methods return new instances.
|
12
|
-
# This follows the
|
12
|
+
# This follows the SNN Specification v1.0.0 with Letter and Side attributes.
|
13
13
|
class Style
|
14
14
|
# SNN validation pattern matching the specification
|
15
|
-
SNN_PATTERN = /\A
|
16
|
-
|
17
|
-
# Pattern for proper name capitalization (first letter uppercase, rest lowercase/digits)
|
18
|
-
PROPER_NAME_PATTERN = /\A[A-Z][a-z0-9]*\z/
|
15
|
+
SNN_PATTERN = /\A[A-Za-z]\z/
|
19
16
|
|
20
17
|
# Player side constants
|
21
18
|
FIRST_PLAYER = :first
|
@@ -26,25 +23,25 @@ module Sashite
|
|
26
23
|
|
27
24
|
# Error messages
|
28
25
|
ERROR_INVALID_SNN = "Invalid SNN string: %s"
|
29
|
-
|
26
|
+
ERROR_INVALID_LETTER = "Letter must be a single ASCII letter symbol (A-Z, a-z), got: %s"
|
30
27
|
ERROR_INVALID_SIDE = "Side must be :first or :second, got: %s"
|
31
28
|
|
32
|
-
# @return [Symbol] the style
|
33
|
-
attr_reader :
|
29
|
+
# @return [Symbol] the style letter (single ASCII letter as symbol)
|
30
|
+
attr_reader :letter
|
34
31
|
|
35
32
|
# @return [Symbol] the player side (:first or :second)
|
36
33
|
attr_reader :side
|
37
34
|
|
38
35
|
# Create a new style instance
|
39
36
|
#
|
40
|
-
# @param
|
37
|
+
# @param letter [Symbol] style letter (single ASCII letter as symbol)
|
41
38
|
# @param side [Symbol] player side (:first or :second)
|
42
39
|
# @raise [ArgumentError] if parameters are invalid
|
43
|
-
def initialize(
|
44
|
-
self.class.
|
40
|
+
def initialize(letter, side)
|
41
|
+
self.class.validate_letter(letter)
|
45
42
|
self.class.validate_side(side)
|
46
43
|
|
47
|
-
@
|
44
|
+
@letter = letter
|
48
45
|
@side = side
|
49
46
|
|
50
47
|
freeze
|
@@ -52,72 +49,90 @@ module Sashite
|
|
52
49
|
|
53
50
|
# Parse an SNN string into a Style object
|
54
51
|
#
|
55
|
-
# @param snn_string [String] SNN notation string
|
56
|
-
# @return [Style]
|
52
|
+
# @param snn_string [String] SNN notation string (single ASCII letter)
|
53
|
+
# @return [Style] parsed style object with letter and inferred side
|
57
54
|
# @raise [ArgumentError] if the SNN string is invalid
|
58
|
-
# @example
|
59
|
-
# Snn::Style.parse("
|
60
|
-
# Snn::Style.parse("
|
61
|
-
# Snn::Style.parse("
|
55
|
+
# @example Parse SNN strings with case-based side inference
|
56
|
+
# Sashite::Snn::Style.parse("C") # => #<Snn::Style letter=:C side=:first>
|
57
|
+
# Sashite::Snn::Style.parse("c") # => #<Snn::Style letter=:c side=:second>
|
58
|
+
# Sashite::Snn::Style.parse("S") # => #<Snn::Style letter=:S side=:first>
|
62
59
|
def self.parse(snn_string)
|
63
60
|
string_value = String(snn_string)
|
64
|
-
|
65
|
-
|
66
|
-
identifier = matches[:identifier]
|
61
|
+
validate_snn_string(string_value)
|
67
62
|
|
68
63
|
# Determine side from case
|
69
|
-
style_side =
|
64
|
+
style_side = string_value == string_value.upcase ? FIRST_PLAYER : SECOND_PLAYER
|
65
|
+
|
66
|
+
# Use the letter directly as symbol
|
67
|
+
style_letter = string_value.to_sym
|
68
|
+
|
69
|
+
new(style_letter, style_side)
|
70
|
+
end
|
70
71
|
|
71
|
-
|
72
|
-
|
72
|
+
# Check if a string is a valid SNN notation
|
73
|
+
#
|
74
|
+
# @param snn_string [String] the string to validate
|
75
|
+
# @return [Boolean] true if valid SNN, false otherwise
|
76
|
+
#
|
77
|
+
# @example Validate SNN strings
|
78
|
+
# Sashite::Snn::Style.valid?("C") # => true
|
79
|
+
# Sashite::Snn::Style.valid?("c") # => true
|
80
|
+
# Sashite::Snn::Style.valid?("CHESS") # => false (multi-character)
|
81
|
+
def self.valid?(snn_string)
|
82
|
+
return false unless snn_string.is_a?(::String)
|
73
83
|
|
74
|
-
|
84
|
+
snn_string.match?(SNN_PATTERN)
|
75
85
|
end
|
76
86
|
|
77
87
|
# Convert the style to its SNN string representation
|
78
88
|
#
|
79
|
-
# @return [String] SNN notation string
|
80
|
-
# @example
|
81
|
-
# style.to_s # => "
|
82
|
-
# style.to_s # => "
|
83
|
-
# style.to_s # => "
|
89
|
+
# @return [String] SNN notation string (single ASCII letter)
|
90
|
+
# @example Display styles
|
91
|
+
# style.to_s # => "C" (first player, C family)
|
92
|
+
# style.to_s # => "c" (second player, C family)
|
93
|
+
# style.to_s # => "S" (first player, S family)
|
84
94
|
def to_s
|
85
|
-
|
95
|
+
letter.to_s
|
86
96
|
end
|
87
97
|
|
88
98
|
# Create a new style with opposite ownership (side)
|
89
99
|
#
|
90
|
-
# @return [Style] new style instance with flipped side
|
91
|
-
# @example
|
92
|
-
# style.flip # (:
|
100
|
+
# @return [Style] new immutable style instance with flipped side
|
101
|
+
# @example Flip player sides
|
102
|
+
# style.flip # (:C, :first) => (:c, :second)
|
93
103
|
def flip
|
94
|
-
|
104
|
+
new_letter = first_player? ? letter.to_s.downcase.to_sym : letter.to_s.upcase.to_sym
|
105
|
+
self.class.new(new_letter, opposite_side)
|
95
106
|
end
|
96
107
|
|
97
|
-
# Create a new style with a different
|
108
|
+
# Create a new style with a different letter (keeping same side)
|
98
109
|
#
|
99
|
-
# @param
|
100
|
-
# @return [Style] new style instance with different
|
101
|
-
# @example
|
102
|
-
# style.
|
103
|
-
def
|
104
|
-
self.class.
|
105
|
-
return self if
|
110
|
+
# @param new_letter [Symbol] new letter (single ASCII letter as symbol)
|
111
|
+
# @return [Style] new immutable style instance with different letter
|
112
|
+
# @example Change style letter
|
113
|
+
# style.with_letter(:S) # (:C, :first) => (:S, :first)
|
114
|
+
def with_letter(new_letter)
|
115
|
+
self.class.validate_letter(new_letter)
|
116
|
+
return self if letter == new_letter
|
106
117
|
|
107
|
-
|
118
|
+
# Ensure the new letter has the correct case for the current side
|
119
|
+
adjusted_letter = first_player? ? new_letter.to_s.upcase.to_sym : new_letter.to_s.downcase.to_sym
|
120
|
+
self.class.new(adjusted_letter, side)
|
108
121
|
end
|
109
122
|
|
110
|
-
# Create a new style with a different side (keeping same
|
123
|
+
# Create a new style with a different side (keeping same letter family)
|
111
124
|
#
|
112
125
|
# @param new_side [Symbol] :first or :second
|
113
|
-
# @return [Style] new style instance with different side
|
114
|
-
# @example
|
115
|
-
# style.with_side(:second) # (:
|
126
|
+
# @return [Style] new immutable style instance with different side
|
127
|
+
# @example Change player side
|
128
|
+
# style.with_side(:second) # (:C, :first) => (:c, :second)
|
116
129
|
def with_side(new_side)
|
117
130
|
self.class.validate_side(new_side)
|
118
131
|
return self if side == new_side
|
119
132
|
|
120
|
-
|
133
|
+
# Adjust letter case for the new side
|
134
|
+
new_letter = new_side == FIRST_PLAYER ? letter.to_s.upcase.to_sym : letter.to_s.downcase.to_sym
|
135
|
+
self.class.new(new_letter, new_side)
|
121
136
|
end
|
122
137
|
|
123
138
|
# Check if the style belongs to the first player
|
@@ -134,22 +149,22 @@ module Sashite
|
|
134
149
|
side == SECOND_PLAYER
|
135
150
|
end
|
136
151
|
|
137
|
-
# Check if this style has the same
|
152
|
+
# Check if this style has the same letter family as another
|
138
153
|
#
|
139
154
|
# @param other [Style] style to compare with
|
140
|
-
# @return [Boolean] true if same
|
141
|
-
# @example
|
142
|
-
#
|
143
|
-
def
|
155
|
+
# @return [Boolean] true if both styles use the same letter family (case-insensitive)
|
156
|
+
# @example Compare style letter families
|
157
|
+
# c_style.same_letter?(C_style) # (:c, :second) and (:C, :first) => true
|
158
|
+
def same_letter?(other)
|
144
159
|
return false unless other.is_a?(self.class)
|
145
160
|
|
146
|
-
|
161
|
+
letter.to_s.upcase == other.letter.to_s.upcase
|
147
162
|
end
|
148
163
|
|
149
164
|
# Check if this style belongs to the same side as another
|
150
165
|
#
|
151
166
|
# @param other [Style] style to compare with
|
152
|
-
# @return [Boolean] true if same side
|
167
|
+
# @return [Boolean] true if both styles belong to the same side
|
153
168
|
def same_side?(other)
|
154
169
|
return false unless other.is_a?(self.class)
|
155
170
|
|
@@ -159,11 +174,11 @@ module Sashite
|
|
159
174
|
# Custom equality comparison
|
160
175
|
#
|
161
176
|
# @param other [Object] object to compare with
|
162
|
-
# @return [Boolean] true if
|
177
|
+
# @return [Boolean] true if both objects are styles with identical letter and side
|
163
178
|
def ==(other)
|
164
179
|
return false unless other.is_a?(self.class)
|
165
180
|
|
166
|
-
|
181
|
+
letter == other.letter && side == other.side
|
167
182
|
end
|
168
183
|
|
169
184
|
# Alias for == to ensure Set functionality works correctly
|
@@ -171,19 +186,19 @@ module Sashite
|
|
171
186
|
|
172
187
|
# Custom hash implementation for use in collections
|
173
188
|
#
|
174
|
-
# @return [Integer] hash value
|
189
|
+
# @return [Integer] hash value based on class, letter, and side
|
175
190
|
def hash
|
176
|
-
[self.class,
|
191
|
+
[self.class, letter, side].hash
|
177
192
|
end
|
178
193
|
|
179
|
-
# Validate that the
|
194
|
+
# Validate that the letter is a valid single ASCII letter symbol
|
180
195
|
#
|
181
|
-
# @param
|
196
|
+
# @param letter [Symbol] the letter to validate
|
182
197
|
# @raise [ArgumentError] if invalid
|
183
|
-
def self.
|
184
|
-
return if
|
198
|
+
def self.validate_letter(letter)
|
199
|
+
return if valid_letter?(letter)
|
185
200
|
|
186
|
-
raise ::ArgumentError, format(
|
201
|
+
raise ::ArgumentError, format(ERROR_INVALID_LETTER, letter.inspect)
|
187
202
|
end
|
188
203
|
|
189
204
|
# Validate that the side is a valid symbol
|
@@ -196,44 +211,31 @@ module Sashite
|
|
196
211
|
raise ::ArgumentError, format(ERROR_INVALID_SIDE, side.inspect)
|
197
212
|
end
|
198
213
|
|
199
|
-
# Check if a
|
214
|
+
# Check if a letter is valid (single ASCII letter symbol)
|
200
215
|
#
|
201
|
-
# @param
|
216
|
+
# @param letter [Object] the letter to check
|
202
217
|
# @return [Boolean] true if valid
|
203
|
-
def self.
|
204
|
-
return false unless
|
218
|
+
def self.valid_letter?(letter)
|
219
|
+
return false unless letter.is_a?(::Symbol)
|
205
220
|
|
206
|
-
|
207
|
-
return false if
|
221
|
+
letter_string = letter.to_s
|
222
|
+
return false if letter_string.empty?
|
208
223
|
|
209
|
-
# Must
|
210
|
-
|
211
|
-
end
|
212
|
-
|
213
|
-
# Normalize identifier to proper capitalization symbol
|
214
|
-
#
|
215
|
-
# @param identifier [String] the identifier to normalize
|
216
|
-
# @return [Symbol] normalized name symbol
|
217
|
-
def self.normalize_name(identifier)
|
218
|
-
# Convert to proper capitalization: first letter uppercase, rest lowercase
|
219
|
-
normalized = identifier.downcase
|
220
|
-
normalized[0] = normalized[0].upcase if normalized.length > 0
|
221
|
-
normalized.to_sym
|
224
|
+
# Must be exactly one ASCII letter
|
225
|
+
letter_string.match?(SNN_PATTERN)
|
222
226
|
end
|
223
227
|
|
224
|
-
#
|
228
|
+
# Validate SNN string format
|
225
229
|
#
|
226
|
-
# @param string [String] string to
|
227
|
-
# @
|
228
|
-
|
229
|
-
|
230
|
-
matches = SNN_PATTERN.match(string)
|
231
|
-
return matches if matches
|
230
|
+
# @param string [String] string to validate
|
231
|
+
# @raise [ArgumentError] if string doesn't match SNN pattern
|
232
|
+
def self.validate_snn_string(string)
|
233
|
+
return if string.match?(SNN_PATTERN)
|
232
234
|
|
233
235
|
raise ::ArgumentError, format(ERROR_INVALID_SNN, string)
|
234
236
|
end
|
235
237
|
|
236
|
-
private_class_method :
|
238
|
+
private_class_method :valid_letter?, :validate_snn_string
|
237
239
|
|
238
240
|
private
|
239
241
|
|
data/lib/sashite/snn.rb
CHANGED
@@ -6,66 +6,60 @@ module Sashite
|
|
6
6
|
# SNN (Style Name Notation) implementation for Ruby
|
7
7
|
#
|
8
8
|
# Provides a rule-agnostic format for identifying styles in abstract strategy board games.
|
9
|
-
# SNN uses
|
10
|
-
# distinction between different
|
9
|
+
# SNN uses single ASCII letters with case-based side encoding, enabling clear
|
10
|
+
# distinction between different style families in multi-style gaming environments.
|
11
11
|
#
|
12
|
-
# Format: <style-
|
13
|
-
# - Uppercase
|
14
|
-
# - Lowercase
|
15
|
-
# -
|
12
|
+
# Format: <style-letter>
|
13
|
+
# - Uppercase letter: First player styles (A, B, C, ..., Z)
|
14
|
+
# - Lowercase letter: Second player styles (a, b, c, ..., z)
|
15
|
+
# - Single character only: Each SNN identifier is exactly one ASCII letter
|
16
16
|
#
|
17
17
|
# Examples:
|
18
|
-
# "
|
19
|
-
# "
|
20
|
-
# "
|
21
|
-
# "
|
18
|
+
# "C" - First player, C style family
|
19
|
+
# "c" - Second player, C style family
|
20
|
+
# "S" - First player, S style family
|
21
|
+
# "s" - Second player, S style family
|
22
22
|
#
|
23
23
|
# See: https://sashite.dev/specs/snn/1.0.0/
|
24
24
|
module Snn
|
25
|
-
# Regular expression for SNN validation
|
26
|
-
# Matches: uppercase alphanumeric identifier OR lowercase alphanumeric identifier
|
27
|
-
SNN_REGEX = /\A([A-Z][A-Z0-9]*|[a-z][a-z0-9]*)\z/
|
28
|
-
|
29
25
|
# Check if a string is a valid SNN notation
|
30
26
|
#
|
31
|
-
# @param
|
27
|
+
# @param snn_string [String] the string to validate
|
32
28
|
# @return [Boolean] true if valid SNN, false otherwise
|
33
29
|
#
|
34
|
-
# @example
|
35
|
-
# Sashite::Snn.valid?("
|
36
|
-
# Sashite::Snn.valid?("
|
37
|
-
# Sashite::Snn.valid?("
|
38
|
-
# Sashite::Snn.valid?("
|
39
|
-
def self.valid?(
|
40
|
-
|
41
|
-
|
42
|
-
snn.match?(SNN_REGEX)
|
30
|
+
# @example Validate various SNN formats
|
31
|
+
# Sashite::Snn.valid?("C") # => true
|
32
|
+
# Sashite::Snn.valid?("c") # => true
|
33
|
+
# Sashite::Snn.valid?("CHESS") # => false (multi-character)
|
34
|
+
# Sashite::Snn.valid?("1") # => false (not a letter)
|
35
|
+
def self.valid?(snn_string)
|
36
|
+
Style.valid?(snn_string)
|
43
37
|
end
|
44
38
|
|
45
39
|
# Parse an SNN string into a Style object
|
46
40
|
#
|
47
41
|
# @param snn_string [String] SNN notation string
|
48
|
-
# @return [Snn::Style]
|
42
|
+
# @return [Snn::Style] parsed style object with letter and side attributes
|
49
43
|
# @raise [ArgumentError] if the SNN string is invalid
|
50
|
-
# @example
|
51
|
-
# Sashite::Snn.parse("
|
52
|
-
# Sashite::Snn.parse("
|
53
|
-
# Sashite::Snn.parse("
|
44
|
+
# @example Parse different SNN formats
|
45
|
+
# Sashite::Snn.parse("C") # => #<Snn::Style letter=:C side=:first>
|
46
|
+
# Sashite::Snn.parse("c") # => #<Snn::Style letter=:c side=:second>
|
47
|
+
# Sashite::Snn.parse("S") # => #<Snn::Style letter=:S side=:first>
|
54
48
|
def self.parse(snn_string)
|
55
49
|
Style.parse(snn_string)
|
56
50
|
end
|
57
51
|
|
58
52
|
# Create a new style instance
|
59
53
|
#
|
60
|
-
# @param
|
54
|
+
# @param letter [Symbol] style letter (single ASCII letter as symbol)
|
61
55
|
# @param side [Symbol] player side (:first or :second)
|
62
|
-
# @return [Snn::Style] new style instance
|
56
|
+
# @return [Snn::Style] new immutable style instance
|
63
57
|
# @raise [ArgumentError] if parameters are invalid
|
64
|
-
# @example
|
65
|
-
# Sashite::Snn.style(:
|
66
|
-
# Sashite::Snn.style(:
|
67
|
-
def self.style(
|
68
|
-
Style.new(
|
58
|
+
# @example Create styles directly
|
59
|
+
# Sashite::Snn.style(:C, :first) # => #<Snn::Style letter=:C side=:first>
|
60
|
+
# Sashite::Snn.style(:s, :second) # => #<Snn::Style letter=:s side=:second>
|
61
|
+
def self.style(letter, side)
|
62
|
+
Style.new(letter, side)
|
69
63
|
end
|
70
64
|
end
|
71
65
|
end
|
data/lib/sashite-snn.rb
CHANGED
@@ -5,9 +5,9 @@ require_relative "sashite/snn"
|
|
5
5
|
# Sashité namespace for board game notation libraries
|
6
6
|
#
|
7
7
|
# Sashité provides a collection of libraries for representing and manipulating
|
8
|
-
# board game concepts according to the
|
8
|
+
# board game concepts according to the Sashité Protocol specifications.
|
9
9
|
#
|
10
|
-
# @see https://sashite.dev/
|
10
|
+
# @see https://sashite.dev/protocol/ Sashité Protocol
|
11
11
|
# @see https://sashite.dev/specs/ Sashité Specifications
|
12
12
|
# @author Sashité
|
13
13
|
module Sashite
|
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:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyril Kato
|
@@ -13,9 +13,10 @@ description: |
|
|
13
13
|
SNN (Style Name Notation) provides a rule-agnostic format for identifying styles
|
14
14
|
in abstract strategy board games. This gem implements the SNN Specification v1.0.0 with
|
15
15
|
a modern Ruby interface featuring immutable style objects and functional programming
|
16
|
-
principles. SNN uses
|
17
|
-
enabling clear distinction between different
|
18
|
-
Perfect for cross-
|
16
|
+
principles. SNN uses single ASCII letters with case-based side encoding (A-Z for first player,
|
17
|
+
a-z for second player), enabling clear distinction between different style families in
|
18
|
+
multi-style gaming environments. Perfect for cross-style matches, game engines, and hybrid
|
19
|
+
gaming systems requiring compact style identification.
|
19
20
|
email: contact@cyril.email
|
20
21
|
executables: []
|
21
22
|
extensions: []
|
@@ -34,7 +35,7 @@ metadata:
|
|
34
35
|
documentation_uri: https://rubydoc.info/github/sashite/snn.rb/main
|
35
36
|
homepage_uri: https://github.com/sashite/snn.rb
|
36
37
|
source_code_uri: https://github.com/sashite/snn.rb
|
37
|
-
specification_uri: https://sashite.dev/
|
38
|
+
specification_uri: https://sashite.dev/specs/snn/1.0.0/
|
38
39
|
rubygems_mfa_required: 'true'
|
39
40
|
rdoc_options: []
|
40
41
|
require_paths:
|