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.
- checksums.yaml +4 -4
- data/README.md +299 -420
- data/lib/sashite/epin.rb +274 -56
- metadata +6 -11
- data/lib/sashite/epin/identifier.rb +0 -588
|
@@ -1,588 +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 encode Piece Style relative to Piece Side.
|
|
10
|
-
#
|
|
11
|
-
# Format: [<state>]<letter>[<terminal>][<derivation>]
|
|
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)
|
|
16
|
-
#
|
|
17
|
-
# Style Derivation Logic:
|
|
18
|
-
# - No apostrophe suffix: piece has the native style of its current side
|
|
19
|
-
# - Apostrophe suffix: piece has the foreign style (opposite side's native style)
|
|
20
|
-
#
|
|
21
|
-
# All instances are immutable - transformations return new instances.
|
|
22
|
-
# This extends the Game Protocol's piece model with Style support through derivation.
|
|
23
|
-
#
|
|
24
|
-
# @see https://sashite.dev/specs/epin/1.0.0/
|
|
25
|
-
class Identifier
|
|
26
|
-
# EPIN validation pattern matching the specification: /\A[-+]?[A-Za-z]\^?'?\z/
|
|
27
|
-
EPIN_PATTERN = /\A[-+]?[A-Za-z]\^?'?\z/
|
|
28
|
-
|
|
29
|
-
# Derivation marker for foreign/derived style
|
|
30
|
-
DERIVATION_MARKER = "'"
|
|
31
|
-
|
|
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
|
-
# Error messages
|
|
43
|
-
ERROR_INVALID_EPIN = "Invalid EPIN string: %s"
|
|
44
|
-
ERROR_INVALID_DERIVATION = "Derivation must be true (native) or false (foreign), got: %s"
|
|
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
|
|
55
|
-
|
|
56
|
-
# @return [Symbol] the piece state (:normal, :enhanced, or :diminished)
|
|
57
|
-
def state
|
|
58
|
-
@pin_identifier.state
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# @return [Boolean] whether the piece is a terminal piece
|
|
62
|
-
def terminal
|
|
63
|
-
@pin_identifier.terminal
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# @return [Boolean] the style derivation (true for native, false for foreign)
|
|
67
|
-
attr_reader :native
|
|
68
|
-
|
|
69
|
-
# Create a new EPIN identifier instance
|
|
70
|
-
#
|
|
71
|
-
# @param type [Symbol] piece type (:A to :Z)
|
|
72
|
-
# @param side [Symbol] player side (:first or :second)
|
|
73
|
-
# @param state [Symbol] piece state (:normal, :enhanced, or :diminished)
|
|
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
|
|
77
|
-
#
|
|
78
|
-
# @example
|
|
79
|
-
# Identifier.new(:K, :first, :normal, true) # => "K"
|
|
80
|
-
# Identifier.new(:K, :first, :normal, true, terminal: true) # => "K^"
|
|
81
|
-
# Identifier.new(:K, :first, :normal, false, terminal: true) # => "K^'"
|
|
82
|
-
# Identifier.new(:P, :second, :enhanced, false) # => "+p'"
|
|
83
|
-
def initialize(type, side, state = Pin::Identifier::NORMAL_STATE, native = NATIVE, terminal: false)
|
|
84
|
-
# Validate using PIN class methods for type, side, and state
|
|
85
|
-
Pin::Identifier.validate_type(type)
|
|
86
|
-
Pin::Identifier.validate_side(side)
|
|
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
|
|
92
|
-
|
|
93
|
-
freeze
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# Parse an EPIN string into an Identifier object
|
|
97
|
-
#
|
|
98
|
-
# EPIN format: [<state>]<letter>[<terminal>][<derivation>]
|
|
99
|
-
# where terminal marker (^) comes BEFORE derivation marker (')
|
|
100
|
-
#
|
|
101
|
-
# @param epin_string [String] EPIN notation string
|
|
102
|
-
# @return [Identifier] new identifier instance
|
|
103
|
-
# @raise [ArgumentError] if the EPIN string is invalid
|
|
104
|
-
#
|
|
105
|
-
# @example
|
|
106
|
-
# Epin::Identifier.parse("k") # => native second player king
|
|
107
|
-
# Epin::Identifier.parse("K^") # => native terminal first player king
|
|
108
|
-
# Epin::Identifier.parse("+R'") # => foreign enhanced first player rook
|
|
109
|
-
# Epin::Identifier.parse("+K^'") # => foreign enhanced terminal first player king
|
|
110
|
-
# Epin::Identifier.parse("-p") # => native diminished second player pawn
|
|
111
|
-
def self.parse(epin_string)
|
|
112
|
-
string_value = String(epin_string)
|
|
113
|
-
|
|
114
|
-
# Validate EPIN pattern first
|
|
115
|
-
raise ::ArgumentError, format(ERROR_INVALID_EPIN, string_value) unless string_value.match?(EPIN_PATTERN)
|
|
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
|
|
125
|
-
|
|
126
|
-
# Validate and parse the PIN part using existing PIN logic
|
|
127
|
-
raise ::ArgumentError, format(ERROR_INVALID_EPIN, string_value) unless Pin::Identifier.valid?(pin_part)
|
|
128
|
-
|
|
129
|
-
pin_identifier = Pin::Identifier.parse(pin_part)
|
|
130
|
-
identifier_native = !derived
|
|
131
|
-
|
|
132
|
-
new(pin_identifier.type, pin_identifier.side, pin_identifier.state, identifier_native, terminal: pin_identifier.terminal)
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Check if a string is a valid EPIN notation
|
|
136
|
-
#
|
|
137
|
-
# Valid EPIN format: [<state>]<letter>[<terminal>][<derivation>]
|
|
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
|
|
144
|
-
# @return [Boolean] true if valid EPIN, false otherwise
|
|
145
|
-
#
|
|
146
|
-
# @example
|
|
147
|
-
# Sashite::Epin::Identifier.valid?("K") # => true
|
|
148
|
-
# Sashite::Epin::Identifier.valid?("K^") # => true
|
|
149
|
-
# Sashite::Epin::Identifier.valid?("+R'") # => true
|
|
150
|
-
# Sashite::Epin::Identifier.valid?("+K^'") # => true
|
|
151
|
-
# Sashite::Epin::Identifier.valid?("KK") # => false
|
|
152
|
-
# Sashite::Epin::Identifier.valid?("++K") # => false
|
|
153
|
-
# Sashite::Epin::Identifier.valid?("K'^") # => false (wrong order)
|
|
154
|
-
def self.valid?(epin_string)
|
|
155
|
-
return false unless epin_string.is_a?(::String)
|
|
156
|
-
return false if epin_string.empty?
|
|
157
|
-
|
|
158
|
-
# Check EPIN pattern
|
|
159
|
-
return false unless epin_string.match?(EPIN_PATTERN)
|
|
160
|
-
|
|
161
|
-
# Extract PIN part (remove derivation marker if present)
|
|
162
|
-
pin_part = epin_string.end_with?(DERIVATION_MARKER) ? epin_string[0...-1] : epin_string
|
|
163
|
-
return false if pin_part.empty? # Can't have just an apostrophe
|
|
164
|
-
|
|
165
|
-
# Validate the PIN part using existing PIN validation
|
|
166
|
-
Pin::Identifier.valid?(pin_part)
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
# Convert the identifier to its EPIN string representation
|
|
170
|
-
#
|
|
171
|
-
# Format: [<state>]<letter>[<terminal>][<derivation>]
|
|
172
|
-
#
|
|
173
|
-
# @return [String] EPIN notation string
|
|
174
|
-
#
|
|
175
|
-
# @example
|
|
176
|
-
# identifier.to_s # => "+R'"
|
|
177
|
-
# identifier.to_s # => "K^"
|
|
178
|
-
# identifier.to_s # => "+K^'"
|
|
179
|
-
# identifier.to_s # => "-p"
|
|
180
|
-
def to_s
|
|
181
|
-
"#{prefix}#{letter}#{terminal_marker}#{derivation_marker}"
|
|
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
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
# Get the derivation marker (EPIN-specific)
|
|
206
|
-
#
|
|
207
|
-
# @return [String] derivation marker ("'" for foreign, "" for native)
|
|
208
|
-
def derivation_marker
|
|
209
|
-
native? ? NATIVE_MARKER : DERIVATION_MARKER
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
# Alias for backward compatibility
|
|
213
|
-
alias suffix derivation_marker
|
|
214
|
-
|
|
215
|
-
# Create a new identifier with enhanced state
|
|
216
|
-
#
|
|
217
|
-
# Preserves type, side, terminal status, and derivation.
|
|
218
|
-
#
|
|
219
|
-
# @return [Identifier] new identifier instance with enhanced state
|
|
220
|
-
#
|
|
221
|
-
# @example
|
|
222
|
-
# identifier.enhance # (:K, :first, :normal, true) => (:K, :first, :enhanced, true)
|
|
223
|
-
def enhance
|
|
224
|
-
return self if enhanced?
|
|
225
|
-
|
|
226
|
-
self.class.new(type, side, Pin::Identifier::ENHANCED_STATE, native, terminal: terminal)
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
# Create a new identifier without enhanced state
|
|
230
|
-
#
|
|
231
|
-
# @return [Identifier] new identifier instance with normal state
|
|
232
|
-
#
|
|
233
|
-
# @example
|
|
234
|
-
# identifier.unenhance # (:K, :first, :enhanced, true) => (:K, :first, :normal, true)
|
|
235
|
-
def unenhance
|
|
236
|
-
return self unless enhanced?
|
|
237
|
-
|
|
238
|
-
self.class.new(type, side, Pin::Identifier::NORMAL_STATE, native, terminal: terminal)
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
# Create a new identifier with diminished state
|
|
242
|
-
#
|
|
243
|
-
# @return [Identifier] new identifier instance with diminished state
|
|
244
|
-
#
|
|
245
|
-
# @example
|
|
246
|
-
# identifier.diminish # (:K, :first, :normal, true) => (:K, :first, :diminished, true)
|
|
247
|
-
def diminish
|
|
248
|
-
return self if diminished?
|
|
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
|
|
323
|
-
return self if derived?
|
|
324
|
-
|
|
325
|
-
self.class.new(type, side, state, FOREIGN, terminal: terminal)
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
# Create a new identifier with native style
|
|
329
|
-
#
|
|
330
|
-
# Converts a foreign piece to native style (current side's native style).
|
|
331
|
-
#
|
|
332
|
-
# @return [Identifier] new identifier instance with native style
|
|
333
|
-
#
|
|
334
|
-
# @example
|
|
335
|
-
# identifier.underive # (:K, :first, :normal, false) => (:K, :first, :normal, true)
|
|
336
|
-
# # "K'" => "K"
|
|
337
|
-
def underive
|
|
338
|
-
return self if native?
|
|
339
|
-
|
|
340
|
-
self.class.new(type, side, state, NATIVE, terminal: terminal)
|
|
341
|
-
end
|
|
342
|
-
|
|
343
|
-
# Create a new identifier with a different type
|
|
344
|
-
#
|
|
345
|
-
# Preserves side, state, terminal status, and derivation.
|
|
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
|
|
474
|
-
def native?
|
|
475
|
-
native == NATIVE
|
|
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
|
|
485
|
-
end
|
|
486
|
-
|
|
487
|
-
# Alias for derived? to match specification terminology
|
|
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.
|
|
493
|
-
#
|
|
494
|
-
# @param other [Identifier] identifier to compare with
|
|
495
|
-
# @return [Boolean] true if same type
|
|
496
|
-
#
|
|
497
|
-
# @example
|
|
498
|
-
# king1.same_type?(king2) # (:K, :first, :normal, true) and (:K, :second, :enhanced, false) => true
|
|
499
|
-
def same_type?(other)
|
|
500
|
-
return false unless other.is_a?(self.class)
|
|
501
|
-
|
|
502
|
-
@pin_identifier.same_type?(other.instance_variable_get(:@pin_identifier))
|
|
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
|
|
543
|
-
end
|
|
544
|
-
|
|
545
|
-
# Custom equality comparison
|
|
546
|
-
#
|
|
547
|
-
# Two identifiers are equal if they have the same type, side, state,
|
|
548
|
-
# terminal status, and derivation.
|
|
549
|
-
#
|
|
550
|
-
# @param other [Object] object to compare with
|
|
551
|
-
# @return [Boolean] true if identifiers are equal
|
|
552
|
-
def ==(other)
|
|
553
|
-
return false unless other.is_a?(self.class)
|
|
554
|
-
|
|
555
|
-
@pin_identifier == other.instance_variable_get(:@pin_identifier) && native == other.native
|
|
556
|
-
end
|
|
557
|
-
|
|
558
|
-
# Alias for == to ensure Set functionality works correctly
|
|
559
|
-
alias eql? ==
|
|
560
|
-
|
|
561
|
-
# Custom hash implementation for use in collections
|
|
562
|
-
#
|
|
563
|
-
# @return [Integer] hash value
|
|
564
|
-
def hash
|
|
565
|
-
[self.class, @pin_identifier, native].hash
|
|
566
|
-
end
|
|
567
|
-
|
|
568
|
-
# Validate that the derivation is a valid boolean
|
|
569
|
-
#
|
|
570
|
-
# @param derivation [Boolean] the derivation to validate
|
|
571
|
-
# @raise [ArgumentError] if invalid
|
|
572
|
-
def self.validate_derivation(derivation)
|
|
573
|
-
return if VALID_DERIVATIONS.include?(derivation)
|
|
574
|
-
|
|
575
|
-
raise ::ArgumentError, format(ERROR_INVALID_DERIVATION, derivation.inspect)
|
|
576
|
-
end
|
|
577
|
-
|
|
578
|
-
private
|
|
579
|
-
|
|
580
|
-
# Get the opposite side of the current identifier
|
|
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)
|
|
585
|
-
end
|
|
586
|
-
end
|
|
587
|
-
end
|
|
588
|
-
end
|