sashite-qpi 1.0.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 +502 -169
- data/lib/sashite/qpi/identifier.rb +115 -298
- data/lib/sashite/qpi.rb +73 -158
- metadata +6 -6
|
@@ -7,35 +7,60 @@ module Sashite
|
|
|
7
7
|
module Qpi
|
|
8
8
|
# Represents an identifier in QPI (Qualified Piece Identifier) format.
|
|
9
9
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# - Type: Piece type (:A to :Z) from PIN component
|
|
13
|
-
# - Side: Player assignment (:first or :second) from both components
|
|
14
|
-
# - State: Piece state (:normal, :enhanced, :diminished) from PIN component
|
|
15
|
-
# - Semantic constraint: SIN and PIN components must represent the same player
|
|
10
|
+
# QPI is pure composition of SIN and PIN primitives with one constraint:
|
|
11
|
+
# both components must represent the same player (side).
|
|
16
12
|
#
|
|
17
|
-
#
|
|
18
|
-
# This follows the QPI Specification v1.0.0 with strict parameter validation
|
|
19
|
-
# consistent with the underlying SIN and PIN primitive specifications.
|
|
13
|
+
# ## Minimal API Design
|
|
20
14
|
#
|
|
21
|
-
#
|
|
15
|
+
# The Identifier class provides only 5 core methods:
|
|
16
|
+
# 1. new(sin, pin) — create from components with validation
|
|
17
|
+
# 2. sin — access SIN component
|
|
18
|
+
# 3. pin — access PIN component
|
|
19
|
+
# 4. to_s — serialize to QPI string
|
|
20
|
+
# 5. flip — flip both components (only convenience method)
|
|
22
21
|
#
|
|
23
|
-
#
|
|
24
|
-
# -
|
|
25
|
-
# -
|
|
26
|
-
# - Side parameter determines the display case, not the input parameters
|
|
22
|
+
# Additionally, component replacement methods:
|
|
23
|
+
# - with_sin(new_sin) — create identifier with different SIN
|
|
24
|
+
# - with_pin(new_pin) — create identifier with different PIN
|
|
27
25
|
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
26
|
+
# All other operations use the component APIs directly:
|
|
27
|
+
# - qpi.sin.family — access Piece Style
|
|
28
|
+
# - qpi.sin.side — access Piece Side
|
|
29
|
+
# - qpi.pin.type — access Piece Name
|
|
30
|
+
# - qpi.pin.state — access Piece State
|
|
31
|
+
# - qpi.pin.terminal? — access Terminal Status
|
|
30
32
|
#
|
|
31
|
-
#
|
|
32
|
-
# # Valid - uppercase symbols only
|
|
33
|
-
# Sashite::Qpi::Identifier.new(:C, :K, :first, :normal) # => "C:K"
|
|
34
|
-
# Sashite::Qpi::Identifier.new(:C, :K, :second, :normal) # => "c:k"
|
|
33
|
+
# ## Why Only flip as Convenience?
|
|
35
34
|
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
35
|
+
# flip is the ONLY transformation that naturally operates on both
|
|
36
|
+
# SIN and PIN components simultaneously. All other transformations
|
|
37
|
+
# work through component replacement:
|
|
38
|
+
#
|
|
39
|
+
# qpi.with_sin(qpi.sin.with_family(:S)) # Transform SIN
|
|
40
|
+
# qpi.with_pin(qpi.pin.with_type(:Q)) # Transform PIN
|
|
41
|
+
# qpi.with_pin(qpi.pin.with_terminal(true)) # Transform PIN
|
|
42
|
+
#
|
|
43
|
+
# This avoids arbitrary conveniences and maintains a clear principle.
|
|
44
|
+
#
|
|
45
|
+
# @example Pure composition
|
|
46
|
+
# sin = Sashite::Sin.parse("C")
|
|
47
|
+
# pin = Sashite::Pin.parse("K^")
|
|
48
|
+
# qpi = Sashite::Qpi::Identifier.new(sin, pin)
|
|
49
|
+
# qpi.to_s # => "C:K^"
|
|
50
|
+
# qpi.sin # => SIN::Identifier
|
|
51
|
+
# qpi.pin # => PIN::Identifier
|
|
52
|
+
#
|
|
53
|
+
# @example Access attributes via components
|
|
54
|
+
# qpi.sin.family # => :C (Piece Style)
|
|
55
|
+
# qpi.pin.type # => :K (Piece Name)
|
|
56
|
+
# qpi.sin.side # => :first (Piece Side)
|
|
57
|
+
# qpi.pin.state # => :normal (Piece State)
|
|
58
|
+
# qpi.pin.terminal? # => true (Terminal Status)
|
|
59
|
+
#
|
|
60
|
+
# @example Transform via components
|
|
61
|
+
# qpi.with_sin(qpi.sin.with_family(:S)) # => "S:K^"
|
|
62
|
+
# qpi.with_pin(qpi.pin.with_type(:Q)) # => "C:Q^"
|
|
63
|
+
# qpi.flip # => "c:k^"
|
|
39
64
|
#
|
|
40
65
|
# @see https://sashite.dev/specs/qpi/1.0.0/ QPI Specification v1.0.0
|
|
41
66
|
class Identifier
|
|
@@ -44,60 +69,30 @@ module Sashite
|
|
|
44
69
|
|
|
45
70
|
# Error messages
|
|
46
71
|
ERROR_INVALID_QPI = "Invalid QPI string: %s"
|
|
47
|
-
ERROR_SEMANTIC_MISMATCH = "
|
|
72
|
+
ERROR_SEMANTIC_MISMATCH = "SIN and PIN components must have same side: sin.side=%s, pin.side=%s"
|
|
48
73
|
ERROR_MISSING_SEPARATOR = "QPI string must contain exactly one colon separator: %s"
|
|
49
74
|
|
|
50
|
-
# @return [
|
|
51
|
-
|
|
52
|
-
@sin_identifier.family
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# @return [Symbol] the piece type (:A to :Z)
|
|
56
|
-
def type
|
|
57
|
-
@pin_identifier.type
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# @return [Symbol] the player side (:first or :second)
|
|
61
|
-
def side
|
|
62
|
-
@pin_identifier.side
|
|
63
|
-
end
|
|
75
|
+
# @return [Sin::Identifier] the SIN component
|
|
76
|
+
attr_reader :sin
|
|
64
77
|
|
|
65
|
-
# @return [
|
|
66
|
-
|
|
67
|
-
@pin_identifier.state
|
|
68
|
-
end
|
|
78
|
+
# @return [Pin::Identifier] the PIN component
|
|
79
|
+
attr_reader :pin
|
|
69
80
|
|
|
70
|
-
# Create a new identifier
|
|
71
|
-
#
|
|
72
|
-
# @param family [Symbol] style family identifier (:A to :Z only)
|
|
73
|
-
# @param type [Symbol] piece type (:A to :Z only)
|
|
74
|
-
# @param side [Symbol] player side (:first or :second)
|
|
75
|
-
# @param state [Symbol] piece state (:normal, :enhanced, or :diminished)
|
|
76
|
-
# @raise [ArgumentError] if parameters are invalid or semantically inconsistent
|
|
81
|
+
# Create a new identifier from SIN and PIN components
|
|
77
82
|
#
|
|
78
|
-
# @
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
# chess_pawn = Sashite::Qpi::Identifier.new(:C, :P, :second, :normal) # => "c:p"
|
|
83
|
+
# @param sin [Sin::Identifier] SIN component
|
|
84
|
+
# @param pin [Pin::Identifier] PIN component
|
|
85
|
+
# @raise [ArgumentError] if components have different sides
|
|
82
86
|
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Pin::Identifier.validate_type(type)
|
|
90
|
-
Pin::Identifier.validate_side(side)
|
|
91
|
-
Pin::Identifier.validate_state(state)
|
|
87
|
+
# @example
|
|
88
|
+
# sin = Sashite::Sin.parse("C")
|
|
89
|
+
# pin = Sashite::Pin.parse("K^")
|
|
90
|
+
# qpi = Sashite::Qpi::Identifier.new(sin, pin)
|
|
91
|
+
def initialize(sin, pin)
|
|
92
|
+
validate_semantic_consistency(sin, pin)
|
|
92
93
|
|
|
93
|
-
|
|
94
|
-
@
|
|
95
|
-
|
|
96
|
-
# Create SIN component - pass family directly without normalization
|
|
97
|
-
@sin_identifier = Sin::Identifier.new(family, side)
|
|
98
|
-
|
|
99
|
-
# Validate semantic consistency
|
|
100
|
-
validate_semantic_consistency
|
|
94
|
+
@sin = sin
|
|
95
|
+
@pin = pin
|
|
101
96
|
|
|
102
97
|
freeze
|
|
103
98
|
end
|
|
@@ -106,28 +101,20 @@ module Sashite
|
|
|
106
101
|
#
|
|
107
102
|
# @param qpi_string [String] QPI notation string (format: sin:pin)
|
|
108
103
|
# @return [Identifier] new identifier instance
|
|
109
|
-
# @raise [ArgumentError] if
|
|
104
|
+
# @raise [ArgumentError] if invalid or semantically inconsistent
|
|
110
105
|
#
|
|
111
|
-
# @example
|
|
112
|
-
# Sashite::Qpi::Identifier.parse("C:K")
|
|
113
|
-
#
|
|
114
|
-
#
|
|
106
|
+
# @example
|
|
107
|
+
# qpi = Sashite::Qpi::Identifier.parse("C:K^")
|
|
108
|
+
# qpi.sin.family # => :C
|
|
109
|
+
# qpi.pin.type # => :K
|
|
115
110
|
def self.parse(qpi_string)
|
|
116
111
|
string_value = String(qpi_string)
|
|
117
112
|
sin_part, pin_part = split_components(string_value)
|
|
118
113
|
|
|
119
|
-
# Parse components
|
|
120
114
|
sin_identifier = Sin::Identifier.parse(sin_part)
|
|
121
115
|
pin_identifier = Pin::Identifier.parse(pin_part)
|
|
122
116
|
|
|
123
|
-
|
|
124
|
-
unless sin_identifier.side == pin_identifier.side
|
|
125
|
-
raise ::ArgumentError, format(ERROR_SEMANTIC_MISMATCH,
|
|
126
|
-
sin_part, sin_identifier.side, pin_identifier.side)
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
# Extract parameters and create new instance
|
|
130
|
-
new(sin_identifier.family, pin_identifier.type, pin_identifier.side, pin_identifier.state)
|
|
117
|
+
new(sin_identifier, pin_identifier)
|
|
131
118
|
end
|
|
132
119
|
|
|
133
120
|
# Check if a string is a valid QPI notation
|
|
@@ -135,19 +122,15 @@ module Sashite
|
|
|
135
122
|
# @param qpi_string [String] the string to validate
|
|
136
123
|
# @return [Boolean] true if valid QPI, false otherwise
|
|
137
124
|
#
|
|
138
|
-
# @example
|
|
139
|
-
# Sashite::Qpi::Identifier.valid?("C:K")
|
|
140
|
-
# Sashite::Qpi::Identifier.valid?("
|
|
141
|
-
# Sashite::Qpi::Identifier.valid?("C:k") # => false (semantic mismatch)
|
|
142
|
-
# Sashite::Qpi::Identifier.valid?("Chess") # => false (no separator)
|
|
125
|
+
# @example
|
|
126
|
+
# Sashite::Qpi::Identifier.valid?("C:K^") # => true
|
|
127
|
+
# Sashite::Qpi::Identifier.valid?("C:k") # => false (side mismatch)
|
|
143
128
|
def self.valid?(qpi_string)
|
|
144
129
|
return false unless qpi_string.is_a?(::String)
|
|
145
130
|
|
|
146
|
-
# Split components and validate each part
|
|
147
131
|
sin_part, pin_part = split_components(qpi_string)
|
|
148
132
|
return false unless Sashite::Sin.valid?(sin_part) && Sashite::Pin.valid?(pin_part)
|
|
149
133
|
|
|
150
|
-
# Semantic consistency check
|
|
151
134
|
sin_identifier = Sashite::Sin.parse(sin_part)
|
|
152
135
|
pin_identifier = Sashite::Pin.parse(pin_part)
|
|
153
136
|
sin_identifier.side == pin_identifier.side
|
|
@@ -158,221 +141,63 @@ module Sashite
|
|
|
158
141
|
# Convert the identifier to its QPI string representation
|
|
159
142
|
#
|
|
160
143
|
# @return [String] QPI notation string (format: sin:pin)
|
|
161
|
-
# @example Display QPI identifiers
|
|
162
|
-
# identifier.to_s # => "C:K"
|
|
163
|
-
def to_s
|
|
164
|
-
"#{@sin_identifier}#{SEPARATOR}#{@pin_identifier}"
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# Convert to SIN string representation (style component only)
|
|
168
|
-
#
|
|
169
|
-
# @return [String] SIN notation string
|
|
170
|
-
# @example Extract style component
|
|
171
|
-
# identifier.to_sin # => "C"
|
|
172
|
-
def to_sin
|
|
173
|
-
@sin_identifier.to_s
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
# Convert to PIN string representation (piece component only)
|
|
177
|
-
#
|
|
178
|
-
# @return [String] PIN notation string
|
|
179
|
-
# @example Extract piece component
|
|
180
|
-
# identifier.to_pin # => "+K"
|
|
181
|
-
def to_pin
|
|
182
|
-
@pin_identifier.to_s
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
# Get the parsed SIN identifier object
|
|
186
144
|
#
|
|
187
|
-
# @
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
# Get the parsed PIN identifier object
|
|
193
|
-
#
|
|
194
|
-
# @return [Sashite::Pin::Identifier] PIN component as identifier object
|
|
195
|
-
def pin_component
|
|
196
|
-
@pin_identifier
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
# Create a new identifier with enhanced state
|
|
200
|
-
#
|
|
201
|
-
# @return [Identifier] new identifier with enhanced PIN component
|
|
202
|
-
def enhance
|
|
203
|
-
return self if enhanced?
|
|
204
|
-
|
|
205
|
-
self.class.new(family, type, side, Pin::Identifier::ENHANCED_STATE)
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
# Create a new identifier with diminished state
|
|
209
|
-
#
|
|
210
|
-
# @return [Identifier] new identifier with diminished PIN component
|
|
211
|
-
def diminish
|
|
212
|
-
return self if diminished?
|
|
213
|
-
|
|
214
|
-
self.class.new(family, type, side, Pin::Identifier::DIMINISHED_STATE)
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
# Create a new identifier with normal state (no modifiers)
|
|
218
|
-
#
|
|
219
|
-
# @return [Identifier] new identifier with normalized PIN component
|
|
220
|
-
def normalize
|
|
221
|
-
return self if normal?
|
|
222
|
-
|
|
223
|
-
self.class.new(family, type, side, Pin::Identifier::NORMAL_STATE)
|
|
145
|
+
# @example
|
|
146
|
+
# qpi.to_s # => "C:K^"
|
|
147
|
+
def to_s
|
|
148
|
+
"#{@sin}#{SEPARATOR}#{@pin}"
|
|
224
149
|
end
|
|
225
150
|
|
|
226
|
-
# Create a new identifier with different
|
|
151
|
+
# Create a new identifier with different SIN component
|
|
227
152
|
#
|
|
228
|
-
# @param
|
|
229
|
-
# @return [Identifier] new identifier
|
|
230
|
-
|
|
231
|
-
return self if type == new_type
|
|
232
|
-
|
|
233
|
-
self.class.new(family, new_type, side, state)
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
# Create a new identifier with different side
|
|
153
|
+
# @param new_sin [Sin::Identifier] new SIN component
|
|
154
|
+
# @return [Identifier] new identifier instance
|
|
155
|
+
# @raise [ArgumentError] if new SIN has different side than PIN
|
|
237
156
|
#
|
|
238
|
-
# @
|
|
239
|
-
#
|
|
240
|
-
def
|
|
241
|
-
return self if
|
|
157
|
+
# @example
|
|
158
|
+
# qpi.with_sin(qpi.sin.with_family(:S)) # => "S:K^"
|
|
159
|
+
def with_sin(new_sin)
|
|
160
|
+
return self if @sin == new_sin
|
|
242
161
|
|
|
243
|
-
self.class.new(
|
|
162
|
+
self.class.new(new_sin, @pin)
|
|
244
163
|
end
|
|
245
164
|
|
|
246
|
-
# Create a new identifier with different
|
|
165
|
+
# Create a new identifier with different PIN component
|
|
247
166
|
#
|
|
248
|
-
# @param
|
|
249
|
-
# @return [Identifier] new identifier
|
|
250
|
-
|
|
251
|
-
return self if state == new_state
|
|
252
|
-
|
|
253
|
-
self.class.new(family, type, side, new_state)
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
# Create a new identifier with different family
|
|
167
|
+
# @param new_pin [Pin::Identifier] new PIN component
|
|
168
|
+
# @return [Identifier] new identifier instance
|
|
169
|
+
# @raise [ArgumentError] if new PIN has different side than SIN
|
|
257
170
|
#
|
|
258
|
-
# @
|
|
259
|
-
#
|
|
260
|
-
def
|
|
261
|
-
return self if
|
|
171
|
+
# @example
|
|
172
|
+
# qpi.with_pin(qpi.pin.with_type(:Q)) # => "C:Q^"
|
|
173
|
+
def with_pin(new_pin)
|
|
174
|
+
return self if @pin == new_pin
|
|
262
175
|
|
|
263
|
-
self.class.new(
|
|
176
|
+
self.class.new(@sin, new_pin)
|
|
264
177
|
end
|
|
265
178
|
|
|
266
|
-
# Create a new identifier with
|
|
267
|
-
#
|
|
268
|
-
# Changes the player assignment (side) while preserving the family and piece attributes.
|
|
269
|
-
# This maintains semantic consistency between the components.
|
|
179
|
+
# Create a new identifier with both components flipped
|
|
270
180
|
#
|
|
271
|
-
#
|
|
181
|
+
# This is the ONLY convenience method because it's the only
|
|
182
|
+
# transformation that naturally operates on both components.
|
|
272
183
|
#
|
|
273
|
-
# @
|
|
274
|
-
# chess_first = Sashite::Qpi::Identifier.parse("C:K") # Chess king, first player
|
|
275
|
-
# chess_second = chess_first.flip # => "c:k" (Chess king, second player)
|
|
184
|
+
# @return [Identifier] new identifier with both components flipped
|
|
276
185
|
#
|
|
277
|
-
#
|
|
278
|
-
#
|
|
186
|
+
# @example
|
|
187
|
+
# qpi = Sashite::Qpi.parse("C:K^")
|
|
188
|
+
# qpi.flip # => "c:k^"
|
|
279
189
|
def flip
|
|
280
|
-
self.class.new(
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
# Check if the identifier has normal state
|
|
284
|
-
#
|
|
285
|
-
# @return [Boolean] true if normal state
|
|
286
|
-
def normal?
|
|
287
|
-
@pin_identifier.normal?
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
# Check if the identifier has enhanced state
|
|
291
|
-
#
|
|
292
|
-
# @return [Boolean] true if enhanced state
|
|
293
|
-
def enhanced?
|
|
294
|
-
@pin_identifier.enhanced?
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
# Check if the identifier has diminished state
|
|
298
|
-
#
|
|
299
|
-
# @return [Boolean] true if diminished state
|
|
300
|
-
def diminished?
|
|
301
|
-
@pin_identifier.diminished?
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
# Check if the identifier belongs to the first player
|
|
305
|
-
#
|
|
306
|
-
# @return [Boolean] true if first player
|
|
307
|
-
def first_player?
|
|
308
|
-
@pin_identifier.first_player?
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
# Check if the identifier belongs to the second player
|
|
312
|
-
#
|
|
313
|
-
# @return [Boolean] true if second player
|
|
314
|
-
def second_player?
|
|
315
|
-
@pin_identifier.second_player?
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
# Check if this identifier has the same family as another
|
|
319
|
-
#
|
|
320
|
-
# @param other [Identifier] identifier to compare with
|
|
321
|
-
# @return [Boolean] true if same family (case-insensitive)
|
|
322
|
-
def same_family?(other)
|
|
323
|
-
return false unless other.is_a?(self.class)
|
|
324
|
-
|
|
325
|
-
@sin_identifier.same_family?(other.sin_component)
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
# Check if this identifier has different family from another
|
|
329
|
-
#
|
|
330
|
-
# @param other [Identifier] identifier to compare with
|
|
331
|
-
# @return [Boolean] true if different families
|
|
332
|
-
def cross_family?(other)
|
|
333
|
-
return false unless other.is_a?(self.class)
|
|
334
|
-
|
|
335
|
-
!same_family?(other)
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
# Check if this identifier has the same side as another
|
|
339
|
-
#
|
|
340
|
-
# @param other [Identifier] identifier to compare with
|
|
341
|
-
# @return [Boolean] true if same side
|
|
342
|
-
def same_side?(other)
|
|
343
|
-
return false unless other.is_a?(self.class)
|
|
344
|
-
|
|
345
|
-
@pin_identifier.same_side?(other.pin_component)
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
# Check if this identifier has the same type as another
|
|
349
|
-
#
|
|
350
|
-
# @param other [Identifier] identifier to compare with
|
|
351
|
-
# @return [Boolean] true if same type
|
|
352
|
-
def same_type?(other)
|
|
353
|
-
return false unless other.is_a?(self.class)
|
|
354
|
-
|
|
355
|
-
@pin_identifier.same_type?(other.pin_component)
|
|
356
|
-
end
|
|
357
|
-
|
|
358
|
-
# Check if this identifier has the same state as another
|
|
359
|
-
#
|
|
360
|
-
# @param other [Identifier] identifier to compare with
|
|
361
|
-
# @return [Boolean] true if same state
|
|
362
|
-
def same_state?(other)
|
|
363
|
-
return false unless other.is_a?(self.class)
|
|
364
|
-
|
|
365
|
-
@pin_identifier.same_state?(other.pin_component)
|
|
190
|
+
self.class.new(@sin.flip, @pin.flip)
|
|
366
191
|
end
|
|
367
192
|
|
|
368
193
|
# Custom equality comparison
|
|
369
194
|
#
|
|
370
195
|
# @param other [Object] object to compare with
|
|
371
|
-
# @return [Boolean] true if
|
|
196
|
+
# @return [Boolean] true if both SIN and PIN components are equal
|
|
372
197
|
def ==(other)
|
|
373
198
|
return false unless other.is_a?(self.class)
|
|
374
199
|
|
|
375
|
-
@
|
|
200
|
+
@sin == other.sin && @pin == other.pin
|
|
376
201
|
end
|
|
377
202
|
|
|
378
203
|
# Alias for == to ensure Set functionality works correctly
|
|
@@ -382,7 +207,7 @@ module Sashite
|
|
|
382
207
|
#
|
|
383
208
|
# @return [Integer] hash value
|
|
384
209
|
def hash
|
|
385
|
-
[self.class, @
|
|
210
|
+
[self.class, @sin, @pin].hash
|
|
386
211
|
end
|
|
387
212
|
|
|
388
213
|
private
|
|
@@ -391,6 +216,7 @@ module Sashite
|
|
|
391
216
|
#
|
|
392
217
|
# @param qpi_string [String] QPI string to split
|
|
393
218
|
# @return [Array<String>] array containing [sin_part, pin_part]
|
|
219
|
+
# @raise [ArgumentError] if string doesn't contain exactly one separator
|
|
394
220
|
def self.split_components(qpi_string)
|
|
395
221
|
parts = qpi_string.split(SEPARATOR, 2)
|
|
396
222
|
raise ::ArgumentError, format(ERROR_MISSING_SEPARATOR, qpi_string) unless parts.size == 2
|
|
@@ -400,24 +226,15 @@ module Sashite
|
|
|
400
226
|
|
|
401
227
|
private_class_method :split_components
|
|
402
228
|
|
|
403
|
-
# Validate
|
|
229
|
+
# Validate that SIN and PIN components have consistent sides
|
|
404
230
|
#
|
|
405
|
-
# @
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
return if expected_side == actual_side
|
|
231
|
+
# @param sin [Sin::Identifier] SIN component to validate
|
|
232
|
+
# @param pin [Pin::Identifier] PIN component to validate
|
|
233
|
+
# @raise [ArgumentError] if sides don't match
|
|
234
|
+
def validate_semantic_consistency(sin, pin)
|
|
235
|
+
return if sin.side == pin.side
|
|
411
236
|
|
|
412
|
-
raise ::ArgumentError, format(ERROR_SEMANTIC_MISMATCH,
|
|
413
|
-
@sin_identifier.letter, expected_side, actual_side)
|
|
414
|
-
end
|
|
415
|
-
|
|
416
|
-
# Get the opposite player side
|
|
417
|
-
#
|
|
418
|
-
# @return [Symbol] the opposite side
|
|
419
|
-
def opposite_side
|
|
420
|
-
first_player? ? Pin::Identifier::SECOND_PLAYER : Pin::Identifier::FIRST_PLAYER
|
|
237
|
+
raise ::ArgumentError, format(ERROR_SEMANTIC_MISMATCH, sin.side, pin.side)
|
|
421
238
|
end
|
|
422
239
|
end
|
|
423
240
|
end
|