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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 189a182b820d000660357679c628df3f1adf451b8fd462f4e667ba9170453a03
4
- data.tar.gz: 17a1049e333427994c977efe5c1170fa8114685515f0ff91b9abfb4bcdbe5963
3
+ metadata.gz: c75eb6af8fa8c0ff0a9791e0ecc0fe49b7e6c0827733e96f2e37e552ddca50b0
4
+ data.tar.gz: 2ac49000ca7ff3f2ae01e0a7d240f2fb99d5cab34a915b9799a5088c7461b4f3
5
5
  SHA512:
6
- metadata.gz: 6843ef5ffa7f5affa4acfe88b52eb1b837112c853d0668df67f75ac3aedbe1e0f569bd52941626417cf0a66956d472bab61f7f76fedf531a36c6fe60695f0539
7
- data.tar.gz: 8b641153ba70b214a4bf7176169d9f456a771347851ec50618777f86b2129381c3f3b6bdb4ccb98b42975651e2d6c14930e69a42bfc233b795251ac2d14fea10
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 consistent and rule-agnostic format for identifying piece styles in abstract strategy board games. It provides unambiguous identification of piece styles by using standardized naming conventions, enabling clear distinction between different piece traditions, variants, or design approaches within 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/documents/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,236 +26,455 @@ 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 an identifier starting with an alphabetic character, followed by optional alphabetic characters and digits:
31
+ ```ruby
32
+ require "sashite/snn"
32
33
 
33
- ```
34
- <style-id>
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
- Where:
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
- ## Basic Usage
78
+ ### Structure
79
+ ```
80
+ <style-identifier>
81
+ ```
45
82
 
46
- ### Creating Style Objects
83
+ ### Components
47
84
 
48
- The primary interface is the `Sashite::Snn::Style` class, which represents a style identifier in SNN format:
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
- require "sashite/snn"
92
+ /\A([A-Z][A-Z0-9]*|[a-z][a-z0-9]*)\z/
93
+ ```
52
94
 
53
- # Parse a SNN string into a style object
54
- style = Sashite::Snn::Style.parse("CHESS")
55
- # => #<Sashite::Snn::Style:0x... @identifier="CHESS">
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
- lowercase_style = Sashite::Snn::Style.parse("shogi")
58
- # => #<Sashite::Snn::Style:0x... @identifier="shogi">
103
+ ## Game Examples
59
104
 
60
- # Create directly with constructor
61
- style = Sashite::Snn::Style.new("CHESS")
62
- lowercase_style = Sashite::Snn::Style.new("makruk")
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
- # Convenience method
65
- style = Sashite::Snn.style("XIANGQI")
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
- ### Converting to String and Symbol
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
- Convert a style object to different representations:
142
+ # Chu Shōgi
143
+ chu_shogi = Sashite::Snn.style(:Chushogi, :first)
144
+ ```
71
145
 
146
+ ### Multi-Style Gaming
72
147
  ```ruby
73
- style = Sashite::Snn::Style.parse("CHESS")
74
- style.to_s # => "CHESS"
75
- style.to_sym # => :CHESS
76
-
77
- variant_style = Sashite::Snn::Style.parse("chess960")
78
- variant_style.to_s # => "chess960"
79
- variant_style.to_sym # => :chess960
80
-
81
- # Useful for hash keys and case statements
82
- game_config = {
83
- style.to_sym => { pieces: chess_pieces, rules: chess_rules }
84
- }
85
-
86
- case style.to_sym
87
- when :CHESS then setup_chess_game
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
- ### Player Association
170
+ ## API Reference
93
171
 
94
- Check which player a style belongs to:
172
+ ### Main Module Methods
95
173
 
96
- ```ruby
97
- first_player_style = Sashite::Snn::Style.parse("CHESS")
98
- first_player_style.first_player? # => true
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
- second_player_style = Sashite::Snn::Style.parse("shogi")
102
- second_player_style.first_player? # => false
103
- second_player_style.second_player? # => true
104
- ```
178
+ ### Style Class
105
179
 
106
- ## Validation
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
- All parsing automatically validates input according to the SNN specification:
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
- # Valid SNN strings
112
- Sashite::Snn::Style.parse("CHESS") #
113
- Sashite::Snn::Style.parse("shogi") #
114
- Sashite::Snn::Style.parse("CHESS960") # ✓
115
- Sashite::Snn::Style.parse("makruk") # ✓
116
-
117
- # Valid constructor calls
118
- Sashite::Snn::Style.new("XIANGQI") # ✓
119
- Sashite::Snn::Style.new("janggi") # ✓
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
- ## Examples of SNN in Practice
205
+ #### Side Queries
206
+ - `#first_player?` - Check if first player style
207
+ - `#second_player?` - Check if second player style
138
208
 
139
- ### Classic Game Styles
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
- # International Chess
143
- first_player = Sashite::Snn::Style.parse("CHESS") # First player uses chess pieces
144
- second_player = Sashite::Snn::Style.parse("chess") # Second player uses chess pieces
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
- # Cross-style game: Chess vs Shogi
147
- first_player = Sashite::Snn::Style.parse("CHESS") # First player uses chess pieces
148
- second_player = Sashite::Snn::Style.parse("shogi") # Second player uses shogi pieces
231
+ # Names are normalized with proper capitalization
232
+ white_chess.name # => :Chess
233
+ black_chess.name # => :Chess (same name!)
149
234
 
150
- # Variant games
151
- first_player = Sashite::Snn::Style.parse("CHESS960") # First player uses Chess960 variant
152
- second_player = Sashite::Snn::Style.parse("chess960") # Second player uses Chess960 variant
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
- ### Variant Styles
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
- # Chess variants
159
- fischer_random = Sashite::Snn::Style.parse("CHESS960")
160
- king_of_hill = Sashite::Snn::Style.parse("CHESSKING")
267
+ class GameConfiguration
268
+ def initialize
269
+ @player_styles = {}
270
+ end
161
271
 
162
- # Shogi variants
163
- mini_shogi = Sashite::Snn::Style.parse("MINISHOGI")
164
- handicap_shogi = Sashite::Snn::Style.parse("SHOGI9")
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
- # Other traditions
167
- thai_makruk = Sashite::Snn::Style.parse("MAKRUK")
168
- korean_janggi = Sashite::Snn::Style.parse("JANGGI")
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
- ### 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
+ ```
172
328
 
329
+ ### Tournament Style Management
173
330
  ```ruby
174
- # Create a cross-style game setup
175
- first_player_style = Sashite::Snn::Style.parse("CHESS")
176
- second_player_style = Sashite::Snn::Style.parse("makruk")
177
-
178
- puts "Game: #{first_player_style} vs #{second_player_style}"
179
- # => "Game: CHESS vs makruk"
180
-
181
- # This represents a unique game where chess pieces face makruk pieces
182
-
183
- # Each player keeps their assigned style throughout the game
184
- game_config = {
185
- first_player: first_player_style, # Fixed: CHESS
186
- second_player: second_player_style # Fixed: makruk
187
- }
188
-
189
- # Using symbols for configuration
190
- style_configs = {
191
- first_player_style.to_sym => { piece_set: :western, board: :"8x8" },
192
- second_player_style.to_sym => { piece_set: :thai, board: :"8x8" }
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
- ## API Reference
374
+ ## Protocol Mapping
197
375
 
198
- ### Module Methods
376
+ Following the [Game Protocol](https://sashite.dev/game-protocol/):
199
377
 
200
- - `Sashite::Snn.valid?(snn_string)` - Check if a string is valid SNN notation
201
- - `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 |
202
382
 
203
- ### 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.
204
384
 
205
- - `Sashite::Snn::Style.parse(snn_string)` - Parse a SNN string into a style object
206
- - `Sashite::Snn::Style.new(identifier)` - Create a new style instance
385
+ **Canonical principle**: Identical styles must have identical SNN representations.
207
386
 
208
- ### Instance Methods
387
+ ## Properties
209
388
 
210
- #### Player Queries
211
- - `#first_player?` - Check if style belongs to first player (uppercase)
212
- - `#second_player?` - Check if style belongs to second player (lowercase)
213
- - `#uppercase?` - Alias for `#first_player?`
214
- - `#lowercase?` - Alias for `#second_player?`
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
- #### Conversion
217
- - `#to_s` - Convert to SNN string representation
218
- - `#to_sym` - Convert to symbol representation
219
- - `#inspect` - Detailed string representation for debugging
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
- ## Properties of SNN
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
- * **Rule-agnostic**: SNN does not encode game states, legality, validity, or game-specific conditions
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
- ## Constraints
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
- * SNN supports exactly **two players**
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
- ## Use Cases
425
+ ## System Constraints
240
426
 
241
- SNN is particularly useful in the following scenarios:
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
- 1. **Multi-style environments**: When games involve pieces from multiple traditions or variants
244
- 2. **Game engine development**: When implementing engines that need to distinguish between different piece style traditions
245
- 3. **Hybrid games**: When creating or analyzing games that combine elements from different piece traditions
246
- 4. **Database systems**: When storing game data that must avoid naming conflicts between similar styles
247
- 5. **Cross-tradition analysis**: When comparing or analyzing strategic elements across different piece traditions
248
- 6. **Tournament systems**: When organizing events that allow players to choose from different piece style traditions
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/documents/snn/1.0.0/)
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
- The [gem](https://rubygems.org/gems/sashite-snn) is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
476
+ Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
258
477
 
259
- ## About Sashité
478
+ ## About
260
479
 
261
- This project is maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of Chinese, Japanese, and Western chess cultures.
480
+ Maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of board game cultures.
@@ -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.0
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: []
@@ -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.7
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: []