sashite-pin 3.2.0 → 4.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
@@ -1,17 +1,15 @@
1
- # Pin.rb
1
+ # pin.rb
2
2
 
3
3
  [![Version](https://img.shields.io/github/v/tag/sashite/pin.rb?label=Version&logo=github)](https://github.com/sashite/pin.rb/tags)
4
4
  [![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/sashite/pin.rb/main)
5
- ![Ruby](https://github.com/sashite/pin.rb/actions/workflows/main.yml/badge.svg?branch=main)
6
- [![License](https://img.shields.io/github/license/sashite/pin.rb?label=License&logo=github)](https://github.com/sashite/pin.rb/raw/main/LICENSE.md)
5
+ [![CI](https://github.com/sashite/pin.rb/actions/workflows/ruby.yml/badge.svg?branch=main)](https://github.com/sashite/pin.rb/actions)
6
+ [![License](https://img.shields.io/github/license/sashite/pin.rb)](https://github.com/sashite/pin.rb/blob/main/LICENSE)
7
7
 
8
- > **PIN** (Piece Identifier Notation) implementation for the Ruby language.
8
+ > **PIN** (Piece Identifier Notation) implementation for Ruby.
9
9
 
10
- ## What is PIN?
10
+ ## Overview
11
11
 
12
- PIN (Piece Identifier Notation) provides an ASCII-based format for representing pieces in abstract strategy board games. PIN translates piece attributes from the [Game Protocol](https://sashite.dev/game-protocol/) into a compact, portable notation system.
13
-
14
- This gem implements the [PIN Specification v1.0.0](https://sashite.dev/specs/pin/1.0.0/), providing a modern Ruby interface with immutable identifier objects and functional programming principles.
12
+ This library implements the [PIN Specification v1.0.0](https://sashite.dev/specs/pin/1.0.0/).
15
13
 
16
14
  ## Installation
17
15
 
@@ -28,482 +26,256 @@ gem install sashite-pin
28
26
 
29
27
  ## Usage
30
28
 
29
+ ### Parsing (String → Identifier)
30
+
31
+ Convert a PIN string into an `Identifier` object.
32
+
31
33
  ```ruby
32
34
  require "sashite/pin"
33
35
 
34
- # Parse PIN strings into identifier objects
35
- identifier = Sashite::Pin.parse("K") # => #<Pin::Identifier type=:K side=:first state=:normal terminal=false>
36
- identifier.to_s # => "K"
37
- identifier.type # => :K
38
- identifier.side # => :first
39
- identifier.state # => :normal
40
- identifier.terminal? # => false
41
-
42
- # Parse terminal pieces (e.g., kings in chess)
43
- terminal_king = Sashite::Pin.parse("K^") # => #<Pin::Identifier type=:K side=:first state=:normal terminal=true>
44
- terminal_king.to_s # => "K^"
45
- terminal_king.terminal? # => true
46
-
47
- # Create identifiers directly
48
- identifier = Sashite::Pin.identifier(:K, :first, :normal) # => #<Pin::Identifier type=:K side=:first state=:normal>
49
- terminal_king = Sashite::Pin.identifier(:K, :first, :normal, terminal: true) # => #<Pin::Identifier type=:K side=:first state=:normal terminal=true>
50
- identifier = Sashite::Pin::Identifier.new(:R, :second, :enhanced) # => #<Pin::Identifier type=:R side=:second state=:enhanced>
51
-
52
- # Validate PIN strings
53
- Sashite::Pin.valid?("K") # => true
54
- Sashite::Pin.valid?("+R") # => true
55
- Sashite::Pin.valid?("K^") # => true
56
- Sashite::Pin.valid?("+K^") # => true
57
- Sashite::Pin.valid?("invalid") # => false
58
-
59
- # State manipulation (returns new immutable instances)
60
- enhanced = identifier.enhance # => #<Pin::Identifier type=:K side=:first state=:enhanced>
61
- enhanced.to_s # => "+K"
62
- diminished = identifier.diminish # => #<Pin::Identifier type=:K side=:first state=:diminished>
63
- diminished.to_s # => "-K"
64
-
65
- # Terminal marker manipulation
66
- normal_king = Sashite::Pin.parse("K")
67
- terminal_king = normal_king.mark_terminal # => "K^"
68
- back_to_normal = terminal_king.unmark_terminal # => "K"
69
-
70
- # Side manipulation
71
- flipped = identifier.flip # => #<Pin::Identifier type=:K side=:second state=:normal>
72
- flipped.to_s # => "k"
73
-
74
- # Type manipulation
75
- queen = identifier.with_type(:Q) # => #<Pin::Identifier type=:Q side=:first state=:normal>
76
- queen.to_s # => "Q"
36
+ # Standard parsing (raises on error)
37
+ pin = Sashite::Pin.parse("K")
38
+ pin.type # => :K
39
+ pin.side # => :first
40
+ pin.state # => :normal
41
+ pin.terminal? # => false
77
42
 
78
- # State queries
79
- identifier.normal? # => true
80
- enhanced.enhanced? # => true
81
- diminished.diminished? # => true
43
+ # With state modifier
44
+ pin = Sashite::Pin.parse("+R")
45
+ pin.state # => :enhanced
82
46
 
83
- # Side queries
84
- identifier.first_player? # => true
85
- flipped.second_player? # => true
86
-
87
- # Attribute access
88
- identifier.letter # => "K"
89
- enhanced.prefix # => "+"
90
- identifier.prefix # => ""
91
- terminal_king.suffix # => "^"
92
-
93
- # Type and side comparison
94
- king1 = Sashite::Pin.parse("K")
95
- king2 = Sashite::Pin.parse("k")
96
- queen = Sashite::Pin.parse("Q")
97
-
98
- king1.same_type?(king2) # => true (both kings)
99
- king1.same_side?(queen) # => true (both first player)
100
- king1.same_type?(queen) # => false (different types)
101
-
102
- # Functional transformations can be chained
103
- pawn = Sashite::Pin.parse("P")
104
- enemy_promoted = pawn.flip.enhance # => "+p" (second player promoted pawn)
105
-
106
- # Transformations preserve terminal status
107
- terminal_piece = Sashite::Pin.parse("K^")
108
- enhanced_terminal = terminal_piece.enhance # => "+K^"
109
- ```
47
+ # With terminal marker
48
+ pin = Sashite::Pin.parse("K^")
49
+ pin.terminal? # => true
110
50
 
111
- ## Format Specification
51
+ # Combined
52
+ pin = Sashite::Pin.parse("+K^")
53
+ pin.state # => :enhanced
54
+ pin.terminal? # => true
112
55
 
113
- ### Structure
114
-
115
- ```
116
- [<state-modifier>]<letter>[<terminal-marker>]
56
+ # Invalid input raises ArgumentError
57
+ Sashite::Pin.parse("invalid") # => raises ArgumentError
117
58
  ```
118
59
 
119
- ### Components
60
+ ### Formatting (Identifier → String)
120
61
 
121
- * **Letter** (`A-Z`, `a-z`): Represents piece type and side
62
+ Convert an `Identifier` back to a PIN string.
122
63
 
123
- * Uppercase: First player pieces
124
- * Lowercase: Second player pieces
125
- * **State Modifier** (optional prefix):
64
+ ```ruby
65
+ # From Identifier object
66
+ pin = Sashite::Pin::Identifier.new(:K, :first)
67
+ pin.to_s # => "K"
126
68
 
127
- * `+`: Enhanced state (promoted, upgraded, empowered)
128
- * `-`: Diminished state (weakened, restricted, temporary)
129
- * No prefix: Normal state
130
- * **Terminal Marker** (optional suffix):
69
+ # With attributes
70
+ pin = Sashite::Pin::Identifier.new(:R, :second, :enhanced)
71
+ pin.to_s # => "+r"
131
72
 
132
- * `^`: Terminal piece (critical to match continuation)
133
- * No suffix: Non-terminal piece
73
+ pin = Sashite::Pin::Identifier.new(:K, :first, :normal, terminal: true)
74
+ pin.to_s # => "K^"
75
+ ```
134
76
 
135
- ### Regular Expression
77
+ ### Validation
136
78
 
137
79
  ```ruby
138
- /\A[-+]?[A-Za-z]\^?\z/
80
+ # Boolean check
81
+ Sashite::Pin.valid?("K") # => true
82
+ Sashite::Pin.valid?("+R") # => true
83
+ Sashite::Pin.valid?("K^") # => true
84
+ Sashite::Pin.valid?("invalid") # => false
139
85
  ```
140
86
 
141
- ### Examples
142
-
143
- * `K` - First player king (normal state)
144
- * `K^` - First player king (normal state, terminal)
145
- * `k` - Second player king (normal state)
146
- * `k^` - Second player king (normal state, terminal)
147
- * `+R` - First player rook (enhanced state)
148
- * `+R^` - First player rook (enhanced state, terminal)
149
- * `-p` - Second player pawn (diminished state)
150
-
151
- ## API Reference
152
-
153
- ### Main Module Methods
154
-
155
- * `Sashite::Pin.valid?(pin_string)` - Check if string is valid PIN notation
156
- * `Sashite::Pin.parse(pin_string)` - Parse PIN string into Identifier object
157
- * `Sashite::Pin.identifier(type, side, state = :normal, terminal: false)` - Create identifier instance directly
158
-
159
- ### Identifier Class
160
-
161
- #### Creation and Parsing
162
-
163
- * `Sashite::Pin::Identifier.new(type, side, state = :normal, terminal: false)` - Create identifier instance
164
- * `Sashite::Pin::Identifier.parse(pin_string)` - Parse PIN string (same as module method)
165
- * `Sashite::Pin::Identifier.valid?(pin_string)` - Validate PIN string (class method)
166
-
167
- #### Attribute Access
168
-
169
- * `#type` - Get piece type (symbol \:A to \:Z, always uppercase)
170
- * `#side` - Get player side (\:first or \:second)
171
- * `#state` - Get state (\:normal, \:enhanced, or \:diminished)
172
- * `#terminal` - Get terminal status (Boolean)
173
- * `#letter` - Get letter representation (string, case determined by side)
174
- * `#prefix` - Get state prefix (string: "+", "-", or "")
175
- * `#suffix` - Get terminal marker (string: "^" or "")
176
- * `#to_s` - Convert to PIN string representation
177
-
178
- #### Type and Case Handling
179
-
180
- **Important**: The `type` attribute is always stored as an uppercase symbol (`:A` to `:Z`), regardless of the input case when parsing. The display case in `#letter` and `#to_s` is determined by the `side` attribute:
87
+ ### Accessing Identifier Data
181
88
 
182
89
  ```ruby
183
- # Both create the same internal type representation
184
- identifier1 = Sashite::Pin.parse("K") # type: :K, side: :first
185
- identifier2 = Sashite::Pin.parse("k") # type: :K, side: :second
186
-
187
- identifier1.type # => :K (uppercase symbol)
188
- identifier2.type # => :K (same uppercase symbol)
189
-
190
- identifier1.letter # => "K" (uppercase display)
191
- identifier2.letter # => "k" (lowercase display)
90
+ pin = Sashite::Pin.parse("+K^")
91
+
92
+ # Get attributes
93
+ pin.type # => :K
94
+ pin.side # => :first
95
+ pin.state # => :enhanced
96
+ pin.terminal? # => true
97
+
98
+ # Get string components
99
+ pin.letter # => "K"
100
+ pin.prefix # => "+"
101
+ pin.suffix # => "^"
192
102
  ```
193
103
 
194
- #### State Queries
195
-
196
- * `#normal?` - Check if normal state (no modifiers)
197
- * `#enhanced?` - Check if enhanced state
198
- * `#diminished?` - Check if diminished state
199
-
200
- #### Side Queries
201
-
202
- * `#first_player?` - Check if first player identifier
203
- * `#second_player?` - Check if second player identifier
204
-
205
- #### Terminal Queries
206
-
207
- * `#terminal?` - Check if terminal piece
208
-
209
- #### State Transformations (immutable - return new instances)
210
-
211
- * `#enhance` - Create enhanced version
212
- * `#unenhance` - Remove enhanced state
213
- * `#diminish` - Create diminished version
214
- * `#undiminish` - Remove diminished state
215
- * `#normalize` - Remove all state modifiers
216
- * `#flip` - Switch player (change side)
217
-
218
- #### Attribute Transformations (immutable - return new instances)
219
-
220
- * `#with_type(new_type)` - Create identifier with different type
221
- * `#with_side(new_side)` - Create identifier with different side
222
- * `#with_state(new_state)` - Create identifier with different state
223
-
224
- #### Terminal Transformations (immutable - return new instances)
225
-
226
- * `#mark_terminal` - Create terminal version
227
- * `#unmark_terminal` - Create non-terminal version
228
- * `#with_terminal(boolean)` - Create identifier with specified terminal status
229
-
230
- #### Comparison Methods
231
-
232
- * `#same_type?(other)` - Check if same piece type
233
- * `#same_side?(other)` - Check if same side
234
- * `#same_state?(other)` - Check if same state
235
- * `#same_terminal?(other)` - Check if same terminal status
236
- * `#==(other)` - Full equality comparison
237
-
238
- ### Constants
239
-
240
- * `Sashite::Pin::Identifier::PIN_PATTERN` - Regular expression for PIN validation (internal use)
241
- * `Sashite::Pin::Identifier::TERMINAL_MARKER` - Terminal marker character (`"^"`)
242
-
243
- ## Advanced Usage
104
+ ### Transformations
244
105
 
245
- ### Type Normalization Examples
106
+ All transformations return new immutable instances.
246
107
 
247
108
  ```ruby
248
- # Parsing different cases results in same type
249
- white_king = Sashite::Pin.parse("K")
250
- black_king = Sashite::Pin.parse("k")
109
+ pin = Sashite::Pin.parse("K")
251
110
 
252
- # Types are normalized to uppercase
253
- white_king.type # => :K
254
- black_king.type # => :K (same type!)
111
+ # State transformations
112
+ pin.enhance.to_s # => "+K"
113
+ pin.diminish.to_s # => "-K"
114
+ pin.normalize.to_s # => "K"
255
115
 
256
- # Sides are different
257
- white_king.side # => :first
258
- black_king.side # => :second
116
+ # Side transformation
117
+ pin.flip.to_s # => "k"
259
118
 
260
- # Display follows side convention
261
- white_king.letter # => "K"
262
- black_king.letter # => "k"
119
+ # Terminal transformations
120
+ pin.mark_terminal.to_s # => "K^"
121
+ pin.unmark_terminal.to_s # => "K"
263
122
 
264
- # Same type, different sides
265
- white_king.same_type?(black_king) # => true
266
- white_king.same_side?(black_king) # => false
123
+ # Attribute changes
124
+ pin.with_type(:Q).to_s # => "Q"
125
+ pin.with_side(:second).to_s # => "k"
126
+ pin.with_state(:enhanced).to_s # => "+K"
127
+ pin.with_terminal(true).to_s # => "K^"
267
128
  ```
268
129
 
269
- ### Immutable Transformations
270
- ```ruby
271
- # All transformations return new instances
272
- original = Sashite::Pin.identifier(:K, :first, :normal)
273
- enhanced = original.enhance
274
- diminished = original.diminish
275
-
276
- # Original piece is never modified
277
- puts original.to_s # => "K"
278
- puts enhanced.to_s # => "+K"
279
- puts diminished.to_s # => "-K"
280
-
281
- # Transformations can be chained
282
- result = original.flip.enhance.with_type(:Q)
283
- puts result.to_s # => "+q"
284
-
285
- # Terminal status is preserved through transformations
286
- terminal_king = Sashite::Pin.parse("K^")
287
- enhanced_terminal = terminal_king.enhance
288
- puts enhanced_terminal.to_s # => "+K^"
289
- ```
130
+ ### Queries
290
131
 
291
- ### Game State Management
292
132
  ```ruby
293
- class GameBoard
294
- def initialize
295
- @pieces = {}
296
- end
297
-
298
- def place(square, piece)
299
- @pieces[square] = piece
300
- end
301
-
302
- def promote(square, new_type = :Q)
303
- piece = @pieces[square]
304
- return nil unless piece&.normal? # Can only promote normal pieces
305
-
306
- @pieces[square] = piece.with_type(new_type).enhance
307
- end
308
-
309
- def capture(from_square, to_square)
310
- captured = @pieces[to_square]
311
- @pieces[to_square] = @pieces.delete(from_square)
312
- captured
313
- end
314
-
315
- def pieces_by_side(side)
316
- @pieces.select { |_, piece| piece.side == side }
317
- end
318
-
319
- def promoted_pieces
320
- @pieces.select { |_, piece| piece.enhanced? }
321
- end
322
-
323
- def terminal_pieces
324
- @pieces.select { |_, piece| piece.terminal? }
325
- end
326
- end
133
+ pin = Sashite::Pin.parse("+K^")
327
134
 
328
- # Usage
329
- board = GameBoard.new
330
- board.place("e1", Sashite::Pin.identifier(:K, :first, :normal, terminal: true))
331
- board.place("e8", Sashite::Pin.identifier(:K, :second, :normal, terminal: true))
332
- board.place("a7", Sashite::Pin.identifier(:P, :first, :normal))
135
+ # State queries
136
+ pin.normal? # => false
137
+ pin.enhanced? # => true
138
+ pin.diminished? # => false
333
139
 
334
- # Promote pawn
335
- board.promote("a7", :Q)
336
- promoted = board.promoted_pieces
337
- puts promoted.values.first.to_s # => "+Q"
140
+ # Side queries
141
+ pin.first_player? # => true
142
+ pin.second_player? # => false
143
+
144
+ # Terminal query
145
+ pin.terminal? # => true
146
+
147
+ # Comparison queries
148
+ other = Sashite::Pin.parse("k")
149
+ pin.same_type?(other) # => true
150
+ pin.same_side?(other) # => false
151
+ pin.same_state?(other) # => false
152
+ pin.same_terminal?(other) # => false
338
153
  ```
339
154
 
340
- ### Piece Analysis
341
- ```ruby
342
- def analyze_pieces(pins)
343
- pieces = pins.map { |pin| Sashite::Pin.parse(pin) }
344
-
345
- {
346
- total: pieces.size,
347
- by_side: pieces.group_by(&:side),
348
- by_type: pieces.group_by(&:type),
349
- by_state: pieces.group_by(&:state),
350
- promoted: pieces.count(&:enhanced?),
351
- weakened: pieces.count(&:diminished?),
352
- terminal: pieces.count(&:terminal?)
353
- }
354
- end
155
+ ## API Reference
355
156
 
356
- pins = %w[K^ Q +R B N P k^ q r +b n -p]
357
- analysis = analyze_pieces(pins)
358
- puts analysis[:by_side][:first].size # => 6
359
- puts analysis[:promoted] # => 2
360
- puts analysis[:terminal] # => 2
361
- ```
157
+ ### Types
362
158
 
363
- ### Move Validation Example
364
159
  ```ruby
365
- def can_promote?(piece, target_rank)
366
- return false unless piece.normal? # Already promoted pieces can't promote again
367
-
368
- case piece.type
369
- when :P # Pawn
370
- (piece.first_player? && target_rank == 8) ||
371
- (piece.second_player? && target_rank == 1)
372
- when :R, :B, :S, :N, :L # Shōgi pieces that can promote
373
- true
374
- else
375
- false
376
- end
160
+ # Identifier represents a parsed PIN with all attributes.
161
+ class Sashite::Pin::Identifier
162
+ # Creates an Identifier from attributes.
163
+ # Raises ArgumentError if attributes are invalid.
164
+ #
165
+ # @param type [Symbol] Piece type (:A to :Z)
166
+ # @param side [Symbol] Player side (:first or :second)
167
+ # @param state [Symbol] Piece state (:normal, :enhanced, or :diminished)
168
+ # @param terminal [Boolean] Terminal status
169
+ # @return [Identifier]
170
+ def initialize(type, side, state = :normal, terminal: false)
171
+
172
+ # Returns the piece type (always uppercase symbol).
173
+ #
174
+ # @return [Symbol]
175
+ def type
176
+
177
+ # Returns the player side.
178
+ #
179
+ # @return [Symbol] :first or :second
180
+ def side
181
+
182
+ # Returns the piece state.
183
+ #
184
+ # @return [Symbol] :normal, :enhanced, or :diminished
185
+ def state
186
+
187
+ # Returns the terminal status.
188
+ #
189
+ # @return [Boolean]
190
+ def terminal?
191
+
192
+ # Returns the PIN string representation.
193
+ #
194
+ # @return [String]
195
+ def to_s
377
196
  end
378
-
379
- pawn = Sashite::Pin.identifier(:P, :first, :normal)
380
- puts can_promote?(pawn, 8) # => true
381
-
382
- promoted_pawn = pawn.enhance
383
- puts can_promote?(promoted_pawn, 8) # => false (already promoted)
384
197
  ```
385
198
 
386
- ## Protocol Mapping
387
-
388
- Following the [Game Protocol](https://sashite.dev/game-protocol/):
389
-
390
- | Protocol Attribute | PIN Encoding | Examples | Notes |
391
- |-------------------|--------------|----------|-------|
392
- | **Piece Name** | ASCII letter choice | `K`/`k` = King, `P`/`p` = Pawn | Type is always stored as uppercase symbol (`:K`, `:P`) |
393
- | **Piece Side** | Letter case in display | `K` = First player, `k` = Second player | Case is determined by side during rendering |
394
- | **Piece State** | Optional prefix | `+K` = Enhanced, `-K` = Diminished, `K` = Normal | |
395
- | **Terminal Status** | Optional suffix | `K^` = Terminal, `K` = Non-terminal | Identifies pieces critical to match continuation |
396
-
397
- **Type Convention**: All piece types are internally represented as uppercase symbols (`:A` to `:Z`). The display case is determined by the `side` attribute: first player pieces display as uppercase, second player pieces as lowercase.
398
-
399
- **Canonical principle**: Identical pieces must have identical PIN representations.
400
-
401
- **Note**: PIN does not represent the **Style** attribute from the Game Protocol. For style-aware piece notation, see [Piece Name Notation (PNN)](https://sashite.dev/specs/pnn/).
402
-
403
- ## Properties
404
-
405
- * **ASCII Compatible**: Maximum portability across systems
406
- * **Rule-Agnostic**: Independent of specific game mechanics
407
- * **Compact Format**: 1-3 characters per piece
408
- * **Visual Distinction**: Clear player differentiation through case
409
- * **Type Normalization**: Consistent uppercase type representation internally
410
- * **Terminal Marker**: Explicit identification of pieces critical to match continuation
411
- * **Protocol Compliant**: Direct implementation of Sashité piece attributes
412
- * **Immutable**: All piece instances are frozen and transformations return new objects
413
- * **Functional**: Pure functions with no side effects
414
-
415
- ## Implementation Notes
416
-
417
- ### Type Normalization Convention
418
-
419
- PIN follows a strict type normalization convention:
420
-
421
- 1. **Internal Storage**: All piece types are stored as uppercase symbols (`:A` to `:Z`)
422
- 2. **Input Flexibility**: Both `"K"` and `"k"` are valid input during parsing
423
- 3. **Case Semantics**: Input case determines the `side` attribute, not the `type`
424
- 4. **Display Logic**: Output case is computed from `side` during rendering
425
-
426
- This design ensures:
427
- - Consistent internal representation regardless of input format
428
- - Clear separation between piece identity (type) and ownership (side)
429
- - Predictable behavior when comparing pieces of the same type
430
-
431
- ### Terminal Marker Convention
432
-
433
- The terminal marker (`^`) identifies pieces whose presence, condition, or capacity for action determines whether the match can continue:
434
-
435
- 1. **Suffix Position**: Always appears as the last character (`K^`, `+K^`, `-k^`)
436
- 2. **Preservation**: Terminal status is preserved through all transformations
437
- 3. **Equality**: Two pieces are equal only if they have the same terminal status
438
- 4. **Independence**: Terminal status is independent of state (normal/enhanced/diminished)
439
-
440
- ### Example Flow
199
+ ### Constants
441
200
 
442
201
  ```ruby
443
- # Input: "k" (lowercase)
444
- # Parsing
445
- # type: :K (normalized to uppercase)
446
- # side: :second (inferred from lowercase input)
447
- # ↓ Display
448
- # letter: "k" (computed from type + side)
449
- # PIN: "k" (final representation)
202
+ Sashite::Pin::Constants::VALID_TYPES # => [:A, :B, ..., :Z]
203
+ Sashite::Pin::Constants::VALID_SIDES # => [:first, :second]
204
+ Sashite::Pin::Constants::VALID_STATES # => [:normal, :enhanced, :diminished]
205
+ Sashite::Pin::Constants::MAX_STRING_LENGTH # => 3
450
206
  ```
451
207
 
452
- This ensures that `parse(pin).to_s == pin` for all valid PIN strings while maintaining internal consistency.
453
-
454
- ## System Constraints
208
+ ### Parsing
455
209
 
456
- - **Maximum 26 piece types** per game system (one per ASCII letter)
457
- - **Exactly 2 players** (uppercase/lowercase distinction)
458
- - **3 state levels** (enhanced, normal, diminished)
459
- - **2 terminal levels** (terminal, non-terminal)
210
+ ```ruby
211
+ # Parses a PIN string into an Identifier.
212
+ # Raises ArgumentError if the string is not valid.
213
+ #
214
+ # @param string [String] PIN string
215
+ # @return [Identifier]
216
+ # @raise [ArgumentError] if invalid
217
+ def Sashite::Pin.parse(string)
218
+ ```
460
219
 
461
- ## Related Specifications
220
+ ### Validation
462
221
 
463
- - [Game Protocol](https://sashite.dev/game-protocol/) - Conceptual foundation for abstract strategy board games
464
- - [PNN](https://sashite.dev/specs/pnn/) - Piece Name Notation (style-aware piece representation)
465
- - [CELL](https://sashite.dev/specs/cell/) - Board position coordinates
466
- - [HAND](https://sashite.dev/specs/hand/) - Reserve location notation
467
- - [PMN](https://sashite.dev/specs/pmn/) - Portable Move Notation
222
+ ```ruby
223
+ # Reports whether string is a valid PIN.
224
+ #
225
+ # @param string [String] PIN string
226
+ # @return [Boolean]
227
+ def Sashite::Pin.valid?(string)
228
+ ```
468
229
 
469
- ## Documentation
230
+ ### Transformations
470
231
 
471
- - [Official PIN Specification v1.0.0](https://sashite.dev/specs/pin/1.0.0/)
472
- - [PIN Examples Documentation](https://sashite.dev/specs/pin/1.0.0/examples/)
473
- - [Game Protocol Foundation](https://sashite.dev/game-protocol/)
474
- - [API Documentation](https://rubydoc.info/github/sashite/pin.rb/main)
232
+ ```ruby
233
+ # State transformations (return new Identifier)
234
+ def enhance # => Identifier with :enhanced state
235
+ def diminish # => Identifier with :diminished state
236
+ def normalize # => Identifier with :normal state
237
+
238
+ # Side transformation
239
+ def flip # => Identifier with opposite side
240
+
241
+ # Terminal transformations
242
+ def mark_terminal # => Identifier with terminal: true
243
+ def unmark_terminal # => Identifier with terminal: false
244
+
245
+ # Attribute changes
246
+ def with_type(new_type) # => Identifier with different type
247
+ def with_side(new_side) # => Identifier with different side
248
+ def with_state(new_state) # => Identifier with different state
249
+ def with_terminal(new_terminal) # => Identifier with specified terminal status
250
+ ```
475
251
 
476
- ## Development
252
+ ### Errors
477
253
 
478
- ```sh
479
- # Clone the repository
480
- git clone https://github.com/sashite/pin.rb.git
481
- cd pin.rb
254
+ All parsing and validation errors raise `ArgumentError` with descriptive messages:
482
255
 
483
- # Install dependencies
484
- bundle install
256
+ | Message | Cause |
257
+ |---------|-------|
258
+ | `"empty input"` | String length is 0 |
259
+ | `"input exceeds 3 characters"` | String too long |
260
+ | `"must contain exactly one letter"` | Missing or multiple letters |
261
+ | `"invalid state modifier"` | Invalid prefix character |
262
+ | `"invalid terminal marker"` | Invalid suffix character |
485
263
 
486
- # Run tests
487
- ruby test.rb
264
+ ## Design Principles
488
265
 
489
- # Generate documentation
490
- yard doc
491
- ```
266
+ - **Bounded values**: Explicit validation of types, sides, states
267
+ - **Object-oriented**: `Identifier` class enables methods and encapsulation
268
+ - **Ruby idioms**: `valid?` predicate, `to_s` conversion, `ArgumentError` for invalid input
269
+ - **Immutable identifiers**: Frozen instances prevent mutation
270
+ - **Transformation methods**: Return new instances for state changes
271
+ - **No dependencies**: Pure Ruby standard library only
492
272
 
493
- ## Contributing
273
+ ## Related Specifications
494
274
 
495
- 1. Fork the repository
496
- 2. Create a feature branch (`git checkout -b feature/new-feature`)
497
- 3. Add tests for your changes
498
- 4. Ensure all tests pass (`ruby test.rb`)
499
- 5. Commit your changes (`git commit -am 'Add new feature'`)
500
- 6. Push to the branch (`git push origin feature/new-feature`)
501
- 7. Create a Pull Request
275
+ - [Game Protocol](https://sashite.dev/game-protocol/) Conceptual foundation
276
+ - [PIN Specification](https://sashite.dev/specs/pin/1.0.0/) Official specification
277
+ - [PIN Examples](https://sashite.dev/specs/pin/1.0.0/examples/) Usage examples
502
278
 
503
279
  ## License
504
280
 
505
- Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
506
-
507
- ## About
508
-
509
- Maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of board game cultures.
281
+ Available as open source under the [Apache License 2.0](https://opensource.org/licenses/Apache-2.0).