sashite-snn 1.1.1 → 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 +207 -236
- data/lib/sashite/snn/style.rb +81 -94
- data/lib/sashite/snn.rb +23 -21
- 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,144 +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
|
-
|
93
|
-
/\A([A-Z][A-Z0-9]*|[a-z][a-z0-9]*)\z/
|
134
|
+
/\A[A-Za-z]\z/
|
94
135
|
```
|
95
136
|
|
96
|
-
###
|
97
|
-
- `CHESS` - First player chess style
|
98
|
-
- `chess` - Second player chess style
|
99
|
-
- `CHESS960` - First player Fischer Random Chess style
|
100
|
-
- `SHOGI` - First player shōgi style
|
101
|
-
- `shogi` - Second player shōgi style
|
102
|
-
- `XIANGQI` - First player xiangqi style
|
137
|
+
### Style Attribute Mapping
|
103
138
|
|
104
|
-
|
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 |
|
105
143
|
|
106
|
-
|
107
|
-
```ruby
|
108
|
-
# Traditional game styles
|
109
|
-
chess = Sashite::Snn.style(:Chess, :first) # => traditional chess
|
110
|
-
shogi = Sashite::Snn.style(:Shogi, :first) # => traditional shōgi
|
111
|
-
xiangqi = Sashite::Snn.style(:Xiangqi, :first) # => traditional xiangqi
|
112
|
-
makruk = Sashite::Snn.style(:Makruk, :first) # => traditional makruk
|
113
|
-
|
114
|
-
# Player variations
|
115
|
-
white_chess = chess # => first player
|
116
|
-
black_chess = chess.flip # => second player
|
117
|
-
black_chess.to_s # => "chess"
|
118
|
-
```
|
119
|
-
|
120
|
-
### Chess Variants
|
121
|
-
```ruby
|
122
|
-
# Fischer Random Chess
|
123
|
-
chess960_white = Sashite::Snn.style(:Chess960, :first)
|
124
|
-
chess960_black = chess960_white.flip
|
125
|
-
chess960_black.to_s # => "chess960"
|
144
|
+
## Game Examples
|
126
145
|
|
127
|
-
|
128
|
-
koth = Sashite::Snn.style(:Koth, :first)
|
129
|
-
koth.to_s # => "KOTH"
|
146
|
+
The SNN specification is rule-agnostic and does not define specific letter assignments. However, here are common usage patterns:
|
130
147
|
|
131
|
-
|
132
|
-
threecheck = Sashite::Snn.style(:Threecheck, :first)
|
133
|
-
```
|
148
|
+
### Traditional Game Families
|
134
149
|
|
135
|
-
### Shōgi Variants
|
136
150
|
```ruby
|
137
|
-
#
|
138
|
-
|
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
|
139
154
|
|
140
|
-
#
|
141
|
-
|
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
|
142
158
|
|
143
|
-
#
|
144
|
-
|
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
|
145
162
|
```
|
146
163
|
|
147
|
-
###
|
164
|
+
### Cross-Style Scenarios
|
165
|
+
|
148
166
|
```ruby
|
149
|
-
#
|
167
|
+
# Different families in one match
|
150
168
|
def create_hybrid_match
|
151
|
-
|
152
|
-
Sashite::Snn.
|
153
|
-
Sashite::Snn.
|
169
|
+
[
|
170
|
+
Sashite::Snn.parse("C"), # First player uses Chess family
|
171
|
+
Sashite::Snn.parse("s") # Second player uses Shōgi family
|
154
172
|
]
|
155
|
-
|
156
|
-
# Each player uses their preferred piece style
|
157
|
-
styles
|
158
173
|
end
|
159
174
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
165
181
|
|
166
|
-
|
167
|
-
|
168
|
-
|
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"
|
169
191
|
```
|
170
192
|
|
171
193
|
## API Reference
|
@@ -174,47 +196,31 @@ puts compatible_styles?(chess_white, shogi_black) # => true
|
|
174
196
|
|
175
197
|
- `Sashite::Snn.valid?(snn_string)` - Check if string is valid SNN notation
|
176
198
|
- `Sashite::Snn.parse(snn_string)` - Parse SNN string into Style object
|
177
|
-
- `Sashite::Snn.style(
|
199
|
+
- `Sashite::Snn.style(letter, side)` - Create style instance directly
|
178
200
|
|
179
201
|
### Style Class
|
180
202
|
|
181
203
|
#### Creation and Parsing
|
182
|
-
- `Sashite::Snn::Style.new(
|
183
|
-
- `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
|
184
206
|
|
185
207
|
#### Attribute Access
|
186
|
-
- `#
|
208
|
+
- `#letter` - Get style letter (symbol :A through :z)
|
187
209
|
- `#side` - Get player side (:first or :second)
|
188
210
|
- `#to_s` - Convert to SNN string representation
|
189
211
|
|
190
|
-
####
|
191
|
-
|
192
|
-
**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:
|
193
|
-
|
194
|
-
```ruby
|
195
|
-
# Both create the same internal name representation
|
196
|
-
style1 = Sashite::Snn.parse("CHESS") # name: :Chess, side: :first
|
197
|
-
style2 = Sashite::Snn.parse("chess") # name: :Chess, side: :second
|
198
|
-
|
199
|
-
style1.name # => :Chess (proper capitalization)
|
200
|
-
style2.name # => :Chess (same capitalization)
|
201
|
-
|
202
|
-
style1.to_s # => "CHESS" (uppercase display)
|
203
|
-
style2.to_s # => "chess" (lowercase display)
|
204
|
-
```
|
205
|
-
|
206
|
-
#### Side Queries
|
212
|
+
#### Player Queries
|
207
213
|
- `#first_player?` - Check if first player style
|
208
214
|
- `#second_player?` - Check if second player style
|
209
215
|
|
210
216
|
#### Transformations (immutable - return new instances)
|
211
|
-
- `#flip` - Switch player
|
212
|
-
- `#
|
217
|
+
- `#flip` - Switch player assignment
|
218
|
+
- `#with_letter(new_letter)` - Create style with different letter
|
213
219
|
- `#with_side(new_side)` - Create style with different side
|
214
220
|
|
215
221
|
#### Comparison Methods
|
216
|
-
- `#
|
217
|
-
- `#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
|
218
224
|
- `#==(other)` - Full equality comparison
|
219
225
|
|
220
226
|
### Style Class Constants
|
@@ -226,91 +232,87 @@ style2.to_s # => "chess" (lowercase display)
|
|
226
232
|
|
227
233
|
## Advanced Usage
|
228
234
|
|
229
|
-
###
|
235
|
+
### Letter Case and Side Mapping
|
230
236
|
|
231
237
|
```ruby
|
232
|
-
#
|
233
|
-
|
234
|
-
|
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) }
|
235
241
|
|
236
|
-
#
|
237
|
-
|
238
|
-
black_chess.name # => :Chess (same name!)
|
242
|
+
# All uppercase letters are first player
|
243
|
+
upper_case_letters.all?(&:first_player?) # => true
|
239
244
|
|
240
|
-
#
|
241
|
-
|
242
|
-
black_chess.side # => :second
|
245
|
+
# All lowercase letters are second player
|
246
|
+
lower_case_letters.all?(&:second_player?) # => true
|
243
247
|
|
244
|
-
#
|
245
|
-
|
246
|
-
|
248
|
+
# Letter families are related by case
|
249
|
+
letter_a_first = Sashite::Snn.parse("A")
|
250
|
+
letter_a_second = Sashite::Snn.parse("a")
|
247
251
|
|
248
|
-
#
|
249
|
-
|
250
|
-
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
|
251
254
|
```
|
252
255
|
|
253
256
|
### Immutable Transformations
|
257
|
+
|
254
258
|
```ruby
|
255
259
|
# All transformations return new instances
|
256
|
-
original = Sashite::Snn.style(:
|
260
|
+
original = Sashite::Snn.style(:C, :first)
|
257
261
|
flipped = original.flip
|
258
|
-
|
262
|
+
changed_letter = original.with_letter(:S)
|
259
263
|
|
260
264
|
# Original style is never modified
|
261
|
-
|
262
|
-
|
263
|
-
|
265
|
+
original.to_s # => "C" (unchanged)
|
266
|
+
flipped.to_s # => "c"
|
267
|
+
changed_letter.to_s # => "S"
|
264
268
|
|
265
269
|
# Transformations can be chained
|
266
|
-
result = original.flip.
|
267
|
-
|
270
|
+
result = original.flip.with_letter(:M).flip
|
271
|
+
result.to_s # => "M"
|
268
272
|
```
|
269
273
|
|
270
274
|
### Game Configuration Management
|
275
|
+
|
271
276
|
```ruby
|
272
277
|
class GameConfiguration
|
273
278
|
def initialize
|
274
279
|
@player_styles = {}
|
275
280
|
end
|
276
281
|
|
277
|
-
def set_player_style(player,
|
282
|
+
def set_player_style(player, letter)
|
278
283
|
side = player == :white ? :first : :second
|
279
|
-
@player_styles[player] = Sashite::Snn.style(
|
284
|
+
@player_styles[player] = Sashite::Snn.style(letter, side)
|
280
285
|
end
|
281
286
|
|
282
287
|
def get_player_style(player)
|
283
288
|
@player_styles[player]
|
284
289
|
end
|
285
290
|
|
286
|
-
def
|
291
|
+
def cross_family_match?
|
287
292
|
return false if @player_styles.size < 2
|
288
293
|
|
289
294
|
styles = @player_styles.values
|
290
|
-
!styles.all? { |style| style.
|
295
|
+
!styles.all? { |style| style.same_letter?(styles.first) }
|
291
296
|
end
|
292
297
|
|
293
|
-
def
|
294
|
-
|
295
|
-
|
296
|
-
style_names = @player_styles.values.map(&:name).uniq
|
297
|
-
style_names.size > 1
|
298
|
+
def same_family_match?
|
299
|
+
!cross_family_match?
|
298
300
|
end
|
299
301
|
end
|
300
302
|
|
301
303
|
# Usage
|
302
304
|
config = GameConfiguration.new
|
303
|
-
config.set_player_style(:white, :Chess
|
304
|
-
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
|
305
307
|
|
306
|
-
|
307
|
-
puts config.style_mismatch? # => true
|
308
|
+
config.cross_family_match? # => true
|
308
309
|
|
309
310
|
white_style = config.get_player_style(:white)
|
310
|
-
|
311
|
+
white_style.to_s # => "C"
|
311
312
|
```
|
312
313
|
|
313
314
|
### Style Analysis
|
315
|
+
|
314
316
|
```ruby
|
315
317
|
def analyze_styles(snns)
|
316
318
|
styles = snns.map { |snn| Sashite::Snn.parse(snn) }
|
@@ -318,29 +320,30 @@ def analyze_styles(snns)
|
|
318
320
|
{
|
319
321
|
total: styles.size,
|
320
322
|
by_side: styles.group_by(&:side),
|
321
|
-
|
322
|
-
|
323
|
-
|
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
|
324
326
|
}
|
325
327
|
end
|
326
328
|
|
327
|
-
snns = %w[
|
329
|
+
snns = %w[C c S s X x]
|
328
330
|
analysis = analyze_styles(snns)
|
329
|
-
|
330
|
-
|
331
|
-
|
331
|
+
analysis[:by_side][:first].size # => 3
|
332
|
+
analysis[:unique_letters] # => 3
|
333
|
+
analysis[:cross_family] # => true
|
332
334
|
```
|
333
335
|
|
334
|
-
### Tournament Style
|
336
|
+
### Tournament Style Registry
|
337
|
+
|
335
338
|
```ruby
|
336
339
|
class TournamentStyleRegistry
|
337
340
|
def initialize
|
338
341
|
@registered_styles = Set.new
|
339
342
|
end
|
340
343
|
|
341
|
-
def
|
342
|
-
# Register both sides of a
|
343
|
-
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)
|
344
347
|
second_player_style = first_player_style.flip
|
345
348
|
|
346
349
|
@registered_styles.add(first_player_style)
|
@@ -359,94 +362,62 @@ class TournamentStyleRegistry
|
|
359
362
|
@registered_styles.select { |style| style.side == side }
|
360
363
|
end
|
361
364
|
|
362
|
-
def
|
363
|
-
@registered_styles.map
|
365
|
+
def supported_families
|
366
|
+
@registered_styles.map { |s| s.letter.to_s.upcase }.uniq.sort
|
364
367
|
end
|
365
368
|
end
|
366
369
|
|
367
370
|
# Usage
|
368
371
|
registry = TournamentStyleRegistry.new
|
369
|
-
registry.
|
370
|
-
registry.
|
372
|
+
registry.register_letter(:C)
|
373
|
+
registry.register_letter(:S)
|
371
374
|
|
372
|
-
chess_white = Sashite::Snn.parse("
|
373
|
-
shogi_black = Sashite::Snn.parse("
|
375
|
+
chess_white = Sashite::Snn.parse("C")
|
376
|
+
shogi_black = Sashite::Snn.parse("s")
|
374
377
|
|
375
|
-
|
376
|
-
|
378
|
+
registry.valid_pairing?(chess_white, shogi_black) # => true
|
379
|
+
registry.supported_families # => ["C", "S"]
|
377
380
|
```
|
378
381
|
|
379
382
|
## Protocol Mapping
|
380
383
|
|
381
|
-
Following the [
|
384
|
+
Following the [Sashité Protocol](https://sashite.dev/protocol/):
|
382
385
|
|
383
386
|
| Protocol Attribute | SNN Encoding | Examples | Notes |
|
384
387
|
|-------------------|--------------|----------|-------|
|
385
|
-
| **Style** |
|
386
|
-
| **
|
387
|
-
|
388
|
-
**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.
|
389
|
-
|
390
|
-
**Canonical principle**: Identical styles must have identical SNN representations.
|
391
|
-
|
392
|
-
## Properties
|
393
|
-
|
394
|
-
* **Rule-Agnostic**: Independent of specific game mechanics
|
395
|
-
* **Cross-Style Support**: Enables multi-tradition gaming environments
|
396
|
-
* **Canonical Representation**: Consistent naming for equivalent styles
|
397
|
-
* **Name Normalization**: Consistent proper capitalization representation internally
|
398
|
-
* **Immutable**: All style instances are frozen and transformations return new objects
|
399
|
-
* **Functional**: Pure functions with no side effects
|
400
|
-
|
401
|
-
## Implementation Notes
|
402
|
-
|
403
|
-
### Name Normalization Convention
|
404
|
-
|
405
|
-
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 |
|
406
390
|
|
407
|
-
|
408
|
-
2. **Input Flexibility**: Both `"CHESS"` and `"chess"` are valid input during parsing
|
409
|
-
3. **Case Semantics**: Input case determines the `side` attribute, not the `name`
|
410
|
-
4. **Display Logic**: Output case is computed from `side` during rendering
|
411
|
-
|
412
|
-
This design ensures:
|
413
|
-
- Consistent internal representation regardless of input format
|
414
|
-
- Clear separation between style identity (name) and ownership (side)
|
415
|
-
- Predictable behavior when comparing styles of the same name
|
416
|
-
|
417
|
-
### Example Flow
|
418
|
-
|
419
|
-
```ruby
|
420
|
-
# Input: "chess" (lowercase)
|
421
|
-
# ↓ Parsing
|
422
|
-
# name: :Chess (normalized with proper capitalization)
|
423
|
-
# side: :second (inferred from lowercase input)
|
424
|
-
# ↓ Display
|
425
|
-
# SNN: "chess" (final representation)
|
426
|
-
```
|
391
|
+
## System Constraints
|
427
392
|
|
428
|
-
|
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
|
429
397
|
|
430
|
-
##
|
398
|
+
## Design Properties
|
431
399
|
|
432
|
-
- **
|
433
|
-
- **
|
434
|
-
- **
|
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
|
435
406
|
|
436
407
|
## Related Specifications
|
437
408
|
|
438
|
-
- [
|
439
|
-
- [
|
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
|
440
413
|
- [PNN](https://sashite.dev/specs/pnn/) - Piece Name Notation (style-aware piece representation)
|
441
|
-
- [
|
442
|
-
- [HAND](https://sashite.dev/specs/hand/) - Reserve location notation
|
443
|
-
- [PMN](https://sashite.dev/specs/pmn/) - Portable Move Notation
|
414
|
+
- [QPI](https://sashite.dev/specs/qpi/) - Qualified Piece Identifier
|
444
415
|
|
445
416
|
## Documentation
|
446
417
|
|
447
418
|
- [Official SNN Specification v1.0.0](https://sashite.dev/specs/snn/1.0.0/)
|
448
419
|
- [SNN Examples Documentation](https://sashite.dev/specs/snn/1.0.0/examples/)
|
449
|
-
- [
|
420
|
+
- [Sashité Protocol Foundation](https://sashite.dev/protocol/)
|
450
421
|
- [API Documentation](https://rubydoc.info/github/sashite/snn.rb/main)
|
451
422
|
|
452
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,26 +49,24 @@ 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] parsed style object with
|
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 Parse SNN strings with case
|
59
|
-
# Sashite::Snn::Style.parse("
|
60
|
-
# Sashite::Snn::Style.parse("
|
61
|
-
# Sashite::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
|
70
65
|
|
71
|
-
#
|
72
|
-
|
66
|
+
# Use the letter directly as symbol
|
67
|
+
style_letter = string_value.to_sym
|
73
68
|
|
74
|
-
new(
|
69
|
+
new(style_letter, style_side)
|
75
70
|
end
|
76
71
|
|
77
72
|
# Check if a string is a valid SNN notation
|
@@ -80,9 +75,9 @@ module Sashite
|
|
80
75
|
# @return [Boolean] true if valid SNN, false otherwise
|
81
76
|
#
|
82
77
|
# @example Validate SNN strings
|
83
|
-
# Sashite::Snn::Style.valid?("
|
84
|
-
# Sashite::Snn::Style.valid?("
|
85
|
-
# Sashite::Snn::Style.valid?("
|
78
|
+
# Sashite::Snn::Style.valid?("C") # => true
|
79
|
+
# Sashite::Snn::Style.valid?("c") # => true
|
80
|
+
# Sashite::Snn::Style.valid?("CHESS") # => false (multi-character)
|
86
81
|
def self.valid?(snn_string)
|
87
82
|
return false unless snn_string.is_a?(::String)
|
88
83
|
|
@@ -91,48 +86,53 @@ module Sashite
|
|
91
86
|
|
92
87
|
# Convert the style to its SNN string representation
|
93
88
|
#
|
94
|
-
# @return [String] SNN notation string
|
95
|
-
# @example Display
|
96
|
-
# style.to_s # => "
|
97
|
-
# style.to_s # => "
|
98
|
-
# 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)
|
99
94
|
def to_s
|
100
|
-
|
95
|
+
letter.to_s
|
101
96
|
end
|
102
97
|
|
103
98
|
# Create a new style with opposite ownership (side)
|
104
99
|
#
|
105
100
|
# @return [Style] new immutable style instance with flipped side
|
106
101
|
# @example Flip player sides
|
107
|
-
# style.flip # (:
|
102
|
+
# style.flip # (:C, :first) => (:c, :second)
|
108
103
|
def flip
|
109
|
-
|
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)
|
110
106
|
end
|
111
107
|
|
112
|
-
# Create a new style with a different
|
108
|
+
# Create a new style with a different letter (keeping same side)
|
113
109
|
#
|
114
|
-
# @param
|
115
|
-
# @return [Style] new immutable style instance with different
|
116
|
-
# @example Change style
|
117
|
-
# style.
|
118
|
-
def
|
119
|
-
self.class.
|
120
|
-
return self if
|
121
|
-
|
122
|
-
|
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
|
117
|
+
|
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)
|
123
121
|
end
|
124
122
|
|
125
|
-
# Create a new style with a different side (keeping same
|
123
|
+
# Create a new style with a different side (keeping same letter family)
|
126
124
|
#
|
127
125
|
# @param new_side [Symbol] :first or :second
|
128
126
|
# @return [Style] new immutable style instance with different side
|
129
127
|
# @example Change player side
|
130
|
-
# style.with_side(:second) # (:
|
128
|
+
# style.with_side(:second) # (:C, :first) => (:c, :second)
|
131
129
|
def with_side(new_side)
|
132
130
|
self.class.validate_side(new_side)
|
133
131
|
return self if side == new_side
|
134
132
|
|
135
|
-
|
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)
|
136
136
|
end
|
137
137
|
|
138
138
|
# Check if the style belongs to the first player
|
@@ -149,16 +149,16 @@ module Sashite
|
|
149
149
|
side == SECOND_PLAYER
|
150
150
|
end
|
151
151
|
|
152
|
-
# Check if this style has the same
|
152
|
+
# Check if this style has the same letter family as another
|
153
153
|
#
|
154
154
|
# @param other [Style] style to compare with
|
155
|
-
# @return [Boolean] true if both styles
|
156
|
-
# @example Compare style
|
157
|
-
#
|
158
|
-
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)
|
159
159
|
return false unless other.is_a?(self.class)
|
160
160
|
|
161
|
-
|
161
|
+
letter.to_s.upcase == other.letter.to_s.upcase
|
162
162
|
end
|
163
163
|
|
164
164
|
# Check if this style belongs to the same side as another
|
@@ -174,11 +174,11 @@ module Sashite
|
|
174
174
|
# Custom equality comparison
|
175
175
|
#
|
176
176
|
# @param other [Object] object to compare with
|
177
|
-
# @return [Boolean] true if both objects are styles with identical
|
177
|
+
# @return [Boolean] true if both objects are styles with identical letter and side
|
178
178
|
def ==(other)
|
179
179
|
return false unless other.is_a?(self.class)
|
180
180
|
|
181
|
-
|
181
|
+
letter == other.letter && side == other.side
|
182
182
|
end
|
183
183
|
|
184
184
|
# Alias for == to ensure Set functionality works correctly
|
@@ -186,19 +186,19 @@ module Sashite
|
|
186
186
|
|
187
187
|
# Custom hash implementation for use in collections
|
188
188
|
#
|
189
|
-
# @return [Integer] hash value based on class,
|
189
|
+
# @return [Integer] hash value based on class, letter, and side
|
190
190
|
def hash
|
191
|
-
[self.class,
|
191
|
+
[self.class, letter, side].hash
|
192
192
|
end
|
193
193
|
|
194
|
-
# Validate that the
|
194
|
+
# Validate that the letter is a valid single ASCII letter symbol
|
195
195
|
#
|
196
|
-
# @param
|
196
|
+
# @param letter [Symbol] the letter to validate
|
197
197
|
# @raise [ArgumentError] if invalid
|
198
|
-
def self.
|
199
|
-
return if
|
198
|
+
def self.validate_letter(letter)
|
199
|
+
return if valid_letter?(letter)
|
200
200
|
|
201
|
-
raise ::ArgumentError, format(
|
201
|
+
raise ::ArgumentError, format(ERROR_INVALID_LETTER, letter.inspect)
|
202
202
|
end
|
203
203
|
|
204
204
|
# Validate that the side is a valid symbol
|
@@ -211,44 +211,31 @@ module Sashite
|
|
211
211
|
raise ::ArgumentError, format(ERROR_INVALID_SIDE, side.inspect)
|
212
212
|
end
|
213
213
|
|
214
|
-
# Check if a
|
214
|
+
# Check if a letter is valid (single ASCII letter symbol)
|
215
215
|
#
|
216
|
-
# @param
|
216
|
+
# @param letter [Object] the letter to check
|
217
217
|
# @return [Boolean] true if valid
|
218
|
-
def self.
|
219
|
-
return false unless
|
218
|
+
def self.valid_letter?(letter)
|
219
|
+
return false unless letter.is_a?(::Symbol)
|
220
220
|
|
221
|
-
|
222
|
-
return false if
|
221
|
+
letter_string = letter.to_s
|
222
|
+
return false if letter_string.empty?
|
223
223
|
|
224
|
-
# Must
|
225
|
-
|
226
|
-
end
|
227
|
-
|
228
|
-
# Normalize identifier to proper capitalization symbol
|
229
|
-
#
|
230
|
-
# @param identifier [String] the identifier to normalize
|
231
|
-
# @return [Symbol] normalized name symbol
|
232
|
-
def self.normalize_name(identifier)
|
233
|
-
# Convert to proper capitalization: first letter uppercase, rest lowercase
|
234
|
-
normalized = identifier.downcase
|
235
|
-
normalized[0] = normalized[0].upcase if normalized.length > 0
|
236
|
-
normalized.to_sym
|
224
|
+
# Must be exactly one ASCII letter
|
225
|
+
letter_string.match?(SNN_PATTERN)
|
237
226
|
end
|
238
227
|
|
239
|
-
#
|
228
|
+
# Validate SNN string format
|
240
229
|
#
|
241
|
-
# @param string [String] string to
|
242
|
-
# @
|
243
|
-
|
244
|
-
|
245
|
-
matches = SNN_PATTERN.match(string)
|
246
|
-
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)
|
247
234
|
|
248
235
|
raise ::ArgumentError, format(ERROR_INVALID_SNN, string)
|
249
236
|
end
|
250
237
|
|
251
|
-
private_class_method :
|
238
|
+
private_class_method :valid_letter?, :validate_snn_string
|
252
239
|
|
253
240
|
private
|
254
241
|
|
data/lib/sashite/snn.rb
CHANGED
@@ -6,19 +6,19 @@ 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
|
@@ -28,8 +28,10 @@ module Sashite
|
|
28
28
|
# @return [Boolean] true if valid SNN, false otherwise
|
29
29
|
#
|
30
30
|
# @example Validate various SNN formats
|
31
|
-
# Sashite::Snn.valid?("
|
32
|
-
# Sashite::Snn.valid?("
|
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)
|
33
35
|
def self.valid?(snn_string)
|
34
36
|
Style.valid?(snn_string)
|
35
37
|
end
|
@@ -37,27 +39,27 @@ module Sashite
|
|
37
39
|
# Parse an SNN string into a Style object
|
38
40
|
#
|
39
41
|
# @param snn_string [String] SNN notation string
|
40
|
-
# @return [Snn::Style] parsed style object with
|
42
|
+
# @return [Snn::Style] parsed style object with letter and side attributes
|
41
43
|
# @raise [ArgumentError] if the SNN string is invalid
|
42
44
|
# @example Parse different SNN formats
|
43
|
-
# Sashite::Snn.parse("
|
44
|
-
# Sashite::Snn.parse("
|
45
|
-
# Sashite::Snn.parse("
|
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>
|
46
48
|
def self.parse(snn_string)
|
47
49
|
Style.parse(snn_string)
|
48
50
|
end
|
49
51
|
|
50
52
|
# Create a new style instance
|
51
53
|
#
|
52
|
-
# @param
|
54
|
+
# @param letter [Symbol] style letter (single ASCII letter as symbol)
|
53
55
|
# @param side [Symbol] player side (:first or :second)
|
54
56
|
# @return [Snn::Style] new immutable style instance
|
55
57
|
# @raise [ArgumentError] if parameters are invalid
|
56
58
|
# @example Create styles directly
|
57
|
-
# Sashite::Snn.style(:
|
58
|
-
# Sashite::Snn.style(:
|
59
|
-
def self.style(
|
60
|
-
Style.new(
|
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)
|
61
63
|
end
|
62
64
|
end
|
63
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:
|