sashite-epin 1.2.0 → 2.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/lib/sashite/epin.rb CHANGED
@@ -5,79 +5,220 @@ require_relative "epin/identifier"
5
5
  module Sashite
6
6
  # EPIN (Extended Piece Identifier Notation) implementation for Ruby
7
7
  #
8
- # Provides style-aware ASCII-based format for representing pieces in abstract strategy board games.
9
- # EPIN extends PIN by adding derivation markers that distinguish pieces by their style origin,
10
- # enabling cross-style game scenarios and piece origin tracking.
11
- #
12
- # Format: [<state>]<letter>[<terminal>][<derivation>]
13
- # - State modifier: "+" (enhanced), "-" (diminished), or none (normal)
14
- # - Letter: A-Z (first player), a-z (second player)
15
- # - Terminal marker: "^" (terminal piece)
16
- # - Derivation marker: "'" (foreign style), or none (native style)
17
- #
18
- # Examples:
19
- # "K" - First player king (native style, normal state, non-terminal)
20
- # "K^" - First player king (native style, normal state, terminal)
21
- # "k'" - Second player king (foreign style, normal state, non-terminal)
22
- # "k^'" - Second player king (foreign style, normal state, terminal)
23
- # "+R'" - First player rook (foreign style, enhanced state, non-terminal)
24
- # "+K^'" - First player king (foreign style, enhanced state, terminal)
25
- # "-p" - Second player pawn (native style, diminished state, non-terminal)
26
- #
27
- # @see https://sashite.dev/specs/epin/1.0.0/
8
+ # Extends PIN (Piece Identifier Notation) with a derivation marker to track piece style
9
+ # in cross-style games. EPIN is simply: PIN + optional style derivation marker (').
10
+ #
11
+ # ## Core Concept
12
+ #
13
+ # EPIN addresses the need to distinguish between:
14
+ # - **Native pieces**: Using their own side's native style (no marker)
15
+ # - **Derived pieces**: Using the opponent's native style (marked with ')
16
+ #
17
+ # This distinction is essential for cross-style games where different players use
18
+ # different game traditions (e.g., Chess vs Makruk, Chess vs Shogi).
19
+ #
20
+ # ## Pure Composition
21
+ #
22
+ # EPIN doesn't reimplement PIN - it's pure composition:
23
+ #
24
+ # EPIN = PIN + derived flag
25
+ #
26
+ # All piece attributes (name, side, state, terminal) come from the PIN component.
27
+ # EPIN adds only the 5th attribute: piece style (native vs derived).
28
+ #
29
+ # ## Minimal API
30
+ #
31
+ # Module-level methods (3 total):
32
+ # 1. valid?(epin_string) - validate EPIN string
33
+ # 2. parse(epin_string) - parse into Identifier
34
+ # 3. new(pin, derived: false) - create from PIN component
35
+ #
36
+ # ## Five Fundamental Attributes
37
+ #
38
+ # EPIN represents all five piece attributes from the Sashité Game Protocol:
39
+ #
40
+ # From PIN component (4 attributes):
41
+ # - **Piece Name**: epin.pin.type
42
+ # - **Piece Side**: epin.pin.side
43
+ # - **Piece State**: epin.pin.state
44
+ # - **Terminal Status**: epin.pin.terminal?
45
+ #
46
+ # From EPIN (5th attribute):
47
+ # - **Piece Style**: epin.derived? (native vs derived)
48
+ #
49
+ # ## Format Structure
50
+ #
51
+ # Structure: `<pin>[']`
52
+ #
53
+ # Grammar (BNF):
54
+ # <epin> ::= <pin> | <pin> "'"
55
+ # <pin> ::= ["+" | "-"] <letter> ["^"]
56
+ # <letter> ::= "A" | ... | "Z" | "a" | ... | "z"
57
+ #
58
+ # Regular Expression: `/\A[-+]?[A-Za-z]\^?'?\z/`
59
+ #
60
+ # ## Semantics
61
+ #
62
+ # ### Native vs Derived
63
+ #
64
+ # In cross-style games (e.g., Chess vs Makruk):
65
+ # - First player's native style: Chess
66
+ # - Second player's native style: Makruk
67
+ #
68
+ # Then:
69
+ # - "K" = First player king in Chess style (native)
70
+ # - "K'" = First player king in Makruk style (derived from opponent)
71
+ # - "k" = Second player king in Makruk style (native)
72
+ # - "k'" = Second player king in Chess style (derived from opponent)
73
+ #
74
+ # ### Backward Compatibility
75
+ #
76
+ # Every valid PIN token is a valid EPIN token:
77
+ # - "K" is valid PIN and valid EPIN (native)
78
+ # - "+R^" is valid PIN and valid EPIN (native)
79
+ # - All PIN semantics preserved
80
+ #
81
+ # EPIN extends PIN by adding the optional derivation marker:
82
+ # - "K'" is valid EPIN (derived)
83
+ # - "+R^'" is valid EPIN (enhanced, terminal, derived)
84
+ #
85
+ # ## Examples
86
+ #
87
+ # ### Basic Usage
88
+ #
89
+ # # Parse EPIN strings
90
+ # native = Sashite::Epin.parse("K^") # Native king
91
+ # derived = Sashite::Epin.parse("K^'") # Derived king
92
+ #
93
+ # # Access attributes via PIN component
94
+ # native.pin.type # => :K
95
+ # native.pin.terminal? # => true
96
+ # native.derived? # => false
97
+ #
98
+ # # Create from PIN component
99
+ # pin = Sashite::Pin.parse("K^")
100
+ # epin = Sashite::Epin.new(pin, derived: false)
101
+ # epin.to_s # => "K^"
102
+ #
103
+ # ### Transformations
104
+ #
105
+ # epin = Sashite::Epin.parse("K^")
106
+ #
107
+ # # Mark as derived
108
+ # derived = epin.mark_derived
109
+ # derived.to_s # => "K^'"
110
+ #
111
+ # # Transform PIN component
112
+ # queen = epin.with_pin(epin.pin.with_type(:Q))
113
+ # queen.to_s # => "Q^"
114
+ #
115
+ # # Transform both
116
+ # derived_queen = epin
117
+ # .with_pin(epin.pin.with_type(:Q))
118
+ # .mark_derived
119
+ # derived_queen.to_s # => "Q^'"
120
+ #
121
+ # ### Cross-Style Games
122
+ #
123
+ # # Chess vs Makruk match
124
+ # # First player = Chess, Second player = Makruk
125
+ #
126
+ # chess_king = Sashite::Epin.parse("K^") # Native Chess king
127
+ # makruk_pawn = Sashite::Epin.parse("P'") # Derived Makruk pawn
128
+ #
129
+ # chess_king.native? # => true (uses Chess style)
130
+ # makruk_pawn.derived? # => true (uses Makruk style)
131
+ #
132
+ # ## Design Properties
133
+ #
134
+ # - **Rule-agnostic**: Independent of game mechanics
135
+ # - **Pure composition**: Extends PIN minimally (PIN + derived flag)
136
+ # - **Minimal API**: Only 3 module methods, 6 instance methods
137
+ # - **Component transparency**: Direct PIN access via epin.pin
138
+ # - **Backward compatible**: All PIN tokens are valid EPIN tokens
139
+ # - **Immutable**: All instances frozen, transformations return new objects
140
+ # - **Type-safe**: Full PIN type preservation
141
+ # - **Style-aware**: Tracks native vs derived pieces
142
+ # - **Compact**: Single character overhead for style information
143
+ #
144
+ # @see https://sashite.dev/specs/epin/1.0.0/ EPIN Specification v1.0.0
145
+ # @see https://sashite.dev/specs/epin/1.0.0/examples/ EPIN Examples
146
+ # @see https://sashite.dev/specs/pin/1.0.0/ PIN Specification (base component)
28
147
  module Epin
29
148
  # Check if a string is a valid EPIN notation
30
149
  #
31
- # @param epin_string [String] The string to validate
150
+ # Validates both the EPIN format and the underlying PIN component.
151
+ #
152
+ # @param epin_string [String] the string to validate
32
153
  # @return [Boolean] true if valid EPIN, false otherwise
33
154
  #
34
- # @example
35
- # Sashite::Epin.valid?("K") # => true
36
- # Sashite::Epin.valid?("K^") # => true
37
- # Sashite::Epin.valid?("+R'") # => true
38
- # Sashite::Epin.valid?("+K^'") # => true
39
- # Sashite::Epin.valid?("-p") # => true
40
- # Sashite::Epin.valid?("KK") # => false
41
- # Sashite::Epin.valid?("++K") # => false
42
- # Sashite::Epin.valid?("K'^") # => false (wrong order)
155
+ # @example Validate EPIN strings
156
+ # Sashite::Epin.valid?("K^") # => true (valid PIN, native)
157
+ # Sashite::Epin.valid?("K^'") # => true (valid PIN with derivation)
158
+ # Sashite::Epin.valid?("+R'") # => true (enhanced derived rook)
159
+ # Sashite::Epin.valid?("K^''") # => false (multiple markers)
160
+ # Sashite::Epin.valid?("KK'") # => false (invalid PIN part)
161
+ # Sashite::Epin.valid?("invalid") # => false (invalid format)
43
162
  def self.valid?(epin_string)
44
163
  Identifier.valid?(epin_string)
45
164
  end
46
165
 
47
166
  # Parse an EPIN string into an Identifier object
48
167
  #
49
- # @param epin_string [String] EPIN notation string
50
- # @return [Epin::Identifier] new identifier instance
168
+ # Creates a new EPIN identifier by parsing the string, extracting the PIN part
169
+ # and derivation marker, validating the PIN component, and creating an identifier
170
+ # with the appropriate derivation status.
171
+ #
172
+ # @param epin_string [String] EPIN notation string (format: <pin>['])
173
+ # @return [Epin::Identifier] parsed identifier with PIN component and derivation flag
51
174
  # @raise [ArgumentError] if the EPIN string is invalid
52
175
  #
53
- # @example
54
- # Sashite::Epin.parse("K") # => #<Epin::Identifier type=:K side=:first state=:normal native=true terminal=false>
55
- # Sashite::Epin.parse("K^") # => #<Epin::Identifier type=:K side=:first state=:normal native=true terminal=true>
56
- # Sashite::Epin.parse("+R'") # => #<Epin::Identifier type=:R side=:first state=:enhanced native=false terminal=false>
57
- # Sashite::Epin.parse("+K^'") # => #<Epin::Identifier type=:K side=:first state=:enhanced native=false terminal=true>
58
- # Sashite::Epin.parse("-p") # => #<Epin::Identifier type=:P side=:second state=:diminished native=true terminal=false>
176
+ # @example Parse different EPIN formats
177
+ # Sashite::Epin.parse("K^") # => Native king, terminal
178
+ # Sashite::Epin.parse("K^'") # => Derived king, terminal
179
+ # Sashite::Epin.parse("+R") # => Native rook, enhanced
180
+ # Sashite::Epin.parse("+R'") # => Derived rook, enhanced
181
+ # Sashite::Epin.parse("-p") # => Native pawn, diminished
182
+ #
183
+ # @example Access all five attributes
184
+ # epin = Sashite::Epin.parse("+R^'")
185
+ # epin.pin.type # => :R (Piece Name)
186
+ # epin.pin.side # => :first (Piece Side)
187
+ # epin.pin.state # => :enhanced (Piece State)
188
+ # epin.pin.terminal? # => true (Terminal Status)
189
+ # epin.derived? # => true (Piece Style)
59
190
  def self.parse(epin_string)
60
191
  Identifier.parse(epin_string)
61
192
  end
62
193
 
63
- # Create a new identifier instance
194
+ # Create a new identifier from a PIN component and derivation flag
195
+ #
196
+ # Constructs an EPIN identifier by combining a PIN component (which provides
197
+ # the four base attributes: name, side, state, terminal) with a derivation flag
198
+ # (which provides the fifth attribute: style).
199
+ #
200
+ # @param pin [Pin::Identifier] PIN component providing base attributes
201
+ # @param derived [Boolean] whether the piece uses derived style (default: false)
202
+ # @return [Epin::Identifier] new immutable identifier instance
203
+ # @raise [ArgumentError] if pin is not a Pin::Identifier
204
+ #
205
+ # @example Create identifiers from PIN components
206
+ # pin = Sashite::Pin.parse("K^")
207
+ # native = Sashite::Epin.new(pin, derived: false)
208
+ # native.to_s # => "K^"
209
+ #
210
+ # derived = Sashite::Epin.new(pin, derived: true)
211
+ # derived.to_s # => "K^'"
64
212
  #
65
- # @param type [Symbol] piece type (:A to :Z)
66
- # @param side [Symbol] player side (:first or :second)
67
- # @param state [Symbol] piece state (:normal, :enhanced, or :diminished)
68
- # @param native [Boolean] style derivation (true for native, false for foreign)
69
- # @param terminal [Boolean] whether the piece is a terminal piece
70
- # @return [Epin::Identifier] new identifier instance
71
- # @raise [ArgumentError] if parameters are invalid
213
+ # @example Cross-style game setup
214
+ # # First player uses Chess style, second uses Makruk style
215
+ # chess_king = Sashite::Epin.new(Sashite::Pin.parse("K^"), derived: false)
216
+ # makruk_pawn = Sashite::Epin.new(Sashite::Pin.parse("P"), derived: true)
72
217
  #
73
- # @example
74
- # Sashite::Epin.identifier(:K, :first, :normal, true) # => "K"
75
- # Sashite::Epin.identifier(:K, :first, :normal, true, terminal: true) # => "K^"
76
- # Sashite::Epin.identifier(:R, :first, :enhanced, false) # => "+R'"
77
- # Sashite::Epin.identifier(:K, :first, :enhanced, false, terminal: true) # => "+K^'"
78
- # Sashite::Epin.identifier(:P, :second, :diminished, true) # => "-p"
79
- def self.identifier(type, side, state, native, terminal: false)
80
- Identifier.new(type, side, state, native, terminal: terminal)
218
+ # chess_king.native? # => true (uses own Chess style)
219
+ # makruk_pawn.derived? # => true (uses opponent's Makruk style)
220
+ def self.new(pin, derived: false)
221
+ Identifier.new(pin, derived: derived)
81
222
  end
82
223
  end
83
224
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sashite-epin
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato