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/lib/sashite/epin.rb CHANGED
@@ -1,83 +1,301 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "epin/identifier"
3
+ require "sashite/pin"
4
4
 
5
5
  module Sashite
6
- # EPIN (Extended Piece Identifier Notation) implementation for Ruby
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.
8
+ # EPIN extends PIN by adding a **derivation marker** to track piece style
9
+ # in cross-style games.
11
10
  #
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)
11
+ # **EPIN is simply: PIN + optional style derivation marker (`'`)**
17
12
  #
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)
13
+ # == Format
26
14
  #
27
- # @see https://sashite.dev/specs/epin/1.0.0/
28
- module Epin
29
- # Check if a string is a valid EPIN notation
15
+ # <pin-token>[<derivation-marker>]
16
+ #
17
+ # Where +<pin-token>+ is a valid PIN token and +<derivation-marker>+ is
18
+ # an optional trailing apostrophe (<tt>'</tt>).
19
+ #
20
+ # == Five Fundamental Attributes
21
+ #
22
+ # EPIN exposes all five attributes from the Sashité Game Protocol:
23
+ #
24
+ # - *Piece Name* â†' +epin.pin.type+
25
+ # - *Piece Side* â†' +epin.pin.side+
26
+ # - *Piece State* â†' +epin.pin.state+
27
+ # - *Terminal Status* â†' +epin.pin.terminal+
28
+ # - *Piece Style* â†' +epin.derived+ (native vs derived)
29
+ #
30
+ # == Examples
31
+ #
32
+ # epin = Sashite::Epin.parse("K^'")
33
+ # epin.pin.type # => :K
34
+ # epin.pin.terminal # => true
35
+ # epin.derived # => true
36
+ #
37
+ # pin = Sashite::Pin.parse("K^")
38
+ # epin = Sashite::Epin.new(pin, derived: true)
39
+ # epin.to_s # => "K^'"
40
+ #
41
+ # Sashite::Epin.valid?("K^'") # => true
42
+ # Sashite::Epin.valid?("K'^") # => false
43
+ #
44
+ # See the EPIN Specification (https://sashite.dev/specs/epin/1.0.0/) for details.
45
+ class Epin
46
+ # Pattern for validating EPIN strings
47
+ EPIN_PATTERN = /\A(?<pin>[-+]?[A-Za-z]\^?)(?<derived>')?\z/
48
+
49
+ # @return [Sashite::Pin] The underlying PIN component
50
+ attr_reader :pin
51
+
52
+ # @return [Boolean] Derivation status (true = derived, false = native)
53
+ attr_reader :derived
54
+
55
+ # ========================================================================
56
+ # Creation and Parsing
57
+ # ========================================================================
58
+
59
+ # Creates a new EPIN instance from a PIN component.
60
+ #
61
+ # @param pin [Sashite::Pin] The underlying PIN instance
62
+ # @param derived [Boolean] Derivation status (default: false)
63
+ # @return [Epin] A new frozen Epin instance
64
+ #
65
+ # @example
66
+ # pin = Sashite::Pin.parse("K^")
67
+ # Sashite::Epin.new(pin)
68
+ # # => #<Sashite::Epin K^>
69
+ #
70
+ # Sashite::Epin.new(pin, derived: true)
71
+ # # => #<Sashite::Epin K^'>
72
+ def initialize(pin, derived: false)
73
+ raise ArgumentError, "Expected a Sashite::Pin instance, got: #{pin.inspect}" unless pin.is_a?(Pin)
74
+
75
+ @pin = pin
76
+ @derived = !!derived
77
+
78
+ freeze
79
+ end
80
+
81
+ # Parses an EPIN string into an Epin instance.
82
+ #
83
+ # @param epin_string [String] The EPIN string to parse
84
+ # @return [Epin] A new Epin instance
85
+ # @raise [ArgumentError] If the string is not a valid EPIN
86
+ #
87
+ # @example
88
+ # Sashite::Epin.parse("K")
89
+ # # => #<Sashite::Epin K>
90
+ #
91
+ # Sashite::Epin.parse("K'")
92
+ # # => #<Sashite::Epin K'>
93
+ #
94
+ # Sashite::Epin.parse("+R^'")
95
+ # # => #<Sashite::Epin +R^'>
96
+ #
97
+ # Sashite::Epin.parse("invalid")
98
+ # # => ArgumentError: Invalid EPIN string: invalid
99
+ def self.parse(epin_string)
100
+ raise ArgumentError, "Invalid EPIN string: #{epin_string.inspect}" unless epin_string.is_a?(String)
101
+
102
+ match = EPIN_PATTERN.match(epin_string)
103
+ raise ArgumentError, "Invalid EPIN string: #{epin_string}" unless match
104
+
105
+ pin_string = match[:pin]
106
+ derived_marker = match[:derived]
107
+
108
+ pin = Pin.parse(pin_string)
109
+ derived = derived_marker == "'"
110
+
111
+ new(pin, derived: derived)
112
+ end
113
+
114
+ # Checks if a string is a valid EPIN notation.
30
115
  #
31
116
  # @param epin_string [String] The string to validate
32
- # @return [Boolean] true if valid EPIN, false otherwise
117
+ # @return [Boolean] true if valid, false otherwise
33
118
  #
34
119
  # @example
35
120
  # 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)
121
+ # Sashite::Epin.valid?("K'") # => true
122
+ # Sashite::Epin.valid?("+R^'") # => true
123
+ # Sashite::Epin.valid?("K'^") # => false
124
+ # Sashite::Epin.valid?("K''") # => false
125
+ # Sashite::Epin.valid?("invalid") # => false
43
126
  def self.valid?(epin_string)
44
- Identifier.valid?(epin_string)
127
+ return false unless epin_string.is_a?(String)
128
+
129
+ EPIN_PATTERN.match?(epin_string)
45
130
  end
46
131
 
47
- # Parse an EPIN string into an Identifier object
132
+ # ========================================================================
133
+ # Conversion
134
+ # ========================================================================
135
+
136
+ # Converts the Epin to its string representation.
48
137
  #
49
- # @param epin_string [String] EPIN notation string
50
- # @return [Epin::Identifier] new identifier instance
51
- # @raise [ArgumentError] if the EPIN string is invalid
138
+ # @return [String] The EPIN string
52
139
  #
53
140
  # @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>
59
- def self.parse(epin_string)
60
- Identifier.parse(epin_string)
141
+ # pin = Sashite::Pin.parse("K^")
142
+ # Sashite::Epin.new(pin).to_s
143
+ # # => "K^"
144
+ #
145
+ # Sashite::Epin.new(pin, derived: true).to_s
146
+ # # => "K^'"
147
+ def to_s
148
+ "#{pin}#{derivation_suffix}"
149
+ end
150
+
151
+ # ========================================================================
152
+ # Transformations
153
+ # ========================================================================
154
+
155
+ # Returns a new Epin with a different PIN component.
156
+ #
157
+ # @param new_pin [Sashite::Pin] The new PIN component
158
+ # @return [Epin] A new Epin with the specified PIN
159
+ #
160
+ # @example
161
+ # epin = Sashite::Epin.parse("K^'")
162
+ # new_pin = epin.pin.with_type(:Q)
163
+ # epin.with_pin(new_pin).to_s
164
+ # # => "Q^'"
165
+ def with_pin(new_pin)
166
+ return self if pin == new_pin
167
+
168
+ self.class.new(new_pin, derived: derived)
169
+ end
170
+
171
+ # Returns a new Epin with a different derivation status.
172
+ #
173
+ # @param new_derived [Boolean] The new derivation status
174
+ # @return [Epin] A new Epin with the specified derivation status
175
+ #
176
+ # @example
177
+ # epin = Sashite::Epin.parse("K^")
178
+ # epin.with_derived(true).to_s
179
+ # # => "K^'"
180
+ #
181
+ # epin = Sashite::Epin.parse("K^'")
182
+ # epin.with_derived(false).to_s
183
+ # # => "K^"
184
+ def with_derived(new_derived)
185
+ return self if derived == !!new_derived
186
+
187
+ self.class.new(pin, derived: !!new_derived)
188
+ end
189
+
190
+ # Returns a new Epin marked as derived.
191
+ #
192
+ # @return [Epin] A new Epin with derived: true
193
+ #
194
+ # @example
195
+ # epin = Sashite::Epin.parse("K^")
196
+ # epin.mark_derived.derived
197
+ # # => true
198
+ def mark_derived
199
+ return self if derived
200
+
201
+ self.class.new(pin, derived: true)
202
+ end
203
+
204
+ # Returns a new Epin marked as native (not derived).
205
+ #
206
+ # @return [Epin] A new Epin with derived: false
207
+ #
208
+ # @example
209
+ # epin = Sashite::Epin.parse("K^'")
210
+ # epin.unmark_derived.derived
211
+ # # => false
212
+ def unmark_derived
213
+ return self unless derived
214
+
215
+ self.class.new(pin, derived: false)
216
+ end
217
+
218
+ # ========================================================================
219
+ # Queries
220
+ # ========================================================================
221
+
222
+ # Checks if the Epin is derived (uses opponent's style).
223
+ #
224
+ # @return [Boolean] true if derived
225
+ #
226
+ # @example
227
+ # Sashite::Epin.parse("K^'").derived? # => true
228
+ # Sashite::Epin.parse("K^").derived? # => false
229
+ def derived?
230
+ derived
231
+ end
232
+
233
+ # Checks if the Epin is native (uses own side's style).
234
+ #
235
+ # @return [Boolean] true if native
236
+ #
237
+ # @example
238
+ # Sashite::Epin.parse("K^").native? # => true
239
+ # Sashite::Epin.parse("K^'").native? # => false
240
+ def native?
241
+ !derived
61
242
  end
62
243
 
63
- # Create a new identifier instance
244
+ # Checks if two Epins have the same derivation status.
64
245
  #
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
246
+ # @param other [Epin] The other Epin to compare
247
+ # @return [Boolean] true if same derivation status
72
248
  #
73
249
  # @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)
250
+ # epin1 = Sashite::Epin.parse("K^'")
251
+ # epin2 = Sashite::Epin.parse("Q'")
252
+ # epin1.same_derived?(epin2)
253
+ # # => true
254
+ #
255
+ # epin3 = Sashite::Epin.parse("K^")
256
+ # epin1.same_derived?(epin3)
257
+ # # => false
258
+ def same_derived?(other)
259
+ derived == other.derived
260
+ end
261
+
262
+ # ========================================================================
263
+ # Comparison
264
+ # ========================================================================
265
+
266
+ # Checks equality with another Epin.
267
+ #
268
+ # @param other [Object] The object to compare
269
+ # @return [Boolean] true if equal
270
+ def ==(other)
271
+ return false unless other.is_a?(self.class)
272
+
273
+ pin == other.pin && derived == other.derived
274
+ end
275
+
276
+ alias eql? ==
277
+
278
+ # Returns a hash code for the Epin.
279
+ #
280
+ # @return [Integer] Hash code
281
+ def hash
282
+ [pin, derived].hash
283
+ end
284
+
285
+ # Returns an inspect string for the Epin.
286
+ #
287
+ # @return [String] Inspect representation
288
+ def inspect
289
+ "#<#{self.class} #{self}>"
290
+ end
291
+
292
+ private
293
+
294
+ # Returns the derivation suffix for string representation.
295
+ #
296
+ # @return [String] "'" if derived, "" otherwise
297
+ def derivation_suffix
298
+ derived ? "'" : ""
81
299
  end
82
300
  end
83
301
  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.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
@@ -15,22 +15,18 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 3.2.0
18
+ version: 3.3.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 3.2.0
25
+ version: 3.3.0
26
26
  description: |
27
- EPIN (Extended Piece Identifier Notation) extends PIN to provide style-aware piece representation
28
- in abstract strategy board games. This gem implements the EPIN Specification v1.0.0 with
29
- a modern Ruby interface featuring immutable identifier objects and functional programming
30
- principles. EPIN adds derivation markers to PIN that distinguish pieces by their style
31
- origin, enabling cross-style game scenarios and piece origin tracking. Represents all
32
- four Game Protocol piece attributes with full PIN backward compatibility. Perfect for
33
- game engines, cross-tradition tournaments, and hybrid board game environments.
27
+ EPIN (Extended Piece Identifier Notation) implementation for Ruby.
28
+ Extends PIN by adding a derivation marker to track piece style in cross-style
29
+ abstract strategy board games with a minimal compositional API.
34
30
  email: contact@cyril.email
35
31
  executables: []
36
32
  extensions: []
@@ -40,7 +36,6 @@ files:
40
36
  - README.md
41
37
  - lib/sashite-epin.rb
42
38
  - lib/sashite/epin.rb
43
- - lib/sashite/epin/identifier.rb
44
39
  homepage: https://github.com/sashite/epin.rb
45
40
  licenses:
46
41
  - MIT