sashite-epin 2.0.0 → 2.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.
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # Epin.rb
1
+ # Sashite::Epin
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
5
  ![Ruby](https://github.com/sashite/epin.rb/actions/workflows/main.yml/badge.svg?branch=main)
6
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)
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
10
  ## What is EPIN?
11
11
 
@@ -15,24 +15,6 @@ EPIN (Extended Piece Identifier Notation) extends [PIN](https://sashite.dev/spec
15
15
 
16
16
  This gem implements the [EPIN Specification v1.0.0](https://sashite.dev/specs/epin/1.0.0/) with a minimal compositional API.
17
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.
35
-
36
18
  ## Installation
37
19
 
38
20
  ```ruby
@@ -46,51 +28,67 @@ Or install manually:
46
28
  gem install sashite-epin
47
29
  ```
48
30
 
49
- ## Dependencies
31
+ This will also install `sashite-pin` as a dependency.
32
+
33
+ ## Core Concept
50
34
 
51
35
  ```ruby
52
- gem "sashite-pin" # Piece Identifier Notation
36
+ require "sashite/epin"
37
+
38
+ # EPIN is just PIN + derived flag
39
+ pin = Sashite::Pin.parse("K^")
40
+ epin = Sashite::Epin.new(pin)
41
+
42
+ epin.to_s # => "K^" (native)
43
+ epin.pin # => #<Sashite::Pin K^>
44
+ epin.derived # => false
45
+
46
+ # Mark as derived
47
+ derived_epin = epin.mark_derived
48
+ derived_epin.to_s # => "K^'" (derived from opposite side's style)
53
49
  ```
54
50
 
55
- ## Quick Start
51
+ **That's it.** All piece attributes come from the PIN component.
52
+
53
+ ## Usage
56
54
 
57
55
  ```ruby
58
56
  require "sashite/epin"
59
57
 
60
- # Parse an EPIN string
58
+ # Parse EPIN strings
61
59
  epin = Sashite::Epin.parse("K^'")
62
60
  epin.to_s # => "K^'"
63
61
 
64
62
  # 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"
74
- ```
63
+ epin.pin.type # => :K (Piece Name)
64
+ epin.pin.side # => :first (Piece Side)
65
+ epin.pin.state # => :normal (Piece State)
66
+ epin.pin.terminal # => true (Terminal Status)
67
+ epin.derived # => true (Piece Style: derived vs native)
75
68
 
76
- ## Basic Usage
69
+ # PIN component is a full Sashite::Pin instance
70
+ epin.pin.enhanced? # => false
71
+ epin.pin.letter # => "K"
72
+ epin.pin.first_player? # => true
73
+ ```
77
74
 
78
75
  ### Creating Identifiers
79
76
 
80
77
  ```ruby
81
78
  # Parse from string
82
- epin = Sashite::Epin.parse("K^") # Native
83
- epin = Sashite::Epin.parse("K^'") # Derived
79
+ epin = Sashite::Epin.parse("K^") # Native
80
+ epin = Sashite::Epin.parse("K^'") # Derived
84
81
 
85
82
  # Create from PIN component
86
83
  pin = Sashite::Pin.parse("K^")
87
- epin = Sashite::Epin.new(pin, derived: false) # Native
88
- epin = Sashite::Epin.new(pin, derived: true) # Derived
84
+ epin = Sashite::Epin.new(pin) # Native (default)
85
+ epin = Sashite::Epin.new(pin, derived: true) # Derived
89
86
 
90
- # Validate
91
- Sashite::Epin.valid?("K^") # => true
92
- Sashite::Epin.valid?("K^'") # => true
93
- Sashite::Epin.valid?("K^''") # => false (multiple markers)
87
+ # Validation
88
+ Sashite::Epin.valid?("K^") # => true
89
+ Sashite::Epin.valid?("K^'") # => true
90
+ Sashite::Epin.valid?("K^''") # => false (multiple markers)
91
+ Sashite::Epin.valid?("K'^") # => false (wrong order)
94
92
  ```
95
93
 
96
94
  ### Accessing Components
@@ -99,38 +97,21 @@ Sashite::Epin.valid?("K^''") # => false (multiple markers)
99
97
  epin = Sashite::Epin.parse("+R^'")
100
98
 
101
99
  # Get PIN component
102
- epin.pin # => #<Pin::Identifier type=:R state=:enhanced terminal=true>
103
- epin.pin.to_s # => "+R^"
100
+ epin.pin # => #<Sashite::Pin +R^>
101
+ epin.pin.to_s # => "+R^"
104
102
 
105
103
  # Check derivation
106
- epin.derived? # => true
104
+ epin.derived # => true
105
+ epin.derived? # => true
106
+ epin.native? # => false
107
107
 
108
108
  # Serialize
109
109
  epin.to_s # => "+R^'"
110
110
  ```
111
111
 
112
- ### Five Fundamental Attributes
112
+ ### Transformations
113
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)
127
- ```
128
-
129
- ## Transformations
130
-
131
- All transformations return new immutable instances:
132
-
133
- ### Change Derivation Status
114
+ All transformations return new immutable instances.
134
115
 
135
116
  ```ruby
136
117
  epin = Sashite::Epin.parse("K^")
@@ -140,11 +121,11 @@ derived = epin.mark_derived
140
121
  derived.to_s # => "K^'"
141
122
 
142
123
  # Mark as native
143
- native = derived.unmark_native
124
+ native = derived.unmark_derived
144
125
  native.to_s # => "K^"
145
126
 
146
- # Toggle
147
- toggled = epin.with_derived(!epin.derived?)
127
+ # Set explicitly
128
+ toggled = epin.with_derived(true)
148
129
  toggled.to_s # => "K^'"
149
130
  ```
150
131
 
@@ -157,17 +138,17 @@ epin = Sashite::Epin.parse("K^'")
157
138
  new_pin = epin.pin.with_type(:Q)
158
139
  epin.with_pin(new_pin).to_s # => "Q^'"
159
140
 
160
- # Change type
161
- epin.with_pin(epin.pin.with_type(:Q)).to_s # => "Q^'"
162
-
163
141
  # Change state
164
- epin.with_pin(epin.pin.with_state(:enhanced)).to_s # => "+K^'"
142
+ new_pin = epin.pin.enhance
143
+ epin.with_pin(new_pin).to_s # => "+K^'"
165
144
 
166
145
  # Remove terminal marker
167
- epin.with_pin(epin.pin.with_terminal(false)).to_s # => "K'"
146
+ new_pin = epin.pin.unmark_terminal
147
+ epin.with_pin(new_pin).to_s # => "K'"
168
148
 
169
149
  # Change side
170
- epin.with_pin(epin.pin.flip).to_s # => "k^'"
150
+ new_pin = epin.pin.flip
151
+ epin.with_pin(new_pin).to_s # => "k^'"
171
152
  ```
172
153
 
173
154
  ### Multiple Transformations
@@ -176,99 +157,57 @@ epin.with_pin(epin.pin.flip).to_s # => "k^'"
176
157
  epin = Sashite::Epin.parse("K^")
177
158
 
178
159
  # Transform PIN and derivation
179
- transformed = epin
180
- .with_pin(epin.pin.with_type(:Q).with_state(:enhanced))
181
- .mark_derived
160
+ new_pin = epin.pin.with_type(:Q).enhance
161
+ transformed = epin.with_pin(new_pin).mark_derived
182
162
 
183
163
  transformed.to_s # => "+Q^'"
184
164
  ```
185
165
 
186
- ## Component Queries
166
+ ### Component Queries
187
167
 
188
- Use the PIN component API directly:
168
+ Use the PIN API directly:
189
169
 
190
170
  ```ruby
191
171
  epin = Sashite::Epin.parse("+P^'")
192
172
 
193
173
  # 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 # => "^"
174
+ epin.pin.type # => :P
175
+ epin.pin.side # => :first
176
+ epin.pin.state # => :enhanced
177
+ epin.pin.terminal # => true
178
+ epin.pin.first_player? # => true
179
+ epin.pin.enhanced? # => true
180
+ epin.pin.letter # => "P"
181
+ epin.pin.prefix # => "+"
182
+ epin.pin.suffix # => "^"
203
183
 
204
184
  # EPIN queries (style)
205
- epin.derived? # => true
206
- epin.native? # => false
185
+ epin.derived? # => true
186
+ epin.native? # => false
207
187
 
208
188
  # Compare EPINs
209
189
  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)
213
- ```
214
-
215
- ## API Reference
216
-
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
190
+ epin.pin.same_type?(other.pin) # => true (both P)
191
+ epin.pin.same_state?(other.pin) # => true (both enhanced)
192
+ epin.same_derived?(other) # => false (different derivation)
259
193
  ```
260
194
 
261
- #### Equality
195
+ ## Five Fundamental Attributes
262
196
 
263
- ```ruby
264
- epin1 == epin2 # True if both PIN and derived flag equal
265
- ```
197
+ EPIN exposes all five attributes from the [Sashité Game Protocol](https://sashite.dev/game-protocol/):
266
198
 
267
- **That's the entire API.** Everything else uses the PIN component API directly.
199
+ | Protocol Attribute | EPIN Access | Example |
200
+ |-------------------|-------------|---------|
201
+ | **Piece Name** | `epin.pin.type` | `:K` (King), `:R` (Rook) |
202
+ | **Piece Side** | `epin.pin.side` | `:first`, `:second` |
203
+ | **Piece State** | `epin.pin.state` | `:normal`, `:enhanced`, `:diminished` |
204
+ | **Terminal Status** | `epin.pin.terminal` | `true`, `false` |
205
+ | **Piece Style** | `epin.derived` | `false` (native), `true` (derived) |
268
206
 
269
207
  ## Format Specification
270
208
 
271
209
  ### Structure
210
+
272
211
  ```
273
212
  <pin>[']
274
213
  ```
@@ -277,148 +216,140 @@ Where:
277
216
  - `<pin>` is any valid PIN token
278
217
  - `'` is the optional derivation marker
279
218
 
280
- ### Grammar (BNF)
281
- ```bnf
282
- <epin> ::= <pin> | <pin> "'"
219
+ ### Grammar (EBNF)
283
220
 
284
- <pin> ::= ["+" | "-"] <letter> ["^"]
285
- <letter> ::= "A" | ... | "Z" | "a" | ... | "z"
221
+ ```ebnf
222
+ epin ::= pin | pin "'"
223
+ pin ::= ["+" | "-"] letter ["^"]
224
+ letter ::= "A" | ... | "Z" | "a" | ... | "z"
286
225
  ```
287
226
 
288
227
  ### Regular Expression
228
+
289
229
  ```ruby
290
230
  /\A[-+]?[A-Za-z]\^?'?\z/
291
231
  ```
292
232
 
293
- ## Examples
233
+ ### Examples
294
234
 
295
- ### Basic Identifiers
235
+ | EPIN | Side | State | Terminal | Derived | Description |
236
+ |------|------|-------|----------|---------|-------------|
237
+ | `K` | First | Normal | No | No | Standard native king |
238
+ | `K'` | First | Normal | No | Yes | Derived king |
239
+ | `K^` | First | Normal | Yes | No | Terminal native king |
240
+ | `K^'` | First | Normal | Yes | Yes | Terminal derived king |
241
+ | `+R'` | First | Enhanced | No | Yes | Enhanced derived rook |
242
+ | `-p` | Second | Diminished | No | No | Diminished native pawn |
296
243
 
297
- ```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
244
+ ## Cross-Style Game Example
320
245
 
321
- Assume first player uses Chess style, second player uses Makruk style:
246
+ In a chess-vs-makruk cross-style match where:
247
+ - First side native style = chess
248
+ - Second side native style = makruk
322
249
 
323
250
  ```ruby
324
251
  # First player pieces
325
- chess_king = Sashite::Epin.parse("K^") # Native Chess king
326
- makruk_pawn = Sashite::Epin.parse("P'") # Derived Makruk pawn (foreign)
252
+ chess_king = Sashite::Epin.parse("K^") # Native Chess king
253
+ makruk_pawn = Sashite::Epin.parse("P'") # Derived Makruk pawn (foreign)
327
254
 
328
- chess_king.native? # => true (uses own style)
329
- makruk_pawn.derived? # => true (uses opponent's style)
255
+ chess_king.native? # => true (uses own style)
256
+ makruk_pawn.derived? # => true (uses opponent's style)
330
257
 
331
258
  # Second player pieces
332
- makruk_king = Sashite::Epin.parse("k^") # Native Makruk king
333
- chess_pawn = Sashite::Epin.parse("p'") # Derived Chess pawn (foreign)
259
+ makruk_king = Sashite::Epin.parse("k^") # Native Makruk king
260
+ chess_pawn = Sashite::Epin.parse("p'") # Derived Chess pawn (foreign)
334
261
 
335
- makruk_king.native? # => true
336
- chess_pawn.derived? # => true
262
+ makruk_king.native? # => true
263
+ chess_pawn.derived? # => true
337
264
  ```
338
265
 
339
- ### Component Manipulation
266
+ ## API Reference
340
267
 
341
- ```ruby
342
- # Start with native king
343
- epin = Sashite::Epin.parse("K^")
268
+ ### Parsing and Validation
344
269
 
345
- # Convert to derived
346
- derived = epin.mark_derived
347
- derived.to_s # => "K^'"
270
+ ```ruby
271
+ Sashite::Epin.parse(epin_string) # => Sashite::Epin | raises ArgumentError
272
+ Sashite::Epin.valid?(epin_string) # => boolean
273
+ ```
348
274
 
349
- # Change to queen (keep derivation)
350
- queen = derived.with_pin(derived.pin.with_type(:Q))
351
- queen.to_s # => "Q^'"
275
+ ### Creation
352
276
 
353
- # Enhance (keep derivation)
354
- enhanced = derived.with_pin(derived.pin.with_state(:enhanced))
355
- enhanced.to_s # => "+K^'"
277
+ ```ruby
278
+ Sashite::Epin.new(pin) # Native (default)
279
+ Sashite::Epin.new(pin, derived: true) # Derived
280
+ ```
356
281
 
357
- # Change side (keep derivation)
358
- opponent = derived.with_pin(derived.pin.flip)
359
- opponent.to_s # => "k^'"
282
+ ### Conversion
360
283
 
361
- # Back to native
362
- native = derived.unmark_native
363
- native.to_s # => "K^"
284
+ ```ruby
285
+ epin.to_s # => String
364
286
  ```
365
287
 
366
- ### Working with PIN Component
288
+ ### Transformations
367
289
 
368
- ```ruby
369
- epin = Sashite::Epin.parse("+R^'")
290
+ All transformations return new `Sashite::Epin` instances:
370
291
 
371
- # Extract PIN component
372
- pin = epin.pin # => "+R^"
292
+ ```ruby
293
+ # PIN replacement
294
+ epin.with_pin(new_pin) # => Sashite::Epin with different PIN
373
295
 
374
- # Transform PIN
375
- new_pin = pin.with_type(:B).with_state(:normal) # => "B^"
296
+ # Derivation
297
+ epin.mark_derived # => Sashite::Epin with derived: true
298
+ epin.unmark_derived # => Sashite::Epin with derived: false
299
+ epin.with_derived(boolean) # => Sashite::Epin with specified derivation
300
+ ```
376
301
 
377
- # Create new EPIN with transformed PIN
378
- new_epin = epin.with_pin(new_pin)
379
- new_epin.to_s # => "B^'"
302
+ ### Queries
380
303
 
381
- # Multiple PIN transformations
382
- complex_pin = pin
383
- .with_type(:Q)
384
- .with_state(:diminished)
385
- .with_terminal(false)
386
- .flip
304
+ ```ruby
305
+ # Derivation
306
+ epin.derived? # => true if derived
307
+ epin.native? # => true if not derived
387
308
 
388
- epin.with_pin(complex_pin).to_s # => "-q'"
309
+ # Comparison
310
+ epin.same_derived?(other) # => true if same derivation status
389
311
  ```
390
312
 
391
- ### Immutability
313
+ ## Data Structure
392
314
 
393
315
  ```ruby
394
- original = Sashite::Epin.parse("K^")
316
+ Sashite::Epin
317
+ #pin => Sashite::Pin # Underlying PIN instance
318
+ #derived => true | false # Derivation status
319
+ ```
395
320
 
396
- # All transformations return new instances
397
- derived = original.mark_derived
398
- changed_pin = original.with_pin(original.pin.with_type(:Q))
321
+ ## Comparison with PIN
399
322
 
400
- # Original unchanged
401
- original.to_s # => "K^"
402
- derived.to_s # => "K^'"
403
- changed_pin.to_s # => "Q^"
323
+ ### What EPIN Adds
404
324
 
405
- # Components are immutable
406
- pin = original.pin
407
- pin.frozen? # => true
408
- original.frozen? # => true
325
+ ```ruby
326
+ # PIN: 4 attributes
327
+ pin = Sashite::Pin.parse("K^")
328
+ pin.type # Piece Name
329
+ pin.side # Piece Side
330
+ pin.state # Piece State
331
+ pin.terminal # Terminal Status
332
+
333
+ # EPIN: 5 attributes (PIN + style)
334
+ epin = Sashite::Epin.parse("K^'")
335
+ epin.pin.type # Piece Name
336
+ epin.pin.side # Piece Side
337
+ epin.pin.state # Piece State
338
+ epin.pin.terminal # Terminal Status
339
+ epin.derived # Piece Style (5th attribute)
409
340
  ```
410
341
 
411
- ## Attribute Mapping
342
+ ### When to Use EPIN vs PIN
412
343
 
413
- EPIN exposes all five fundamental attributes from the Sashité Game Protocol:
344
+ **Use PIN when:**
345
+ - Single-style games (both players use same style)
346
+ - Style information not needed
347
+ - Maximum compatibility required
414
348
 
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) |
349
+ **Use EPIN when:**
350
+ - Cross-style games (different styles per player)
351
+ - Pieces can change style (promotion to foreign piece)
352
+ - Need to track native vs derived pieces
422
353
 
423
354
  ## Design Principles
424
355
 
@@ -427,26 +358,24 @@ EPIN exposes all five fundamental attributes from the Sashité Game Protocol:
427
358
  EPIN doesn't reimplement PIN features — it extends PIN minimally:
428
359
 
429
360
  ```ruby
430
- # EPIN is just PIN + derived flag
431
- class Identifier
432
- def initialize(pin, derived: false)
433
- @pin = pin
434
- @derived = !!derived
435
- end
361
+ def initialize(pin, derived: false)
362
+ @pin = pin
363
+ @derived = !!derived
364
+ freeze
436
365
  end
437
366
  ```
438
367
 
439
- ### 2. Absolute Minimal API
368
+ ### 2. Minimal API
440
369
 
441
370
  **6 core methods only:**
442
- 1. `new(pin, derived: false)` — create from PIN
371
+ 1. `new` — create from PIN
443
372
  2. `pin` — get PIN component
444
- 3. `derived?` — check derivation
373
+ 3. `derived` / `derived?` — check derivation
445
374
  4. `to_s` — serialize
446
- 5. `with_pin(new_pin)` — replace PIN
447
- 6. `with_derived(boolean)` — change derivation
375
+ 5. `with_pin` — replace PIN
376
+ 6. `with_derived` / `mark_derived` / `unmark_derived` — change derivation
448
377
 
449
- Everything else uses the PIN component API directly.
378
+ Everything else uses the PIN API directly.
450
379
 
451
380
  ### 3. Component Transparency
452
381
 
@@ -461,7 +390,7 @@ epin.pin.flip
461
390
 
462
391
  # No need for wrapper methods like:
463
392
  # epin.type
464
- # epin.with_type
393
+ # epin.with_type(:Q)
465
394
  # epin.enhanced?
466
395
  # epin.flip
467
396
  ```
@@ -472,182 +401,19 @@ Every valid PIN is a valid EPIN (without derivation marker):
472
401
 
473
402
  ```ruby
474
403
  # All PIN identifiers work as EPIN
475
- pin_tokens = ["K", "+R", "-p", "K^", "+R^"]
476
- pin_tokens.each do |token|
404
+ %w[K +R -p K^ +R^].each do |token|
477
405
  epin = Sashite::Epin.parse(token)
478
- epin.native? # => true
479
- epin.to_s == token # => true
406
+ epin.native? # => true
407
+ epin.to_s # => token
480
408
  end
481
409
  ```
482
410
 
483
- ### 5. Immutability
484
-
485
- All instances frozen. Transformations return new instances:
486
-
487
- ```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
493
- ```
494
-
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)
502
-
503
- ```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)
511
- ```
512
-
513
- ### Style Derivation Logic
514
-
515
- ```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
525
-
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
533
- ```
534
-
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
544
-
545
- # Multiple derivation markers
546
- begin
547
- Sashite::Epin.parse("K''")
548
- rescue ArgumentError => e
549
- # Invalid format
550
- end
551
-
552
- # PIN validation errors delegate
553
- begin
554
- Sashite::Epin.parse("KK'")
555
- rescue ArgumentError => e
556
- # PIN validation error
557
- end
558
- ```
559
-
560
- ## Performance Considerations
561
-
562
- ### Efficient Composition
563
-
564
- ```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)
619
- ```
620
-
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
634
-
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
644
-
645
411
  ## Related Specifications
646
412
 
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
413
+ - [EPIN Specification v1.0.0](https://sashite.dev/specs/epin/1.0.0/) Technical specification
414
+ - [EPIN Examples](https://sashite.dev/specs/epin/1.0.0/examples/) Usage examples
415
+ - [PIN Specification v1.0.0](https://sashite.dev/specs/pin/1.0.0/) Base component
416
+ - [Sashité Game Protocol](https://sashite.dev/game-protocol/) Foundation
651
417
 
652
418
  ## License
653
419