sashite-epin 1.1.0 → 1.2.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/lib/sashite/epin/identifier.rb +224 -77
- data/lib/sashite/epin.rb +32 -18
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 43dd91dce36507e42cf07b57202cff4d323fb889fde7d8daf62498b8bf588f04
|
|
4
|
+
data.tar.gz: b4237ab78e18f3e879893fd934f438aa3729d60508dbf8c5378727b84b2a9b7c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 75122035fe9b0a3a0d4bda91518761b57a12a1551eb7743b27e31775c5007df75da1bd470f8b0cd69037d33bfafbc2c3c1773f7f7eb52fc083e6928989266c76
|
|
7
|
+
data.tar.gz: '087da6655e1528df88d86b276eff52fb1ae71e4d3b83295b105135b4ba22f5d8c75f2675fac9f3cbc61dcb94aeda9ab7c6de274dc4a8e614ca9549d4583df002'
|
|
@@ -6,37 +6,44 @@ module Sashite
|
|
|
6
6
|
module Epin
|
|
7
7
|
# Represents an identifier in EPIN (Extended Piece Identifier Notation) format.
|
|
8
8
|
#
|
|
9
|
-
#
|
|
10
|
-
# - PIN component: [<state>]<letter> (from PIN specification)
|
|
11
|
-
# - Derivation marker: "'" (foreign style) or none (native style)
|
|
9
|
+
# EPIN extends PIN by adding a derivation marker to encode Piece Style relative to Piece Side.
|
|
12
10
|
#
|
|
13
|
-
#
|
|
14
|
-
# -
|
|
15
|
-
# -
|
|
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
16
|
#
|
|
17
|
-
# Style
|
|
18
|
-
# - No suffix: piece has the native style of its current side
|
|
17
|
+
# Style Derivation Logic:
|
|
18
|
+
# - No apostrophe suffix: piece has the native style of its current side
|
|
19
19
|
# - Apostrophe suffix: piece has the foreign style (opposite side's native style)
|
|
20
20
|
#
|
|
21
|
-
# All instances are immutable -
|
|
21
|
+
# All instances are immutable - transformations return new instances.
|
|
22
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/
|
|
23
25
|
class Identifier
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
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 = ""
|
|
27
34
|
|
|
28
|
-
#
|
|
35
|
+
# Style constants
|
|
29
36
|
NATIVE = true
|
|
30
37
|
FOREIGN = false
|
|
31
38
|
|
|
32
|
-
# Valid
|
|
39
|
+
# Valid derivation values
|
|
33
40
|
VALID_DERIVATIONS = [NATIVE, FOREIGN].freeze
|
|
34
41
|
|
|
35
42
|
# Error messages
|
|
36
43
|
ERROR_INVALID_EPIN = "Invalid EPIN string: %s"
|
|
37
44
|
ERROR_INVALID_DERIVATION = "Derivation must be true (native) or false (foreign), got: %s"
|
|
38
45
|
|
|
39
|
-
# @return [Symbol] the piece type (:A to :Z)
|
|
46
|
+
# @return [Symbol] the piece type (:A to :Z, always uppercase)
|
|
40
47
|
def type
|
|
41
48
|
@pin_identifier.type
|
|
42
49
|
end
|
|
@@ -51,27 +58,36 @@ module Sashite
|
|
|
51
58
|
@pin_identifier.state
|
|
52
59
|
end
|
|
53
60
|
|
|
61
|
+
# @return [Boolean] whether the piece is a terminal piece
|
|
62
|
+
def terminal
|
|
63
|
+
@pin_identifier.terminal
|
|
64
|
+
end
|
|
65
|
+
|
|
54
66
|
# @return [Boolean] the style derivation (true for native, false for foreign)
|
|
55
67
|
attr_reader :native
|
|
56
68
|
|
|
57
|
-
# Create a new identifier instance
|
|
69
|
+
# Create a new EPIN identifier instance
|
|
58
70
|
#
|
|
59
71
|
# @param type [Symbol] piece type (:A to :Z)
|
|
60
72
|
# @param side [Symbol] player side (:first or :second)
|
|
61
73
|
# @param state [Symbol] piece state (:normal, :enhanced, or :diminished)
|
|
62
74
|
# @param native [Boolean] style derivation (true for native, false for foreign)
|
|
75
|
+
# @param terminal [Boolean] whether the piece is a terminal piece
|
|
63
76
|
# @raise [ArgumentError] if parameters are invalid
|
|
77
|
+
#
|
|
64
78
|
# @example
|
|
65
|
-
# Identifier.new(:K, :first, :normal, true)
|
|
66
|
-
# Identifier.new(:
|
|
67
|
-
|
|
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)
|
|
68
84
|
# Validate using PIN class methods for type, side, and state
|
|
69
85
|
Pin::Identifier.validate_type(type)
|
|
70
86
|
Pin::Identifier.validate_side(side)
|
|
71
87
|
Pin::Identifier.validate_state(state)
|
|
72
88
|
self.class.validate_derivation(native)
|
|
73
89
|
|
|
74
|
-
@pin_identifier = Pin::Identifier.new(type, side, state)
|
|
90
|
+
@pin_identifier = Pin::Identifier.new(type, side, state, terminal: terminal)
|
|
75
91
|
@native = native
|
|
76
92
|
|
|
77
93
|
freeze
|
|
@@ -79,56 +95,72 @@ module Sashite
|
|
|
79
95
|
|
|
80
96
|
# Parse an EPIN string into an Identifier object
|
|
81
97
|
#
|
|
98
|
+
# EPIN format: [<state>]<letter>[<terminal>][<derivation>]
|
|
99
|
+
# where terminal marker (^) comes BEFORE derivation marker (')
|
|
100
|
+
#
|
|
82
101
|
# @param epin_string [String] EPIN notation string
|
|
83
102
|
# @return [Identifier] new identifier instance
|
|
84
103
|
# @raise [ArgumentError] if the EPIN string is invalid
|
|
104
|
+
#
|
|
85
105
|
# @example
|
|
86
|
-
# Epin::Identifier.parse("k")
|
|
87
|
-
# Epin::Identifier.parse("
|
|
88
|
-
# Epin::Identifier.parse("
|
|
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
|
|
89
111
|
def self.parse(epin_string)
|
|
90
112
|
string_value = String(epin_string)
|
|
91
113
|
|
|
92
|
-
#
|
|
93
|
-
|
|
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)
|
|
94
119
|
pin_part = string_value[0...-1] # Remove the apostrophe
|
|
95
|
-
|
|
120
|
+
derived = true
|
|
96
121
|
else
|
|
97
122
|
pin_part = string_value
|
|
98
|
-
|
|
123
|
+
derived = false
|
|
99
124
|
end
|
|
100
125
|
|
|
101
126
|
# Validate and parse the PIN part using existing PIN logic
|
|
102
127
|
raise ::ArgumentError, format(ERROR_INVALID_EPIN, string_value) unless Pin::Identifier.valid?(pin_part)
|
|
103
128
|
|
|
104
129
|
pin_identifier = Pin::Identifier.parse(pin_part)
|
|
105
|
-
identifier_native = !
|
|
130
|
+
identifier_native = !derived
|
|
106
131
|
|
|
107
|
-
new(pin_identifier.type, pin_identifier.side, pin_identifier.state, identifier_native)
|
|
132
|
+
new(pin_identifier.type, pin_identifier.side, pin_identifier.state, identifier_native, terminal: pin_identifier.terminal)
|
|
108
133
|
end
|
|
109
134
|
|
|
110
135
|
# Check if a string is a valid EPIN notation
|
|
111
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
|
+
#
|
|
112
143
|
# @param epin_string [String] The string to validate
|
|
113
144
|
# @return [Boolean] true if valid EPIN, false otherwise
|
|
114
145
|
#
|
|
115
146
|
# @example
|
|
116
|
-
# Sashite::Epin::Identifier.valid?("K")
|
|
117
|
-
# Sashite::Epin::Identifier.valid?("
|
|
118
|
-
# Sashite::Epin::Identifier.valid?("
|
|
119
|
-
# Sashite::Epin::Identifier.valid?("
|
|
120
|
-
# Sashite::Epin::Identifier.valid?("
|
|
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)
|
|
121
154
|
def self.valid?(epin_string)
|
|
122
155
|
return false unless epin_string.is_a?(::String)
|
|
123
156
|
return false if epin_string.empty?
|
|
124
157
|
|
|
125
|
-
# Check
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
end
|
|
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
|
|
132
164
|
|
|
133
165
|
# Validate the PIN part using existing PIN validation
|
|
134
166
|
Pin::Identifier.valid?(pin_part)
|
|
@@ -136,13 +168,17 @@ module Sashite
|
|
|
136
168
|
|
|
137
169
|
# Convert the identifier to its EPIN string representation
|
|
138
170
|
#
|
|
171
|
+
# Format: [<state>]<letter>[<terminal>][<derivation>]
|
|
172
|
+
#
|
|
139
173
|
# @return [String] EPIN notation string
|
|
174
|
+
#
|
|
140
175
|
# @example
|
|
141
176
|
# identifier.to_s # => "+R'"
|
|
177
|
+
# identifier.to_s # => "K^"
|
|
178
|
+
# identifier.to_s # => "+K^'"
|
|
142
179
|
# identifier.to_s # => "-p"
|
|
143
|
-
# identifier.to_s # => "K"
|
|
144
180
|
def to_s
|
|
145
|
-
"#{prefix}#{letter}#{
|
|
181
|
+
"#{prefix}#{letter}#{terminal_marker}#{derivation_marker}"
|
|
146
182
|
end
|
|
147
183
|
|
|
148
184
|
# Get the letter representation (inherited from PIN logic)
|
|
@@ -152,156 +188,237 @@ module Sashite
|
|
|
152
188
|
@pin_identifier.letter
|
|
153
189
|
end
|
|
154
190
|
|
|
155
|
-
# Get the prefix
|
|
191
|
+
# Get the state prefix (inherited from PIN logic)
|
|
156
192
|
#
|
|
157
|
-
# @return [String] prefix representing the state
|
|
193
|
+
# @return [String] prefix representing the state ("+" / "-" / "")
|
|
158
194
|
def prefix
|
|
159
195
|
@pin_identifier.prefix
|
|
160
196
|
end
|
|
161
197
|
|
|
162
|
-
# Get the
|
|
198
|
+
# Get the terminal marker (inherited from PIN logic)
|
|
163
199
|
#
|
|
164
|
-
# @return [String]
|
|
165
|
-
def
|
|
166
|
-
|
|
200
|
+
# @return [String] terminal marker ("^" or "")
|
|
201
|
+
def terminal_marker
|
|
202
|
+
@pin_identifier.suffix
|
|
167
203
|
end
|
|
168
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
|
+
|
|
169
215
|
# Create a new identifier with enhanced state
|
|
170
216
|
#
|
|
217
|
+
# Preserves type, side, terminal status, and derivation.
|
|
218
|
+
#
|
|
171
219
|
# @return [Identifier] new identifier instance with enhanced state
|
|
220
|
+
#
|
|
172
221
|
# @example
|
|
173
222
|
# identifier.enhance # (:K, :first, :normal, true) => (:K, :first, :enhanced, true)
|
|
174
223
|
def enhance
|
|
175
224
|
return self if enhanced?
|
|
176
225
|
|
|
177
|
-
self.class.new(type, side, Pin::Identifier::ENHANCED_STATE, native)
|
|
226
|
+
self.class.new(type, side, Pin::Identifier::ENHANCED_STATE, native, terminal: terminal)
|
|
178
227
|
end
|
|
179
228
|
|
|
180
229
|
# Create a new identifier without enhanced state
|
|
181
230
|
#
|
|
182
|
-
# @return [Identifier] new identifier instance
|
|
231
|
+
# @return [Identifier] new identifier instance with normal state
|
|
232
|
+
#
|
|
183
233
|
# @example
|
|
184
234
|
# identifier.unenhance # (:K, :first, :enhanced, true) => (:K, :first, :normal, true)
|
|
185
235
|
def unenhance
|
|
186
236
|
return self unless enhanced?
|
|
187
237
|
|
|
188
|
-
self.class.new(type, side, Pin::Identifier::NORMAL_STATE, native)
|
|
238
|
+
self.class.new(type, side, Pin::Identifier::NORMAL_STATE, native, terminal: terminal)
|
|
189
239
|
end
|
|
190
240
|
|
|
191
241
|
# Create a new identifier with diminished state
|
|
192
242
|
#
|
|
193
243
|
# @return [Identifier] new identifier instance with diminished state
|
|
244
|
+
#
|
|
194
245
|
# @example
|
|
195
246
|
# identifier.diminish # (:K, :first, :normal, true) => (:K, :first, :diminished, true)
|
|
196
247
|
def diminish
|
|
197
248
|
return self if diminished?
|
|
198
249
|
|
|
199
|
-
self.class.new(type, side, Pin::Identifier::DIMINISHED_STATE, native)
|
|
250
|
+
self.class.new(type, side, Pin::Identifier::DIMINISHED_STATE, native, terminal: terminal)
|
|
200
251
|
end
|
|
201
252
|
|
|
202
253
|
# Create a new identifier without diminished state
|
|
203
254
|
#
|
|
204
|
-
# @return [Identifier] new identifier instance
|
|
255
|
+
# @return [Identifier] new identifier instance with normal state
|
|
256
|
+
#
|
|
205
257
|
# @example
|
|
206
258
|
# identifier.undiminish # (:K, :first, :diminished, true) => (:K, :first, :normal, true)
|
|
207
259
|
def undiminish
|
|
208
260
|
return self unless diminished?
|
|
209
261
|
|
|
210
|
-
self.class.new(type, side, Pin::Identifier::NORMAL_STATE, native)
|
|
262
|
+
self.class.new(type, side, Pin::Identifier::NORMAL_STATE, native, terminal: terminal)
|
|
211
263
|
end
|
|
212
264
|
|
|
213
|
-
# Create a new identifier with normal state (no modifiers)
|
|
265
|
+
# Create a new identifier with normal state (no state modifiers)
|
|
214
266
|
#
|
|
215
267
|
# @return [Identifier] new identifier instance with normal state
|
|
268
|
+
#
|
|
216
269
|
# @example
|
|
217
270
|
# identifier.normalize # (:K, :first, :enhanced, true) => (:K, :first, :normal, true)
|
|
218
271
|
def normalize
|
|
219
272
|
return self if normal?
|
|
220
273
|
|
|
221
|
-
self.class.new(type, side, Pin::Identifier::NORMAL_STATE, native)
|
|
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)
|
|
222
301
|
end
|
|
223
302
|
|
|
224
303
|
# Create a new identifier with opposite side
|
|
225
304
|
#
|
|
226
305
|
# @return [Identifier] new identifier instance with opposite side
|
|
306
|
+
#
|
|
227
307
|
# @example
|
|
228
308
|
# identifier.flip # (:K, :first, :normal, true) => (:K, :second, :normal, true)
|
|
229
309
|
def flip
|
|
230
|
-
self.class.new(type, opposite_side, state, native)
|
|
310
|
+
self.class.new(type, opposite_side, state, native, terminal: terminal)
|
|
231
311
|
end
|
|
232
312
|
|
|
233
|
-
# Create a new identifier with foreign style
|
|
313
|
+
# Create a new identifier with foreign/derived style
|
|
314
|
+
#
|
|
315
|
+
# Converts a native piece to foreign style (opposite side's native style).
|
|
234
316
|
#
|
|
235
317
|
# @return [Identifier] new identifier instance with foreign style
|
|
318
|
+
#
|
|
236
319
|
# @example
|
|
237
320
|
# identifier.derive # (:K, :first, :normal, true) => (:K, :first, :normal, false)
|
|
321
|
+
# # "K" => "K'"
|
|
238
322
|
def derive
|
|
239
323
|
return self if derived?
|
|
240
324
|
|
|
241
|
-
self.class.new(type, side, state, FOREIGN)
|
|
325
|
+
self.class.new(type, side, state, FOREIGN, terminal: terminal)
|
|
242
326
|
end
|
|
243
327
|
|
|
244
|
-
# Create a new identifier with native style
|
|
328
|
+
# Create a new identifier with native style
|
|
329
|
+
#
|
|
330
|
+
# Converts a foreign piece to native style (current side's native style).
|
|
245
331
|
#
|
|
246
332
|
# @return [Identifier] new identifier instance with native style
|
|
333
|
+
#
|
|
247
334
|
# @example
|
|
248
335
|
# identifier.underive # (:K, :first, :normal, false) => (:K, :first, :normal, true)
|
|
336
|
+
# # "K'" => "K"
|
|
249
337
|
def underive
|
|
250
338
|
return self if native?
|
|
251
339
|
|
|
252
|
-
self.class.new(type, side, state, NATIVE)
|
|
340
|
+
self.class.new(type, side, state, NATIVE, terminal: terminal)
|
|
253
341
|
end
|
|
254
342
|
|
|
255
|
-
# Create a new identifier with a different type
|
|
343
|
+
# Create a new identifier with a different type
|
|
344
|
+
#
|
|
345
|
+
# Preserves side, state, terminal status, and derivation.
|
|
256
346
|
#
|
|
257
347
|
# @param new_type [Symbol] new type (:A to :Z)
|
|
258
348
|
# @return [Identifier] new identifier instance with different type
|
|
349
|
+
#
|
|
259
350
|
# @example
|
|
260
351
|
# identifier.with_type(:Q) # (:K, :first, :normal, true) => (:Q, :first, :normal, true)
|
|
261
352
|
def with_type(new_type)
|
|
262
353
|
Pin::Identifier.validate_type(new_type)
|
|
263
354
|
return self if type == new_type
|
|
264
355
|
|
|
265
|
-
self.class.new(new_type, side, state, native)
|
|
356
|
+
self.class.new(new_type, side, state, native, terminal: terminal)
|
|
266
357
|
end
|
|
267
358
|
|
|
268
|
-
# Create a new identifier with a different side
|
|
359
|
+
# Create a new identifier with a different side
|
|
360
|
+
#
|
|
361
|
+
# Preserves type, state, terminal status, and derivation.
|
|
269
362
|
#
|
|
270
363
|
# @param new_side [Symbol] :first or :second
|
|
271
364
|
# @return [Identifier] new identifier instance with different side
|
|
365
|
+
#
|
|
272
366
|
# @example
|
|
273
367
|
# identifier.with_side(:second) # (:K, :first, :normal, true) => (:K, :second, :normal, true)
|
|
274
368
|
def with_side(new_side)
|
|
275
369
|
Pin::Identifier.validate_side(new_side)
|
|
276
370
|
return self if side == new_side
|
|
277
371
|
|
|
278
|
-
self.class.new(type, new_side, state, native)
|
|
372
|
+
self.class.new(type, new_side, state, native, terminal: terminal)
|
|
279
373
|
end
|
|
280
374
|
|
|
281
|
-
# Create a new identifier with a different state
|
|
375
|
+
# Create a new identifier with a different state
|
|
376
|
+
#
|
|
377
|
+
# Preserves type, side, terminal status, and derivation.
|
|
282
378
|
#
|
|
283
379
|
# @param new_state [Symbol] :normal, :enhanced, or :diminished
|
|
284
380
|
# @return [Identifier] new identifier instance with different state
|
|
381
|
+
#
|
|
285
382
|
# @example
|
|
286
383
|
# identifier.with_state(:enhanced) # (:K, :first, :normal, true) => (:K, :first, :enhanced, true)
|
|
287
384
|
def with_state(new_state)
|
|
288
385
|
Pin::Identifier.validate_state(new_state)
|
|
289
386
|
return self if state == new_state
|
|
290
387
|
|
|
291
|
-
self.class.new(type, side, new_state, native)
|
|
388
|
+
self.class.new(type, side, new_state, native, terminal: terminal)
|
|
292
389
|
end
|
|
293
390
|
|
|
294
|
-
# Create a new identifier with a different derivation
|
|
391
|
+
# Create a new identifier with a different derivation
|
|
392
|
+
#
|
|
393
|
+
# Preserves type, side, state, and terminal status.
|
|
295
394
|
#
|
|
296
395
|
# @param new_native [Boolean] true for native, false for foreign
|
|
297
396
|
# @return [Identifier] new identifier instance with different derivation
|
|
397
|
+
#
|
|
298
398
|
# @example
|
|
299
399
|
# identifier.with_derivation(false) # (:K, :first, :normal, true) => (:K, :first, :normal, false)
|
|
300
400
|
def with_derivation(new_native)
|
|
301
401
|
self.class.validate_derivation(new_native)
|
|
302
402
|
return self if native == new_native
|
|
303
403
|
|
|
304
|
-
self.class.new(type, side, state, new_native)
|
|
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)
|
|
305
422
|
end
|
|
306
423
|
|
|
307
424
|
# Check if the identifier has enhanced state
|
|
@@ -318,9 +435,9 @@ module Sashite
|
|
|
318
435
|
@pin_identifier.diminished?
|
|
319
436
|
end
|
|
320
437
|
|
|
321
|
-
# Check if the identifier has normal state (no modifiers)
|
|
438
|
+
# Check if the identifier has normal state (no state modifiers)
|
|
322
439
|
#
|
|
323
|
-
# @return [Boolean] true if
|
|
440
|
+
# @return [Boolean] true if normal
|
|
324
441
|
def normal?
|
|
325
442
|
@pin_identifier.normal?
|
|
326
443
|
end
|
|
@@ -339,27 +456,44 @@ module Sashite
|
|
|
339
456
|
@pin_identifier.second_player?
|
|
340
457
|
end
|
|
341
458
|
|
|
342
|
-
# Check if the identifier
|
|
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.
|
|
343
472
|
#
|
|
344
473
|
# @return [Boolean] true if native style
|
|
345
474
|
def native?
|
|
346
475
|
native == NATIVE
|
|
347
476
|
end
|
|
348
477
|
|
|
349
|
-
# Check if the identifier has foreign style
|
|
478
|
+
# Check if the identifier has foreign/derived style
|
|
479
|
+
#
|
|
480
|
+
# A derived piece has the foreign style (opposite side's native style).
|
|
350
481
|
#
|
|
351
|
-
# @return [Boolean] true if foreign style
|
|
482
|
+
# @return [Boolean] true if foreign/derived style
|
|
352
483
|
def derived?
|
|
353
484
|
native == FOREIGN
|
|
354
485
|
end
|
|
355
486
|
|
|
356
|
-
# Alias for derived? to match
|
|
487
|
+
# Alias for derived? to match specification terminology
|
|
357
488
|
alias foreign? derived?
|
|
358
489
|
|
|
359
|
-
# Check if this identifier is the same type as another
|
|
490
|
+
# Check if this identifier is the same type as another
|
|
491
|
+
#
|
|
492
|
+
# Ignores side, state, terminal status, and derivation.
|
|
360
493
|
#
|
|
361
494
|
# @param other [Identifier] identifier to compare with
|
|
362
495
|
# @return [Boolean] true if same type
|
|
496
|
+
#
|
|
363
497
|
# @example
|
|
364
498
|
# king1.same_type?(king2) # (:K, :first, :normal, true) and (:K, :second, :enhanced, false) => true
|
|
365
499
|
def same_type?(other)
|
|
@@ -388,6 +522,16 @@ module Sashite
|
|
|
388
522
|
@pin_identifier.same_state?(other.instance_variable_get(:@pin_identifier))
|
|
389
523
|
end
|
|
390
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
|
+
|
|
391
535
|
# Check if this identifier has the same style derivation as another
|
|
392
536
|
#
|
|
393
537
|
# @param other [Identifier] identifier to compare with
|
|
@@ -400,6 +544,9 @@ module Sashite
|
|
|
400
544
|
|
|
401
545
|
# Custom equality comparison
|
|
402
546
|
#
|
|
547
|
+
# Two identifiers are equal if they have the same type, side, state,
|
|
548
|
+
# terminal status, and derivation.
|
|
549
|
+
#
|
|
403
550
|
# @param other [Object] object to compare with
|
|
404
551
|
# @return [Boolean] true if identifiers are equal
|
|
405
552
|
def ==(other)
|
data/lib/sashite/epin.rb
CHANGED
|
@@ -9,16 +9,20 @@ module Sashite
|
|
|
9
9
|
# EPIN extends PIN by adding derivation markers that distinguish pieces by their style origin,
|
|
10
10
|
# enabling cross-style game scenarios and piece origin tracking.
|
|
11
11
|
#
|
|
12
|
-
# Format: [<state>]<letter>[<derivation>]
|
|
12
|
+
# Format: [<state>]<letter>[<terminal>][<derivation>]
|
|
13
13
|
# - State modifier: "+" (enhanced), "-" (diminished), or none (normal)
|
|
14
14
|
# - Letter: A-Z (first player), a-z (second player)
|
|
15
|
+
# - Terminal marker: "^" (terminal piece)
|
|
15
16
|
# - Derivation marker: "'" (foreign style), or none (native style)
|
|
16
17
|
#
|
|
17
18
|
# Examples:
|
|
18
|
-
# "K"
|
|
19
|
-
# "
|
|
20
|
-
# "
|
|
21
|
-
# "
|
|
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)
|
|
22
26
|
#
|
|
23
27
|
# @see https://sashite.dev/specs/epin/1.0.0/
|
|
24
28
|
module Epin
|
|
@@ -28,11 +32,14 @@ module Sashite
|
|
|
28
32
|
# @return [Boolean] true if valid EPIN, false otherwise
|
|
29
33
|
#
|
|
30
34
|
# @example
|
|
31
|
-
# Sashite::Epin.valid?("K")
|
|
32
|
-
# Sashite::Epin.valid?("
|
|
33
|
-
# Sashite::Epin.valid?("
|
|
34
|
-
# Sashite::Epin.valid?("
|
|
35
|
-
# Sashite::Epin.valid?("
|
|
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)
|
|
36
43
|
def self.valid?(epin_string)
|
|
37
44
|
Identifier.valid?(epin_string)
|
|
38
45
|
end
|
|
@@ -42,10 +49,13 @@ module Sashite
|
|
|
42
49
|
# @param epin_string [String] EPIN notation string
|
|
43
50
|
# @return [Epin::Identifier] new identifier instance
|
|
44
51
|
# @raise [ArgumentError] if the EPIN string is invalid
|
|
52
|
+
#
|
|
45
53
|
# @example
|
|
46
|
-
# Sashite::Epin.parse("K")
|
|
47
|
-
# Sashite::Epin.parse("
|
|
48
|
-
# Sashite::Epin.parse("
|
|
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>
|
|
49
59
|
def self.parse(epin_string)
|
|
50
60
|
Identifier.parse(epin_string)
|
|
51
61
|
end
|
|
@@ -56,14 +66,18 @@ module Sashite
|
|
|
56
66
|
# @param side [Symbol] player side (:first or :second)
|
|
57
67
|
# @param state [Symbol] piece state (:normal, :enhanced, or :diminished)
|
|
58
68
|
# @param native [Boolean] style derivation (true for native, false for foreign)
|
|
69
|
+
# @param terminal [Boolean] whether the piece is a terminal piece
|
|
59
70
|
# @return [Epin::Identifier] new identifier instance
|
|
60
71
|
# @raise [ArgumentError] if parameters are invalid
|
|
72
|
+
#
|
|
61
73
|
# @example
|
|
62
|
-
# Sashite::Epin.identifier(:K, :first, :normal, true)
|
|
63
|
-
# Sashite::Epin.identifier(:
|
|
64
|
-
# Sashite::Epin.identifier(:
|
|
65
|
-
|
|
66
|
-
|
|
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)
|
|
67
81
|
end
|
|
68
82
|
end
|
|
69
83
|
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.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cyril Kato
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 3.
|
|
18
|
+
version: 3.2.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.
|
|
25
|
+
version: 3.2.0
|
|
26
26
|
description: |
|
|
27
27
|
EPIN (Extended Piece Identifier Notation) extends PIN to provide style-aware piece representation
|
|
28
28
|
in abstract strategy board games. This gem implements the EPIN Specification v1.0.0 with
|
|
@@ -65,7 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
65
65
|
- !ruby/object:Gem::Version
|
|
66
66
|
version: '0'
|
|
67
67
|
requirements: []
|
|
68
|
-
rubygems_version: 3.
|
|
68
|
+
rubygems_version: 3.7.2
|
|
69
69
|
specification_version: 4
|
|
70
70
|
summary: EPIN (Extended Piece Identifier Notation) implementation for Ruby extending
|
|
71
71
|
PIN with style derivation markers.
|