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.
- checksums.yaml +4 -4
- data/README.md +192 -426
- data/lib/sashite/epin.rb +268 -191
- metadata +6 -11
- data/lib/sashite/epin/identifier.rb +0 -293
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "sashite/pin"
|
|
4
|
-
|
|
5
|
-
module Sashite
|
|
6
|
-
module Epin
|
|
7
|
-
# Represents an identifier in EPIN (Extended Piece Identifier Notation) format.
|
|
8
|
-
#
|
|
9
|
-
# EPIN extends PIN by adding a derivation marker to track piece style in cross-style games.
|
|
10
|
-
# An EPIN identifier is simply a PIN identifier plus a boolean flag indicating whether
|
|
11
|
-
# the piece uses its own side's native style (native) or the opponent's style (derived).
|
|
12
|
-
#
|
|
13
|
-
# ## Pure Composition Design
|
|
14
|
-
#
|
|
15
|
-
# EPIN doesn't reimplement PIN features - it's pure composition:
|
|
16
|
-
# - All piece attributes (name, side, state, terminal) come from the PIN component
|
|
17
|
-
# - EPIN adds only style derivation tracking (native vs derived)
|
|
18
|
-
# - Zero code duplication
|
|
19
|
-
#
|
|
20
|
-
# ## Minimal API
|
|
21
|
-
#
|
|
22
|
-
# Core methods (6 total):
|
|
23
|
-
# 1. new(pin, derived: false) - create from PIN component
|
|
24
|
-
# 2. pin - get PIN component
|
|
25
|
-
# 3. derived? - check derivation status
|
|
26
|
-
# 4. to_s - serialize
|
|
27
|
-
# 5. with_pin(new_pin) - replace PIN component
|
|
28
|
-
# 6. with_derived(boolean) - change derivation status
|
|
29
|
-
#
|
|
30
|
-
# Everything else uses the PIN component API directly.
|
|
31
|
-
#
|
|
32
|
-
# All instances are immutable - transformation methods return new instances.
|
|
33
|
-
#
|
|
34
|
-
# @example Basic usage
|
|
35
|
-
# # Create from PIN component
|
|
36
|
-
# pin = Sashite::Pin.parse("K^")
|
|
37
|
-
# epin = Sashite::Epin::Identifier.new(pin, derived: false)
|
|
38
|
-
# epin.to_s # => "K^" (native)
|
|
39
|
-
#
|
|
40
|
-
# # Mark as derived
|
|
41
|
-
# derived = epin.mark_derived
|
|
42
|
-
# derived.to_s # => "K^'" (derived from opponent's style)
|
|
43
|
-
#
|
|
44
|
-
# @example Accessing attributes via PIN component
|
|
45
|
-
# epin = Sashite::Epin.parse("+R^'")
|
|
46
|
-
# epin.pin.type # => :R (Piece Name)
|
|
47
|
-
# epin.pin.side # => :first (Piece Side)
|
|
48
|
-
# epin.pin.state # => :enhanced (Piece State)
|
|
49
|
-
# epin.pin.terminal? # => true (Terminal Status)
|
|
50
|
-
# epin.derived? # => true (Piece Style)
|
|
51
|
-
#
|
|
52
|
-
# @example Transformations
|
|
53
|
-
# epin = Sashite::Epin.parse("K^")
|
|
54
|
-
#
|
|
55
|
-
# # Transform PIN component
|
|
56
|
-
# epin.with_pin(epin.pin.with_type(:Q)) # => "Q^"
|
|
57
|
-
#
|
|
58
|
-
# # Transform derivation
|
|
59
|
-
# epin.mark_derived # => "K^'"
|
|
60
|
-
# epin.with_derived(true) # => "K^'"
|
|
61
|
-
#
|
|
62
|
-
# @see https://sashite.dev/specs/epin/1.0.0/ EPIN Specification v1.0.0
|
|
63
|
-
class Identifier
|
|
64
|
-
# EPIN validation pattern matching the specification
|
|
65
|
-
# Grammar: <epin> ::= <pin> | <pin> "'"
|
|
66
|
-
EPIN_PATTERN = /\A[-+]?[A-Za-z]\^?'?\z/
|
|
67
|
-
|
|
68
|
-
# Derivation marker character
|
|
69
|
-
DERIVATION_MARKER = "'"
|
|
70
|
-
|
|
71
|
-
# Error messages
|
|
72
|
-
ERROR_INVALID_EPIN = "Invalid EPIN string: %s"
|
|
73
|
-
ERROR_INVALID_PIN = "PIN component must be a Pin::Identifier, got: %s"
|
|
74
|
-
ERROR_MULTIPLE_MARKERS = "EPIN string cannot have multiple derivation markers: %s"
|
|
75
|
-
|
|
76
|
-
# @return [Pin::Identifier] the PIN component
|
|
77
|
-
attr_reader :pin
|
|
78
|
-
|
|
79
|
-
# @return [Boolean] whether the piece uses derived style (opponent's native style)
|
|
80
|
-
def derived?
|
|
81
|
-
@derived
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Create a new EPIN identifier from PIN component and derivation flag
|
|
85
|
-
#
|
|
86
|
-
# @param pin [Pin::Identifier] the PIN component
|
|
87
|
-
# @param derived [Boolean] whether the piece uses derived style (default: false)
|
|
88
|
-
# @raise [ArgumentError] if pin is not a Pin::Identifier
|
|
89
|
-
#
|
|
90
|
-
# @example Create EPIN identifiers
|
|
91
|
-
# pin = Sashite::Pin.parse("K^")
|
|
92
|
-
# native = Sashite::Epin::Identifier.new(pin, derived: false) # => "K^"
|
|
93
|
-
# derived = Sashite::Epin::Identifier.new(pin, derived: true) # => "K^'"
|
|
94
|
-
def initialize(pin, derived: false)
|
|
95
|
-
raise ::ArgumentError, format(ERROR_INVALID_PIN, pin.class) unless pin.is_a?(Pin::Identifier)
|
|
96
|
-
|
|
97
|
-
@pin = pin
|
|
98
|
-
@derived = !!derived
|
|
99
|
-
|
|
100
|
-
freeze
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# Parse an EPIN string into an Identifier object
|
|
104
|
-
#
|
|
105
|
-
# @param epin_string [String] EPIN notation string
|
|
106
|
-
# @return [Identifier] new identifier instance
|
|
107
|
-
# @raise [ArgumentError] if the EPIN string is invalid
|
|
108
|
-
#
|
|
109
|
-
# @example Parse EPIN strings
|
|
110
|
-
# Sashite::Epin::Identifier.parse("K^") # => Native king
|
|
111
|
-
# Sashite::Epin::Identifier.parse("K^'") # => Derived king
|
|
112
|
-
# Sashite::Epin::Identifier.parse("+R'") # => Derived enhanced rook
|
|
113
|
-
def self.parse(epin_string)
|
|
114
|
-
string_value = String(epin_string)
|
|
115
|
-
validate_epin_string(string_value)
|
|
116
|
-
|
|
117
|
-
# Check for derivation marker
|
|
118
|
-
has_marker = string_value.end_with?(DERIVATION_MARKER)
|
|
119
|
-
|
|
120
|
-
# Extract PIN part (remove derivation marker if present)
|
|
121
|
-
pin_part = has_marker ? string_value[0...-1] : string_value
|
|
122
|
-
|
|
123
|
-
# Parse PIN component
|
|
124
|
-
pin_identifier = Pin::Identifier.parse(pin_part)
|
|
125
|
-
|
|
126
|
-
new(pin_identifier, derived: has_marker)
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
# Check if a string is a valid EPIN notation
|
|
130
|
-
#
|
|
131
|
-
# @param epin_string [String] the string to validate
|
|
132
|
-
# @return [Boolean] true if valid EPIN, false otherwise
|
|
133
|
-
#
|
|
134
|
-
# @example Validate EPIN strings
|
|
135
|
-
# Sashite::Epin::Identifier.valid?("K^") # => true
|
|
136
|
-
# Sashite::Epin::Identifier.valid?("K^'") # => true
|
|
137
|
-
# Sashite::Epin::Identifier.valid?("+R'") # => true
|
|
138
|
-
# Sashite::Epin::Identifier.valid?("K^''") # => false (multiple markers)
|
|
139
|
-
# Sashite::Epin::Identifier.valid?("KK'") # => false (invalid PIN)
|
|
140
|
-
def self.valid?(epin_string)
|
|
141
|
-
return false unless epin_string.is_a?(::String)
|
|
142
|
-
return false unless epin_string.match?(EPIN_PATTERN)
|
|
143
|
-
|
|
144
|
-
# Check for multiple derivation markers
|
|
145
|
-
marker_count = epin_string.count(DERIVATION_MARKER)
|
|
146
|
-
return false if marker_count > 1
|
|
147
|
-
|
|
148
|
-
# Validate PIN part
|
|
149
|
-
has_marker = epin_string.end_with?(DERIVATION_MARKER)
|
|
150
|
-
pin_part = has_marker ? epin_string[0...-1] : epin_string
|
|
151
|
-
|
|
152
|
-
Pin::Identifier.valid?(pin_part)
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# Convert the identifier to its EPIN string representation
|
|
156
|
-
#
|
|
157
|
-
# @return [String] EPIN notation string
|
|
158
|
-
#
|
|
159
|
-
# @example Serialize identifiers
|
|
160
|
-
# native.to_s # => "K^"
|
|
161
|
-
# derived.to_s # => "K^'"
|
|
162
|
-
def to_s
|
|
163
|
-
pin.to_s + suffix
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
# Get the derivation marker suffix
|
|
167
|
-
#
|
|
168
|
-
# @return [String] derivation marker if derived, empty string if native
|
|
169
|
-
def suffix
|
|
170
|
-
derived? ? DERIVATION_MARKER : ""
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
# Create a new identifier with a different PIN component
|
|
174
|
-
#
|
|
175
|
-
# @param new_pin [Pin::Identifier] new PIN component
|
|
176
|
-
# @return [Identifier] new identifier with different PIN
|
|
177
|
-
# @raise [ArgumentError] if new_pin is not a Pin::Identifier
|
|
178
|
-
#
|
|
179
|
-
# @example Replace PIN component
|
|
180
|
-
# epin = Sashite::Epin.parse("K^'")
|
|
181
|
-
# new_pin = epin.pin.with_type(:Q)
|
|
182
|
-
# epin.with_pin(new_pin).to_s # => "Q^'"
|
|
183
|
-
def with_pin(new_pin)
|
|
184
|
-
raise ::ArgumentError, format(ERROR_INVALID_PIN, new_pin.class) unless new_pin.is_a?(Pin::Identifier)
|
|
185
|
-
return self if pin == new_pin
|
|
186
|
-
|
|
187
|
-
self.class.new(new_pin, derived: derived?)
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
# Create a new identifier with different derivation status
|
|
191
|
-
#
|
|
192
|
-
# @param new_derived [Boolean] new derivation status
|
|
193
|
-
# @return [Identifier] new identifier with different derivation
|
|
194
|
-
#
|
|
195
|
-
# @example Change derivation status
|
|
196
|
-
# native = Sashite::Epin.parse("K^")
|
|
197
|
-
# derived = native.with_derived(true)
|
|
198
|
-
# derived.to_s # => "K^'"
|
|
199
|
-
def with_derived(new_derived)
|
|
200
|
-
new_derived_bool = !!new_derived
|
|
201
|
-
return self if derived? == new_derived_bool
|
|
202
|
-
|
|
203
|
-
self.class.new(pin, derived: new_derived_bool)
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
# Create a new identifier marked as derived (opponent's native style)
|
|
207
|
-
#
|
|
208
|
-
# @return [Identifier] new identifier marked as derived
|
|
209
|
-
#
|
|
210
|
-
# @example Mark as derived
|
|
211
|
-
# native = Sashite::Epin.parse("K^")
|
|
212
|
-
# derived = native.mark_derived
|
|
213
|
-
# derived.to_s # => "K^'"
|
|
214
|
-
def mark_derived
|
|
215
|
-
return self if derived?
|
|
216
|
-
|
|
217
|
-
self.class.new(pin, derived: true)
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
# Create a new identifier marked as native (own side's native style)
|
|
221
|
-
#
|
|
222
|
-
# @return [Identifier] new identifier marked as native
|
|
223
|
-
#
|
|
224
|
-
# @example Mark as native
|
|
225
|
-
# derived = Sashite::Epin.parse("K^'")
|
|
226
|
-
# native = derived.unmark_native
|
|
227
|
-
# native.to_s # => "K^"
|
|
228
|
-
def unmark_native
|
|
229
|
-
return self unless derived?
|
|
230
|
-
|
|
231
|
-
self.class.new(pin, derived: false)
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
# Check if the identifier uses native style (own side's native style)
|
|
235
|
-
#
|
|
236
|
-
# @return [Boolean] true if native (not derived)
|
|
237
|
-
def native?
|
|
238
|
-
!derived?
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
# Check if this identifier has the same derivation status as another
|
|
242
|
-
#
|
|
243
|
-
# @param other [Identifier] identifier to compare with
|
|
244
|
-
# @return [Boolean] true if same derivation status
|
|
245
|
-
#
|
|
246
|
-
# @example Compare derivation status
|
|
247
|
-
# native = Sashite::Epin.parse("K^")
|
|
248
|
-
# derived = Sashite::Epin.parse("K^'")
|
|
249
|
-
# native.same_derivation?(derived) # => false
|
|
250
|
-
def same_derivation?(other)
|
|
251
|
-
return false unless other.is_a?(self.class)
|
|
252
|
-
|
|
253
|
-
derived? == other.derived?
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
# Custom equality comparison
|
|
257
|
-
#
|
|
258
|
-
# @param other [Object] object to compare with
|
|
259
|
-
# @return [Boolean] true if identifiers are equal
|
|
260
|
-
def ==(other)
|
|
261
|
-
return false unless other.is_a?(self.class)
|
|
262
|
-
|
|
263
|
-
pin == other.pin && derived? == other.derived?
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
# Alias for == to ensure Set functionality works correctly
|
|
267
|
-
alias eql? ==
|
|
268
|
-
|
|
269
|
-
# Custom hash implementation for use in collections
|
|
270
|
-
#
|
|
271
|
-
# @return [Integer] hash value
|
|
272
|
-
def hash
|
|
273
|
-
[self.class, pin, derived?].hash
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
# Validate EPIN string format
|
|
277
|
-
#
|
|
278
|
-
# @param string [String] string to validate
|
|
279
|
-
# @raise [ArgumentError] if string doesn't match EPIN pattern or has multiple markers
|
|
280
|
-
def self.validate_epin_string(string)
|
|
281
|
-
raise ::ArgumentError, format(ERROR_INVALID_EPIN, string) unless string.match?(EPIN_PATTERN)
|
|
282
|
-
|
|
283
|
-
# Check for multiple derivation markers
|
|
284
|
-
marker_count = string.count(DERIVATION_MARKER)
|
|
285
|
-
return unless marker_count > 1
|
|
286
|
-
|
|
287
|
-
raise ::ArgumentError, format(ERROR_MULTIPLE_MARKERS, string)
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
private_class_method :validate_epin_string
|
|
291
|
-
end
|
|
292
|
-
end
|
|
293
|
-
end
|