sashite-snn 1.0.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f0f7d4b0f73aa8c0d12c8d7cdbcd910d667f6f87a2edf24ad2d97c177749d21
4
- data.tar.gz: 13e22095a02f4b7f51d0b93d602974a2d6e3571db880524330e897f584d08e00
3
+ metadata.gz: c75eb6af8fa8c0ff0a9791e0ecc0fe49b7e6c0827733e96f2e37e552ddca50b0
4
+ data.tar.gz: 2ac49000ca7ff3f2ae01e0a7d240f2fb99d5cab34a915b9799a5088c7461b4f3
5
5
  SHA512:
6
- metadata.gz: b6e76c3b7058d5ae8cf82cde6a67f7690bc87017b127da8eae4c1389dc0d67924956bad2d4347119fa4e870f63a2e76d57910a1184f6e761b23bad53d80aad9b
7
- data.tar.gz: 8b17d5c49b83f6408d5c99a28a5bf06929e6b1088daeea9b679b67eb24c8cd0dc8bf45db780ccda5497fb368fc9e8037b61003fa4c4d10d10797f41f4cde941d
6
+ metadata.gz: 9ec03703cf421329344a597f91d4157e4b44f1d5347cb4f8eea1a5c9e79e6800ad0880f6d9a8ad003df18d48ab5afb084d6526ffcbf6df14323b21ee751fc54e
7
+ data.tar.gz: e95c9a8e58fbe6254e546456a5de92a79de1eeb33688292ec1c58f58ba30b6a94357a61d1396a1a639e0051c772b888af355e421a3726b2faf3ae7179450afd6
data/README.md CHANGED
@@ -5,13 +5,13 @@
5
5
  ![Ruby](https://github.com/sashite/snn.rb/actions/workflows/main.yml/badge.svg?branch=main)
6
6
  [![License](https://img.shields.io/github/license/sashite/snn.rb?label=License&logo=github)](https://github.com/sashite/snn.rb/raw/main/LICENSE.md)
7
7
 
8
- > **SNN** (Style Name Notation) support for the Ruby language.
8
+ > **SNN** (Style Name Notation) implementation for the Ruby language.
9
9
 
10
10
  ## What is SNN?
11
11
 
12
- SNN (Style Name Notation) is a rule-agnostic format for identifying piece styles in abstract strategy board games, as defined in the [Game Protocol](https://sashite.dev/game-protocol/). It provides unambiguous identification of piece styles using standardized naming conventions with case-based player assignment, enabling clear distinction between different piece traditions in multi-style gaming environments.
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/specs/snn/1.0.0/), providing a Ruby interface for working with style identifiers through a clean and minimal API.
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,289 +26,416 @@ Or install manually:
26
26
  gem install sashite-snn
27
27
  ```
28
28
 
29
- ## SNN Format
29
+ ## Usage
30
30
 
31
- A SNN record consists of a style identifier that maps to the **Style attribute** from the [Game Protocol](https://sashite.dev/game-protocol/):
31
+ ```ruby
32
+ require "sashite/snn"
33
+
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"
74
+ ```
32
75
 
76
+ ## Format Specification
77
+
78
+ ### Structure
33
79
  ```
34
80
  <style-identifier>
35
81
  ```
36
82
 
37
- ### Grammar Specification
83
+ ### Components
84
+
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)
38
89
 
39
- ```bnf
40
- <snn> ::= <uppercase-style> | <lowercase-style>
90
+ ### Regular Expression
91
+ ```ruby
92
+ /\A([A-Z][A-Z0-9]*|[a-z][a-z0-9]*)\z/
93
+ ```
41
94
 
42
- <uppercase-style> ::= <letter-uppercase> <identifier-tail-uppercase>*
43
- <lowercase-style> ::= <letter-lowercase> <identifier-tail-lowercase>*
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
44
102
 
45
- <identifier-tail-uppercase> ::= <letter-uppercase> | <digit>
46
- <identifier-tail-lowercase> ::= <letter-lowercase> | <digit>
103
+ ## Game Examples
47
104
 
48
- <letter-uppercase> ::= "A" | "B" | "C" | ... | "Z"
49
- <letter-lowercase> ::= "a" | "b" | "c" | ... | "z"
50
- <digit> ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
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"
51
117
  ```
52
118
 
53
- ### Protocol Mapping
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"
54
125
 
55
- SNN encodes style information with player association according to the Game Protocol:
126
+ # King of the Hill Chess
127
+ koth = Sashite::Snn.style(:Koth, :first)
128
+ koth.to_s # => "KOTH"
56
129
 
57
- | Protocol Attribute | SNN Encoding | Examples |
58
- |-------------------|--------------|----------|
59
- | **Style** | Alphanumeric identifier | `CHESS`, `SHOGI`, `XIANGQI` |
60
- | **Player Association** | Case encoding | `CHESS` = First player, `chess` = Second player |
130
+ # Three-Check Chess
131
+ threecheck = Sashite::Snn.style(:Threecheck, :first)
132
+ ```
61
133
 
62
- ### Format Rules
134
+ ### Shōgi Variants
135
+ ```ruby
136
+ # Traditional Shōgi
137
+ standard_shogi = Sashite::Snn.style(:Shogi, :first)
63
138
 
64
- - **Start character**: Must be alphabetic (`A-Z` for first player, `a-z` for second player)
65
- - **Subsequent characters**: Alphabetic characters and digits only
66
- - **Case consistency**: Entire identifier must be uppercase or lowercase (no mixed case)
67
- - **Player assignment**: Uppercase = first player, lowercase = second player
68
- - **Fixed assignment**: Player-style association remains constant throughout the match
139
+ # Mini Shōgi
140
+ mini_shogi = Sashite::Snn.style(:Minishogi, :first)
69
141
 
70
- ## Basic Usage
142
+ # Chu Shōgi
143
+ chu_shogi = Sashite::Snn.style(:Chushogi, :first)
144
+ ```
71
145
 
72
- ### Creating Style Objects
146
+ ### Multi-Style Gaming
147
+ ```ruby
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
73
158
 
74
- The primary interface is the `Sashite::Snn::Style` class, which represents a style identifier in SNN format:
159
+ # Style compatibility check
160
+ def compatible_styles?(style1, style2)
161
+ # Styles are compatible if they have different sides
162
+ !style1.same_side?(style2)
163
+ end
75
164
 
76
- ```ruby
77
- require "sashite/snn"
165
+ chess_white = Sashite::Snn.parse("CHESS")
166
+ shogi_black = Sashite::Snn.parse("shogi")
167
+ puts compatible_styles?(chess_white, shogi_black) # => true
168
+ ```
169
+
170
+ ## API Reference
78
171
 
79
- # Parse a SNN string into a style object
80
- style = Sashite::Snn::Style.parse("CHESS")
81
- # => #<Sashite::Snn::Style:0x... @identifier="CHESS">
172
+ ### Main Module Methods
82
173
 
83
- lowercase_style = Sashite::Snn::Style.parse("shogi")
84
- # => #<Sashite::Snn::Style:0x... @identifier="shogi">
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
85
177
 
86
- # Create directly with constructor
87
- style = Sashite::Snn::Style.new("CHESS")
88
- lowercase_style = Sashite::Snn::Style.new("makruk")
178
+ ### Style Class
89
179
 
90
- # Convenience method
91
- style = Sashite::Snn.style("XIANGQI")
92
- ```
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)
183
+
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
93
188
 
94
- ### Converting to String and Symbol
189
+ #### Name and Case Handling
95
190
 
96
- Convert a style object to different representations:
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:
97
192
 
98
193
  ```ruby
99
- style = Sashite::Snn::Style.parse("CHESS")
100
- style.to_s # => "CHESS"
101
- style.to_sym # => :CHESS
102
-
103
- variant_style = Sashite::Snn::Style.parse("chess960")
104
- variant_style.to_s # => "chess960"
105
- variant_style.to_sym # => :chess960
106
-
107
- # Useful for hash keys and case statements
108
- game_config = {
109
- style.to_sym => { pieces: chess_pieces, rules: chess_rules }
110
- }
111
-
112
- case style.to_sym
113
- when :CHESS then setup_chess_game
114
- when :SHOGI then setup_shogi_game
115
- end
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)
116
203
  ```
117
204
 
118
- ### Player Association
205
+ #### Side Queries
206
+ - `#first_player?` - Check if first player style
207
+ - `#second_player?` - Check if second player style
119
208
 
120
- Check which player a style belongs to according to the Game Protocol:
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
121
213
 
122
- ```ruby
123
- first_player_style = Sashite::Snn::Style.parse("CHESS")
124
- first_player_style.first_player? # => true (uppercase = first player)
125
- first_player_style.second_player? # => false
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
126
218
 
127
- second_player_style = Sashite::Snn::Style.parse("shogi")
128
- second_player_style.first_player? # => false
129
- second_player_style.second_player? # => true (lowercase = second player)
130
- ```
219
+ ### Constants
220
+ - `Sashite::Snn::SNN_REGEX` - Regular expression for SNN validation
131
221
 
132
- ## Validation
222
+ ## Advanced Usage
133
223
 
134
- All parsing automatically validates input according to the SNN specification:
224
+ ### Name Normalization Examples
135
225
 
136
226
  ```ruby
137
- # Valid SNN strings
138
- Sashite::Snn::Style.parse("CHESS") # ✓ First player chess
139
- Sashite::Snn::Style.parse("shogi") # ✓ Second player shogi
140
- Sashite::Snn::Style.parse("CHESS960") # ✓ First player Chess960 variant
141
- Sashite::Snn::Style.parse("makruk") # ✓ Second player makruk
142
-
143
- # Valid constructor calls
144
- Sashite::Snn::Style.new("XIANGQI") # ✓ First player xiangqi
145
- Sashite::Snn::Style.new("janggi") # ✓ Second player janggi
146
-
147
- # Convenience method
148
- Sashite::Snn.style("MINISHOGI") # ✓ First player minishogi
149
-
150
- # Check validity
151
- Sashite::Snn.valid?("CHESS") # => true
152
- Sashite::Snn.valid?("Chess") # => false (mixed case not allowed)
153
- Sashite::Snn.valid?("123") # => false (must start with letter)
154
- Sashite::Snn.valid?("") # => false (empty string)
155
-
156
- # Invalid SNN strings raise ArgumentError
157
- Sashite::Snn::Style.parse("") # ✗ ArgumentError
158
- Sashite::Snn::Style.parse("Chess") # ✗ ArgumentError (mixed case)
159
- Sashite::Snn::Style.parse("9CHESS") # ✗ ArgumentError (starts with digit)
160
- Sashite::Snn::Style.parse("CHESS-960") # ✗ ArgumentError (contains hyphen)
161
- ```
227
+ # Parsing different cases results in same name
228
+ white_chess = Sashite::Snn.parse("CHESS")
229
+ black_chess = Sashite::Snn.parse("chess")
162
230
 
163
- ## Examples of SNN in Practice
231
+ # Names are normalized with proper capitalization
232
+ white_chess.name # => :Chess
233
+ black_chess.name # => :Chess (same name!)
164
234
 
165
- ### Classic Game Styles
235
+ # Sides are different
236
+ white_chess.side # => :first
237
+ black_chess.side # => :second
166
238
 
167
- ```ruby
168
- # Traditional chess match (both players use chess pieces)
169
- first_player = Sashite::Snn::Style.parse("CHESS") # First player
170
- second_player = Sashite::Snn::Style.parse("chess") # Second player
239
+ # Display follows side convention
240
+ white_chess.to_s # => "CHESS"
241
+ black_chess.to_s # => "chess"
171
242
 
172
- # Cross-style game: Chess vs Shogi
173
- first_player = Sashite::Snn::Style.parse("CHESS") # First player uses chess pieces
174
- second_player = Sashite::Snn::Style.parse("shogi") # Second player uses shogi pieces
243
+ # Same name, different sides
244
+ white_chess.same_name?(black_chess) # => true
245
+ white_chess.same_side?(black_chess) # => false
246
+ ```
175
247
 
176
- # Variant games
177
- first_player = Sashite::Snn::Style.parse("CHESS960") # First player uses Chess960 variant
178
- second_player = Sashite::Snn::Style.parse("chess960") # Second player uses Chess960 variant
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"
179
263
  ```
180
264
 
181
- ### Style Examples from SNN Specification
265
+ ### Game Configuration Management
266
+ ```ruby
267
+ class GameConfiguration
268
+ def initialize
269
+ @player_styles = {}
270
+ end
182
271
 
183
- According to the [SNN Examples Documentation](https://sashite.dev/specs/snn/1.0.0/examples/):
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
184
276
 
185
- | SNN | Interpretation |
186
- |-----|----------------|
187
- | `CHESS` | Chess first player (white) |
188
- | `chess` | Chess second player (black) |
189
- | `SHOGI` | Shōgi first player (sente) |
190
- | `shogi` | Shōgi second player (gote) |
191
- | `XIANGQI` | Xiangqi first player (red) |
192
- | `xiangqi` | Xiangqi second player (black) |
277
+ def get_player_style(player)
278
+ @player_styles[player]
279
+ end
193
280
 
194
- ```ruby
195
- # Standard game representations
196
- chess_white = Sashite::Snn::Style.parse("CHESS") # White pieces (first player)
197
- chess_black = Sashite::Snn::Style.parse("chess") # Black pieces (second player)
281
+ def style_mismatch?
282
+ return false if @player_styles.size < 2
198
283
 
199
- shogi_sente = Sashite::Snn::Style.parse("SHOGI") # Sente (first player)
200
- shogi_gote = Sashite::Snn::Style.parse("shogi") # Gote (second player)
284
+ styles = @player_styles.values
285
+ !styles.all? { |style| style.same_name?(styles.first) }
286
+ end
201
287
 
202
- xiangqi_red = Sashite::Snn::Style.parse("XIANGQI") # Red pieces (first player)
203
- xiangqi_black = Sashite::Snn::Style.parse("xiangqi") # Black pieces (second player)
204
- ```
288
+ def cross_tradition_match?
289
+ return false if @player_styles.size < 2
205
290
 
206
- ### Variant Styles
291
+ style_names = @player_styles.values.map(&:name).uniq
292
+ style_names.size > 1
293
+ end
294
+ end
207
295
 
208
- ```ruby
209
- # Chess variants
210
- fischer_random = Sashite::Snn::Style.parse("CHESS960")
211
- king_of_hill = Sashite::Snn::Style.parse("CHESSKING")
296
+ # Usage
297
+ config = GameConfiguration.new
298
+ config.set_player_style(:white, :Chess)
299
+ config.set_player_style(:black, :Shogi)
212
300
 
213
- # Shogi variants
214
- mini_shogi = Sashite::Snn::Style.parse("MINISHOGI")
215
- handicap_shogi = Sashite::Snn::Style.parse("SHOGI9")
301
+ puts config.cross_tradition_match? # => true
302
+ puts config.style_mismatch? # => true
216
303
 
217
- # Other traditions
218
- thai_makruk = Sashite::Snn::Style.parse("MAKRUK")
219
- korean_janggi = Sashite::Snn::Style.parse("JANGGI")
304
+ white_style = config.get_player_style(:white)
305
+ puts white_style.to_s # => "CHESS"
220
306
  ```
221
307
 
222
- ### Cross-Style Gaming
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
+ ```
223
328
 
329
+ ### Tournament Style Management
224
330
  ```ruby
225
- # Create a cross-style game setup
226
- first_player_style = Sashite::Snn::Style.parse("CHESS")
227
- second_player_style = Sashite::Snn::Style.parse("makruk")
228
-
229
- puts "Game: #{first_player_style} vs #{second_player_style}"
230
- # => "Game: CHESS vs makruk"
231
-
232
- # This represents a unique game where chess pieces face makruk pieces
233
-
234
- # Each player keeps their assigned style throughout the game
235
- game_config = {
236
- first_player: first_player_style, # Fixed: CHESS
237
- second_player: second_player_style # Fixed: makruk
238
- }
239
-
240
- # Using symbols for configuration
241
- style_configs = {
242
- first_player_style.to_sym => { piece_set: :western, board: :"8x8" },
243
- second_player_style.to_sym => { piece_set: :thai, board: :"8x8" }
244
- }
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]
245
372
  ```
246
373
 
247
- ## API Reference
374
+ ## Protocol Mapping
248
375
 
249
- ### Module Methods
376
+ Following the [Game Protocol](https://sashite.dev/game-protocol/):
250
377
 
251
- - `Sashite::Snn.valid?(snn_string)` - Check if a string is valid SNN notation
252
- - `Sashite::Snn.style(identifier)` - Convenience method to create style objects
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 |
253
382
 
254
- ### Sashite::Snn::Style Class Methods
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.
255
384
 
256
- - `Sashite::Snn::Style.parse(snn_string)` - Parse a SNN string into a style object
257
- - `Sashite::Snn::Style.new(identifier)` - Create a new style instance
385
+ **Canonical principle**: Identical styles must have identical SNN representations.
258
386
 
259
- ### Instance Methods
387
+ ## Properties
260
388
 
261
- #### Player Queries
262
- - `#first_player?` - Check if style belongs to first player (uppercase)
263
- - `#second_player?` - Check if style belongs to second player (lowercase)
264
- - `#uppercase?` - Check if identifier is uppercase
265
- - `#lowercase?` - Check if identifier is lowercase
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
266
395
 
267
- #### Conversion
268
- - `#to_s` - Convert to SNN string representation
269
- - `#to_sym` - Convert to symbol representation
270
- - `#inspect` - Detailed string representation for debugging
396
+ ## Implementation Notes
271
397
 
272
- ## Design Properties
398
+ ### Name Normalization Convention
273
399
 
274
- Following the SNN specification, this implementation provides:
400
+ SNN follows a strict name normalization convention:
275
401
 
276
- - **Rule-agnostic**: Independent of specific game mechanics
277
- - **Unambiguous identification**: Clear distinction between different piece traditions
278
- - **Cross-style support**: Enables multi-tradition gaming environments
279
- - **Canonical representation**: Consistent naming for equivalent styles
280
- - **Player clarity**: Case-based player association throughout gameplay
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
281
406
 
282
- ## System Constraints
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
283
411
 
284
- * SNN supports exactly **two players**
285
- * Players are distinguished by casing: **uppercase** for first player, **lowercase** for second player
286
- * Style identifiers must start with an alphabetic character
287
- * Subsequent characters may include alphabetic characters and digits only
288
- * Mixed casing is not permitted within a single identifier
289
- * Style assignment to players remains **fixed throughout a game**
290
- * Total piece count must remain constant (Game Protocol conservation principle)
412
+ ### Example Flow
291
413
 
292
- ## Use Cases
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
+ ```
293
422
 
294
- SNN is particularly useful in the following scenarios:
423
+ This ensures that `parse(snn).to_s == snn` for all valid SNN strings while maintaining internal consistency.
295
424
 
296
- 1. **Multi-style environments**: When games involve pieces from multiple traditions or variants
297
- 2. **Game engine development**: When implementing engines that need to distinguish between different piece style traditions
298
- 3. **Hybrid games**: When creating or analyzing games that combine elements from different piece traditions
299
- 4. **Database systems**: When storing game data that must avoid naming conflicts between similar styles
300
- 5. **Cross-tradition analysis**: When comparing or analyzing strategic elements across different piece traditions
301
- 6. **Tournament systems**: When organizing events that allow players to choose from different piece style traditions
425
+ ## System Constraints
302
426
 
303
- ## Game Protocol Compliance
427
+ - **Alphanumeric identifiers** starting with a letter
428
+ - **Exactly 2 players** (uppercase/lowercase distinction)
429
+ - **Case consistency** within each identifier (no mixed case)
304
430
 
305
- This implementation fully complies with the [Game Protocol](https://sashite.dev/game-protocol/) by:
431
+ ## Related Specifications
306
432
 
307
- - Representing the **Style attribute** of pieces
308
- - Supporting **two-player** constraint
309
- - Maintaining **piece conservation** (styles are immutable)
310
- - Enabling **cross-style** gameplay scenarios
311
- - Providing **deterministic** style identification
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
312
439
 
313
440
  ## Documentation
314
441
 
@@ -317,6 +444,33 @@ This implementation fully complies with the [Game Protocol](https://sashite.dev/
317
444
  - [Game Protocol Foundation](https://sashite.dev/game-protocol/)
318
445
  - [API Documentation](https://rubydoc.info/github/sashite/snn.rb/main)
319
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
+
320
474
  ## License
321
475
 
322
476
  Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
@@ -2,141 +2,246 @@
2
2
 
3
3
  module Sashite
4
4
  module Snn
5
- # Represents a style identifier in SNN format
5
+ # Represents a style in SNN (Style Name Notation) format.
6
6
  #
7
- # A style represents a particular tradition, variant, or design approach for game pieces.
8
- # The casing of the identifier determines player association:
9
- # - Uppercase identifiers belong to the first player
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
- # @example
13
- # # First player styles (uppercase)
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
- # @return [String] The style identifier
22
- attr_reader :identifier
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 identifier [String] The style identifier
27
- # @raise [ArgumentError] if the identifier is invalid SNN notation
28
- #
29
- # @example
30
- # style = Sashite::Snn::Style.new("CHESS")
31
- # # => #<Sashite::Snn::Style:0x... @identifier="CHESS">
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
- @identifier = identifier.freeze
47
+ @name = name
48
+ @side = side
36
49
 
37
50
  freeze
38
51
  end
39
52
 
40
- # Parse a SNN string into a style object
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
- # style = Sashite::Snn::Style.parse("CHESS")
48
- # # => #<Sashite::Snn::Style:0x... @identifier="CHESS">
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
- new(snn_string)
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
- # Check if this style belongs to the first player
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
- # Sashite::Snn::Style.new("CHESS").first_player? # => true
59
- # Sashite::Snn::Style.new("shogi").first_player? # => false
60
- def first_player?
61
- uppercase?
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
- # Check if this style belongs to the second player
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
- # Sashite::Snn::Style.new("CHESS").second_player? # => false
70
- # Sashite::Snn::Style.new("shogi").second_player? # => true
71
- def second_player?
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
- # Check if the style identifier is uppercase
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
- # Sashite::Snn::Style.new("CHESS").uppercase? # => true
81
- # Sashite::Snn::Style.new("chess").uppercase? # => false
82
- def uppercase?
83
- identifier == identifier.upcase
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
- # Check if the style identifier is lowercase
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
- # Sashite::Snn::Style.new("CHESS").lowercase? # => false
92
- # Sashite::Snn::Style.new("chess").lowercase? # => true
93
- def lowercase?
94
- identifier == identifier.downcase
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
- # Convert the style to its string representation
123
+ # Check if the style belongs to the first player
98
124
  #
99
- # @return [String] The style identifier
100
- #
101
- # @example
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
- # Convert the style to a symbol
130
+ # Check if the style belongs to the second player
108
131
  #
109
- # @return [Symbol] The style identifier as a symbol
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
- # Sashite::Snn::Style.new("CHESS").to_sym # => :CHESS
113
- def to_sym
114
- identifier.to_sym
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
- # String representation for debugging
149
+ # Check if this style belongs to the same side as another
118
150
  #
119
- # @return [String] A detailed string representation
120
- def inspect
121
- "#<#{self.class}:0x#{object_id.to_s(16)} @identifier=#{identifier.inspect}>"
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
- # Equality comparison
159
+ # Custom equality comparison
125
160
  #
126
- # @param other [Object] The object to compare with
127
- # @return [Boolean] true if both objects are Style instances with the same identifier
161
+ # @param other [Object] object to compare with
162
+ # @return [Boolean] true if styles are equal
128
163
  def ==(other)
129
- other.is_a?(Style) && identifier == other.identifier
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 equality comparison
169
+ # Alias for == to ensure Set functionality works correctly
133
170
  alias eql? ==
134
171
 
135
- # Hash code for use in hashes and sets
172
+ # Custom hash implementation for use in collections
136
173
  #
137
- # @return [Integer] The hash code
174
+ # @return [Integer] hash value
138
175
  def hash
139
- [self.class, identifier].hash
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 (SNN) module
6
+ # SNN (Style Name Notation) implementation for Ruby
7
7
  #
8
- # SNN provides a consistent and rule-agnostic format for identifying piece styles
9
- # in abstract strategy board games. It enables clear distinction between different
10
- # piece traditions, variants, or design approaches within multi-style gaming environments.
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
- # @see https://sashite.dev/documents/snn/1.0.0/ SNN Specification v1.0.0
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 regular expression
15
- # Matches: uppercase style (A-Z followed by A-Z0-9*) or lowercase style (a-z followed by a-z0-9*)
16
- VALIDATION_REGEX = /\A([A-Z][A-Z0-9]*|[a-z][a-z0-9]*)\z/
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 snn_string [String] The string to validate
21
- # @return [Boolean] true if the string is valid SNN notation, false otherwise
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") # => true
25
- # Sashite::Snn.valid?("shogi") # => true
26
- # Sashite::Snn.valid?("Chess") # => false (mixed case)
27
- # Sashite::Snn.valid?("123") # => false (must start with letter)
28
- # Sashite::Snn.valid?("") # => false (empty string)
29
- def self.valid?(snn_string)
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
- VALIDATION_REGEX.match?(snn_string)
42
+ snn.match?(SNN_REGEX)
34
43
  end
35
44
 
36
- # Convenience method to create a style object
45
+ # Parse an SNN string into a Style object
37
46
  #
38
- # @param identifier [String] The style identifier
39
- # @return [Sashite::Snn::Style] A new style object
40
- # @raise [ArgumentError] if the identifier is invalid
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
- # style = Sashite::Snn.style("CHESS")
44
- # # => #<Sashite::Snn::Style:0x... @identifier="CHESS">
45
- def self.style(identifier)
46
- Style.new(identifier)
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.0.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
- A clean, minimal Ruby implementation of Style Name Notation (SNN) for abstract strategy games.
14
- SNN provides a consistent and rule-agnostic format for identifying piece styles, enabling
15
- clear distinction between different piece traditions, variants, or design approaches within
16
- multi-style gaming environments. Features include player-based casing, style validation,
17
- and cross-style compatibility. Perfect for game engines, multi-tradition environments,
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: []
@@ -52,5 +52,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
52
52
  requirements: []
53
53
  rubygems_version: 3.6.9
54
54
  specification_version: 4
55
- summary: Style Name Notation (SNN) support for the Ruby language.
55
+ summary: SNN (Style Name Notation) implementation for Ruby with immutable style objects
56
56
  test_files: []