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.
- checksums.yaml +4 -4
- data/README.md +509 -396
- data/lib/sashite/epin/identifier.rb +168 -463
- data/lib/sashite/epin.rb +195 -54
- metadata +1 -1
|
@@ -6,553 +6,261 @@ module Sashite
|
|
|
6
6
|
module Epin
|
|
7
7
|
# Represents an identifier in EPIN (Extended Piece Identifier Notation) format.
|
|
8
8
|
#
|
|
9
|
-
# EPIN extends PIN by adding a derivation marker to
|
|
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).
|
|
10
12
|
#
|
|
11
|
-
#
|
|
12
|
-
# - State modifier: "+" (enhanced), "-" (diminished), or none (normal)
|
|
13
|
-
# - Letter: A-Z (first player), a-z (second player)
|
|
14
|
-
# - Terminal marker: "^" (terminal piece)
|
|
15
|
-
# - Derivation marker: "'" (foreign/derived style) or none (native style)
|
|
13
|
+
# ## Pure Composition Design
|
|
16
14
|
#
|
|
17
|
-
#
|
|
18
|
-
# -
|
|
19
|
-
# -
|
|
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
|
|
20
19
|
#
|
|
21
|
-
#
|
|
22
|
-
# This extends the Game Protocol's piece model with Style support through derivation.
|
|
20
|
+
# ## Minimal API
|
|
23
21
|
#
|
|
24
|
-
#
|
|
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
|
|
25
63
|
class Identifier
|
|
26
|
-
# EPIN validation pattern matching the specification
|
|
64
|
+
# EPIN validation pattern matching the specification
|
|
65
|
+
# Grammar: <epin> ::= <pin> | <pin> "'"
|
|
27
66
|
EPIN_PATTERN = /\A[-+]?[A-Za-z]\^?'?\z/
|
|
28
67
|
|
|
29
|
-
# Derivation marker
|
|
68
|
+
# Derivation marker character
|
|
30
69
|
DERIVATION_MARKER = "'"
|
|
31
70
|
|
|
32
|
-
# No derivation marker (native style)
|
|
33
|
-
NATIVE_MARKER = ""
|
|
34
|
-
|
|
35
|
-
# Style constants
|
|
36
|
-
NATIVE = true
|
|
37
|
-
FOREIGN = false
|
|
38
|
-
|
|
39
|
-
# Valid derivation values
|
|
40
|
-
VALID_DERIVATIONS = [NATIVE, FOREIGN].freeze
|
|
41
|
-
|
|
42
71
|
# Error messages
|
|
43
72
|
ERROR_INVALID_EPIN = "Invalid EPIN string: %s"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# @return [Symbol] the piece type (:A to :Z, always uppercase)
|
|
47
|
-
def type
|
|
48
|
-
@pin_identifier.type
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# @return [Symbol] the player side (:first or :second)
|
|
52
|
-
def side
|
|
53
|
-
@pin_identifier.side
|
|
54
|
-
end
|
|
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"
|
|
55
75
|
|
|
56
|
-
# @return [
|
|
57
|
-
|
|
58
|
-
@pin_identifier.state
|
|
59
|
-
end
|
|
76
|
+
# @return [Pin::Identifier] the PIN component
|
|
77
|
+
attr_reader :pin
|
|
60
78
|
|
|
61
|
-
# @return [Boolean] whether the piece
|
|
62
|
-
def
|
|
63
|
-
@
|
|
79
|
+
# @return [Boolean] whether the piece uses derived style (opponent's native style)
|
|
80
|
+
def derived?
|
|
81
|
+
@derived
|
|
64
82
|
end
|
|
65
83
|
|
|
66
|
-
#
|
|
67
|
-
attr_reader :native
|
|
68
|
-
|
|
69
|
-
# Create a new EPIN identifier instance
|
|
84
|
+
# Create a new EPIN identifier from PIN component and derivation flag
|
|
70
85
|
#
|
|
71
|
-
# @param
|
|
72
|
-
# @param
|
|
73
|
-
# @
|
|
74
|
-
# @param native [Boolean] style derivation (true for native, false for foreign)
|
|
75
|
-
# @param terminal [Boolean] whether the piece is a terminal piece
|
|
76
|
-
# @raise [ArgumentError] if parameters are invalid
|
|
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
|
|
77
89
|
#
|
|
78
|
-
# @example
|
|
79
|
-
#
|
|
80
|
-
# Identifier.new(
|
|
81
|
-
# Identifier.new(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Pin::Identifier.validate_state(state)
|
|
88
|
-
self.class.validate_derivation(native)
|
|
89
|
-
|
|
90
|
-
@pin_identifier = Pin::Identifier.new(type, side, state, terminal: terminal)
|
|
91
|
-
@native = native
|
|
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
|
|
92
99
|
|
|
93
100
|
freeze
|
|
94
101
|
end
|
|
95
102
|
|
|
96
103
|
# Parse an EPIN string into an Identifier object
|
|
97
104
|
#
|
|
98
|
-
# EPIN format: [<state>]<letter>[<terminal>][<derivation>]
|
|
99
|
-
# where terminal marker (^) comes BEFORE derivation marker (')
|
|
100
|
-
#
|
|
101
105
|
# @param epin_string [String] EPIN notation string
|
|
102
106
|
# @return [Identifier] new identifier instance
|
|
103
107
|
# @raise [ArgumentError] if the EPIN string is invalid
|
|
104
108
|
#
|
|
105
|
-
# @example
|
|
106
|
-
# Epin::Identifier.parse("
|
|
107
|
-
# Epin::Identifier.parse("K^")
|
|
108
|
-
# Epin::Identifier.parse("+R'")
|
|
109
|
-
# Epin::Identifier.parse("+K^'") # => foreign enhanced terminal first player king
|
|
110
|
-
# Epin::Identifier.parse("-p") # => native diminished second player pawn
|
|
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
|
|
111
113
|
def self.parse(epin_string)
|
|
112
114
|
string_value = String(epin_string)
|
|
115
|
+
validate_epin_string(string_value)
|
|
113
116
|
|
|
114
|
-
#
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
# Check for derivation marker (must be at the end)
|
|
118
|
-
if string_value.end_with?(DERIVATION_MARKER)
|
|
119
|
-
pin_part = string_value[0...-1] # Remove the apostrophe
|
|
120
|
-
derived = true
|
|
121
|
-
else
|
|
122
|
-
pin_part = string_value
|
|
123
|
-
derived = false
|
|
124
|
-
end
|
|
117
|
+
# Check for derivation marker
|
|
118
|
+
has_marker = string_value.end_with?(DERIVATION_MARKER)
|
|
125
119
|
|
|
126
|
-
#
|
|
127
|
-
|
|
120
|
+
# Extract PIN part (remove derivation marker if present)
|
|
121
|
+
pin_part = has_marker ? string_value[0...-1] : string_value
|
|
128
122
|
|
|
123
|
+
# Parse PIN component
|
|
129
124
|
pin_identifier = Pin::Identifier.parse(pin_part)
|
|
130
|
-
identifier_native = !derived
|
|
131
125
|
|
|
132
|
-
new(pin_identifier
|
|
126
|
+
new(pin_identifier, derived: has_marker)
|
|
133
127
|
end
|
|
134
128
|
|
|
135
129
|
# Check if a string is a valid EPIN notation
|
|
136
130
|
#
|
|
137
|
-
#
|
|
138
|
-
# - State: + or - (optional)
|
|
139
|
-
# - Letter: A-Z or a-z (required)
|
|
140
|
-
# - Terminal: ^ (optional)
|
|
141
|
-
# - Derivation: ' (optional)
|
|
142
|
-
#
|
|
143
|
-
# @param epin_string [String] The string to validate
|
|
131
|
+
# @param epin_string [String] the string to validate
|
|
144
132
|
# @return [Boolean] true if valid EPIN, false otherwise
|
|
145
133
|
#
|
|
146
|
-
# @example
|
|
147
|
-
# Sashite::Epin::Identifier.valid?("K") # => true
|
|
134
|
+
# @example Validate EPIN strings
|
|
148
135
|
# Sashite::Epin::Identifier.valid?("K^") # => true
|
|
136
|
+
# Sashite::Epin::Identifier.valid?("K^'") # => true
|
|
149
137
|
# Sashite::Epin::Identifier.valid?("+R'") # => true
|
|
150
|
-
# Sashite::Epin::Identifier.valid?("
|
|
151
|
-
# Sashite::Epin::Identifier.valid?("KK")
|
|
152
|
-
# Sashite::Epin::Identifier.valid?("++K") # => false
|
|
153
|
-
# Sashite::Epin::Identifier.valid?("K'^") # => false (wrong order)
|
|
138
|
+
# Sashite::Epin::Identifier.valid?("K^''") # => false (multiple markers)
|
|
139
|
+
# Sashite::Epin::Identifier.valid?("KK'") # => false (invalid PIN)
|
|
154
140
|
def self.valid?(epin_string)
|
|
155
141
|
return false unless epin_string.is_a?(::String)
|
|
156
|
-
return false if epin_string.empty?
|
|
157
|
-
|
|
158
|
-
# Check EPIN pattern
|
|
159
142
|
return false unless epin_string.match?(EPIN_PATTERN)
|
|
160
143
|
|
|
161
|
-
#
|
|
162
|
-
|
|
163
|
-
return false if
|
|
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
|
|
164
151
|
|
|
165
|
-
# Validate the PIN part using existing PIN validation
|
|
166
152
|
Pin::Identifier.valid?(pin_part)
|
|
167
153
|
end
|
|
168
154
|
|
|
169
155
|
# Convert the identifier to its EPIN string representation
|
|
170
156
|
#
|
|
171
|
-
# Format: [<state>]<letter>[<terminal>][<derivation>]
|
|
172
|
-
#
|
|
173
157
|
# @return [String] EPIN notation string
|
|
174
158
|
#
|
|
175
|
-
# @example
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
# identifier.to_s # => "+K^'"
|
|
179
|
-
# identifier.to_s # => "-p"
|
|
159
|
+
# @example Serialize identifiers
|
|
160
|
+
# native.to_s # => "K^"
|
|
161
|
+
# derived.to_s # => "K^'"
|
|
180
162
|
def to_s
|
|
181
|
-
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
# Get the letter representation (inherited from PIN logic)
|
|
185
|
-
#
|
|
186
|
-
# @return [String] letter representation combining type and side
|
|
187
|
-
def letter
|
|
188
|
-
@pin_identifier.letter
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
# Get the state prefix (inherited from PIN logic)
|
|
192
|
-
#
|
|
193
|
-
# @return [String] prefix representing the state ("+" / "-" / "")
|
|
194
|
-
def prefix
|
|
195
|
-
@pin_identifier.prefix
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
# Get the terminal marker (inherited from PIN logic)
|
|
199
|
-
#
|
|
200
|
-
# @return [String] terminal marker ("^" or "")
|
|
201
|
-
def terminal_marker
|
|
202
|
-
@pin_identifier.suffix
|
|
163
|
+
pin.to_s + suffix
|
|
203
164
|
end
|
|
204
165
|
|
|
205
|
-
# Get the derivation marker
|
|
166
|
+
# Get the derivation marker suffix
|
|
206
167
|
#
|
|
207
|
-
# @return [String] derivation marker
|
|
208
|
-
def
|
|
209
|
-
|
|
168
|
+
# @return [String] derivation marker if derived, empty string if native
|
|
169
|
+
def suffix
|
|
170
|
+
derived? ? DERIVATION_MARKER : ""
|
|
210
171
|
end
|
|
211
172
|
|
|
212
|
-
#
|
|
213
|
-
alias suffix derivation_marker
|
|
214
|
-
|
|
215
|
-
# Create a new identifier with enhanced state
|
|
173
|
+
# Create a new identifier with a different PIN component
|
|
216
174
|
#
|
|
217
|
-
#
|
|
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
|
|
218
178
|
#
|
|
219
|
-
# @
|
|
220
|
-
#
|
|
221
|
-
#
|
|
222
|
-
#
|
|
223
|
-
def
|
|
224
|
-
|
|
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
|
|
225
186
|
|
|
226
|
-
self.class.new(
|
|
187
|
+
self.class.new(new_pin, derived: derived?)
|
|
227
188
|
end
|
|
228
189
|
|
|
229
|
-
# Create a new identifier
|
|
190
|
+
# Create a new identifier with different derivation status
|
|
230
191
|
#
|
|
231
|
-
# @
|
|
192
|
+
# @param new_derived [Boolean] new derivation status
|
|
193
|
+
# @return [Identifier] new identifier with different derivation
|
|
232
194
|
#
|
|
233
|
-
# @example
|
|
234
|
-
#
|
|
235
|
-
|
|
236
|
-
|
|
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
|
|
237
202
|
|
|
238
|
-
self.class.new(
|
|
203
|
+
self.class.new(pin, derived: new_derived_bool)
|
|
239
204
|
end
|
|
240
205
|
|
|
241
|
-
# Create a new identifier
|
|
206
|
+
# Create a new identifier marked as derived (opponent's native style)
|
|
242
207
|
#
|
|
243
|
-
# @return [Identifier] new identifier
|
|
208
|
+
# @return [Identifier] new identifier marked as derived
|
|
244
209
|
#
|
|
245
|
-
# @example
|
|
246
|
-
#
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
self.class.new(type, side, Pin::Identifier::DIMINISHED_STATE, native, terminal: terminal)
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
# Create a new identifier without diminished state
|
|
254
|
-
#
|
|
255
|
-
# @return [Identifier] new identifier instance with normal state
|
|
256
|
-
#
|
|
257
|
-
# @example
|
|
258
|
-
# identifier.undiminish # (:K, :first, :diminished, true) => (:K, :first, :normal, true)
|
|
259
|
-
def undiminish
|
|
260
|
-
return self unless diminished?
|
|
261
|
-
|
|
262
|
-
self.class.new(type, side, Pin::Identifier::NORMAL_STATE, native, terminal: terminal)
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
# Create a new identifier with normal state (no state modifiers)
|
|
266
|
-
#
|
|
267
|
-
# @return [Identifier] new identifier instance with normal state
|
|
268
|
-
#
|
|
269
|
-
# @example
|
|
270
|
-
# identifier.normalize # (:K, :first, :enhanced, true) => (:K, :first, :normal, true)
|
|
271
|
-
def normalize
|
|
272
|
-
return self if normal?
|
|
273
|
-
|
|
274
|
-
self.class.new(type, side, Pin::Identifier::NORMAL_STATE, native, terminal: terminal)
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
# Create a new identifier marked as terminal
|
|
278
|
-
#
|
|
279
|
-
# @return [Identifier] new identifier instance marked as terminal
|
|
280
|
-
#
|
|
281
|
-
# @example
|
|
282
|
-
# identifier.mark_terminal # "K" => "K^"
|
|
283
|
-
# identifier.mark_terminal # "K'" => "K^'"
|
|
284
|
-
def mark_terminal
|
|
285
|
-
return self if terminal?
|
|
286
|
-
|
|
287
|
-
self.class.new(type, side, state, native, terminal: true)
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
# Create a new identifier unmarked as terminal
|
|
291
|
-
#
|
|
292
|
-
# @return [Identifier] new identifier instance unmarked as terminal
|
|
293
|
-
#
|
|
294
|
-
# @example
|
|
295
|
-
# identifier.unmark_terminal # "K^" => "K"
|
|
296
|
-
# identifier.unmark_terminal # "K^'" => "K'"
|
|
297
|
-
def unmark_terminal
|
|
298
|
-
return self unless terminal?
|
|
299
|
-
|
|
300
|
-
self.class.new(type, side, state, native, terminal: false)
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
# Create a new identifier with opposite side
|
|
304
|
-
#
|
|
305
|
-
# @return [Identifier] new identifier instance with opposite side
|
|
306
|
-
#
|
|
307
|
-
# @example
|
|
308
|
-
# identifier.flip # (:K, :first, :normal, true) => (:K, :second, :normal, true)
|
|
309
|
-
def flip
|
|
310
|
-
self.class.new(type, opposite_side, state, native, terminal: terminal)
|
|
311
|
-
end
|
|
312
|
-
|
|
313
|
-
# Create a new identifier with foreign/derived style
|
|
314
|
-
#
|
|
315
|
-
# Converts a native piece to foreign style (opposite side's native style).
|
|
316
|
-
#
|
|
317
|
-
# @return [Identifier] new identifier instance with foreign style
|
|
318
|
-
#
|
|
319
|
-
# @example
|
|
320
|
-
# identifier.derive # (:K, :first, :normal, true) => (:K, :first, :normal, false)
|
|
321
|
-
# # "K" => "K'"
|
|
322
|
-
def derive
|
|
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
|
|
323
215
|
return self if derived?
|
|
324
216
|
|
|
325
|
-
self.class.new(
|
|
217
|
+
self.class.new(pin, derived: true)
|
|
326
218
|
end
|
|
327
219
|
|
|
328
|
-
# Create a new identifier
|
|
329
|
-
#
|
|
330
|
-
# Converts a foreign piece to native style (current side's native style).
|
|
220
|
+
# Create a new identifier marked as native (own side's native style)
|
|
331
221
|
#
|
|
332
|
-
# @return [Identifier] new identifier
|
|
222
|
+
# @return [Identifier] new identifier marked as native
|
|
333
223
|
#
|
|
334
|
-
# @example
|
|
335
|
-
#
|
|
336
|
-
#
|
|
337
|
-
|
|
338
|
-
|
|
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?
|
|
339
230
|
|
|
340
|
-
self.class.new(
|
|
231
|
+
self.class.new(pin, derived: false)
|
|
341
232
|
end
|
|
342
233
|
|
|
343
|
-
#
|
|
234
|
+
# Check if the identifier uses native style (own side's native style)
|
|
344
235
|
#
|
|
345
|
-
#
|
|
346
|
-
#
|
|
347
|
-
# @param new_type [Symbol] new type (:A to :Z)
|
|
348
|
-
# @return [Identifier] new identifier instance with different type
|
|
349
|
-
#
|
|
350
|
-
# @example
|
|
351
|
-
# identifier.with_type(:Q) # (:K, :first, :normal, true) => (:Q, :first, :normal, true)
|
|
352
|
-
def with_type(new_type)
|
|
353
|
-
Pin::Identifier.validate_type(new_type)
|
|
354
|
-
return self if type == new_type
|
|
355
|
-
|
|
356
|
-
self.class.new(new_type, side, state, native, terminal: terminal)
|
|
357
|
-
end
|
|
358
|
-
|
|
359
|
-
# Create a new identifier with a different side
|
|
360
|
-
#
|
|
361
|
-
# Preserves type, state, terminal status, and derivation.
|
|
362
|
-
#
|
|
363
|
-
# @param new_side [Symbol] :first or :second
|
|
364
|
-
# @return [Identifier] new identifier instance with different side
|
|
365
|
-
#
|
|
366
|
-
# @example
|
|
367
|
-
# identifier.with_side(:second) # (:K, :first, :normal, true) => (:K, :second, :normal, true)
|
|
368
|
-
def with_side(new_side)
|
|
369
|
-
Pin::Identifier.validate_side(new_side)
|
|
370
|
-
return self if side == new_side
|
|
371
|
-
|
|
372
|
-
self.class.new(type, new_side, state, native, terminal: terminal)
|
|
373
|
-
end
|
|
374
|
-
|
|
375
|
-
# Create a new identifier with a different state
|
|
376
|
-
#
|
|
377
|
-
# Preserves type, side, terminal status, and derivation.
|
|
378
|
-
#
|
|
379
|
-
# @param new_state [Symbol] :normal, :enhanced, or :diminished
|
|
380
|
-
# @return [Identifier] new identifier instance with different state
|
|
381
|
-
#
|
|
382
|
-
# @example
|
|
383
|
-
# identifier.with_state(:enhanced) # (:K, :first, :normal, true) => (:K, :first, :enhanced, true)
|
|
384
|
-
def with_state(new_state)
|
|
385
|
-
Pin::Identifier.validate_state(new_state)
|
|
386
|
-
return self if state == new_state
|
|
387
|
-
|
|
388
|
-
self.class.new(type, side, new_state, native, terminal: terminal)
|
|
389
|
-
end
|
|
390
|
-
|
|
391
|
-
# Create a new identifier with a different derivation
|
|
392
|
-
#
|
|
393
|
-
# Preserves type, side, state, and terminal status.
|
|
394
|
-
#
|
|
395
|
-
# @param new_native [Boolean] true for native, false for foreign
|
|
396
|
-
# @return [Identifier] new identifier instance with different derivation
|
|
397
|
-
#
|
|
398
|
-
# @example
|
|
399
|
-
# identifier.with_derivation(false) # (:K, :first, :normal, true) => (:K, :first, :normal, false)
|
|
400
|
-
def with_derivation(new_native)
|
|
401
|
-
self.class.validate_derivation(new_native)
|
|
402
|
-
return self if native == new_native
|
|
403
|
-
|
|
404
|
-
self.class.new(type, side, state, new_native, terminal: terminal)
|
|
405
|
-
end
|
|
406
|
-
|
|
407
|
-
# Create a new identifier with a different terminal status
|
|
408
|
-
#
|
|
409
|
-
# Preserves type, side, state, and derivation.
|
|
410
|
-
#
|
|
411
|
-
# @param new_terminal_bool [Boolean] terminal status
|
|
412
|
-
# @return [Identifier] new identifier instance with different terminal status
|
|
413
|
-
#
|
|
414
|
-
# @example
|
|
415
|
-
# identifier.with_terminal(true) # "K" => "K^"
|
|
416
|
-
def with_terminal(new_terminal_bool)
|
|
417
|
-
raise ::TypeError unless [true, false].include?(new_terminal_bool)
|
|
418
|
-
|
|
419
|
-
return self if terminal? == new_terminal_bool
|
|
420
|
-
|
|
421
|
-
self.class.new(type, side, state, native, terminal: new_terminal_bool)
|
|
422
|
-
end
|
|
423
|
-
|
|
424
|
-
# Check if the identifier has enhanced state
|
|
425
|
-
#
|
|
426
|
-
# @return [Boolean] true if enhanced
|
|
427
|
-
def enhanced?
|
|
428
|
-
@pin_identifier.enhanced?
|
|
429
|
-
end
|
|
430
|
-
|
|
431
|
-
# Check if the identifier has diminished state
|
|
432
|
-
#
|
|
433
|
-
# @return [Boolean] true if diminished
|
|
434
|
-
def diminished?
|
|
435
|
-
@pin_identifier.diminished?
|
|
436
|
-
end
|
|
437
|
-
|
|
438
|
-
# Check if the identifier has normal state (no state modifiers)
|
|
439
|
-
#
|
|
440
|
-
# @return [Boolean] true if normal
|
|
441
|
-
def normal?
|
|
442
|
-
@pin_identifier.normal?
|
|
443
|
-
end
|
|
444
|
-
|
|
445
|
-
# Check if the identifier belongs to the first player
|
|
446
|
-
#
|
|
447
|
-
# @return [Boolean] true if first player
|
|
448
|
-
def first_player?
|
|
449
|
-
@pin_identifier.first_player?
|
|
450
|
-
end
|
|
451
|
-
|
|
452
|
-
# Check if the identifier belongs to the second player
|
|
453
|
-
#
|
|
454
|
-
# @return [Boolean] true if second player
|
|
455
|
-
def second_player?
|
|
456
|
-
@pin_identifier.second_player?
|
|
457
|
-
end
|
|
458
|
-
|
|
459
|
-
# Check if the identifier is a terminal piece
|
|
460
|
-
#
|
|
461
|
-
# A terminal piece is one whose presence, condition, or capacity for action
|
|
462
|
-
# determines whether the match can continue.
|
|
463
|
-
#
|
|
464
|
-
# @return [Boolean] true if terminal
|
|
465
|
-
def terminal?
|
|
466
|
-
@pin_identifier.terminal?
|
|
467
|
-
end
|
|
468
|
-
|
|
469
|
-
# Check if the identifier has native style
|
|
470
|
-
#
|
|
471
|
-
# A native piece has the native style of its current side.
|
|
472
|
-
#
|
|
473
|
-
# @return [Boolean] true if native style
|
|
236
|
+
# @return [Boolean] true if native (not derived)
|
|
474
237
|
def native?
|
|
475
|
-
|
|
476
|
-
end
|
|
477
|
-
|
|
478
|
-
# Check if the identifier has foreign/derived style
|
|
479
|
-
#
|
|
480
|
-
# A derived piece has the foreign style (opposite side's native style).
|
|
481
|
-
#
|
|
482
|
-
# @return [Boolean] true if foreign/derived style
|
|
483
|
-
def derived?
|
|
484
|
-
native == FOREIGN
|
|
238
|
+
!derived?
|
|
485
239
|
end
|
|
486
240
|
|
|
487
|
-
#
|
|
488
|
-
alias foreign? derived?
|
|
489
|
-
|
|
490
|
-
# Check if this identifier is the same type as another
|
|
491
|
-
#
|
|
492
|
-
# Ignores side, state, terminal status, and derivation.
|
|
241
|
+
# Check if this identifier has the same derivation status as another
|
|
493
242
|
#
|
|
494
243
|
# @param other [Identifier] identifier to compare with
|
|
495
|
-
# @return [Boolean] true if same
|
|
244
|
+
# @return [Boolean] true if same derivation status
|
|
496
245
|
#
|
|
497
|
-
# @example
|
|
498
|
-
#
|
|
499
|
-
|
|
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)
|
|
500
251
|
return false unless other.is_a?(self.class)
|
|
501
252
|
|
|
502
|
-
|
|
503
|
-
end
|
|
504
|
-
|
|
505
|
-
# Check if this identifier belongs to the same side as another
|
|
506
|
-
#
|
|
507
|
-
# @param other [Identifier] identifier to compare with
|
|
508
|
-
# @return [Boolean] true if same side
|
|
509
|
-
def same_side?(other)
|
|
510
|
-
return false unless other.is_a?(self.class)
|
|
511
|
-
|
|
512
|
-
@pin_identifier.same_side?(other.instance_variable_get(:@pin_identifier))
|
|
513
|
-
end
|
|
514
|
-
|
|
515
|
-
# Check if this identifier has the same state as another
|
|
516
|
-
#
|
|
517
|
-
# @param other [Identifier] identifier to compare with
|
|
518
|
-
# @return [Boolean] true if same state
|
|
519
|
-
def same_state?(other)
|
|
520
|
-
return false unless other.is_a?(self.class)
|
|
521
|
-
|
|
522
|
-
@pin_identifier.same_state?(other.instance_variable_get(:@pin_identifier))
|
|
523
|
-
end
|
|
524
|
-
|
|
525
|
-
# Check if this identifier has the same terminal status as another
|
|
526
|
-
#
|
|
527
|
-
# @param other [Identifier] identifier to compare with
|
|
528
|
-
# @return [Boolean] true if same terminal status
|
|
529
|
-
def same_terminal?(other)
|
|
530
|
-
return false unless other.is_a?(self.class)
|
|
531
|
-
|
|
532
|
-
@pin_identifier.same_terminal?(other.instance_variable_get(:@pin_identifier))
|
|
533
|
-
end
|
|
534
|
-
|
|
535
|
-
# Check if this identifier has the same style derivation as another
|
|
536
|
-
#
|
|
537
|
-
# @param other [Identifier] identifier to compare with
|
|
538
|
-
# @return [Boolean] true if same style derivation
|
|
539
|
-
def same_style?(other)
|
|
540
|
-
return false unless other.is_a?(self.class)
|
|
541
|
-
|
|
542
|
-
native == other.native
|
|
253
|
+
derived? == other.derived?
|
|
543
254
|
end
|
|
544
255
|
|
|
545
256
|
# Custom equality comparison
|
|
546
257
|
#
|
|
547
|
-
# Two identifiers are equal if they have the same type, side, state,
|
|
548
|
-
# terminal status, and derivation.
|
|
549
|
-
#
|
|
550
258
|
# @param other [Object] object to compare with
|
|
551
259
|
# @return [Boolean] true if identifiers are equal
|
|
552
260
|
def ==(other)
|
|
553
261
|
return false unless other.is_a?(self.class)
|
|
554
262
|
|
|
555
|
-
|
|
263
|
+
pin == other.pin && derived? == other.derived?
|
|
556
264
|
end
|
|
557
265
|
|
|
558
266
|
# Alias for == to ensure Set functionality works correctly
|
|
@@ -562,27 +270,24 @@ module Sashite
|
|
|
562
270
|
#
|
|
563
271
|
# @return [Integer] hash value
|
|
564
272
|
def hash
|
|
565
|
-
[self.class,
|
|
273
|
+
[self.class, pin, derived?].hash
|
|
566
274
|
end
|
|
567
275
|
|
|
568
|
-
# Validate
|
|
276
|
+
# Validate EPIN string format
|
|
569
277
|
#
|
|
570
|
-
# @param
|
|
571
|
-
# @raise [ArgumentError] if
|
|
572
|
-
def self.
|
|
573
|
-
|
|
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)
|
|
574
282
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
private
|
|
283
|
+
# Check for multiple derivation markers
|
|
284
|
+
marker_count = string.count(DERIVATION_MARKER)
|
|
285
|
+
return unless marker_count > 1
|
|
579
286
|
|
|
580
|
-
|
|
581
|
-
#
|
|
582
|
-
# @return [Symbol] :first if current side is :second, :second if current side is :first
|
|
583
|
-
def opposite_side
|
|
584
|
-
@pin_identifier.send(:opposite_side)
|
|
287
|
+
raise ::ArgumentError, format(ERROR_MULTIPLE_MARKERS, string)
|
|
585
288
|
end
|
|
289
|
+
|
|
290
|
+
private_class_method :validate_epin_string
|
|
586
291
|
end
|
|
587
292
|
end
|
|
588
293
|
end
|