sashite-epin 2.0.0 → 2.2.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,37 +1,17 @@
1
- # Epin.rb
1
+ # epin.rb
2
2
 
3
3
  [![Version](https://img.shields.io/github/v/tag/sashite/epin.rb?label=Version&logo=github)](https://github.com/sashite/epin.rb/tags)
4
4
  [![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/sashite/epin.rb/main)
5
- ![Ruby](https://github.com/sashite/epin.rb/actions/workflows/main.yml/badge.svg?branch=main)
6
- [![License](https://img.shields.io/github/license/sashite/epin.rb?label=License&logo=github)](https://github.com/sashite/epin.rb/raw/main/LICENSE.md)
5
+ [![CI](https://github.com/sashite/epin.rb/actions/workflows/ruby.yml/badge.svg?branch=main)](https://github.com/sashite/epin.rb/actions)
6
+ [![License](https://img.shields.io/github/license/sashite/epin.rb?label=License&logo=github)](https://github.com/sashite/epin.rb/raw/main/LICENSE)
7
7
 
8
- > **EPIN** (Extended Piece Identifier Notation) implementation for the Ruby language.
8
+ > **EPIN** (Extended Piece Identifier Notation) implementation for Ruby.
9
9
 
10
- ## What is EPIN?
10
+ ## Overview
11
11
 
12
- EPIN (Extended Piece Identifier Notation) extends [PIN](https://sashite.dev/specs/pin/1.0.0/) by adding a **derivation marker** to track piece style in cross-style games.
12
+ This library implements the [EPIN Specification v1.0.0](https://sashite.dev/specs/epin/1.0.0/).
13
13
 
14
- **EPIN is simply: PIN + optional style derivation marker (`'`)**
15
-
16
- This gem implements the [EPIN Specification v1.0.0](https://sashite.dev/specs/epin/1.0.0/) with a minimal compositional API.
17
-
18
- ## Core Concept
19
-
20
- ```ruby
21
- # EPIN is just PIN + derived flag
22
- pin = Sashite::Pin.parse("K^")
23
- epin = Sashite::Epin.new(pin, derived: false)
24
-
25
- epin.to_s # => "K^" (native)
26
- epin.pin # => PIN::Identifier instance
27
- epin.derived? # => false
28
-
29
- # Mark as derived
30
- derived_epin = epin.mark_derived
31
- derived_epin.to_s # => "K^'" (derived from opposite side's style)
32
- ```
33
-
34
- **That's it.** All piece attributes come from the PIN component.
14
+ EPIN extends [PIN](https://sashite.dev/specs/pin/1.0.0/) with an optional derivation marker (`'`) that flags whether a piece uses a native or derived style.
35
15
 
36
16
  ## Installation
37
17
 
@@ -49,48 +29,64 @@ gem install sashite-epin
49
29
  ## Dependencies
50
30
 
51
31
  ```ruby
52
- gem "sashite-pin" # Piece Identifier Notation
32
+ gem "sashite-pin" # Piece Identifier Notation
53
33
  ```
54
34
 
55
- ## Quick Start
35
+ ## Usage
36
+
37
+ ### Parsing (String → Identifier)
38
+
39
+ Convert an EPIN string into an `Identifier` object.
56
40
 
57
41
  ```ruby
58
42
  require "sashite/epin"
59
43
 
60
- # Parse an EPIN string
44
+ # Standard parsing (raises on error)
61
45
  epin = Sashite::Epin.parse("K^'")
62
- epin.to_s # => "K^'"
63
-
64
- # Access five fundamental attributes through PIN component + derived flag
65
- epin.pin.type # => :K (Piece Name)
66
- epin.pin.side # => :first (Piece Side)
67
- epin.pin.state # => :normal (Piece State)
68
- epin.pin.terminal? # => true (Terminal Status)
69
- epin.derived? # => true (Piece Style: derived vs native)
70
-
71
- # PIN component is a full PIN::Identifier
72
- epin.pin.enhanced? # => false
73
- epin.pin.letter # => "K"
46
+ epin.to_s # => "K^'"
47
+
48
+ # Access PIN attributes through the component
49
+ epin.pin.abbr # => :K
50
+ epin.pin.side # => :first
51
+ epin.pin.state # => :normal
52
+ epin.pin.terminal? # => true
53
+
54
+ # Access derivation status
55
+ epin.derived? # => true
56
+ epin.native? # => false
57
+
58
+ # PIN component is a full Sashite::Pin::Identifier instance
59
+ epin.pin.enhanced? # => false
60
+ epin.pin.first_player? # => true
61
+
62
+ # Invalid input raises ArgumentError
63
+ Sashite::Epin.parse("invalid") # => raises ArgumentError
74
64
  ```
75
65
 
76
- ## Basic Usage
66
+ ### Formatting (Identifier → String)
77
67
 
78
- ### Creating Identifiers
68
+ Convert an `Identifier` back to an EPIN string.
79
69
 
80
70
  ```ruby
81
- # Parse from string
82
- epin = Sashite::Epin.parse("K^") # Native
83
- epin = Sashite::Epin.parse("K^'") # Derived
84
-
85
- # Create from PIN component
71
+ # From PIN component
86
72
  pin = Sashite::Pin.parse("K^")
87
- epin = Sashite::Epin.new(pin, derived: false) # Native
88
- epin = Sashite::Epin.new(pin, derived: true) # Derived
73
+ epin = Sashite::Epin::Identifier.new(pin)
74
+ epin.to_s # => "K^"
89
75
 
90
- # Validate
91
- Sashite::Epin.valid?("K^") # => true
92
- Sashite::Epin.valid?("K^'") # => true
93
- Sashite::Epin.valid?("K^''") # => false (multiple markers)
76
+ # With derivation
77
+ epin = Sashite::Epin::Identifier.new(pin, derived: true)
78
+ epin.to_s # => "K^'"
79
+ ```
80
+
81
+ ### Validation
82
+
83
+ ```ruby
84
+ # Boolean check
85
+ Sashite::Epin.valid?("K") # => true
86
+ Sashite::Epin.valid?("+R^'") # => true
87
+ Sashite::Epin.valid?("invalid") # => false
88
+ Sashite::Epin.valid?("K''") # => false
89
+ Sashite::Epin.valid?("K'^") # => false
94
90
  ```
95
91
 
96
92
  ### Accessing Components
@@ -99,53 +95,31 @@ Sashite::Epin.valid?("K^''") # => false (multiple markers)
99
95
  epin = Sashite::Epin.parse("+R^'")
100
96
 
101
97
  # Get PIN component
102
- epin.pin # => #<Pin::Identifier type=:R state=:enhanced terminal=true>
103
- epin.pin.to_s # => "+R^"
98
+ epin.pin # => #<Sashite::Pin::Identifier +R^>
99
+ epin.pin.to_s # => "+R^"
104
100
 
105
101
  # Check derivation
106
- epin.derived? # => true
102
+ epin.derived? # => true
103
+ epin.native? # => false
107
104
 
108
105
  # Serialize
109
- epin.to_s # => "+R^'"
110
- ```
111
-
112
- ### Five Fundamental Attributes
113
-
114
- All attributes accessible via PIN component + derived flag:
115
-
116
- ```ruby
117
- epin = Sashite::Epin.parse("+R^'")
118
-
119
- # From PIN component (4 attributes)
120
- epin.pin.type # => :R (Piece Name)
121
- epin.pin.side # => :first (Piece Side)
122
- epin.pin.state # => :enhanced (Piece State)
123
- epin.pin.terminal? # => true (Terminal Status)
124
-
125
- # From EPIN (5th attribute)
126
- epin.derived? # => true (Piece Style: native vs derived)
106
+ epin.to_s # => "+R^'"
127
107
  ```
128
108
 
129
- ## Transformations
109
+ ### Transformations
130
110
 
131
- All transformations return new immutable instances:
132
-
133
- ### Change Derivation Status
111
+ All transformations return new immutable instances.
134
112
 
135
113
  ```ruby
136
114
  epin = Sashite::Epin.parse("K^")
137
115
 
138
- # Mark as derived
139
- derived = epin.mark_derived
140
- derived.to_s # => "K^'"
141
-
142
- # Mark as native
143
- native = derived.unmark_native
144
- native.to_s # => "K^"
116
+ # Derivation transformations
117
+ epin.derive.to_s # => "K^'"
118
+ epin.native.to_s # => "K^"
145
119
 
146
- # Toggle
147
- toggled = epin.with_derived(!epin.derived?)
148
- toggled.to_s # => "K^'"
120
+ # Replace PIN component
121
+ new_pin = Sashite::Pin.parse("+Q^")
122
+ epin.with_pin(new_pin).to_s # => "+Q^"
149
123
  ```
150
124
 
151
125
  ### Transform via PIN Component
@@ -153,506 +127,151 @@ toggled.to_s # => "K^'"
153
127
  ```ruby
154
128
  epin = Sashite::Epin.parse("K^'")
155
129
 
156
- # Replace PIN component
157
- new_pin = epin.pin.with_type(:Q)
158
- epin.with_pin(new_pin).to_s # => "Q^'"
159
-
160
- # Change type
161
- epin.with_pin(epin.pin.with_type(:Q)).to_s # => "Q^'"
130
+ # Change abbr
131
+ epin.with_pin(epin.pin.with_abbr(:Q)).to_s # => "Q^'"
162
132
 
163
133
  # Change state
164
- epin.with_pin(epin.pin.with_state(:enhanced)).to_s # => "+K^'"
165
-
166
- # Remove terminal marker
167
- epin.with_pin(epin.pin.with_terminal(false)).to_s # => "K'"
134
+ epin.with_pin(epin.pin.enhance).to_s # => "+K^'"
168
135
 
169
136
  # Change side
170
- epin.with_pin(epin.pin.flip).to_s # => "k^'"
171
- ```
172
-
173
- ### Multiple Transformations
174
-
175
- ```ruby
176
- epin = Sashite::Epin.parse("K^")
137
+ epin.with_pin(epin.pin.flip).to_s # => "k^'"
177
138
 
178
- # Transform PIN and derivation
179
- transformed = epin
180
- .with_pin(epin.pin.with_type(:Q).with_state(:enhanced))
181
- .mark_derived
182
-
183
- transformed.to_s # => "+Q^'"
139
+ # Remove terminal
140
+ epin.with_pin(epin.pin.non_terminal).to_s # => "K'"
184
141
  ```
185
142
 
186
- ## Component Queries
143
+ ### Component Queries
187
144
 
188
- Use the PIN component API directly:
145
+ Use the PIN API directly:
189
146
 
190
147
  ```ruby
191
148
  epin = Sashite::Epin.parse("+P^'")
192
149
 
193
- # PIN queries (name, side, state, terminal)
194
- epin.pin.type # => :P
195
- epin.pin.side # => :first
196
- epin.pin.state # => :enhanced
197
- epin.pin.terminal? # => true
198
- epin.pin.first_player? # => true
199
- epin.pin.enhanced? # => true
200
- epin.pin.letter # => "P"
201
- epin.pin.prefix # => "+"
202
- epin.pin.suffix # => "^"
203
-
204
- # EPIN queries (style)
205
- epin.derived? # => true
206
- epin.native? # => false
150
+ # PIN queries
151
+ epin.pin.abbr # => :P
152
+ epin.pin.side # => :first
153
+ epin.pin.state # => :enhanced
154
+ epin.pin.terminal? # => true
155
+ epin.pin.first_player? # => true
156
+ epin.pin.enhanced? # => true
157
+
158
+ # EPIN queries
159
+ epin.derived? # => true
160
+ epin.native? # => false
207
161
 
208
162
  # Compare EPINs
209
163
  other = Sashite::Epin.parse("+P^")
210
- epin.pin.same_type?(other.pin) # => true (both P)
211
- epin.pin.same_state?(other.pin) # => true (both enhanced)
212
- epin.same_derivation?(other) # => false (different derivation)
164
+ epin.pin.same_abbr?(other.pin) # => true
165
+ epin.pin.same_state?(other.pin) # => true
166
+ epin.same_derived?(other) # => false
213
167
  ```
214
168
 
215
169
  ## API Reference
216
170
 
217
- ### Main Module
218
-
219
- ```ruby
220
- # Parse EPIN string
221
- Sashite::Epin.parse(epin_string) # => Epin::Identifier
222
-
223
- # Create from PIN component
224
- Sashite::Epin.new(pin, derived: false) # => Epin::Identifier
225
-
226
- # Validate string
227
- Sashite::Epin.valid?(epin_string) # => Boolean
228
- ```
229
-
230
- ### Identifier Class
231
-
232
- #### Core Methods (6 total)
233
-
234
- ```ruby
235
- # Creation
236
- Sashite::Epin.new(pin, derived: false) # Create from PIN + derivation flag
237
-
238
- # Component access
239
- epin.pin # => PIN::Identifier
240
- epin.derived? # => Boolean
241
-
242
- # Serialization
243
- epin.to_s # => "K^'" or "K^"
244
-
245
- # PIN replacement
246
- epin.with_pin(new_pin) # New EPIN with different PIN
247
-
248
- # Derivation transformation
249
- epin.mark_derived # Mark as derived (add ')
250
- epin.unmark_native # Mark as native (remove ')
251
- epin.with_derived(boolean) # Set derivation explicitly
252
- ```
253
-
254
- #### Convenience Queries
255
-
256
- ```ruby
257
- epin.native? # !derived?
258
- epin.same_derivation?(other) # Compare derivation status
259
- ```
260
-
261
- #### Equality
262
-
263
- ```ruby
264
- epin1 == epin2 # True if both PIN and derived flag equal
265
- ```
266
-
267
- **That's the entire API.** Everything else uses the PIN component API directly.
268
-
269
- ## Format Specification
270
-
271
- ### Structure
272
- ```
273
- <pin>[']
274
- ```
275
-
276
- Where:
277
- - `<pin>` is any valid PIN token
278
- - `'` is the optional derivation marker
279
-
280
- ### Grammar (BNF)
281
- ```bnf
282
- <epin> ::= <pin> | <pin> "'"
283
-
284
- <pin> ::= ["+" | "-"] <letter> ["^"]
285
- <letter> ::= "A" | ... | "Z" | "a" | ... | "z"
286
- ```
287
-
288
- ### Regular Expression
289
- ```ruby
290
- /\A[-+]?[A-Za-z]\^?'?\z/
291
- ```
292
-
293
- ## Examples
294
-
295
- ### Basic Identifiers
171
+ ### Types
296
172
 
297
173
  ```ruby
298
- # Native pieces (no derivation marker)
299
- native_king = Sashite::Epin.parse("K^")
300
- native_king.pin.type # => :K
301
- native_king.derived? # => false
302
-
303
- # Derived pieces (with derivation marker)
304
- derived_king = Sashite::Epin.parse("K^'")
305
- derived_king.pin.type # => :K
306
- derived_king.derived? # => true
307
-
308
- # Enhanced pieces
309
- enhanced_rook = Sashite::Epin.parse("+R'")
310
- enhanced_rook.pin.enhanced? # => true
311
- enhanced_rook.derived? # => true
312
-
313
- # Diminished pieces
314
- diminished_pawn = Sashite::Epin.parse("-p")
315
- diminished_pawn.pin.diminished? # => true
316
- diminished_pawn.native? # => true
317
- ```
318
-
319
- ### Cross-Style Scenarios
320
-
321
- Assume first player uses Chess style, second player uses Makruk style:
322
-
323
- ```ruby
324
- # First player pieces
325
- chess_king = Sashite::Epin.parse("K^") # Native Chess king
326
- makruk_pawn = Sashite::Epin.parse("P'") # Derived Makruk pawn (foreign)
327
-
328
- chess_king.native? # => true (uses own style)
329
- makruk_pawn.derived? # => true (uses opponent's style)
330
-
331
- # Second player pieces
332
- makruk_king = Sashite::Epin.parse("k^") # Native Makruk king
333
- chess_pawn = Sashite::Epin.parse("p'") # Derived Chess pawn (foreign)
334
-
335
- makruk_king.native? # => true
336
- chess_pawn.derived? # => true
337
- ```
338
-
339
- ### Component Manipulation
340
-
341
- ```ruby
342
- # Start with native king
343
- epin = Sashite::Epin.parse("K^")
344
-
345
- # Convert to derived
346
- derived = epin.mark_derived
347
- derived.to_s # => "K^'"
348
-
349
- # Change to queen (keep derivation)
350
- queen = derived.with_pin(derived.pin.with_type(:Q))
351
- queen.to_s # => "Q^'"
352
-
353
- # Enhance (keep derivation)
354
- enhanced = derived.with_pin(derived.pin.with_state(:enhanced))
355
- enhanced.to_s # => "+K^'"
356
-
357
- # Change side (keep derivation)
358
- opponent = derived.with_pin(derived.pin.flip)
359
- opponent.to_s # => "k^'"
360
-
361
- # Back to native
362
- native = derived.unmark_native
363
- native.to_s # => "K^"
364
- ```
365
-
366
- ### Working with PIN Component
367
-
368
- ```ruby
369
- epin = Sashite::Epin.parse("+R^'")
370
-
371
- # Extract PIN component
372
- pin = epin.pin # => "+R^"
373
-
374
- # Transform PIN
375
- new_pin = pin.with_type(:B).with_state(:normal) # => "B^"
376
-
377
- # Create new EPIN with transformed PIN
378
- new_epin = epin.with_pin(new_pin)
379
- new_epin.to_s # => "B^'"
380
-
381
- # Multiple PIN transformations
382
- complex_pin = pin
383
- .with_type(:Q)
384
- .with_state(:diminished)
385
- .with_terminal(false)
386
- .flip
387
-
388
- epin.with_pin(complex_pin).to_s # => "-q'"
389
- ```
390
-
391
- ### Immutability
392
-
393
- ```ruby
394
- original = Sashite::Epin.parse("K^")
395
-
396
- # All transformations return new instances
397
- derived = original.mark_derived
398
- changed_pin = original.with_pin(original.pin.with_type(:Q))
399
-
400
- # Original unchanged
401
- original.to_s # => "K^"
402
- derived.to_s # => "K^'"
403
- changed_pin.to_s # => "Q^"
404
-
405
- # Components are immutable
406
- pin = original.pin
407
- pin.frozen? # => true
408
- original.frozen? # => true
409
- ```
410
-
411
- ## Attribute Mapping
412
-
413
- EPIN exposes all five fundamental attributes from the Sashité Game Protocol:
414
-
415
- | Protocol Attribute | EPIN Access | Example |
416
- |-------------------|-------------|---------|
417
- | **Piece Name** | `epin.pin.type` | `:K` (King), `:R` (Rook) |
418
- | **Piece Side** | `epin.pin.side` | `:first`, `:second` |
419
- | **Piece State** | `epin.pin.state` | `:normal`, `:enhanced`, `:diminished` |
420
- | **Terminal Status** | `epin.pin.terminal?` | `true`, `false` |
421
- | **Piece Style** | `epin.derived?` | `false` (native), `true` (derived) |
422
-
423
- ## Design Principles
424
-
425
- ### 1. Pure Composition
426
-
427
- EPIN doesn't reimplement PIN features — it extends PIN minimally:
428
-
429
- ```ruby
430
- # EPIN is just PIN + derived flag
431
- class Identifier
174
+ # Identifier represents a parsed EPIN combining PIN with derivation status.
175
+ class Sashite::Epin::Identifier
176
+ # Creates an Identifier from a PIN component.
177
+ # Raises ArgumentError if the PIN is invalid.
178
+ #
179
+ # @param pin [Sashite::Pin::Identifier] PIN component
180
+ # @param derived [Boolean] Derived status
181
+ # @return [Identifier]
432
182
  def initialize(pin, derived: false)
433
- @pin = pin
434
- @derived = !!derived
435
- end
436
- end
437
- ```
438
-
439
- ### 2. Absolute Minimal API
440
-
441
- **6 core methods only:**
442
- 1. `new(pin, derived: false)` — create from PIN
443
- 2. `pin` — get PIN component
444
- 3. `derived?` — check derivation
445
- 4. `to_s` — serialize
446
- 5. `with_pin(new_pin)` — replace PIN
447
- 6. `with_derived(boolean)` — change derivation
448
-
449
- Everything else uses the PIN component API directly.
450
-
451
- ### 3. Component Transparency
452
-
453
- Access PIN directly — no wrappers:
454
-
455
- ```ruby
456
- # Use PIN API directly
457
- epin.pin.type
458
- epin.pin.with_type(:Q)
459
- epin.pin.enhanced?
460
- epin.pin.flip
461
-
462
- # No need for wrapper methods like:
463
- # epin.type
464
- # epin.with_type
465
- # epin.enhanced?
466
- # epin.flip
467
- ```
468
183
 
469
- ### 4. Backward Compatibility
470
-
471
- Every valid PIN is a valid EPIN (without derivation marker):
472
-
473
- ```ruby
474
- # All PIN identifiers work as EPIN
475
- pin_tokens = ["K", "+R", "-p", "K^", "+R^"]
476
- pin_tokens.each do |token|
477
- epin = Sashite::Epin.parse(token)
478
- epin.native? # => true
479
- epin.to_s == token # => true
184
+ # Returns the PIN component.
185
+ #
186
+ # @return [Sashite::Pin::Identifier]
187
+ def pin
188
+
189
+ # Returns true if derived style.
190
+ #
191
+ # @return [Boolean]
192
+ def derived?
193
+
194
+ # Returns true if native style.
195
+ #
196
+ # @return [Boolean]
197
+ def native?
198
+
199
+ # Returns the EPIN string representation.
200
+ #
201
+ # @return [String]
202
+ def to_s
480
203
  end
481
204
  ```
482
205
 
483
- ### 5. Immutability
484
-
485
- All instances frozen. Transformations return new instances:
206
+ ### Parsing
486
207
 
487
208
  ```ruby
488
- epin1 = Sashite::Epin.parse("K^")
489
- epin2 = epin1.mark_derived
490
- epin1.frozen? # => true
491
- epin2.frozen? # => true
492
- epin1.equal?(epin2) # => false
209
+ # Parses an EPIN string into an Identifier.
210
+ # Raises ArgumentError if the string is not valid.
211
+ #
212
+ # @param string [String] EPIN string
213
+ # @return [Identifier]
214
+ # @raise [ArgumentError] if invalid
215
+ def Sashite::Epin.parse(string)
493
216
  ```
494
217
 
495
- ## Semantics
496
-
497
- ### Native vs Derived
498
-
499
- In cross-style games:
500
- - **Native piece**: Uses its own side's style (no `'` marker)
501
- - **Derived piece**: Uses opponent's style (`'` marker present)
218
+ ### Validation
502
219
 
503
220
  ```ruby
504
- # Chess vs Makruk match
505
- # First player = Chess, Second player = Makruk
506
-
507
- "K" # First player king in Chess style (native)
508
- "K'" # First player king in Makruk style (derived from opponent)
509
- "k" # Second player king in Makruk style (native)
510
- "k'" # Second player king in Chess style (derived from opponent)
221
+ # Reports whether string is a valid EPIN.
222
+ #
223
+ # @param string [String] EPIN string
224
+ # @return [Boolean]
225
+ def Sashite::Epin.valid?(string)
511
226
  ```
512
227
 
513
- ### Style Derivation Logic
228
+ ### Transformations
514
229
 
515
230
  ```ruby
516
- # Assume: first=Chess, second=Makruk
517
-
518
- epin = Sashite::Epin.parse("P")
519
- # Piece Side: first
520
- # Style: Chess (first's native) → Native piece
521
-
522
- epin = Sashite::Epin.parse("P'")
523
- # Piece Side: first
524
- # Style: Makruk (second's native) → Derived piece
231
+ # PIN replacement (returns new Identifier)
232
+ def with_pin(new_pin) # => Identifier with different PIN
525
233
 
526
- epin = Sashite::Epin.parse("p")
527
- # Piece Side: second
528
- # Style: Makruk (second's native) → Native piece
529
-
530
- epin = Sashite::Epin.parse("p'")
531
- # Piece Side: second
532
- # Style: Chess (first's native) → Derived piece
234
+ # Derivation transformations
235
+ def derive # => Identifier with derived: true
236
+ def native # => Identifier with derived: false
533
237
  ```
534
238
 
535
- ## Error Handling
536
-
537
- ```ruby
538
- # Invalid EPIN string
539
- begin
540
- Sashite::Epin.parse("invalid")
541
- rescue ArgumentError => e
542
- e.message # => "Invalid EPIN string: invalid"
543
- end
239
+ ### Errors
544
240
 
545
- # Multiple derivation markers
546
- begin
547
- Sashite::Epin.parse("K''")
548
- rescue ArgumentError => e
549
- # Invalid format
550
- end
241
+ All parsing and validation errors raise `ArgumentError` with descriptive messages:
551
242
 
552
- # PIN validation errors delegate
553
- begin
554
- Sashite::Epin.parse("KK'")
555
- rescue ArgumentError => e
556
- # PIN validation error
557
- end
558
- ```
243
+ | Message | Cause |
244
+ |---------|-------|
245
+ | `"invalid derivation marker"` | Derivation marker misplaced or duplicated |
246
+ | `"invalid PIN component: ..."` | PIN parsing failed |
559
247
 
560
- ## Performance Considerations
248
+ ## PIN Compatibility
561
249
 
562
- ### Efficient Composition
250
+ Every valid PIN is a valid EPIN (native by default):
563
251
 
564
252
  ```ruby
565
- # PIN component created once
566
- pin = Sashite::Pin.parse("K^")
567
- epin = Sashite::Epin.new(pin, derived: true)
568
-
569
- # Accessing components is O(1)
570
- epin.pin # => direct reference
571
- epin.derived? # => direct boolean check
572
-
573
- # No overhead from method delegation
574
- epin.pin.type # => direct method call on PIN component
575
- ```
576
-
577
- ### Transformation Patterns
578
-
579
- ```ruby
580
- epin = Sashite::Epin.parse("K^'")
581
-
582
- # Pattern 1: Change derivation only
583
- epin.unmark_native
584
-
585
- # Pattern 2: Change PIN only
586
- epin.with_pin(epin.pin.with_type(:Q))
587
-
588
- # Pattern 3: Change both
589
- new_pin = epin.pin.with_type(:Q)
590
- epin.with_pin(new_pin).unmark_native
591
-
592
- # Pattern 4: Complex PIN transformation
593
- new_pin = epin.pin
594
- .with_type(:Q)
595
- .with_state(:enhanced)
596
- .flip
597
- epin.with_pin(new_pin).mark_derived
598
- ```
599
-
600
- ## Comparison with PIN
601
-
602
- ### What EPIN Adds
603
-
604
- ```ruby
605
- # PIN: 4 attributes
606
- pin = Sashite::Pin.parse("K^")
607
- pin.type # Piece Name
608
- pin.side # Piece Side
609
- pin.state # Piece State
610
- pin.terminal? # Terminal Status
611
-
612
- # EPIN: 5 attributes (PIN + style)
613
- epin = Sashite::Epin.parse("K^'")
614
- epin.pin.type # Piece Name
615
- epin.pin.side # Piece Side
616
- epin.pin.state # Piece State
617
- epin.pin.terminal? # Terminal Status
618
- epin.derived? # Piece Style (5th attribute)
253
+ %w[K +R -p K^ +R^].each do |pin_token|
254
+ epin = Sashite::Epin.parse(pin_token)
255
+ epin.native? # => true
256
+ epin.to_s # => pin_token
257
+ end
619
258
  ```
620
259
 
621
- ### When to Use EPIN vs PIN
622
-
623
- **Use PIN when:**
624
- - Single-style games (both players use same style)
625
- - Style information not needed
626
- - Maximum compatibility required
627
-
628
- **Use EPIN when:**
629
- - Cross-style games (different styles per player)
630
- - Pieces can change style (promotion to foreign piece)
631
- - Need to track native vs derived pieces
632
-
633
- ## Design Properties
260
+ ## Design Principles
634
261
 
635
- - **Rule-agnostic**: Independent of game mechanics
636
- - **Pure composition**: Extends PIN minimally
637
- - **Minimal API**: Only 6 core methods
638
- - **Component transparency**: Direct PIN access
639
- - **Backward compatible**: All PIN tokens valid
640
- - **Immutable**: Frozen instances
641
- - **Type-safe**: Full PIN type preservation
642
- - **Style-aware**: Tracks native vs derived
643
- - **Compact**: Single character overhead for derivation
262
+ - **Pure composition**: EPIN composes PIN without reimplementing features
263
+ - **Minimal API**: Core methods (`pin`, `derived?`, `native?`, `to_s`) plus transformations
264
+ - **Component transparency**: Access PIN directly, no wrapper methods
265
+ - **Immutable identifiers**: Frozen instances prevent mutation
266
+ - **Ruby idioms**: `valid?` predicate, `to_s` conversion, `ArgumentError` for invalid input
644
267
 
645
268
  ## Related Specifications
646
269
 
647
- - [EPIN Specification v1.0.0](https://sashite.dev/specs/epin/1.0.0/) - Technical specification
648
- - [EPIN Examples](https://sashite.dev/specs/epin/1.0.0/examples/) - Usage examples
649
- - [PIN Specification v1.0.0](https://sashite.dev/specs/pin/1.0.0/) - Base component
650
- - [Sashité Game Protocol](https://sashite.dev/game-protocol/) - Foundation
270
+ - [Game Protocol](https://sashite.dev/game-protocol/) Conceptual foundation
271
+ - [EPIN Specification](https://sashite.dev/specs/epin/1.0.0/) Official specification
272
+ - [EPIN Examples](https://sashite.dev/specs/epin/1.0.0/examples/) Usage examples
273
+ - [PIN Specification](https://sashite.dev/specs/pin/1.0.0/) Base component
651
274
 
652
275
  ## License
653
276
 
654
- Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
655
-
656
- ## About
657
-
658
- Maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of board game cultures.
277
+ Available as open source under the [Apache License 2.0](https://opensource.org/licenses/Apache-2.0).