sashite-snn 1.0.0 → 1.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 +385 -166
- data/lib/sashite/snn/style.rb +189 -84
- data/lib/sashite/snn.rb +50 -28
- data/lib/sashite-snn.rb +9 -13
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c75eb6af8fa8c0ff0a9791e0ecc0fe49b7e6c0827733e96f2e37e552ddca50b0
|
4
|
+
data.tar.gz: 2ac49000ca7ff3f2ae01e0a7d240f2fb99d5cab34a915b9799a5088c7461b4f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ec03703cf421329344a597f91d4157e4b44f1d5347cb4f8eea1a5c9e79e6800ad0880f6d9a8ad003df18d48ab5afb084d6526ffcbf6df14323b21ee751fc54e
|
7
|
+
data.tar.gz: e95c9a8e58fbe6254e546456a5de92a79de1eeb33688292ec1c58f58ba30b6a94357a61d1396a1a639e0051c772b888af355e421a3726b2faf3ae7179450afd6
|
data/README.md
CHANGED
@@ -5,13 +5,13 @@
|
|
5
5
|

|
6
6
|
[](https://github.com/sashite/snn.rb/raw/main/LICENSE.md)
|
7
7
|
|
8
|
-
> **SNN** (Style Name Notation)
|
8
|
+
> **SNN** (Style Name Notation) implementation for the Ruby language.
|
9
9
|
|
10
10
|
## What is SNN?
|
11
11
|
|
12
|
-
SNN (Style Name Notation)
|
12
|
+
SNN (Style Name Notation) provides a rule-agnostic format for identifying styles in abstract strategy board games. SNN uses standardized naming conventions with case-based side encoding, enabling clear distinction between different traditions in multi-style gaming environments.
|
13
13
|
|
14
|
-
This gem implements the [SNN Specification v1.0.0](https://sashite.dev/
|
14
|
+
This gem implements the [SNN Specification v1.0.0](https://sashite.dev/specs/snn/1.0.0/), providing a modern Ruby interface with immutable style objects and functional programming principles.
|
15
15
|
|
16
16
|
## Installation
|
17
17
|
|
@@ -26,236 +26,455 @@ Or install manually:
|
|
26
26
|
gem install sashite-snn
|
27
27
|
```
|
28
28
|
|
29
|
-
##
|
29
|
+
## Usage
|
30
30
|
|
31
|
-
|
31
|
+
```ruby
|
32
|
+
require "sashite/snn"
|
32
33
|
|
33
|
-
|
34
|
-
|
34
|
+
# Parse SNN strings into style objects
|
35
|
+
style = Sashite::Snn.parse("CHESS") # => #<Snn::Style name=:Chess side=:first>
|
36
|
+
style.to_s # => "CHESS"
|
37
|
+
style.name # => :Chess
|
38
|
+
style.side # => :first
|
39
|
+
|
40
|
+
# Create styles directly
|
41
|
+
style = Sashite::Snn.style(:Chess, :first) # => #<Snn::Style name=:Chess side=:first>
|
42
|
+
style = Sashite::Snn::Style.new(:Shogi, :second) # => #<Snn::Style name=:Shogi side=:second>
|
43
|
+
|
44
|
+
# Validate SNN strings
|
45
|
+
Sashite::Snn.valid?("CHESS") # => true
|
46
|
+
Sashite::Snn.valid?("chess") # => true
|
47
|
+
Sashite::Snn.valid?("Chess") # => false (mixed case)
|
48
|
+
Sashite::Snn.valid?("123") # => false (must start with letter)
|
49
|
+
|
50
|
+
# Side manipulation (returns new immutable instances)
|
51
|
+
black_chess = style.flip # => #<Snn::Style name=:Chess side=:second>
|
52
|
+
black_chess.to_s # => "chess"
|
53
|
+
|
54
|
+
# Name manipulation
|
55
|
+
shogi_style = style.with_name(:Shogi) # => #<Snn::Style name=:Shogi side=:first>
|
56
|
+
shogi_style.to_s # => "SHOGI"
|
57
|
+
|
58
|
+
# Side queries
|
59
|
+
style.first_player? # => true
|
60
|
+
black_chess.second_player? # => true
|
61
|
+
|
62
|
+
# Name and side comparison
|
63
|
+
chess1 = Sashite::Snn.parse("CHESS")
|
64
|
+
chess2 = Sashite::Snn.parse("chess")
|
65
|
+
shogi = Sashite::Snn.parse("SHOGI")
|
66
|
+
|
67
|
+
chess1.same_name?(chess2) # => true (both chess)
|
68
|
+
chess1.same_side?(shogi) # => true (both first player)
|
69
|
+
chess1.same_name?(shogi) # => false (different styles)
|
70
|
+
|
71
|
+
# Functional transformations can be chained
|
72
|
+
black_shogi = Sashite::Snn.parse("CHESS").with_name(:Shogi).flip
|
73
|
+
black_shogi.to_s # => "shogi"
|
35
74
|
```
|
36
75
|
|
37
|
-
|
38
|
-
- The identifier starts with an alphabetic character (`A-Z` for uppercase, `a-z` for lowercase)
|
39
|
-
- Subsequent characters may include alphabetic characters and digits (`A-Z`, `0-9` for uppercase styles; `a-z`, `0-9` for lowercase styles)
|
40
|
-
- **Uppercase** format denotes styles belonging to the first player
|
41
|
-
- **Lowercase** format denotes styles belonging to the second player
|
42
|
-
- The entire identifier must be entirely uppercase or entirely lowercase
|
76
|
+
## Format Specification
|
43
77
|
|
44
|
-
|
78
|
+
### Structure
|
79
|
+
```
|
80
|
+
<style-identifier>
|
81
|
+
```
|
45
82
|
|
46
|
-
###
|
83
|
+
### Components
|
47
84
|
|
48
|
-
|
85
|
+
- **Identifier**: Alphanumeric string starting with a letter
|
86
|
+
- Uppercase: First player styles (`CHESS`, `SHOGI`, `XIANGQI`)
|
87
|
+
- Lowercase: Second player styles (`chess`, `shogi`, `xiangqi`)
|
88
|
+
- **Case Consistency**: Entire identifier must be uppercase or lowercase (no mixed case)
|
49
89
|
|
90
|
+
### Regular Expression
|
50
91
|
```ruby
|
51
|
-
|
92
|
+
/\A([A-Z][A-Z0-9]*|[a-z][a-z0-9]*)\z/
|
93
|
+
```
|
52
94
|
|
53
|
-
|
54
|
-
|
55
|
-
|
95
|
+
### Examples
|
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
|
56
102
|
|
57
|
-
|
58
|
-
# => #<Sashite::Snn::Style:0x... @identifier="shogi">
|
103
|
+
## Game Examples
|
59
104
|
|
60
|
-
|
61
|
-
|
62
|
-
|
105
|
+
### Classic Styles
|
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
|
+
```
|
63
118
|
|
64
|
-
|
65
|
-
|
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"
|
125
|
+
|
126
|
+
# King of the Hill Chess
|
127
|
+
koth = Sashite::Snn.style(:Koth, :first)
|
128
|
+
koth.to_s # => "KOTH"
|
129
|
+
|
130
|
+
# Three-Check Chess
|
131
|
+
threecheck = Sashite::Snn.style(:Threecheck, :first)
|
66
132
|
```
|
67
133
|
|
68
|
-
###
|
134
|
+
### Shōgi Variants
|
135
|
+
```ruby
|
136
|
+
# Traditional Shōgi
|
137
|
+
standard_shogi = Sashite::Snn.style(:Shogi, :first)
|
138
|
+
|
139
|
+
# Mini Shōgi
|
140
|
+
mini_shogi = Sashite::Snn.style(:Minishogi, :first)
|
69
141
|
|
70
|
-
|
142
|
+
# Chu Shōgi
|
143
|
+
chu_shogi = Sashite::Snn.style(:Chushogi, :first)
|
144
|
+
```
|
71
145
|
|
146
|
+
### Multi-Style Gaming
|
72
147
|
```ruby
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
when :SHOGI then setup_shogi_game
|
148
|
+
# Cross-tradition match
|
149
|
+
def create_hybrid_match
|
150
|
+
styles = [
|
151
|
+
Sashite::Snn.style(:Chess, :first), # White uses chess pieces
|
152
|
+
Sashite::Snn.style(:Shogi, :second) # Black uses shōgi pieces
|
153
|
+
]
|
154
|
+
|
155
|
+
# Each player uses their preferred piece style
|
156
|
+
styles
|
157
|
+
end
|
158
|
+
|
159
|
+
# Style compatibility check
|
160
|
+
def compatible_styles?(style1, style2)
|
161
|
+
# Styles are compatible if they have different sides
|
162
|
+
!style1.same_side?(style2)
|
89
163
|
end
|
164
|
+
|
165
|
+
chess_white = Sashite::Snn.parse("CHESS")
|
166
|
+
shogi_black = Sashite::Snn.parse("shogi")
|
167
|
+
puts compatible_styles?(chess_white, shogi_black) # => true
|
90
168
|
```
|
91
169
|
|
92
|
-
|
170
|
+
## API Reference
|
93
171
|
|
94
|
-
|
172
|
+
### Main Module Methods
|
95
173
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
first_player_style.second_player? # => false
|
174
|
+
- `Sashite::Snn.valid?(snn_string)` - Check if string is valid SNN notation
|
175
|
+
- `Sashite::Snn.parse(snn_string)` - Parse SNN string into Style object
|
176
|
+
- `Sashite::Snn.style(name, side)` - Create style instance directly
|
100
177
|
|
101
|
-
|
102
|
-
second_player_style.first_player? # => false
|
103
|
-
second_player_style.second_player? # => true
|
104
|
-
```
|
178
|
+
### Style Class
|
105
179
|
|
106
|
-
|
180
|
+
#### Creation and Parsing
|
181
|
+
- `Sashite::Snn::Style.new(name, side)` - Create style instance
|
182
|
+
- `Sashite::Snn::Style.parse(snn_string)` - Parse SNN string (same as module method)
|
107
183
|
|
108
|
-
|
184
|
+
#### Attribute Access
|
185
|
+
- `#name` - Get style name (symbol with proper capitalization)
|
186
|
+
- `#side` - Get player side (:first or :second)
|
187
|
+
- `#to_s` - Convert to SNN string representation
|
188
|
+
|
189
|
+
#### Name and Case Handling
|
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:
|
109
192
|
|
110
193
|
```ruby
|
111
|
-
#
|
112
|
-
Sashite::Snn
|
113
|
-
Sashite::Snn
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
# Convenience method
|
122
|
-
Sashite::Snn.style("MINISHOGI") # ✓
|
123
|
-
|
124
|
-
# Check validity
|
125
|
-
Sashite::Snn.valid?("CHESS") # => true
|
126
|
-
Sashite::Snn.valid?("Chess") # => false (mixed case)
|
127
|
-
Sashite::Snn.valid?("123") # => false (must start with letter)
|
128
|
-
Sashite::Snn.valid?("") # => false (empty string)
|
129
|
-
|
130
|
-
# Invalid SNN strings raise ArgumentError
|
131
|
-
Sashite::Snn::Style.parse("") # ✗ ArgumentError
|
132
|
-
Sashite::Snn::Style.parse("Chess") # ✗ ArgumentError (mixed case)
|
133
|
-
Sashite::Snn::Style.parse("9CHESS") # ✗ ArgumentError (starts with digit)
|
134
|
-
Sashite::Snn::Style.parse("CHESS-960") # ✗ ArgumentError (contains hyphen)
|
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)
|
135
203
|
```
|
136
204
|
|
137
|
-
|
205
|
+
#### Side Queries
|
206
|
+
- `#first_player?` - Check if first player style
|
207
|
+
- `#second_player?` - Check if second player style
|
138
208
|
|
139
|
-
|
209
|
+
#### Transformations (immutable - return new instances)
|
210
|
+
- `#flip` - Switch player (change side)
|
211
|
+
- `#with_name(new_name)` - Create style with different name
|
212
|
+
- `#with_side(new_side)` - Create style with different side
|
213
|
+
|
214
|
+
#### Comparison Methods
|
215
|
+
- `#same_name?(other)` - Check if same style name
|
216
|
+
- `#same_side?(other)` - Check if same side
|
217
|
+
- `#==(other)` - Full equality comparison
|
218
|
+
|
219
|
+
### Constants
|
220
|
+
- `Sashite::Snn::SNN_REGEX` - Regular expression for SNN validation
|
221
|
+
|
222
|
+
## Advanced Usage
|
223
|
+
|
224
|
+
### Name Normalization Examples
|
140
225
|
|
141
226
|
```ruby
|
142
|
-
#
|
143
|
-
|
144
|
-
|
227
|
+
# Parsing different cases results in same name
|
228
|
+
white_chess = Sashite::Snn.parse("CHESS")
|
229
|
+
black_chess = Sashite::Snn.parse("chess")
|
145
230
|
|
146
|
-
#
|
147
|
-
|
148
|
-
|
231
|
+
# Names are normalized with proper capitalization
|
232
|
+
white_chess.name # => :Chess
|
233
|
+
black_chess.name # => :Chess (same name!)
|
149
234
|
|
150
|
-
#
|
151
|
-
|
152
|
-
|
235
|
+
# Sides are different
|
236
|
+
white_chess.side # => :first
|
237
|
+
black_chess.side # => :second
|
238
|
+
|
239
|
+
# Display follows side convention
|
240
|
+
white_chess.to_s # => "CHESS"
|
241
|
+
black_chess.to_s # => "chess"
|
242
|
+
|
243
|
+
# Same name, different sides
|
244
|
+
white_chess.same_name?(black_chess) # => true
|
245
|
+
white_chess.same_side?(black_chess) # => false
|
153
246
|
```
|
154
247
|
|
155
|
-
###
|
248
|
+
### Immutable Transformations
|
249
|
+
```ruby
|
250
|
+
# All transformations return new instances
|
251
|
+
original = Sashite::Snn.style(:Chess, :first)
|
252
|
+
flipped = original.flip
|
253
|
+
renamed = original.with_name(:Shogi)
|
254
|
+
|
255
|
+
# Original style is never modified
|
256
|
+
puts original.to_s # => "CHESS"
|
257
|
+
puts flipped.to_s # => "chess"
|
258
|
+
puts renamed.to_s # => "SHOGI"
|
259
|
+
|
260
|
+
# Transformations can be chained
|
261
|
+
result = original.flip.with_name(:Xiangqi)
|
262
|
+
puts result.to_s # => "xiangqi"
|
263
|
+
```
|
156
264
|
|
265
|
+
### Game Configuration Management
|
157
266
|
```ruby
|
158
|
-
|
159
|
-
|
160
|
-
|
267
|
+
class GameConfiguration
|
268
|
+
def initialize
|
269
|
+
@player_styles = {}
|
270
|
+
end
|
161
271
|
|
162
|
-
|
163
|
-
|
164
|
-
|
272
|
+
def set_player_style(player, style_name)
|
273
|
+
side = player == :white ? :first : :second
|
274
|
+
@player_styles[player] = Sashite::Snn.style(style_name, side)
|
275
|
+
end
|
165
276
|
|
166
|
-
|
167
|
-
|
168
|
-
|
277
|
+
def get_player_style(player)
|
278
|
+
@player_styles[player]
|
279
|
+
end
|
280
|
+
|
281
|
+
def style_mismatch?
|
282
|
+
return false if @player_styles.size < 2
|
283
|
+
|
284
|
+
styles = @player_styles.values
|
285
|
+
!styles.all? { |style| style.same_name?(styles.first) }
|
286
|
+
end
|
287
|
+
|
288
|
+
def cross_tradition_match?
|
289
|
+
return false if @player_styles.size < 2
|
290
|
+
|
291
|
+
style_names = @player_styles.values.map(&:name).uniq
|
292
|
+
style_names.size > 1
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# Usage
|
297
|
+
config = GameConfiguration.new
|
298
|
+
config.set_player_style(:white, :Chess)
|
299
|
+
config.set_player_style(:black, :Shogi)
|
300
|
+
|
301
|
+
puts config.cross_tradition_match? # => true
|
302
|
+
puts config.style_mismatch? # => true
|
303
|
+
|
304
|
+
white_style = config.get_player_style(:white)
|
305
|
+
puts white_style.to_s # => "CHESS"
|
169
306
|
```
|
170
307
|
|
171
|
-
###
|
308
|
+
### Style Analysis
|
309
|
+
```ruby
|
310
|
+
def analyze_styles(snns)
|
311
|
+
styles = snns.map { |snn| Sashite::Snn.parse(snn) }
|
312
|
+
|
313
|
+
{
|
314
|
+
total: styles.size,
|
315
|
+
by_side: styles.group_by(&:side),
|
316
|
+
by_name: styles.group_by(&:name),
|
317
|
+
unique_names: styles.map(&:name).uniq.size,
|
318
|
+
cross_tradition: styles.map(&:name).uniq.size > 1
|
319
|
+
}
|
320
|
+
end
|
321
|
+
|
322
|
+
snns = %w[CHESS chess SHOGI shogi XIANGQI xiangqi]
|
323
|
+
analysis = analyze_styles(snns)
|
324
|
+
puts analysis[:by_side][:first].size # => 3
|
325
|
+
puts analysis[:unique_names] # => 3
|
326
|
+
puts analysis[:cross_tradition] # => true
|
327
|
+
```
|
172
328
|
|
329
|
+
### Tournament Style Management
|
173
330
|
```ruby
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
331
|
+
class TournamentStyleRegistry
|
332
|
+
def initialize
|
333
|
+
@registered_styles = Set.new
|
334
|
+
end
|
335
|
+
|
336
|
+
def register_style(style_name)
|
337
|
+
# Register both sides of a style
|
338
|
+
first_player_style = Sashite::Snn.style(style_name, :first)
|
339
|
+
second_player_style = first_player_style.flip
|
340
|
+
|
341
|
+
@registered_styles.add(first_player_style)
|
342
|
+
@registered_styles.add(second_player_style)
|
343
|
+
|
344
|
+
[first_player_style, second_player_style]
|
345
|
+
end
|
346
|
+
|
347
|
+
def valid_pairing?(style1, style2)
|
348
|
+
@registered_styles.include?(style1) &&
|
349
|
+
@registered_styles.include?(style2) &&
|
350
|
+
!style1.same_side?(style2)
|
351
|
+
end
|
352
|
+
|
353
|
+
def available_styles_for_side(side)
|
354
|
+
@registered_styles.select { |style| style.side == side }
|
355
|
+
end
|
356
|
+
|
357
|
+
def supported_traditions
|
358
|
+
@registered_styles.map(&:name).uniq
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
# Usage
|
363
|
+
registry = TournamentStyleRegistry.new
|
364
|
+
registry.register_style(:Chess)
|
365
|
+
registry.register_style(:Shogi)
|
366
|
+
|
367
|
+
chess_white = Sashite::Snn.parse("CHESS")
|
368
|
+
shogi_black = Sashite::Snn.parse("shogi")
|
369
|
+
|
370
|
+
puts registry.valid_pairing?(chess_white, shogi_black) # => true
|
371
|
+
puts registry.supported_traditions # => [:Chess, :Shogi]
|
194
372
|
```
|
195
373
|
|
196
|
-
##
|
374
|
+
## Protocol Mapping
|
197
375
|
|
198
|
-
|
376
|
+
Following the [Game Protocol](https://sashite.dev/game-protocol/):
|
199
377
|
|
200
|
-
|
201
|
-
|
378
|
+
| Protocol Attribute | SNN Encoding | Examples | Notes |
|
379
|
+
|-------------------|--------------|----------|-------|
|
380
|
+
| **Style** | Alphanumeric identifier | `CHESS`, `SHOGI`, `XIANGQI` | Name is always stored as uppercase symbol |
|
381
|
+
| **Side** | Case encoding | `CHESS` = First player, `chess` = Second player | Case is determined by side during rendering |
|
202
382
|
|
203
|
-
|
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.
|
204
384
|
|
205
|
-
|
206
|
-
- `Sashite::Snn::Style.new(identifier)` - Create a new style instance
|
385
|
+
**Canonical principle**: Identical styles must have identical SNN representations.
|
207
386
|
|
208
|
-
|
387
|
+
## Properties
|
209
388
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
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
|
215
395
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
396
|
+
## Implementation Notes
|
397
|
+
|
398
|
+
### Name Normalization Convention
|
399
|
+
|
400
|
+
SNN follows a strict name normalization convention:
|
401
|
+
|
402
|
+
1. **Internal Storage**: All style names are stored with proper capitalization (first letter uppercase, rest lowercase)
|
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
|
220
406
|
|
221
|
-
|
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
|
222
411
|
|
223
|
-
|
224
|
-
* **Unambiguous identification**: Different piece styles can coexist without naming conflicts
|
225
|
-
* **Canonical representation**: Equivalent styles yield identical strings
|
226
|
-
* **Cross-style support**: Enables games where players use different piece traditions
|
227
|
-
* **Case consistency**: Each identifier is entirely uppercase or entirely lowercase
|
228
|
-
* **Fixed assignment**: Style assignment to players remains constant throughout a game
|
412
|
+
### Example Flow
|
229
413
|
|
230
|
-
|
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
|
+
```
|
231
422
|
|
232
|
-
|
233
|
-
* Players are distinguished by casing: **uppercase** for first player, **lowercase** for second player
|
234
|
-
* Style identifiers must start with an alphabetic character
|
235
|
-
* Subsequent characters may include alphabetic characters and digits
|
236
|
-
* Mixed casing is not permitted within a single identifier
|
237
|
-
* Style assignment to players remains **fixed throughout a game**
|
423
|
+
This ensures that `parse(snn).to_s == snn` for all valid SNN strings while maintaining internal consistency.
|
238
424
|
|
239
|
-
##
|
425
|
+
## System Constraints
|
240
426
|
|
241
|
-
|
427
|
+
- **Alphanumeric identifiers** starting with a letter
|
428
|
+
- **Exactly 2 players** (uppercase/lowercase distinction)
|
429
|
+
- **Case consistency** within each identifier (no mixed case)
|
242
430
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
431
|
+
## Related Specifications
|
432
|
+
|
433
|
+
- [Game Protocol](https://sashite.dev/game-protocol/) - Conceptual foundation for abstract strategy board games
|
434
|
+
- [PIN](https://sashite.dev/specs/pin/) - Piece Identifier Notation (ASCII piece representation)
|
435
|
+
- [PNN](https://sashite.dev/specs/pnn/) - Piece Name Notation (style-aware piece representation)
|
436
|
+
- [CELL](https://sashite.dev/specs/cell/) - Board position coordinates
|
437
|
+
- [HAND](https://sashite.dev/specs/hand/) - Reserve location notation
|
438
|
+
- [PMN](https://sashite.dev/specs/pmn/) - Portable Move Notation
|
249
439
|
|
250
440
|
## Documentation
|
251
441
|
|
252
|
-
- [Official SNN Specification](https://sashite.dev/
|
442
|
+
- [Official SNN Specification v1.0.0](https://sashite.dev/specs/snn/1.0.0/)
|
443
|
+
- [SNN Examples Documentation](https://sashite.dev/specs/snn/1.0.0/examples/)
|
444
|
+
- [Game Protocol Foundation](https://sashite.dev/game-protocol/)
|
253
445
|
- [API Documentation](https://rubydoc.info/github/sashite/snn.rb/main)
|
254
446
|
|
447
|
+
## Development
|
448
|
+
|
449
|
+
```sh
|
450
|
+
# Clone the repository
|
451
|
+
git clone https://github.com/sashite/snn.rb.git
|
452
|
+
cd snn.rb
|
453
|
+
|
454
|
+
# Install dependencies
|
455
|
+
bundle install
|
456
|
+
|
457
|
+
# Run tests
|
458
|
+
ruby test.rb
|
459
|
+
|
460
|
+
# Generate documentation
|
461
|
+
yard doc
|
462
|
+
```
|
463
|
+
|
464
|
+
## Contributing
|
465
|
+
|
466
|
+
1. Fork the repository
|
467
|
+
2. Create a feature branch (`git checkout -b feature/new-feature`)
|
468
|
+
3. Add tests for your changes
|
469
|
+
4. Ensure all tests pass (`ruby test.rb`)
|
470
|
+
5. Commit your changes (`git commit -am 'Add new feature'`)
|
471
|
+
6. Push to the branch (`git push origin feature/new-feature`)
|
472
|
+
7. Create a Pull Request
|
473
|
+
|
255
474
|
## License
|
256
475
|
|
257
|
-
|
476
|
+
Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
|
258
477
|
|
259
|
-
## About
|
478
|
+
## About
|
260
479
|
|
261
|
-
|
480
|
+
Maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of board game cultures.
|
data/lib/sashite/snn/style.rb
CHANGED
@@ -2,141 +2,246 @@
|
|
2
2
|
|
3
3
|
module Sashite
|
4
4
|
module Snn
|
5
|
-
# Represents a style
|
5
|
+
# Represents a style in SNN (Style Name Notation) format.
|
6
6
|
#
|
7
|
-
# A style
|
8
|
-
#
|
9
|
-
# -
|
10
|
-
# - Lowercase identifiers belong to the second player
|
7
|
+
# A style consists of an alphanumeric identifier with case-based side encoding:
|
8
|
+
# - Uppercase identifier: first player (CHESS, SHOGI, XIANGQI)
|
9
|
+
# - Lowercase identifier: second player (chess, shogi, xiangqi)
|
11
10
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
# chess_style = Sashite::Snn::Style.new("CHESS")
|
15
|
-
# shogi_style = Sashite::Snn::Style.new("SHOGI")
|
16
|
-
#
|
17
|
-
# # Second player styles (lowercase)
|
18
|
-
# makruk_style = Sashite::Snn::Style.new("makruk")
|
19
|
-
# chess960_style = Sashite::Snn::Style.new("chess960")
|
11
|
+
# All instances are immutable - transformation methods return new instances.
|
12
|
+
# This follows the Game Protocol's style model with Name and Side attributes.
|
20
13
|
class Style
|
21
|
-
#
|
22
|
-
|
14
|
+
# SNN validation pattern matching the specification
|
15
|
+
SNN_PATTERN = /\A(?<identifier>[A-Z][A-Z0-9]*|[a-z][a-z0-9]*)\z/
|
16
|
+
|
17
|
+
# Pattern for proper name capitalization (first letter uppercase, rest lowercase/digits)
|
18
|
+
PROPER_NAME_PATTERN = /\A[A-Z][a-z0-9]*\z/
|
19
|
+
|
20
|
+
# Player side constants
|
21
|
+
FIRST_PLAYER = :first
|
22
|
+
SECOND_PLAYER = :second
|
23
|
+
|
24
|
+
# Valid sides
|
25
|
+
VALID_SIDES = [FIRST_PLAYER, SECOND_PLAYER].freeze
|
26
|
+
|
27
|
+
# Error messages
|
28
|
+
ERROR_INVALID_SNN = "Invalid SNN string: %s"
|
29
|
+
ERROR_INVALID_NAME = "Name must be a symbol with proper capitalization (first letter uppercase, rest lowercase), got: %s"
|
30
|
+
ERROR_INVALID_SIDE = "Side must be :first or :second, got: %s"
|
31
|
+
|
32
|
+
# @return [Symbol] the style name (with proper capitalization)
|
33
|
+
attr_reader :name
|
34
|
+
|
35
|
+
# @return [Symbol] the player side (:first or :second)
|
36
|
+
attr_reader :side
|
23
37
|
|
24
38
|
# Create a new style instance
|
25
39
|
#
|
26
|
-
# @param
|
27
|
-
# @
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
def initialize(identifier)
|
33
|
-
raise ArgumentError, "Invalid SNN format: #{identifier.inspect}" unless Snn.valid?(identifier)
|
40
|
+
# @param name [Symbol] style name (with proper capitalization)
|
41
|
+
# @param side [Symbol] player side (:first or :second)
|
42
|
+
# @raise [ArgumentError] if parameters are invalid
|
43
|
+
def initialize(name, side)
|
44
|
+
self.class.validate_name(name)
|
45
|
+
self.class.validate_side(side)
|
34
46
|
|
35
|
-
@
|
47
|
+
@name = name
|
48
|
+
@side = side
|
36
49
|
|
37
50
|
freeze
|
38
51
|
end
|
39
52
|
|
40
|
-
# Parse
|
41
|
-
#
|
42
|
-
# @param snn_string [String] The SNN string to parse
|
43
|
-
# @return [Sashite::Snn::Style] A new style object
|
44
|
-
# @raise [ArgumentError] if the string is invalid SNN notation
|
53
|
+
# Parse an SNN string into a Style object
|
45
54
|
#
|
55
|
+
# @param snn_string [String] SNN notation string
|
56
|
+
# @return [Style] new style instance
|
57
|
+
# @raise [ArgumentError] if the SNN string is invalid
|
46
58
|
# @example
|
47
|
-
#
|
48
|
-
# # => #<
|
59
|
+
# Snn::Style.parse("CHESS") # => #<Snn::Style name=:Chess side=:first>
|
60
|
+
# Snn::Style.parse("chess") # => #<Snn::Style name=:Chess side=:second>
|
61
|
+
# Snn::Style.parse("SHOGI") # => #<Snn::Style name=:Shogi side=:first>
|
49
62
|
def self.parse(snn_string)
|
50
|
-
|
63
|
+
string_value = String(snn_string)
|
64
|
+
matches = match_pattern(string_value)
|
65
|
+
|
66
|
+
identifier = matches[:identifier]
|
67
|
+
|
68
|
+
# Determine side from case
|
69
|
+
style_side = identifier == identifier.upcase ? FIRST_PLAYER : SECOND_PLAYER
|
70
|
+
|
71
|
+
# Normalize name to proper capitalization
|
72
|
+
style_name = normalize_name(identifier)
|
73
|
+
|
74
|
+
new(style_name, style_side)
|
51
75
|
end
|
52
76
|
|
53
|
-
#
|
54
|
-
#
|
55
|
-
# @return [Boolean] true if the style is uppercase (first player), false otherwise
|
77
|
+
# Convert the style to its SNN string representation
|
56
78
|
#
|
79
|
+
# @return [String] SNN notation string
|
57
80
|
# @example
|
58
|
-
#
|
59
|
-
#
|
60
|
-
|
61
|
-
|
81
|
+
# style.to_s # => "CHESS"
|
82
|
+
# style.to_s # => "chess"
|
83
|
+
# style.to_s # => "SHOGI"
|
84
|
+
def to_s
|
85
|
+
first_player? ? name.to_s.upcase : name.to_s.downcase
|
62
86
|
end
|
63
87
|
|
64
|
-
#
|
65
|
-
#
|
66
|
-
# @return [Boolean] true if the style is lowercase (second player), false otherwise
|
88
|
+
# Create a new style with opposite ownership (side)
|
67
89
|
#
|
90
|
+
# @return [Style] new style instance with flipped side
|
68
91
|
# @example
|
69
|
-
#
|
70
|
-
|
71
|
-
|
72
|
-
lowercase?
|
92
|
+
# style.flip # (:Chess, :first) => (:Chess, :second)
|
93
|
+
def flip
|
94
|
+
self.class.new(name, opposite_side)
|
73
95
|
end
|
74
96
|
|
75
|
-
#
|
76
|
-
#
|
77
|
-
# @return [Boolean] true if the identifier is uppercase, false otherwise
|
97
|
+
# Create a new style with a different name (keeping same side)
|
78
98
|
#
|
99
|
+
# @param new_name [Symbol] new name (with proper capitalization)
|
100
|
+
# @return [Style] new style instance with different name
|
79
101
|
# @example
|
80
|
-
#
|
81
|
-
|
82
|
-
|
83
|
-
|
102
|
+
# style.with_name(:Shogi) # (:Chess, :first) => (:Shogi, :first)
|
103
|
+
def with_name(new_name)
|
104
|
+
self.class.validate_name(new_name)
|
105
|
+
return self if name == new_name
|
106
|
+
|
107
|
+
self.class.new(new_name, side)
|
84
108
|
end
|
85
109
|
|
86
|
-
#
|
87
|
-
#
|
88
|
-
# @return [Boolean] true if the identifier is lowercase, false otherwise
|
110
|
+
# Create a new style with a different side (keeping same name)
|
89
111
|
#
|
112
|
+
# @param new_side [Symbol] :first or :second
|
113
|
+
# @return [Style] new style instance with different side
|
90
114
|
# @example
|
91
|
-
#
|
92
|
-
|
93
|
-
|
94
|
-
|
115
|
+
# style.with_side(:second) # (:Chess, :first) => (:Chess, :second)
|
116
|
+
def with_side(new_side)
|
117
|
+
self.class.validate_side(new_side)
|
118
|
+
return self if side == new_side
|
119
|
+
|
120
|
+
self.class.new(name, new_side)
|
95
121
|
end
|
96
122
|
|
97
|
-
#
|
123
|
+
# Check if the style belongs to the first player
|
98
124
|
#
|
99
|
-
# @return [
|
100
|
-
|
101
|
-
|
102
|
-
# Sashite::Snn::Style.new("CHESS").to_s # => "CHESS"
|
103
|
-
def to_s
|
104
|
-
identifier
|
125
|
+
# @return [Boolean] true if first player
|
126
|
+
def first_player?
|
127
|
+
side == FIRST_PLAYER
|
105
128
|
end
|
106
129
|
|
107
|
-
#
|
130
|
+
# Check if the style belongs to the second player
|
108
131
|
#
|
109
|
-
# @return [
|
132
|
+
# @return [Boolean] true if second player
|
133
|
+
def second_player?
|
134
|
+
side == SECOND_PLAYER
|
135
|
+
end
|
136
|
+
|
137
|
+
# Check if this style has the same name as another
|
110
138
|
#
|
139
|
+
# @param other [Style] style to compare with
|
140
|
+
# @return [Boolean] true if same name
|
111
141
|
# @example
|
112
|
-
#
|
113
|
-
def
|
114
|
-
|
142
|
+
# chess1.same_name?(chess2) # (:Chess, :first) and (:Chess, :second) => true
|
143
|
+
def same_name?(other)
|
144
|
+
return false unless other.is_a?(self.class)
|
145
|
+
|
146
|
+
name == other.name
|
115
147
|
end
|
116
148
|
|
117
|
-
#
|
149
|
+
# Check if this style belongs to the same side as another
|
118
150
|
#
|
119
|
-
# @
|
120
|
-
|
121
|
-
|
151
|
+
# @param other [Style] style to compare with
|
152
|
+
# @return [Boolean] true if same side
|
153
|
+
def same_side?(other)
|
154
|
+
return false unless other.is_a?(self.class)
|
155
|
+
|
156
|
+
side == other.side
|
122
157
|
end
|
123
158
|
|
124
|
-
#
|
159
|
+
# Custom equality comparison
|
125
160
|
#
|
126
|
-
# @param other [Object]
|
127
|
-
# @return [Boolean] true if
|
161
|
+
# @param other [Object] object to compare with
|
162
|
+
# @return [Boolean] true if styles are equal
|
128
163
|
def ==(other)
|
129
|
-
other.is_a?(
|
164
|
+
return false unless other.is_a?(self.class)
|
165
|
+
|
166
|
+
name == other.name && side == other.side
|
130
167
|
end
|
131
168
|
|
132
|
-
# Alias for
|
169
|
+
# Alias for == to ensure Set functionality works correctly
|
133
170
|
alias eql? ==
|
134
171
|
|
135
|
-
#
|
172
|
+
# Custom hash implementation for use in collections
|
136
173
|
#
|
137
|
-
# @return [Integer]
|
174
|
+
# @return [Integer] hash value
|
138
175
|
def hash
|
139
|
-
[self.class,
|
176
|
+
[self.class, name, side].hash
|
177
|
+
end
|
178
|
+
|
179
|
+
# Validate that the name is a valid symbol with proper capitalization
|
180
|
+
#
|
181
|
+
# @param name [Symbol] the name to validate
|
182
|
+
# @raise [ArgumentError] if invalid
|
183
|
+
def self.validate_name(name)
|
184
|
+
return if valid_name?(name)
|
185
|
+
|
186
|
+
raise ::ArgumentError, format(ERROR_INVALID_NAME, name.inspect)
|
187
|
+
end
|
188
|
+
|
189
|
+
# Validate that the side is a valid symbol
|
190
|
+
#
|
191
|
+
# @param side [Symbol] the side to validate
|
192
|
+
# @raise [ArgumentError] if invalid
|
193
|
+
def self.validate_side(side)
|
194
|
+
return if VALID_SIDES.include?(side)
|
195
|
+
|
196
|
+
raise ::ArgumentError, format(ERROR_INVALID_SIDE, side.inspect)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Check if a name is valid (symbol with proper capitalization)
|
200
|
+
#
|
201
|
+
# @param name [Object] the name to check
|
202
|
+
# @return [Boolean] true if valid
|
203
|
+
def self.valid_name?(name)
|
204
|
+
return false unless name.is_a?(::Symbol)
|
205
|
+
|
206
|
+
name_string = name.to_s
|
207
|
+
return false if name_string.empty?
|
208
|
+
|
209
|
+
# Must match proper capitalization pattern
|
210
|
+
name_string.match?(PROPER_NAME_PATTERN)
|
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
|
222
|
+
end
|
223
|
+
|
224
|
+
# Match SNN pattern against string
|
225
|
+
#
|
226
|
+
# @param string [String] string to match
|
227
|
+
# @return [MatchData] match data
|
228
|
+
# @raise [ArgumentError] if string doesn't match
|
229
|
+
def self.match_pattern(string)
|
230
|
+
matches = SNN_PATTERN.match(string)
|
231
|
+
return matches if matches
|
232
|
+
|
233
|
+
raise ::ArgumentError, format(ERROR_INVALID_SNN, string)
|
234
|
+
end
|
235
|
+
|
236
|
+
private_class_method :valid_name?, :normalize_name, :match_pattern
|
237
|
+
|
238
|
+
private
|
239
|
+
|
240
|
+
# Get the opposite side
|
241
|
+
#
|
242
|
+
# @return [Symbol] the opposite side
|
243
|
+
def opposite_side
|
244
|
+
first_player? ? SECOND_PLAYER : FIRST_PLAYER
|
140
245
|
end
|
141
246
|
end
|
142
247
|
end
|
data/lib/sashite/snn.rb
CHANGED
@@ -3,47 +3,69 @@
|
|
3
3
|
require_relative "snn/style"
|
4
4
|
|
5
5
|
module Sashite
|
6
|
-
# Style Name Notation
|
6
|
+
# SNN (Style Name Notation) implementation for Ruby
|
7
7
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
8
|
+
# Provides a rule-agnostic format for identifying styles in abstract strategy board games.
|
9
|
+
# SNN uses standardized naming conventions with case-based side encoding, enabling clear
|
10
|
+
# distinction between different traditions in multi-style gaming environments.
|
11
11
|
#
|
12
|
-
#
|
12
|
+
# Format: <style-identifier>
|
13
|
+
# - Uppercase identifier: First player styles (CHESS, SHOGI, XIANGQI)
|
14
|
+
# - Lowercase identifier: Second player styles (chess, shogi, xiangqi)
|
15
|
+
# - Case consistency: Entire identifier must be uppercase or lowercase
|
16
|
+
#
|
17
|
+
# Examples:
|
18
|
+
# "CHESS" - First player chess style
|
19
|
+
# "chess" - Second player chess style
|
20
|
+
# "SHOGI" - First player shōgi style
|
21
|
+
# "shogi" - Second player shōgi style
|
22
|
+
#
|
23
|
+
# See: https://sashite.dev/specs/snn/1.0.0/
|
13
24
|
module Snn
|
14
|
-
# SNN validation
|
15
|
-
# Matches: uppercase
|
16
|
-
|
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/
|
17
28
|
|
18
|
-
# Check if a string is valid SNN notation
|
29
|
+
# Check if a string is a valid SNN notation
|
19
30
|
#
|
20
|
-
# @param
|
21
|
-
# @return [Boolean] true if
|
31
|
+
# @param snn [String] The string to validate
|
32
|
+
# @return [Boolean] true if valid SNN, false otherwise
|
22
33
|
#
|
23
34
|
# @example
|
24
|
-
# Sashite::Snn.valid?("CHESS")
|
25
|
-
# Sashite::Snn.valid?("
|
26
|
-
# Sashite::Snn.valid?("Chess")
|
27
|
-
# Sashite::Snn.valid?("123")
|
28
|
-
|
29
|
-
|
30
|
-
return false unless snn_string.is_a?(String)
|
31
|
-
return false if snn_string.empty?
|
35
|
+
# Sashite::Snn.valid?("CHESS") # => true
|
36
|
+
# Sashite::Snn.valid?("chess") # => true
|
37
|
+
# Sashite::Snn.valid?("Chess") # => false
|
38
|
+
# Sashite::Snn.valid?("123") # => false
|
39
|
+
def self.valid?(snn)
|
40
|
+
return false unless snn.is_a?(::String)
|
32
41
|
|
33
|
-
|
42
|
+
snn.match?(SNN_REGEX)
|
34
43
|
end
|
35
44
|
|
36
|
-
#
|
45
|
+
# Parse an SNN string into a Style object
|
37
46
|
#
|
38
|
-
# @param
|
39
|
-
# @return [
|
40
|
-
# @raise [ArgumentError] if the
|
47
|
+
# @param snn_string [String] SNN notation string
|
48
|
+
# @return [Snn::Style] new style instance
|
49
|
+
# @raise [ArgumentError] if the SNN string is invalid
|
50
|
+
# @example
|
51
|
+
# Sashite::Snn.parse("CHESS") # => #<Snn::Style name=:Chess side=:first>
|
52
|
+
# Sashite::Snn.parse("chess") # => #<Snn::Style name=:Chess side=:second>
|
53
|
+
# Sashite::Snn.parse("SHOGI") # => #<Snn::Style name=:Shogi side=:first>
|
54
|
+
def self.parse(snn_string)
|
55
|
+
Style.parse(snn_string)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Create a new style instance
|
41
59
|
#
|
60
|
+
# @param name [Symbol] style name (with proper capitalization)
|
61
|
+
# @param side [Symbol] player side (:first or :second)
|
62
|
+
# @return [Snn::Style] new style instance
|
63
|
+
# @raise [ArgumentError] if parameters are invalid
|
42
64
|
# @example
|
43
|
-
#
|
44
|
-
# # => #<
|
45
|
-
def self.style(
|
46
|
-
Style.new(
|
65
|
+
# Sashite::Snn.style(:Chess, :first) # => #<Snn::Style name=:Chess side=:first>
|
66
|
+
# Sashite::Snn.style(:Shogi, :second) # => #<Snn::Style name=:Shogi side=:second>
|
67
|
+
def self.style(name, side)
|
68
|
+
Style.new(name, side)
|
47
69
|
end
|
48
70
|
end
|
49
71
|
end
|
data/lib/sashite-snn.rb
CHANGED
@@ -1,18 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "sashite/snn"
|
4
|
+
|
3
5
|
# Sashité namespace for board game notation libraries
|
6
|
+
#
|
7
|
+
# Sashité provides a collection of libraries for representing and manipulating
|
8
|
+
# board game concepts according to the Game Protocol specifications.
|
9
|
+
#
|
10
|
+
# @see https://sashite.dev/game-protocol/ Game Protocol Foundation
|
11
|
+
# @see https://sashite.dev/specs/ Sashité Specifications
|
12
|
+
# @author Sashité
|
4
13
|
module Sashite
|
5
|
-
# Style Name Notation (SNN) implementation for Ruby
|
6
|
-
#
|
7
|
-
# SNN is a consistent and rule-agnostic format for identifying piece styles
|
8
|
-
# in abstract strategy board games. It provides unambiguous identification
|
9
|
-
# of piece styles by using standardized naming conventions, enabling clear
|
10
|
-
# distinction between different piece traditions, variants, or design
|
11
|
-
# approaches within multi-style gaming environments.
|
12
|
-
#
|
13
|
-
# @see https://sashite.dev/documents/snn/1.0.0/ SNN Specification v1.0.0
|
14
|
-
# @author Sashité
|
15
|
-
# @since 1.0.0
|
16
14
|
end
|
17
|
-
|
18
|
-
require_relative "sashite/snn"
|
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: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyril Kato
|
@@ -10,12 +10,12 @@ cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies: []
|
12
12
|
description: |
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
and hybrid gaming systems.
|
13
|
+
SNN (Style Name Notation) provides a rule-agnostic format for identifying styles
|
14
|
+
in abstract strategy board games. This gem implements the SNN Specification v1.0.0 with
|
15
|
+
a modern Ruby interface featuring immutable style objects and functional programming
|
16
|
+
principles. SNN uses standardized naming conventions with case-based side encoding,
|
17
|
+
enabling clear distinction between different traditions in multi-style gaming environments.
|
18
|
+
Perfect for cross-tradition matches, game engines, and hybrid gaming systems.
|
19
19
|
email: contact@cyril.email
|
20
20
|
executables: []
|
21
21
|
extensions: []
|
@@ -50,7 +50,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
50
|
- !ruby/object:Gem::Version
|
51
51
|
version: '0'
|
52
52
|
requirements: []
|
53
|
-
rubygems_version: 3.6.
|
53
|
+
rubygems_version: 3.6.9
|
54
54
|
specification_version: 4
|
55
|
-
summary: Style Name Notation
|
55
|
+
summary: SNN (Style Name Notation) implementation for Ruby with immutable style objects
|
56
56
|
test_files: []
|