sashite-epin 1.2.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,17 +1,19 @@
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
 
12
- EPIN (Extended Piece Identifier 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. EPIN adds a derivation marker that distinguishes pieces by their style origin, enabling cross-style game scenarios and piece origin tracking.
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.
13
13
 
14
- This gem implements the [EPIN Specification v1.0.0](https://sashite.dev/specs/epin/1.0.0/), providing a modern Ruby interface with immutable identifier objects and full backward compatibility with PIN while adding style differentiation capabilities.
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.
15
17
 
16
18
  ## Installation
17
19
 
@@ -26,515 +28,392 @@ Or install manually:
26
28
  gem install sashite-epin
27
29
  ```
28
30
 
29
- ## Usage
31
+ This will also install `sashite-pin` as a dependency.
32
+
33
+ ## Core Concept
30
34
 
31
35
  ```ruby
32
36
  require "sashite/epin"
33
37
 
34
- # Parse EPIN strings into identifier objects
35
- identifier = Sashite::Epin.parse("K") # => #<Epin::Identifier type=:K side=:first state=:normal native=true>
36
- identifier.to_s # => "K"
37
- identifier.type # => :K
38
- identifier.side # => :first
39
- identifier.state # => :normal
40
- identifier.native? # => true
41
-
42
- # Create identifiers directly
43
- identifier = Sashite::Epin::Identifier.new(:R, :second, :enhanced, false) # => #<Epin::Identifier type=:R side=:second state=:enhanced native=false>
44
-
45
- # Validate EPIN strings
46
- Sashite::Epin.valid?("K") # => true
47
- Sashite::Epin.valid?("+R'") # => true
48
- Sashite::Epin.valid?("invalid") # => false
49
-
50
- # Style derivation with apostrophe suffix
51
- native_king = Sashite::Epin.parse("K") # => #<Epin::Identifier type=:K side=:first state=:normal native=true>
52
- foreign_king = Sashite::Epin.parse("K'") # => #<Epin::Identifier type=:K side=:first state=:normal native=false>
53
-
54
- native_king.to_s # => "K"
55
- foreign_king.to_s # => "K'"
56
-
57
- # State manipulation (returns new immutable instances)
58
- enhanced = identifier.enhance # => #<Epin::Identifier type=:K side=:first state=:enhanced native=true>
59
- enhanced.to_s # => "+K"
60
- diminished = identifier.diminish # => #<Epin::Identifier type=:K side=:first state=:diminished native=true>
61
- diminished.to_s # => "-K"
62
-
63
- # Style derivation manipulation
64
- foreign_piece = identifier.derive # => #<Epin::Identifier type=:K side=:first state=:normal native=false>
65
- foreign_piece.to_s # => "K'"
66
- back_to_native = foreign_piece.underive # => #<Epin::Identifier type=:K side=:first state=:normal native=true>
67
- back_to_native.to_s # => "K"
68
-
69
- # Side manipulation
70
- flipped = identifier.flip # => #<Epin::Identifier type=:K side=:second state=:normal native=true>
71
- flipped.to_s # => "k"
72
-
73
- # Type manipulation
74
- queen = identifier.with_type(:Q) # => #<Epin::Identifier type=:Q side=:first state=:normal native=true>
75
- queen.to_s # => "Q"
76
-
77
- # Style queries
78
- identifier.native? # => true
79
- foreign_king.derived? # => true
80
- foreign_king.foreign? # => true (alias for derived?)
81
-
82
- # State queries
83
- identifier.normal? # => true
84
- enhanced.enhanced? # => true
85
- diminished.diminished? # => true
86
-
87
- # Side queries
88
- identifier.first_player? # => true
89
- flipped.second_player? # => true
90
-
91
- # Attribute access
92
- identifier.letter # => "K"
93
- enhanced.prefix # => "+"
94
- foreign_king.suffix # => "'"
95
- identifier.suffix # => ""
96
-
97
- # Type and side comparison
98
- king1 = Sashite::Epin.parse("K")
99
- king2 = Sashite::Epin.parse("k")
100
- queen = Sashite::Epin.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::Epin.parse("K")
108
- foreign_king = Sashite::Epin.parse("K'")
109
-
110
- native_king.same_style?(foreign_king) # => false (different derivation)
111
-
112
- # Functional transformations can be chained
113
- pawn = Sashite::Epin.parse("P")
114
- enemy_foreign_promoted = pawn.flip.derive.enhance # => "+p'" (second player foreign promoted pawn)
115
- ```
38
+ # EPIN is just PIN + derived flag
39
+ pin = Sashite::Pin.parse("K^")
40
+ epin = Sashite::Epin.new(pin)
116
41
 
117
- ## Format Specification
42
+ epin.to_s # => "K^" (native)
43
+ epin.pin # => #<Sashite::Pin K^>
44
+ epin.derived # => false
118
45
 
119
- ### Structure
120
- ```
121
- <pin>[<suffix>]
46
+ # Mark as derived
47
+ derived_epin = epin.mark_derived
48
+ derived_epin.to_s # => "K^'" (derived from opposite side's style)
122
49
  ```
123
50
 
124
- Where `<pin>` follows the PIN format: `[<state>]<letter>`
51
+ **That's it.** All piece attributes come from the PIN component.
52
+
53
+ ## Usage
125
54
 
126
- ### Components
55
+ ```ruby
56
+ require "sashite/epin"
127
57
 
128
- - **PIN part** (`[<state>]<letter>`): Standard PIN notation
129
- - **Letter** (`A-Z`, `a-z`): Represents piece type and side
130
- - Uppercase: First player pieces
131
- - Lowercase: Second player pieces
132
- - **State** (optional prefix):
133
- - `+`: Enhanced state (promoted, upgraded, empowered)
134
- - `-`: Diminished state (weakened, restricted, temporary)
135
- - No prefix: Normal state
58
+ # Parse EPIN strings
59
+ epin = Sashite::Epin.parse("K^'")
60
+ epin.to_s # => "K^'"
61
+
62
+ # Access five fundamental attributes through PIN component + derived flag
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)
68
+
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
+ ```
136
74
 
137
- - **Derivation suffix** (optional):
138
- - `'`: Foreign style (piece has opposite side's native style)
139
- - No suffix: Native style (piece has current side's native style)
75
+ ### Creating Identifiers
140
76
 
141
- ### Regular Expression
142
77
  ```ruby
143
- /\A[-+]?[A-Za-z]'?\z/
78
+ # Parse from string
79
+ epin = Sashite::Epin.parse("K^") # Native
80
+ epin = Sashite::Epin.parse("K^'") # Derived
81
+
82
+ # Create from PIN component
83
+ pin = Sashite::Pin.parse("K^")
84
+ epin = Sashite::Epin.new(pin) # Native (default)
85
+ epin = Sashite::Epin.new(pin, derived: true) # Derived
86
+
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)
144
92
  ```
145
93
 
146
- ### Examples
147
- - `K` - First player king (native style, normal state)
148
- - `k'` - Second player king (foreign style, normal state)
149
- - `+R'` - First player rook (foreign style, enhanced state)
150
- - `-p` - Second player pawn (native style, diminished state)
94
+ ### Accessing Components
151
95
 
152
- ## Game Examples
96
+ ```ruby
97
+ epin = Sashite::Epin.parse("+R^'")
153
98
 
154
- ### Cross-Style Chess vs. Shōgi
99
+ # Get PIN component
100
+ epin.pin # => #<Sashite::Pin +R^>
101
+ epin.pin.to_s # => "+R^"
155
102
 
156
- ```ruby
157
- # Match setup: First player uses Chess, Second player uses Shōgi
158
- # Native styles: first=Chess, second=Shōgi
103
+ # Check derivation
104
+ epin.derived # => true
105
+ epin.derived? # => true
106
+ epin.native? # => false
107
+
108
+ # Serialize
109
+ epin.to_s # => "+R^'"
110
+ ```
159
111
 
160
- # Native pieces (no derivation suffix)
161
- white_king = Sashite::Epin.identifier(:K, :first, :normal, true) # => "K" (Chess king)
162
- black_king = Sashite::Epin.identifier(:K, :second, :normal, true) # => "k" (Shōgi king)
112
+ ### Transformations
163
113
 
164
- # Foreign pieces (with derivation suffix)
165
- white_shogi_king = Sashite::Epin.identifier(:K, :first, :normal, false) # => "K'" (Shōgi king for white)
166
- black_chess_king = Sashite::Epin.identifier(:K, :second, :normal, false) # => "k'" (Chess king for black)
114
+ All transformations return new immutable instances.
167
115
 
168
- # Promoted pieces in cross-style context
169
- white_promoted_rook = Sashite::Epin.parse("+R'") # White shōgi rook promoted to Dragon King
170
- black_promoted_pawn = Sashite::Epin.parse("+p") # Black shōgi pawn promoted to Tokin
116
+ ```ruby
117
+ epin = Sashite::Epin.parse("K^")
118
+
119
+ # Mark as derived
120
+ derived = epin.mark_derived
121
+ derived.to_s # => "K^'"
122
+
123
+ # Mark as native
124
+ native = derived.unmark_derived
125
+ native.to_s # => "K^"
171
126
 
172
- white_promoted_rook.enhanced? # => true
173
- white_promoted_rook.derived? # => true
174
- black_promoted_pawn.enhanced? # => true
175
- black_promoted_pawn.native? # => true
127
+ # Set explicitly
128
+ toggled = epin.with_derived(true)
129
+ toggled.to_s # => "K^'"
176
130
  ```
177
131
 
178
- ### Single-Style Games (PIN Compatibility)
132
+ ### Transform via PIN Component
179
133
 
180
134
  ```ruby
181
- # Traditional Chess (both players use Chess style)
182
- # All pieces are native, so EPIN behaves exactly like PIN
135
+ epin = Sashite::Epin.parse("K^'")
183
136
 
184
- white_pieces = %w[K Q +R B N P].map { |pin| Sashite::Epin.parse(pin) }
185
- black_pieces = %w[k q +r b n p].map { |pin| Sashite::Epin.parse(pin) }
137
+ # Replace PIN component
138
+ new_pin = epin.pin.with_type(:Q)
139
+ epin.with_pin(new_pin).to_s # => "Q^'"
186
140
 
187
- white_pieces.all?(&:native?) # => true
188
- black_pieces.all?(&:native?) # => true
141
+ # Change state
142
+ new_pin = epin.pin.enhance
143
+ epin.with_pin(new_pin).to_s # => "+K^'"
189
144
 
190
- # EPIN strings match PIN strings for native pieces
191
- white_pieces.map(&:to_s) # => ["K", "Q", "+R", "B", "N", "P"]
192
- black_pieces.map(&:to_s) # => ["k", "q", "+r", "b", "n", "p"]
145
+ # Remove terminal marker
146
+ new_pin = epin.pin.unmark_terminal
147
+ epin.with_pin(new_pin).to_s # => "K'"
148
+
149
+ # Change side
150
+ new_pin = epin.pin.flip
151
+ epin.with_pin(new_pin).to_s # => "k^'"
193
152
  ```
194
153
 
195
- ### Style Mutation During Gameplay
154
+ ### Multiple Transformations
196
155
 
197
156
  ```ruby
198
- # Simulate capture with style change (Ōgi rules)
199
- chess_queen = Sashite::Epin.parse("q'") # Black chess queen (foreign for shōgi player)
200
- captured = chess_queen.flip.with_type(:P).underive # Becomes white native pawn
157
+ epin = Sashite::Epin.parse("K^")
201
158
 
202
- chess_queen.to_s # => "q'" (black foreign queen)
203
- captured.to_s # => "P" (white native pawn)
159
+ # Transform PIN and derivation
160
+ new_pin = epin.pin.with_type(:Q).enhance
161
+ transformed = epin.with_pin(new_pin).mark_derived
204
162
 
205
- # Style derivation changes during gameplay
206
- shogi_piece = Sashite::Epin.parse("r") # Black shōgi rook (native)
207
- foreign_piece = shogi_piece.derive # Convert to foreign style
208
- foreign_piece.to_s # => "r'" (black foreign rook)
163
+ transformed.to_s # => "+Q^'"
209
164
  ```
210
165
 
211
- ## API Reference
166
+ ### Component Queries
212
167
 
213
- ### Main Module Methods
214
-
215
- - `Sashite::Epin.valid?(epin_string)` - Check if string is valid EPIN notation
216
- - `Sashite::Epin.parse(epin_string)` - Parse EPIN string into Identifier object
217
- - `Sashite::Epin.identifier(type, side, state, native)` - Create identifier instance directly
218
-
219
- ### Identifier Class
220
-
221
- #### Creation and Parsing
222
- - `Sashite::Epin::Identifier.new(type, side, state = :normal, native = true)` - Create identifier instance
223
- - `Sashite::Epin::Identifier.parse(epin_string)` - Parse EPIN string (same as module method)
224
- - `Sashite::Epin::Identifier.valid?(epin_string)` - Validate EPIN string (class method)
225
-
226
- #### Attribute Access
227
- - `#type` - Get piece type (symbol :A to :Z, always uppercase)
228
- - `#side` - Get player side (:first or :second)
229
- - `#state` - Get state (:normal, :enhanced, or :diminished)
230
- - `#native` - Get style derivation (true for native, false for foreign)
231
- - `#letter` - Get letter representation (string, case determined by side)
232
- - `#prefix` - Get state prefix (string: "+", "-", or "")
233
- - `#suffix` - Get derivation suffix (string: "'" or "")
234
- - `#to_s` - Convert to EPIN string representation
235
-
236
- #### Style Queries
237
- - `#native?` - Check if native style (current side's native style)
238
- - `#derived?` - Check if foreign style (opposite side's native style)
239
- - `#foreign?` - Alias for `#derived?`
240
-
241
- #### State Queries
242
- - `#normal?` - Check if normal state (no modifiers)
243
- - `#enhanced?` - Check if enhanced state
244
- - `#diminished?` - Check if diminished state
245
-
246
- #### Side Queries
247
- - `#first_player?` - Check if first player identifier
248
- - `#second_player?` - Check if second player identifier
249
-
250
- #### State Transformations (immutable - return new instances)
251
- - `#enhance` - Create enhanced version
252
- - `#unenhance` - Remove enhanced state
253
- - `#diminish` - Create diminished version
254
- - `#undiminish` - Remove diminished state
255
- - `#normalize` - Remove all state modifiers
256
-
257
- #### Style Transformations (immutable - return new instances)
258
- - `#derive` - Convert to foreign style (add derivation suffix)
259
- - `#underive` - Convert to native style (remove derivation suffix)
260
- - `#flip` - Switch player (change side)
261
-
262
- #### Attribute Transformations (immutable - return new instances)
263
- - `#with_type(new_type)` - Create identifier with different type
264
- - `#with_side(new_side)` - Create identifier with different side
265
- - `#with_state(new_state)` - Create identifier with different state
266
- - `#with_derivation(native)` - Create identifier with different derivation
267
-
268
- #### Comparison Methods
269
- - `#same_type?(other)` - Check if same piece type
270
- - `#same_side?(other)` - Check if same side
271
- - `#same_state?(other)` - Check if same state
272
- - `#same_style?(other)` - Check if same style derivation
273
- - `#==(other)` - Full equality comparison
274
-
275
- ### Constants
276
- - `Sashite::Epin::Identifier::NATIVE` - Constant for native style (`true`)
277
- - `Sashite::Epin::Identifier::FOREIGN` - Constant for foreign style (`false`)
278
- - `Sashite::Epin::Identifier::DERIVATION_SUFFIX` - Derivation suffix for foreign pieces (`"'"`)
279
-
280
- ## Advanced Usage
281
-
282
- ### Style Derivation Examples
168
+ Use the PIN API directly:
283
169
 
284
170
  ```ruby
285
- # Understanding native vs. foreign pieces
286
- # In a Chess vs. Shōgi match:
287
- # - First player native style: Chess
288
- # - Second player native style: Shōgi
289
-
290
- native_chess_king = Sashite::Epin.parse("K") # First player native (Chess king)
291
- foreign_shogi_king = Sashite::Epin.parse("K'") # First player foreign (Shōgi king)
292
-
293
- native_shogi_king = Sashite::Epin.parse("k") # Second player native (Shōgi king)
294
- foreign_chess_king = Sashite::Epin.parse("k'") # Second player foreign (Chess king)
295
-
296
- # Style queries
297
- native_chess_king.native? # => true
298
- foreign_shogi_king.derived? # => true
299
- native_shogi_king.native? # => true
300
- foreign_chess_king.derived? # => true
171
+ epin = Sashite::Epin.parse("+P^'")
172
+
173
+ # PIN queries (name, side, state, terminal)
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 # => "^"
183
+
184
+ # EPIN queries (style)
185
+ epin.derived? # => true
186
+ epin.native? # => false
187
+
188
+ # Compare EPINs
189
+ other = Sashite::Epin.parse("+P^")
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)
301
193
  ```
302
194
 
303
- ### Immutable Transformations
304
- ```ruby
305
- # All transformations return new instances
306
- original = Sashite::Epin.identifier(:K, :first, :normal, true)
307
- enhanced = original.enhance
308
- derived = original.derive
309
- flipped = original.flip
310
-
311
- # Original piece is never modified
312
- puts original # => "K"
313
- puts enhanced # => "+K"
314
- puts derived # => "K'"
315
- puts flipped # => "k"
316
-
317
- # Transformations can be chained
318
- result = original.flip.derive.enhance.with_type(:Q)
319
- puts result # => "+q'"
195
+ ## Five Fundamental Attributes
196
+
197
+ EPIN exposes all five attributes from the [Sashité Game Protocol](https://sashite.dev/game-protocol/):
198
+
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) |
206
+
207
+ ## Format Specification
208
+
209
+ ### Structure
210
+
211
+ ```
212
+ <pin>[']
320
213
  ```
321
214
 
322
- ### Cross-Style Game State Management
323
- ```ruby
324
- class CrossStyleGameBoard
325
- def initialize(first_style, second_style)
326
- @first_style = first_style
327
- @second_style = second_style
328
- @pieces = {}
329
- end
330
-
331
- def place(square, piece)
332
- @pieces[square] = piece
333
- end
334
-
335
- def capture_with_style_change(from_square, to_square, new_type = nil)
336
- captured = @pieces[to_square]
337
- capturing = @pieces.delete(from_square)
338
-
339
- return nil unless captured && capturing
340
-
341
- # Style mutation: captured piece becomes native to capturing side
342
- mutated = captured.flip.underive
343
- mutated = mutated.with_type(new_type) if new_type
344
-
345
- @pieces[to_square] = capturing
346
- mutated # Return mutated captured piece for hand
347
- end
348
-
349
- def pieces_by_style_derivation
350
- {
351
- native: @pieces.select { |_, piece| piece.native? },
352
- foreign: @pieces.select { |_, piece| piece.derived? }
353
- }
354
- end
355
- end
215
+ Where:
216
+ - `<pin>` is any valid PIN token
217
+ - `'` is the optional derivation marker
356
218
 
357
- # Usage
358
- board = CrossStyleGameBoard.new(:chess, :shogi)
359
- board.place("e1", Sashite::Epin.identifier(:K, :first, :normal, true)) # Chess king
360
- board.place("e8", Sashite::Epin.identifier(:K, :second, :normal, true)) # Shōgi king
361
- board.place("d4", Sashite::Epin.identifier(:Q, :first, :normal, false)) # Chess queen using Shōgi style
219
+ ### Grammar (EBNF)
362
220
 
363
- analysis = board.pieces_by_style_derivation
364
- puts analysis[:native].size # => 2
365
- puts analysis[:foreign].size # => 1
221
+ ```ebnf
222
+ epin ::= pin | pin "'"
223
+ pin ::= ["+" | "-"] letter ["^"]
224
+ letter ::= "A" | ... | "Z" | "a" | ... | "z"
366
225
  ```
367
226
 
368
- ### PIN Compatibility Layer
227
+ ### Regular Expression
228
+
369
229
  ```ruby
370
- # EPIN is fully backward compatible with PIN
371
- def convert_pin_to_epin(pin_string)
372
- # All PIN strings are valid EPIN strings (native pieces)
373
- Sashite::Epin.parse(pin_string)
374
- end
230
+ /\A[-+]?[A-Za-z]\^?'?\z/
231
+ ```
375
232
 
376
- def convert_epin_to_pin(epin_identifier)
377
- # Only native EPIN pieces can be converted to PIN
378
- return nil unless epin_identifier.native?
233
+ ### Examples
379
234
 
380
- "#{epin_identifier.prefix}#{epin_identifier.letter}"
381
- end
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 |
382
243
 
383
- # Usage
384
- pin_pieces = %w[K Q +R -P k q r p]
385
- epin_pieces = pin_pieces.map { |pin| convert_pin_to_epin(pin) }
244
+ ## Cross-Style Game Example
386
245
 
387
- epin_pieces.all?(&:native?) # => true
388
- epin_pieces.map { |p| convert_epin_to_pin(p) } # => ["K", "Q", "+R", "-P", "k", "q", "r", "p"]
389
- ```
246
+ In a chess-vs-makruk cross-style match where:
247
+ - First side native style = chess
248
+ - Second side native style = makruk
390
249
 
391
- ### Move Validation Example
392
250
  ```ruby
393
- def can_promote_in_style?(piece, target_rank, style_rules)
394
- return false unless piece.normal? # Already promoted pieces can't promote again
395
-
396
- case [piece.type, piece.native? ? style_rules[:native] : style_rules[:foreign]]
397
- when %i[P chess] # Chess pawn
398
- (piece.first_player? && target_rank == 8) ||
399
- (piece.second_player? && target_rank == 1)
400
- when %i[P shogi] # Shōgi pawn
401
- (piece.first_player? && target_rank >= 7) ||
402
- (piece.second_player? && target_rank <= 3)
403
- when %i[R shogi], %i[B shogi] # Shōgi major pieces
404
- true
405
- else
406
- false
407
- end
408
- end
251
+ # First player pieces
252
+ chess_king = Sashite::Epin.parse("K^") # Native Chess king
253
+ makruk_pawn = Sashite::Epin.parse("P'") # Derived Makruk pawn (foreign)
409
254
 
410
- # Usage
411
- shogi_pawn = Sashite::Epin.identifier(:P, :first, :normal, false)
255
+ chess_king.native? # => true (uses own style)
256
+ makruk_pawn.derived? # => true (uses opponent's style)
412
257
 
413
- style_rules = { native: :chess, foreign: :shogi }
258
+ # Second player pieces
259
+ makruk_king = Sashite::Epin.parse("k^") # Native Makruk king
260
+ chess_pawn = Sashite::Epin.parse("p'") # Derived Chess pawn (foreign)
414
261
 
415
- puts can_promote_in_style?(chess_pawn, 8, style_rules) # => true (chess pawn on 8th rank)
416
- puts can_promote_in_style?(shogi_pawn, 8, style_rules) # => true (shogi pawn on 8th rank)
262
+ makruk_king.native? # => true
263
+ chess_pawn.derived? # => true
417
264
  ```
418
265
 
419
- ## Implementation Architecture
266
+ ## API Reference
420
267
 
421
- This gem uses **composition over inheritance** by building upon the proven [sashite-pin](https://github.com/sashite/pin.rb) gem:
268
+ ### Parsing and Validation
422
269
 
423
- - **PIN Foundation**: All type, side, and state logic is handled by an internal `Pin::Identifier` object
424
- - **EPIN Extension**: Only the derivation (`native`) attribute and related methods are added
425
- - **Delegation Pattern**: Core PIN methods are delegated to the internal PIN identifier
426
- - **Immutability**: All transformations return new instances, maintaining functional programming principles
270
+ ```ruby
271
+ Sashite::Epin.parse(epin_string) # => Sashite::Epin | raises ArgumentError
272
+ Sashite::Epin.valid?(epin_string) # => boolean
273
+ ```
427
274
 
428
- This architecture ensures:
429
- - **Reliability**: Reuses battle-tested PIN logic
430
- - **Maintainability**: PIN updates automatically benefit EPIN
431
- - **Consistency**: PIN and EPIN identifiers behave identically for shared attributes
432
- - **Performance**: Minimal overhead over pure PIN implementation
275
+ ### Creation
433
276
 
434
- ## Protocol Mapping
277
+ ```ruby
278
+ Sashite::Epin.new(pin) # Native (default)
279
+ Sashite::Epin.new(pin, derived: true) # Derived
280
+ ```
435
281
 
436
- Following the [Game Protocol](https://sashite.dev/game-protocol/):
282
+ ### Conversion
437
283
 
438
- | Protocol Attribute | EPIN Encoding | Examples | Notes |
439
- |-------------------|--------------|----------|-------|
440
- | **Type** | ASCII letter choice | `K`/`k` = King, `P`/`p` = Pawn | Type is always stored as uppercase symbol (`:K`, `:P`) |
441
- | **Side** | Letter case in display | `K` = First player, `k` = Second player | Case is determined by side during rendering |
442
- | **State** | Optional prefix | `+K` = Enhanced, `-K` = Diminished, `K` = Normal | |
443
- | **Style** | Derivation suffix | `K` = Native style, `K'` = Foreign style | |
284
+ ```ruby
285
+ epin.to_s # => String
286
+ ```
444
287
 
445
- **Style Derivation Logic**:
446
- - **No suffix**: Piece has the **native style** of its current side
447
- - **Apostrophe suffix (`'`)**: Piece has the **foreign style** (opposite side's native style)
288
+ ### Transformations
448
289
 
449
- **Canonical principle**: Identical pieces must have identical EPIN representations.
290
+ All transformations return new `Sashite::Epin` instances:
450
291
 
451
- ## Properties
292
+ ```ruby
293
+ # PIN replacement
294
+ epin.with_pin(new_pin) # => Sashite::Epin with different PIN
452
295
 
453
- * **PIN Compatible**: All valid PIN strings are valid EPIN strings
454
- * **Style Aware**: Distinguishes pieces by their style origin through derivation markers
455
- * **ASCII Compatible**: Maximum portability across systems
456
- * **Rule-Agnostic**: Independent of specific game mechanics
457
- * **Compact Format**: Minimal character usage (1-3 characters per piece)
458
- * **Visual Distinction**: Clear player and style differentiation
459
- * **Protocol Compliant**: Complete implementation of Sashité piece attributes
460
- * **Immutable**: All identifier instances are frozen and transformations return new objects
461
- * **Functional**: Pure functions with no side effects
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
+ ```
462
301
 
463
- ## Implementation Notes
302
+ ### Queries
464
303
 
465
- ### Style Derivation Convention
304
+ ```ruby
305
+ # Derivation
306
+ epin.derived? # => true if derived
307
+ epin.native? # => true if not derived
466
308
 
467
- EPIN follows a strict style derivation convention:
309
+ # Comparison
310
+ epin.same_derived?(other) # => true if same derivation status
311
+ ```
468
312
 
469
- 1. **Native pieces** (no suffix): Use the current side's native style
470
- 2. **Foreign pieces** (`'` suffix): Use the opposite side's native style
471
- 3. **Match context**: Each side has a defined native style for the entire match
472
- 4. **Style mutations**: Pieces can change derivation through gameplay mechanics
313
+ ## Data Structure
473
314
 
474
- ### Example Flow
315
+ ```ruby
316
+ Sashite::Epin
317
+ #pin => Sashite::Pin # Underlying PIN instance
318
+ #derived => true | false # Derivation status
319
+ ```
320
+
321
+ ## Comparison with PIN
322
+
323
+ ### What EPIN Adds
475
324
 
476
325
  ```ruby
477
- # Match context: First player=Chess, Second player=Shōgi
478
- # Input: "K'" (first player foreign)
479
- # Parsing
480
- # type: :K, side: :first, state: :normal, native: false
481
- # Style resolution
482
- # Effective style: Shōgi (second player's native style)
483
- # ↓ Display
484
- # EPIN: "K'" (first player king with foreign/Shōgi style)
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)
485
340
  ```
486
341
 
487
- This ensures that `parse(epin).to_s == epin` for all valid EPIN strings while enabling cross-style gameplay.
342
+ ### When to Use EPIN vs PIN
488
343
 
489
- ## System Constraints
344
+ **Use PIN when:**
345
+ - Single-style games (both players use same style)
346
+ - Style information not needed
347
+ - Maximum compatibility required
490
348
 
491
- - **Maximum 26 piece types** per game system (one per ASCII letter)
492
- - **Exactly 2 players** (uppercase/lowercase distinction)
493
- - **3 state levels** (enhanced, normal, diminished)
494
- - **2 style derivations** (native, foreign)
495
- - **Style context dependency**: Requires match-level side-style associations
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
496
353
 
497
- ## Related Specifications
354
+ ## Design Principles
498
355
 
499
- - [PIN](https://sashite.dev/specs/pin/1.0.0/) - Piece Identifier Notation (style-agnostic base)
500
- - [Game Protocol](https://sashite.dev/game-protocol/) - Conceptual foundation for abstract strategy board games
501
- - [CELL](https://sashite.dev/specs/cell/) - Board position coordinates
502
- - [HAND](https://sashite.dev/specs/hand/) - Reserve location notation
503
- - [PMN](https://sashite.dev/specs/pmn/) - Portable Move Notation
356
+ ### 1. Pure Composition
504
357
 
505
- ## Documentation
358
+ EPIN doesn't reimplement PIN features — it extends PIN minimally:
506
359
 
507
- - [Official EPIN Specification v1.0.0](https://sashite.dev/specs/epin/1.0.0/)
508
- - [EPIN Examples Documentation](https://sashite.dev/specs/epin/1.0.0/examples/)
509
- - [Game Protocol Foundation](https://sashite.dev/game-protocol/)
510
- - [API Documentation](https://rubydoc.info/github/sashite/epin.rb/main)
360
+ ```ruby
361
+ def initialize(pin, derived: false)
362
+ @pin = pin
363
+ @derived = !!derived
364
+ freeze
365
+ end
366
+ ```
511
367
 
512
- ## Development
368
+ ### 2. Minimal API
513
369
 
514
- ```sh
515
- # Clone the repository
516
- git clone https://github.com/sashite/epin.rb.git
517
- cd epin.rb
370
+ **6 core methods only:**
371
+ 1. `new` create from PIN
372
+ 2. `pin` — get PIN component
373
+ 3. `derived` / `derived?` — check derivation
374
+ 4. `to_s` — serialize
375
+ 5. `with_pin` — replace PIN
376
+ 6. `with_derived` / `mark_derived` / `unmark_derived` — change derivation
377
+
378
+ Everything else uses the PIN API directly.
518
379
 
519
- # Install dependencies
520
- bundle install
380
+ ### 3. Component Transparency
521
381
 
522
- # Run tests
523
- ruby test.rb
382
+ Access PIN directly — no wrappers:
524
383
 
525
- # Generate documentation
526
- yard doc
384
+ ```ruby
385
+ # Use PIN API directly
386
+ epin.pin.type
387
+ epin.pin.with_type(:Q)
388
+ epin.pin.enhanced?
389
+ epin.pin.flip
390
+
391
+ # No need for wrapper methods like:
392
+ # epin.type
393
+ # epin.with_type(:Q)
394
+ # epin.enhanced?
395
+ # epin.flip
396
+ ```
397
+
398
+ ### 4. Backward Compatibility
399
+
400
+ Every valid PIN is a valid EPIN (without derivation marker):
401
+
402
+ ```ruby
403
+ # All PIN identifiers work as EPIN
404
+ %w[K +R -p K^ +R^].each do |token|
405
+ epin = Sashite::Epin.parse(token)
406
+ epin.native? # => true
407
+ epin.to_s # => token
408
+ end
527
409
  ```
528
410
 
529
- ## Contributing
411
+ ## Related Specifications
530
412
 
531
- 1. Fork the repository
532
- 2. Create a feature branch (`git checkout -b feature/new-feature`)
533
- 3. Add tests for your changes
534
- 4. Ensure all tests pass (`ruby test.rb`)
535
- 5. Commit your changes (`git commit -am 'Add new feature'`)
536
- 6. Push to the branch (`git push origin feature/new-feature`)
537
- 7. Create a Pull Request
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
538
417
 
539
418
  ## License
540
419