sashite-pnn 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -9,9 +9,9 @@
9
9
 
10
10
  ## What is PNN?
11
11
 
12
- PNN (Piece Name Notation) extends [PIN (Piece Identifier Notation)](https://sashite.dev/specs/pin/1.0.0/) to provide style-aware piece representation in abstract strategy board games. PNN adds a derivation marker that distinguishes pieces by their style origin, enabling cross-style game scenarios and piece origin tracking.
12
+ PNN (Piece Name Notation) is a formal, rule-agnostic naming system for identifying **pieces** in abstract strategy board games such as chess, shÅgi, xiangqi, and their many variants. Each piece is represented by a canonical, human-readable ASCII name with optional state modifiers (e.g., `"KING"`, `"queen"`, `"+ROOK"`, `"-pawn"`).
13
13
 
14
- This gem implements the [PNN Specification v1.0.0](https://sashite.dev/specs/pnn/1.0.0/), providing a modern Ruby interface with immutable piece objects and full backward compatibility with PIN while adding style differentiation capabilities.
14
+ This gem implements the [PNN Specification v1.0.0](https://sashite.dev/specs/pnn/1.0.0/), supporting validation, parsing, and comparison of piece names with integrated state management.
15
15
 
16
16
  ## Installation
17
17
 
@@ -28,490 +28,184 @@ gem install sashite-pnn
28
28
 
29
29
  ## Usage
30
30
 
31
+ ### Basic Operations
32
+
31
33
  ```ruby
32
34
  require "sashite/pnn"
33
35
 
34
- # Parse PNN strings into piece objects
35
- piece = Sashite::Pnn.parse("K") # => #<Pnn::Piece type=:K side=:first state=:normal native=true>
36
- piece.to_s # => "K"
37
- piece.type # => :K
38
- piece.side # => :first
39
- piece.state # => :normal
40
- piece.native? # => true
36
+ # Parse PNN strings into piece name objects
37
+ name = Sashite::Pnn.parse("KING") # => #<Pnn::Name value="KING">
38
+ name.to_s # => "KING"
39
+ name.value # => "KING"
41
40
 
42
- # Create pieces directly
43
- piece = Sashite::Pnn.piece(:K, :first) # => #<Pnn::Piece type=:K side=:first state=:normal native=true>
44
- piece = Sashite::Pnn::Piece.new(:R, :second, :enhanced, false) # => #<Pnn::Piece type=:R side=:second state=:enhanced native=false>
41
+ # Create from string or symbol
42
+ name = Sashite::Pnn.name("queen") # => #<Pnn::Name value="queen">
43
+ name = Sashite::Pnn::Name.new(:ROOK) # => #<Pnn::Name value="ROOK">
45
44
 
46
45
  # Validate PNN strings
47
- Sashite::Pnn.valid?("K") # => true
48
- Sashite::Pnn.valid?("+R'") # => true
49
- Sashite::Pnn.valid?("invalid") # => false
50
-
51
- # Style derivation with apostrophe suffix
52
- native_king = Sashite::Pnn.parse("K") # => #<Pnn::Piece type=:K side=:first state=:normal native=true>
53
- foreign_king = Sashite::Pnn.parse("K'") # => #<Pnn::Piece type=:K side=:first state=:normal native=false>
54
-
55
- native_king.to_s # => "K"
56
- foreign_king.to_s # => "K'"
57
-
58
- # State manipulation (returns new immutable instances)
59
- enhanced = piece.enhance # => #<Pnn::Piece type=:K side=:first state=:enhanced native=true>
60
- enhanced.to_s # => "+K"
61
- diminished = piece.diminish # => #<Pnn::Piece type=:K side=:first state=:diminished native=true>
62
- diminished.to_s # => "-K"
63
-
64
- # Style derivation manipulation
65
- foreign_piece = piece.derive # => #<Pnn::Piece type=:K side=:first state=:normal native=false>
66
- foreign_piece.to_s # => "K'"
67
- back_to_native = foreign_piece.underive # => #<Pnn::Piece type=:K side=:first state=:normal native=true>
68
- back_to_native.to_s # => "K"
69
-
70
- # Side manipulation
71
- flipped = piece.flip # => #<Pnn::Piece type=:K side=:second state=:normal native=true>
72
- flipped.to_s # => "k"
73
-
74
- # Type manipulation
75
- queen = piece.with_type(:Q) # => #<Pnn::Piece type=:Q side=:first state=:normal native=true>
76
- queen.to_s # => "Q"
77
-
78
- # Style queries
79
- piece.native? # => true
80
- foreign_king.derived? # => true
81
-
82
- # State queries
83
- piece.normal? # => true
84
- enhanced.enhanced? # => true
85
- diminished.diminished? # => true
86
-
87
- # Side queries
88
- piece.first_player? # => true
89
- flipped.second_player? # => true
90
-
91
- # Attribute access
92
- piece.letter # => "K"
93
- enhanced.prefix # => "+"
94
- foreign_king.suffix # => "'"
95
- piece.suffix # => ""
96
-
97
- # Type and side comparison
98
- king1 = Sashite::Pnn.parse("K")
99
- king2 = Sashite::Pnn.parse("k")
100
- queen = Sashite::Pnn.parse("Q")
101
-
102
- king1.same_type?(king2) # => true (both kings)
103
- king1.same_side?(queen) # => true (both first player)
104
- king1.same_type?(queen) # => false (different types)
105
-
106
- # Style comparison
107
- native_king = Sashite::Pnn.parse("K")
108
- foreign_king = Sashite::Pnn.parse("K'")
109
-
110
- native_king.same_style?(foreign_king) # => false (different derivation)
111
-
112
- # Functional transformations can be chained
113
- pawn = Sashite::Pnn.parse("P")
114
- enemy_foreign_promoted = pawn.flip.derive.enhance # => "+p'" (second player foreign promoted pawn)
46
+ Sashite::Pnn.valid?("BISHOP") # => true
47
+ Sashite::Pnn.valid?("King") # => false (mixed case not allowed)
48
+ Sashite::Pnn.valid?("+ROOK") # => true (enhanced state)
49
+ Sashite::Pnn.valid?("-pawn") # => true (diminished state)
115
50
  ```
116
51
 
117
- ## Format Specification
118
-
119
- ### Structure
120
- ```
121
- <pin>[<suffix>]
122
- ```
52
+ ### State Modifiers
123
53
 
124
- ### Components
125
-
126
- - **PIN part** (`[<state>]<letter>`): Standard PIN notation
127
- - **Letter** (`A-Z`, `a-z`): Represents piece type and side
128
- - Uppercase: First player pieces
129
- - Lowercase: Second player pieces
130
- - **State** (optional prefix):
131
- - `+`: Enhanced state (promoted, upgraded, empowered)
132
- - `-`: Diminished state (weakened, restricted, temporary)
133
- - No prefix: Normal state
134
-
135
- - **Derivation suffix** (optional):
136
- - `'`: Foreign style (piece has opposite side's native style)
137
- - No suffix: Native style (piece has current side's native style)
138
-
139
- ### Regular Expression
140
54
  ```ruby
141
- /\A[-+]?[A-Za-z]'?\z/
142
- ```
55
+ # Enhanced pieces (+ prefix)
56
+ enhanced = Sashite::Pnn.parse("+QUEEN")
57
+ enhanced.enhanced? # => true
58
+ enhanced.normal? # => false
59
+ enhanced.base_name # => "QUEEN"
143
60
 
144
- ### Examples
145
- - `K` - First player king (native style, normal state)
146
- - `k'` - Second player king (foreign style, normal state)
147
- - `+R'` - First player rook (foreign style, enhanced state)
148
- - `-p` - Second player pawn (native style, diminished state)
61
+ # Diminished pieces (- prefix)
62
+ diminished = Sashite::Pnn.parse("-pawn")
63
+ diminished.diminished? # => true
64
+ diminished.base_name # => "pawn"
149
65
 
150
- ## Game Examples
151
-
152
- ### Cross-Style Chess vs. Shōgi
153
-
154
- ```ruby
155
- # Match setup: First player uses Chess, Second player uses Shōgi
156
- # Native styles: first=Chess, second=Shōgi
157
-
158
- # Native pieces (no derivation suffix)
159
- white_king = Sashite::Pnn.piece(:K, :first) # => "K" (Chess king)
160
- black_king = Sashite::Pnn.piece(:K, :second) # => "k" (Shōgi king)
161
-
162
- # Foreign pieces (with derivation suffix)
163
- white_shogi_king = Sashite::Pnn.piece(:K, :first, :normal, false) # => "K'" (Shōgi king for white)
164
- black_chess_king = Sashite::Pnn.piece(:K, :second, :normal, false) # => "k'" (Chess king for black)
165
-
166
- # Promoted pieces in cross-style context
167
- white_promoted_rook = Sashite::Pnn.parse("+R'") # White shōgi rook promoted to Dragon King
168
- black_promoted_pawn = Sashite::Pnn.parse("+p") # Black shōgi pawn promoted to Tokin
169
-
170
- white_promoted_rook.enhanced? # => true
171
- white_promoted_rook.derived? # => true
172
- black_promoted_pawn.enhanced? # => true
173
- black_promoted_pawn.native? # => true
66
+ # Normal pieces (no prefix)
67
+ normal = Sashite::Pnn.parse("KNIGHT")
68
+ normal.normal? # => true
69
+ normal.enhanced? # => false
70
+ normal.diminished? # => false
174
71
  ```
175
72
 
176
- ### Single-Style Games (PIN Compatibility)
73
+ ### Player Assignment
177
74
 
178
75
  ```ruby
179
- # Traditional Chess (both players use Chess style)
180
- # All pieces are native, so PNN behaves exactly like PIN
181
-
182
- white_pieces = %w[K Q +R B N P].map { |pin| Sashite::Pnn.parse(pin) }
183
- black_pieces = %w[k q +r b n p].map { |pin| Sashite::Pnn.parse(pin) }
76
+ # First player pieces (uppercase)
77
+ first_player = Sashite::Pnn.parse("KING")
78
+ first_player.first_player? # => true
79
+ first_player.second_player? # => false
184
80
 
185
- white_pieces.all?(&:native?) # => true
186
- black_pieces.all?(&:native?) # => true
187
-
188
- # PNN strings match PIN strings for native pieces
189
- white_pieces.map(&:to_s) # => ["K", "Q", "+R", "B", "N", "P"]
190
- black_pieces.map(&:to_s) # => ["k", "q", "+r", "b", "n", "p"]
81
+ # Second player pieces (lowercase)
82
+ second_player = Sashite::Pnn.parse("king")
83
+ second_player.first_player? # => false
84
+ second_player.second_player? # => true
191
85
  ```
192
86
 
193
- ### Style Mutation During Gameplay
87
+ ### Normalization and Comparison
194
88
 
195
89
  ```ruby
196
- # Simulate capture with style change (Ōgi rules)
197
- chess_queen = Sashite::Pnn.parse("q'") # Black chess queen (foreign for shōgi player)
198
- captured = chess_queen.flip.with_type(:P).underive # Becomes white native pawn
199
-
200
- chess_queen.to_s # => "q'" (black foreign queen)
201
- captured.to_s # => "P" (white native pawn)
90
+ a = Sashite::Pnn.parse("ROOK")
91
+ b = Sashite::Pnn.parse("ROOK")
202
92
 
203
- # Style derivation changes during gameplay
204
- shogi_piece = Sashite::Pnn.parse("r") # Black shōgi rook (native)
205
- foreign_piece = shogi_piece.derive # Convert to foreign style
206
- foreign_piece.to_s # => "r'" (black foreign rook)
93
+ a == b # => true
94
+ a.same_base_name?(Sashite::Pnn.parse("rook")) # => true (same piece, different player)
95
+ a.to_s # => "ROOK"
207
96
  ```
208
97
 
209
- ## API Reference
210
-
211
- ### Main Module Methods
212
-
213
- - `Sashite::Pnn.valid?(pnn_string)` - Check if string is valid PNN notation
214
- - `Sashite::Pnn.parse(pnn_string)` - Parse PNN string into Piece object
215
- - `Sashite::Pnn.piece(type, side, state = :normal, native = true)` - Create piece instance directly
216
-
217
- ### Piece Class
218
-
219
- #### Creation and Parsing
220
- - `Sashite::Pnn::Piece.new(type, side, state = :normal, native = true)` - Create piece instance
221
- - `Sashite::Pnn::Piece.parse(pnn_string)` - Parse PNN string (same as module method)
222
- - `Sashite::Pnn::Piece.valid?(pnn_string)` - Validate PNN string (class method)
223
-
224
- #### Attribute Access
225
- - `#type` - Get piece type (symbol :A to :Z, always uppercase)
226
- - `#side` - Get player side (:first or :second)
227
- - `#state` - Get state (:normal, :enhanced, or :diminished)
228
- - `#native` - Get style derivation (true for native, false for foreign)
229
- - `#letter` - Get letter representation (string, case determined by side)
230
- - `#prefix` - Get state prefix (string: "+", "-", or "")
231
- - `#suffix` - Get derivation suffix (string: "'" or "")
232
- - `#to_s` - Convert to PNN string representation
233
-
234
- #### Style Queries
235
- - `#native?` - Check if native style (current side's native style)
236
- - `#derived?` - Check if foreign style (opposite side's native style)
237
- - `#foreign?` - Alias for `#derived?`
238
-
239
- #### State Queries
240
- - `#normal?` - Check if normal state (no modifiers)
241
- - `#enhanced?` - Check if enhanced state
242
- - `#diminished?` - Check if diminished state
243
-
244
- #### Side Queries
245
- - `#first_player?` - Check if first player piece
246
- - `#second_player?` - Check if second player piece
247
-
248
- #### State Transformations (immutable - return new instances)
249
- - `#enhance` - Create enhanced version
250
- - `#unenhance` - Remove enhanced state
251
- - `#diminish` - Create diminished version
252
- - `#undiminish` - Remove diminished state
253
- - `#normalize` - Remove all state modifiers
254
-
255
- #### Style Transformations (immutable - return new instances)
256
- - `#derive` - Convert to foreign style (add derivation suffix)
257
- - `#underive` - Convert to native style (remove derivation suffix)
258
- - `#flip` - Switch player (change side)
259
-
260
- #### Attribute Transformations (immutable - return new instances)
261
- - `#with_type(new_type)` - Create piece with different type
262
- - `#with_side(new_side)` - Create piece with different side
263
- - `#with_state(new_state)` - Create piece with different state
264
- - `#with_derivation(native)` - Create piece with different derivation
265
-
266
- #### Comparison Methods
267
- - `#same_type?(other)` - Check if same piece type
268
- - `#same_side?(other)` - Check if same side
269
- - `#same_state?(other)` - Check if same state
270
- - `#same_style?(other)` - Check if same style derivation
271
- - `#==(other)` - Full equality comparison
272
-
273
- ### Constants
274
- - `Sashite::Pnn::Piece::NATIVE` - Constant for native style (`true`)
275
- - `Sashite::Pnn::Piece::FOREIGN` - Constant for foreign style (`false`)
276
- - `Sashite::Pnn::Piece::FOREIGN_SUFFIX` - Derivation suffix for foreign pieces (`"'"`)
277
- - `Sashite::Pnn::Piece::NATIVE_SUFFIX` - Derivation suffix for native pieces (`""`)
278
-
279
- **Note**: PNN validation leverages the existing `Sashite::Pin::Piece::PIN_PATTERN` for the PIN component, with additional logic for the optional derivation suffix.
280
-
281
- ## Advanced Usage
282
-
283
- ### Style Derivation Examples
98
+ ### Collections and Filtering
284
99
 
285
100
  ```ruby
286
- # Understanding native vs. foreign pieces
287
- # In a Chess vs. Shōgi match:
288
- # - First player native style: Chess
289
- # - Second player native style: Shōgi
290
-
291
- native_chess_king = Sashite::Pnn.parse("K") # First player native (Chess king)
292
- foreign_shogi_king = Sashite::Pnn.parse("K'") # First player foreign (Shōgi king)
293
-
294
- native_shogi_king = Sashite::Pnn.parse("k") # Second player native (Shōgi king)
295
- foreign_chess_king = Sashite::Pnn.parse("k'") # Second player foreign (Chess king)
296
-
297
- # Style queries
298
- native_chess_king.native? # => true
299
- foreign_shogi_king.derived? # => true
300
- native_shogi_king.native? # => true
301
- foreign_chess_king.derived? # => true
302
- ```
101
+ pieces = %w[KING queen +ROOK -pawn BISHOP knight].map { |n| Sashite::Pnn.parse(n) }
303
102
 
304
- ### Immutable Transformations
305
- ```ruby
306
- # All transformations return new instances
307
- original = Sashite::Pnn.piece(:K, :first)
308
- enhanced = original.enhance
309
- derived = original.derive
310
- flipped = original.flip
311
-
312
- # Original piece is never modified
313
- puts original # => "K"
314
- puts enhanced # => "+K"
315
- puts derived # => "K'"
316
- puts flipped # => "k"
317
-
318
- # Transformations can be chained
319
- result = original.flip.derive.enhance.with_type(:Q)
320
- puts result # => "+q'"
321
- ```
103
+ # Filter by player
104
+ first_player_pieces = pieces.select(&:first_player?).map(&:to_s)
105
+ # => ["KING", "+ROOK", "BISHOP"]
322
106
 
323
- ### Cross-Style Game State Management
324
- ```ruby
325
- class CrossStyleGameBoard
326
- def initialize(first_style, second_style)
327
- @first_style = first_style
328
- @second_style = second_style
329
- @pieces = {}
330
- end
331
-
332
- def place(square, piece)
333
- @pieces[square] = piece
334
- end
335
-
336
- def capture_with_style_change(from_square, to_square, new_type = nil)
337
- captured = @pieces[to_square]
338
- capturing = @pieces.delete(from_square)
339
-
340
- return nil unless captured && capturing
341
-
342
- # Style mutation: captured piece becomes native to capturing side
343
- mutated = captured.flip.underive
344
- mutated = mutated.with_type(new_type) if new_type
345
-
346
- @pieces[to_square] = capturing
347
- mutated # Return mutated captured piece for hand
348
- end
349
-
350
- def pieces_by_style_derivation
351
- {
352
- native: @pieces.select { |_, piece| piece.native? },
353
- foreign: @pieces.select { |_, piece| piece.derived? }
354
- }
355
- end
356
- end
357
-
358
- # Usage
359
- board = CrossStyleGameBoard.new(:chess, :shogi)
360
- board.place("e1", Sashite::Pnn.piece(:K, :first)) # Chess king
361
- board.place("e8", Sashite::Pnn.piece(:K, :second)) # Shōgi king
362
- board.place("d4", Sashite::Pnn.piece(:Q, :first, :normal, false)) # Chess queen using Shōgi style
363
-
364
- analysis = board.pieces_by_style_derivation
365
- puts analysis[:native].size # => 2
366
- puts analysis[:foreign].size # => 1
367
- ```
368
-
369
- ### PIN Compatibility Layer
370
- ```ruby
371
- # PNN is fully backward compatible with PIN
372
- def convert_pin_to_pnn(pin_string)
373
- # All PIN strings are valid PNN strings (native pieces)
374
- Sashite::Pnn.parse(pin_string)
375
- end
107
+ # Filter by state
108
+ enhanced_pieces = pieces.select(&:enhanced?).map(&:to_s)
109
+ # => ["+ROOK"]
376
110
 
377
- def convert_pnn_to_pin(pnn_piece)
378
- # Only native PNN pieces can be converted to PIN
379
- return nil unless pnn_piece.native?
111
+ diminished_pieces = pieces.select(&:diminished?).map(&:to_s)
112
+ # => ["-pawn"]
113
+ ```
380
114
 
381
- "#{pnn_piece.prefix}#{pnn_piece.letter}"
382
- end
115
+ ## Format Specification
383
116
 
384
- # Usage
385
- pin_pieces = %w[K Q +R -P k q r p]
386
- pnn_pieces = pin_pieces.map { |pin| convert_pin_to_pnn(pin) }
117
+ ### Structure
387
118
 
388
- pnn_pieces.all?(&:native?) # => true
389
- pnn_pieces.map { |p| convert_pnn_to_pin(p) } # => ["K", "Q", "+R", "-P", "k", "q", "r", "p"]
390
119
  ```
391
-
392
- ### Move Validation Example
393
- ```ruby
394
- def can_promote_in_style?(piece, target_rank, style_rules)
395
- return false unless piece.normal? # Already promoted pieces can't promote again
396
-
397
- case [piece.type, piece.native? ? style_rules[:native] : style_rules[:foreign]]
398
- when %i[P chess] # Chess pawn
399
- (piece.first_player? && target_rank == 8) ||
400
- (piece.second_player? && target_rank == 1)
401
- when %i[P shogi] # Shōgi pawn
402
- (piece.first_player? && target_rank >= 7) ||
403
- (piece.second_player? && target_rank <= 3)
404
- when %i[R shogi], %i[B shogi] # Shōgi major pieces
405
- true
406
- else
407
- false
408
- end
409
- end
410
-
411
- # Usage
412
- chess_pawn = Sashite::Pnn.piece(:P, :first)
413
- shogi_pawn = Sashite::Pnn.piece(:P, :first, :normal, false)
414
-
415
- style_rules = { native: :chess, foreign: :shogi }
416
-
417
- puts can_promote_in_style?(chess_pawn, 8, style_rules) # => true (chess pawn on 8th rank)
418
- puts can_promote_in_style?(shogi_pawn, 8, style_rules) # => true (shogi pawn on 8th rank)
120
+ <state-modifier>?<piece-name>
419
121
  ```
420
122
 
421
- ## Implementation Architecture
123
+ Where:
124
+ - `<state-modifier>` is optional `+` (enhanced) or `-` (diminished)
125
+ - `<piece-name>` is case-consistent alphabetic characters
422
126
 
423
- This gem uses **composition over inheritance** by building upon the proven [sashite-pin](https://github.com/sashite/pin.rb) gem:
127
+ ### Grammar (BNF)
424
128
 
425
- - **PIN Foundation**: All type, side, and state logic is handled by an internal `Pin::Piece` object
426
- - **PNN Extension**: Only the derivation (`native`) attribute and related methods are added
427
- - **Delegation Pattern**: Core PIN methods are delegated to the internal PIN piece
428
- - **Immutability**: All transformations return new instances, maintaining functional programming principles
129
+ ```bnf
130
+ <pnn> ::= <state-modifier> <name-body>
131
+ | <name-body>
429
132
 
430
- This architecture ensures:
431
- - **Reliability**: Reuses battle-tested PIN logic
432
- - **Maintainability**: PIN updates automatically benefit PNN
433
- - **Consistency**: PIN and PNN pieces behave identically for shared attributes
434
- - **Performance**: Minimal overhead over pure PIN implementation
133
+ <state-modifier> ::= "+" | "-"
435
134
 
436
- ## Protocol Mapping
135
+ <name-body> ::= <uppercase-name> | <lowercase-name>
437
136
 
438
- Following the [Game Protocol](https://sashite.dev/game-protocol/):
137
+ <uppercase-name> ::= <uppercase-letter>+
138
+ <lowercase-name> ::= <lowercase-letter>+
439
139
 
440
- | Protocol Attribute | PNN Encoding | Examples | Notes |
441
- |-------------------|--------------|----------|-------|
442
- | **Type** | ASCII letter choice | `K`/`k` = King, `P`/`p` = Pawn | Type is always stored as uppercase symbol (`:K`, `:P`) |
443
- | **Side** | Letter case in display | `K` = First player, `k` = Second player | Case is determined by side during rendering |
444
- | **State** | Optional prefix | `+K` = Enhanced, `-K` = Diminished, `K` = Normal | |
445
- | **Style** | Derivation suffix | `K` = Native style, `K'` = Foreign style | |
140
+ <uppercase-letter> ::= "A" | "B" | "C" | ... | "Z"
141
+ <lowercase-letter> ::= "a" | "b" | "c" | ... | "z"
142
+ ```
446
143
 
447
- **Style Derivation Logic**:
448
- - **No suffix**: Piece has the **native style** of its current side
449
- - **Apostrophe suffix (`'`)**: Piece has the **foreign style** (opposite side's native style)
144
+ ### Regular Expression
450
145
 
451
- **Canonical principle**: Identical pieces must have identical PNN representations.
146
+ ```ruby
147
+ /\A[+-]?([A-Z]+|[a-z]+)\z/
148
+ ```
452
149
 
453
- ## Properties
150
+ ## Design Principles
454
151
 
455
- * **PIN Compatible**: All valid PIN strings are valid PNN strings
456
- * **Style Aware**: Distinguishes pieces by their style origin through derivation markers
457
- * **ASCII Compatible**: Maximum portability across systems
458
- * **Rule-Agnostic**: Independent of specific game mechanics
459
- * **Compact Format**: Minimal character usage (1-3 characters per piece)
460
- * **Visual Distinction**: Clear player and style differentiation
461
- * **Protocol Compliant**: Complete implementation of Sashité piece attributes
462
- * **Immutable**: All piece instances are frozen and transformations return new objects
463
- * **Functional**: Pure functions with no side effects
152
+ * **Human-readable**: Names like `"KING"` or `"queen"` are intuitive and descriptive.
153
+ * **State-aware**: Integrated state management through `+` and `-` modifiers.
154
+ * **Rule-agnostic**: Independent of specific game mechanics.
155
+ * **Case-consistent**: Visual distinction between players through case.
156
+ * **Canonical**: One valid name per piece state within a given context.
157
+ * **ASCII-only**: Compatible with all systems.
464
158
 
465
- ## Implementation Notes
159
+ ## Integration with PIN
466
160
 
467
- ### Style Derivation Convention
161
+ PNN names serve as the formal source for PIN character identifiers. For example:
468
162
 
469
- PNN follows a strict style derivation convention:
163
+ | PNN | PIN | Description |
164
+ | --------- | ------- | ----------- |
165
+ | `KING` | `K` | First player king |
166
+ | `king` | `k` | Second player king |
167
+ | `+ROOK` | `+R` | Enhanced first player rook |
168
+ | `-pawn` | `-p` | Diminished second player pawn |
470
169
 
471
- 1. **Native pieces** (no suffix): Use the current side's native style
472
- 2. **Foreign pieces** (`'` suffix): Use the opposite side's native style
473
- 3. **Match context**: Each side has a defined native style for the entire match
474
- 4. **Style mutations**: Pieces can change derivation through gameplay mechanics
170
+ Multiple PNN names may map to the same PIN character (e.g., `"KING"` and `"KHAN"` both â†' `K`), but PNN provides unambiguous naming within broader contexts.
475
171
 
476
- ### Example Flow
172
+ ## Examples
477
173
 
478
174
  ```ruby
479
- # Match context: First player=Chess, Second player=Shōgi
480
- # Input: "K'" (first player foreign)
481
- # Parsing
482
- # type: :K, side: :first, state: :normal, native: false
483
- # ↓ Style resolution
484
- # Effective style: Shōgi (second player's native style)
485
- # ↓ Display
486
- # PNN: "K'" (first player king with foreign/Shōgi style)
487
- ```
175
+ # Traditional pieces
176
+ Sashite::Pnn.parse("KING") # => #<Pnn::Name value="KING">
177
+ Sashite::Pnn.parse("queen") # => #<Pnn::Name value="queen">
488
178
 
489
- This ensures that `parse(pnn).to_s == pnn` for all valid PNN strings while enabling cross-style gameplay.
179
+ # State modifiers
180
+ Sashite::Pnn.parse("+ROOK") # => #<Pnn::Name value="+ROOK">
181
+ Sashite::Pnn.parse("-pawn") # => #<Pnn::Name value="-pawn">
490
182
 
491
- ## System Constraints
183
+ # Validation
184
+ Sashite::Pnn.valid?("BISHOP") # => true
185
+ Sashite::Pnn.valid?("Bishop") # => false (mixed case)
186
+ Sashite::Pnn.valid?("KING1") # => false (no digits allowed)
187
+ ```
492
188
 
493
- - **Maximum 26 piece types** per game system (one per ASCII letter)
494
- - **Exactly 2 players** (uppercase/lowercase distinction)
495
- - **3 state levels** (enhanced, normal, diminished)
496
- - **2 style derivations** (native, foreign)
497
- - **Style context dependency**: Requires match-level side-style associations
189
+ ## API Reference
498
190
 
499
- ## Related Specifications
191
+ ### Main Module
500
192
 
501
- - [PIN](https://sashite.dev/specs/pin/1.0.0/) - Piece Identifier Notation (style-agnostic base)
502
- - [Game Protocol](https://sashite.dev/game-protocol/) - Conceptual foundation for abstract strategy board games
503
- - [SNN](https://sashite.dev/specs/snn/1.0.0/) - Style Name Notation
504
- - [GAN](https://sashite.dev/specs/gan/1.0.0/) - General Actor Notation (alternative style-aware format)
505
- - [CELL](https://sashite.dev/specs/cell/) - Board position coordinates
506
- - [HAND](https://sashite.dev/specs/hand/) - Reserve location notation
507
- - [PMN](https://sashite.dev/specs/pmn/) - Portable Move Notation
193
+ * `Sashite::Pnn.valid?(str)` â€" Returns `true` if the string is valid PNN.
194
+ * `Sashite::Pnn.parse(str)` â€" Returns a `Sashite::Pnn::Name` object.
195
+ * `Sashite::Pnn.name(sym_or_str)` â€" Alias for constructing a name.
508
196
 
509
- ## Documentation
197
+ ### `Sashite::Pnn::Name`
510
198
 
511
- - [Official PNN Specification v1.0.0](https://sashite.dev/specs/pnn/1.0.0/)
512
- - [PNN Examples Documentation](https://sashite.dev/specs/pnn/1.0.0/examples/)
513
- - [Game Protocol Foundation](https://sashite.dev/game-protocol/)
514
- - [API Documentation](https://rubydoc.info/github/sashite/pnn.rb/main)
199
+ * `#value` â€" Returns the canonical string value.
200
+ * `#to_s` â€" Returns the string representation.
201
+ * `#base_name` â€" Returns the name without state modifier.
202
+ * `#enhanced?` â€" Returns `true` if piece has enhanced state (`+` prefix).
203
+ * `#diminished?` â€" Returns `true` if piece has diminished state (`-` prefix).
204
+ * `#normal?` â€" Returns `true` if piece has normal state (no prefix).
205
+ * `#first_player?` â€" Returns `true` if piece belongs to first player (uppercase).
206
+ * `#second_player?` â€" Returns `true` if piece belongs to second player (lowercase).
207
+ * `#same_base_name?(other)` â€" Returns `true` if both pieces have same base name.
208
+ * `#==`, `#eql?`, `#hash` â€" Value-based equality.
515
209
 
516
210
  ## Development
517
211
 
@@ -546,4 +240,4 @@ Available as open source under the [MIT License](https://opensource.org/licenses
546
240
 
547
241
  ## About
548
242
 
549
- Maintained by [Sashité](https://sashite.com/) promoting chess variants and sharing the beauty of board game cultures.
243
+ Maintained by [Sashité](https://sashite.com/) â€" promoting chess variants and sharing the beauty of board game cultures.