sashite-qpi 1.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +201 -0
- data/README.md +260 -210
- data/lib/sashite/qpi/constants.rb +13 -0
- data/lib/sashite/qpi/errors/argument/messages.rb +23 -0
- data/lib/sashite/qpi/errors/argument.rb +16 -0
- data/lib/sashite/qpi/errors.rb +3 -0
- data/lib/sashite/qpi/identifier.rb +153 -333
- data/lib/sashite/qpi/parser.rb +122 -0
- data/lib/sashite/qpi.rb +60 -188
- data/lib/sashite-qpi.rb +0 -11
- metadata +18 -17
- data/LICENSE.md +0 -22
|
@@ -1,423 +1,243 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
require_relative "constants"
|
|
4
|
+
require_relative "errors"
|
|
5
5
|
|
|
6
6
|
module Sashite
|
|
7
7
|
module Qpi
|
|
8
|
-
# Represents
|
|
8
|
+
# Represents a parsed QPI (Qualified Piece Identifier) identifier.
|
|
9
9
|
#
|
|
10
|
-
# A QPI identifier
|
|
11
|
-
# -
|
|
12
|
-
# -
|
|
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
|
+
# A QPI identifier encodes complete Piece Identity by combining:
|
|
11
|
+
# - A SIN (Style Identifier Notation) component for Piece Style
|
|
12
|
+
# - A PIN (Piece Identifier Notation) component for Piece Name, Side, State, and Terminal Status
|
|
16
13
|
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# consistent with the underlying SIN and PIN primitive specifications.
|
|
14
|
+
# Additionally, QPI defines a Native/Derived relationship based on case comparison
|
|
15
|
+
# between the SIN and PIN letters.
|
|
20
16
|
#
|
|
21
|
-
#
|
|
17
|
+
# Instances are immutable (frozen after creation).
|
|
22
18
|
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
19
|
+
# @example Creating identifiers
|
|
20
|
+
# sin = Sashite::Sin.parse("C")
|
|
21
|
+
# pin = Sashite::Pin.parse("K^")
|
|
22
|
+
# qpi = Identifier.new(sin, pin)
|
|
27
23
|
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
24
|
+
# @example Accessing components
|
|
25
|
+
# qpi.sin.style # => :C
|
|
26
|
+
# qpi.pin.type # => :K
|
|
27
|
+
# qpi.pin.side # => :first
|
|
28
|
+
# qpi.pin.state # => :normal
|
|
29
|
+
# qpi.pin.terminal? # => true
|
|
30
30
|
#
|
|
31
|
-
# @example
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
# Sashite::Qpi::Identifier.new(:C, :K, :second, :normal) # => "c:k"
|
|
31
|
+
# @example Native/Derived relationship
|
|
32
|
+
# qpi = Identifier.new(Sin.parse("C"), Pin.parse("K"))
|
|
33
|
+
# qpi.native? # => true (both uppercase/first)
|
|
35
34
|
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
# Sashite::Qpi::Identifier.new(:C, :k, :second, :normal) # => ArgumentError
|
|
35
|
+
# qpi = Identifier.new(Sin.parse("C"), Pin.parse("k"))
|
|
36
|
+
# qpi.derived? # => true (SIN uppercase, PIN lowercase)
|
|
39
37
|
#
|
|
40
|
-
# @see https://sashite.dev/specs/qpi/1.0.0/
|
|
38
|
+
# @see https://sashite.dev/specs/qpi/1.0.0/
|
|
41
39
|
class Identifier
|
|
42
|
-
#
|
|
43
|
-
|
|
40
|
+
# @return [Sashite::Sin::Identifier] The SIN component
|
|
41
|
+
attr_reader :sin
|
|
44
42
|
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
ERROR_SEMANTIC_MISMATCH = "Family and side must represent the same player: family=%s (side=%s), side=%s"
|
|
48
|
-
ERROR_MISSING_SEPARATOR = "QPI string must contain exactly one colon separator: %s"
|
|
43
|
+
# @return [Sashite::Pin::Identifier] The PIN component
|
|
44
|
+
attr_reader :pin
|
|
49
45
|
|
|
50
|
-
#
|
|
51
|
-
def family
|
|
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
|
|
64
|
-
|
|
65
|
-
# @return [Symbol] the piece state (:normal, :enhanced, or :diminished)
|
|
66
|
-
def state
|
|
67
|
-
@pin_identifier.state
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Create a new identifier instance
|
|
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
|
|
46
|
+
# Creates a new Identifier instance.
|
|
77
47
|
#
|
|
78
|
-
# @
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
48
|
+
# @param sin [Sashite::Sin::Identifier] The SIN component
|
|
49
|
+
# @param pin [Sashite::Pin::Identifier] The PIN component
|
|
50
|
+
# @return [Identifier] A new frozen Identifier instance
|
|
51
|
+
# @raise [Errors::Argument] If components are invalid
|
|
82
52
|
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
Pin::Identifier.validate_side(side)
|
|
91
|
-
Pin::Identifier.validate_state(state)
|
|
53
|
+
# @example
|
|
54
|
+
# sin = Sashite::Sin.parse("C")
|
|
55
|
+
# pin = Sashite::Pin.parse("K^")
|
|
56
|
+
# Identifier.new(sin, pin)
|
|
57
|
+
def initialize(sin, pin)
|
|
58
|
+
validate_sin!(sin)
|
|
59
|
+
validate_pin!(pin)
|
|
92
60
|
|
|
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
|
|
61
|
+
@sin = sin
|
|
62
|
+
@pin = pin
|
|
101
63
|
|
|
102
64
|
freeze
|
|
103
65
|
end
|
|
104
66
|
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
# @return [Identifier] new identifier instance
|
|
109
|
-
# @raise [ArgumentError] if the QPI string is invalid
|
|
110
|
-
#
|
|
111
|
-
# @example Parse QPI strings with automatic component separation
|
|
112
|
-
# Sashite::Qpi::Identifier.parse("C:K") # => #<Qpi::Identifier family=:C type=:K side=:first state=:normal>
|
|
113
|
-
# Sashite::Qpi::Identifier.parse("s:+r") # => #<Qpi::Identifier family=:S type=:R side=:second state=:enhanced>
|
|
114
|
-
# Sashite::Qpi::Identifier.parse("X:-S") # => #<Qpi::Identifier family=:X type=:S side=:first state=:diminished>
|
|
115
|
-
def self.parse(qpi_string)
|
|
116
|
-
string_value = String(qpi_string)
|
|
117
|
-
sin_part, pin_part = split_components(string_value)
|
|
118
|
-
|
|
119
|
-
# Parse components
|
|
120
|
-
sin_identifier = Sin::Identifier.parse(sin_part)
|
|
121
|
-
pin_identifier = Pin::Identifier.parse(pin_part)
|
|
122
|
-
|
|
123
|
-
# Validate semantic consistency BEFORE creating new instance
|
|
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)
|
|
131
|
-
end
|
|
67
|
+
# ========================================================================
|
|
68
|
+
# String Conversion
|
|
69
|
+
# ========================================================================
|
|
132
70
|
|
|
133
|
-
#
|
|
71
|
+
# Returns the QPI string representation.
|
|
134
72
|
#
|
|
135
|
-
# @
|
|
136
|
-
# @return [Boolean] true if valid QPI, false otherwise
|
|
73
|
+
# @return [String] The QPI string in format "SIN:PIN"
|
|
137
74
|
#
|
|
138
|
-
# @example
|
|
139
|
-
#
|
|
140
|
-
# Sashite::Qpi::Identifier.valid?("s:+r") # => true
|
|
141
|
-
# Sashite::Qpi::Identifier.valid?("C:k") # => false (semantic mismatch)
|
|
142
|
-
# Sashite::Qpi::Identifier.valid?("Chess") # => false (no separator)
|
|
143
|
-
def self.valid?(qpi_string)
|
|
144
|
-
return false unless qpi_string.is_a?(::String)
|
|
145
|
-
|
|
146
|
-
# Split components and validate each part
|
|
147
|
-
sin_part, pin_part = split_components(qpi_string)
|
|
148
|
-
return false unless Sashite::Sin.valid?(sin_part) && Sashite::Pin.valid?(pin_part)
|
|
149
|
-
|
|
150
|
-
# Semantic consistency check
|
|
151
|
-
sin_identifier = Sashite::Sin.parse(sin_part)
|
|
152
|
-
pin_identifier = Sashite::Pin.parse(pin_part)
|
|
153
|
-
sin_identifier.side == pin_identifier.side
|
|
154
|
-
rescue ArgumentError
|
|
155
|
-
false
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# Convert the identifier to its QPI string representation
|
|
159
|
-
#
|
|
160
|
-
# @return [String] QPI notation string (format: sin:pin)
|
|
161
|
-
# @example Display QPI identifiers
|
|
162
|
-
# identifier.to_s # => "C:K"
|
|
75
|
+
# @example
|
|
76
|
+
# qpi.to_s # => "C:K^"
|
|
163
77
|
def to_s
|
|
164
|
-
"#{
|
|
78
|
+
"#{sin}#{Constants::SEPARATOR}#{pin}"
|
|
165
79
|
end
|
|
166
80
|
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
# @example Extract style component
|
|
171
|
-
# identifier.to_sin # => "C"
|
|
172
|
-
def to_sin
|
|
173
|
-
@sin_identifier.to_s
|
|
174
|
-
end
|
|
81
|
+
# ========================================================================
|
|
82
|
+
# Native/Derived Relationship
|
|
83
|
+
# ========================================================================
|
|
175
84
|
|
|
176
|
-
#
|
|
85
|
+
# Checks if the identifier is native.
|
|
177
86
|
#
|
|
178
|
-
#
|
|
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
|
|
87
|
+
# A QPI is native when sin.side equals pin.side (same case).
|
|
186
88
|
#
|
|
187
|
-
# @return [
|
|
188
|
-
def sin_component
|
|
189
|
-
@sin_identifier
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
# Get the parsed PIN identifier object
|
|
89
|
+
# @return [Boolean] true if native
|
|
193
90
|
#
|
|
194
|
-
# @
|
|
195
|
-
|
|
196
|
-
|
|
91
|
+
# @example
|
|
92
|
+
# Identifier.new(Sin.parse("C"), Pin.parse("K")).native? # => true
|
|
93
|
+
# Identifier.new(Sin.parse("c"), Pin.parse("k")).native? # => true
|
|
94
|
+
# Identifier.new(Sin.parse("C"), Pin.parse("k")).native? # => false
|
|
95
|
+
def native?
|
|
96
|
+
sin.side.equal?(pin.side)
|
|
197
97
|
end
|
|
198
98
|
|
|
199
|
-
#
|
|
99
|
+
# Checks if the identifier is derived.
|
|
200
100
|
#
|
|
201
|
-
#
|
|
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
|
|
101
|
+
# A QPI is derived when sin.side differs from pin.side (different case).
|
|
209
102
|
#
|
|
210
|
-
# @return [
|
|
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)
|
|
103
|
+
# @return [Boolean] true if derived
|
|
218
104
|
#
|
|
219
|
-
# @
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
105
|
+
# @example
|
|
106
|
+
# Identifier.new(Sin.parse("C"), Pin.parse("k")).derived? # => true
|
|
107
|
+
# Identifier.new(Sin.parse("c"), Pin.parse("K")).derived? # => true
|
|
108
|
+
# Identifier.new(Sin.parse("C"), Pin.parse("K")).derived? # => false
|
|
109
|
+
def derived?
|
|
110
|
+
!native?
|
|
224
111
|
end
|
|
225
112
|
|
|
226
|
-
#
|
|
227
|
-
#
|
|
228
|
-
#
|
|
229
|
-
# @return [Identifier] new identifier with different type
|
|
230
|
-
def with_type(new_type)
|
|
231
|
-
return self if type == new_type
|
|
232
|
-
|
|
233
|
-
self.class.new(family, new_type, side, state)
|
|
234
|
-
end
|
|
113
|
+
# ========================================================================
|
|
114
|
+
# Native/Derived Transformations
|
|
115
|
+
# ========================================================================
|
|
235
116
|
|
|
236
|
-
#
|
|
117
|
+
# Returns a new Identifier with PIN case aligned to SIN case.
|
|
237
118
|
#
|
|
238
|
-
#
|
|
239
|
-
# @return [Identifier] new identifier with different side
|
|
240
|
-
def with_side(new_side)
|
|
241
|
-
return self if side == new_side
|
|
242
|
-
|
|
243
|
-
self.class.new(family, type, new_side, state)
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
# Create a new identifier with different state
|
|
119
|
+
# If already native, returns self.
|
|
247
120
|
#
|
|
248
|
-
# @
|
|
249
|
-
#
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
self.class.new(family, type, side, new_state)
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
# Create a new identifier with different family
|
|
121
|
+
# @return [Identifier] A native Identifier
|
|
122
|
+
#
|
|
123
|
+
# @example
|
|
124
|
+
# qpi = Identifier.new(Sin.parse("C"), Pin.parse("r"))
|
|
125
|
+
# qpi.native.to_s # => "C:R"
|
|
257
126
|
#
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
def
|
|
261
|
-
return self if
|
|
127
|
+
# qpi = Identifier.new(Sin.parse("C"), Pin.parse("R"))
|
|
128
|
+
# qpi.native.to_s # => "C:R" (unchanged)
|
|
129
|
+
def native
|
|
130
|
+
return self if native?
|
|
262
131
|
|
|
263
|
-
self.class.new(
|
|
132
|
+
self.class.new(sin, pin.with_side(sin.side))
|
|
264
133
|
end
|
|
265
134
|
|
|
266
|
-
#
|
|
135
|
+
# Returns a new Identifier with PIN case opposite to SIN case.
|
|
267
136
|
#
|
|
268
|
-
#
|
|
269
|
-
# This maintains semantic consistency between the components.
|
|
137
|
+
# If already derived, returns self.
|
|
270
138
|
#
|
|
271
|
-
# @return [Identifier]
|
|
139
|
+
# @return [Identifier] A derived Identifier
|
|
272
140
|
#
|
|
273
|
-
# @example
|
|
274
|
-
#
|
|
275
|
-
#
|
|
141
|
+
# @example
|
|
142
|
+
# qpi = Identifier.new(Sin.parse("C"), Pin.parse("R"))
|
|
143
|
+
# qpi.derive.to_s # => "C:r"
|
|
276
144
|
#
|
|
277
|
-
#
|
|
278
|
-
#
|
|
279
|
-
def
|
|
280
|
-
self
|
|
281
|
-
end
|
|
145
|
+
# qpi = Identifier.new(Sin.parse("C"), Pin.parse("r"))
|
|
146
|
+
# qpi.derive.to_s # => "C:r" (unchanged)
|
|
147
|
+
def derive
|
|
148
|
+
return self if derived?
|
|
282
149
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
# @return [Boolean] true if normal state
|
|
286
|
-
def normal?
|
|
287
|
-
@pin_identifier.normal?
|
|
150
|
+
opposite_side = sin.side.equal?(:first) ? :second : :first
|
|
151
|
+
self.class.new(sin, pin.with_side(opposite_side))
|
|
288
152
|
end
|
|
289
153
|
|
|
290
|
-
#
|
|
291
|
-
#
|
|
292
|
-
#
|
|
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
|
|
154
|
+
# ========================================================================
|
|
155
|
+
# Component Transformations
|
|
156
|
+
# ========================================================================
|
|
303
157
|
|
|
304
|
-
#
|
|
158
|
+
# Returns a new Identifier with a different SIN component.
|
|
305
159
|
#
|
|
306
|
-
# @
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
# Check if the identifier belongs to the second player
|
|
160
|
+
# @param new_sin [Sashite::Sin::Identifier] The new SIN component
|
|
161
|
+
# @return [Identifier] A new Identifier with the specified SIN
|
|
162
|
+
# @raise [Errors::Argument] If the SIN is invalid
|
|
312
163
|
#
|
|
313
|
-
# @
|
|
314
|
-
|
|
315
|
-
|
|
164
|
+
# @example
|
|
165
|
+
# qpi = Identifier.new(Sin.parse("C"), Pin.parse("K"))
|
|
166
|
+
# qpi.with_sin(Sin.parse("S")).to_s # => "S:K"
|
|
167
|
+
def with_sin(new_sin)
|
|
168
|
+
self.class.new(new_sin, pin)
|
|
316
169
|
end
|
|
317
170
|
|
|
318
|
-
#
|
|
171
|
+
# Returns a new Identifier with a different PIN component.
|
|
319
172
|
#
|
|
320
|
-
# @param
|
|
321
|
-
# @return [
|
|
322
|
-
|
|
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
|
|
173
|
+
# @param new_pin [Sashite::Pin::Identifier] The new PIN component
|
|
174
|
+
# @return [Identifier] A new Identifier with the specified PIN
|
|
175
|
+
# @raise [Errors::Argument] If the PIN is invalid
|
|
329
176
|
#
|
|
330
|
-
# @
|
|
331
|
-
#
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
!same_family?(other)
|
|
177
|
+
# @example
|
|
178
|
+
# qpi = Identifier.new(Sin.parse("C"), Pin.parse("K"))
|
|
179
|
+
# qpi.with_pin(Pin.parse("+Q^")).to_s # => "C:+Q^"
|
|
180
|
+
def with_pin(new_pin)
|
|
181
|
+
self.class.new(sin, new_pin)
|
|
336
182
|
end
|
|
337
183
|
|
|
338
|
-
#
|
|
184
|
+
# Returns a new Identifier with both components flipped.
|
|
339
185
|
#
|
|
340
|
-
# @
|
|
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
|
|
186
|
+
# @return [Identifier] A new Identifier with both SIN and PIN flipped
|
|
349
187
|
#
|
|
350
|
-
# @
|
|
351
|
-
#
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
@pin_identifier.same_type?(other.pin_component)
|
|
188
|
+
# @example
|
|
189
|
+
# qpi = Identifier.new(Sin.parse("C"), Pin.parse("K^"))
|
|
190
|
+
# qpi.flip.to_s # => "c:k^"
|
|
191
|
+
def flip
|
|
192
|
+
self.class.new(sin.flip, pin.flip)
|
|
356
193
|
end
|
|
357
194
|
|
|
358
|
-
#
|
|
359
|
-
#
|
|
360
|
-
#
|
|
361
|
-
# @return [Boolean] true if same state
|
|
362
|
-
def same_state?(other)
|
|
363
|
-
return false unless other.is_a?(self.class)
|
|
195
|
+
# ========================================================================
|
|
196
|
+
# Equality
|
|
197
|
+
# ========================================================================
|
|
364
198
|
|
|
365
|
-
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
# Custom equality comparison
|
|
199
|
+
# Checks equality with another Identifier.
|
|
369
200
|
#
|
|
370
|
-
# @param other [Object] object to compare
|
|
371
|
-
# @return [Boolean] true if
|
|
201
|
+
# @param other [Object] The object to compare
|
|
202
|
+
# @return [Boolean] true if equal
|
|
372
203
|
def ==(other)
|
|
373
|
-
return false unless
|
|
204
|
+
return false unless self.class === other
|
|
374
205
|
|
|
375
|
-
|
|
206
|
+
sin == other.sin && pin == other.pin
|
|
376
207
|
end
|
|
377
208
|
|
|
378
|
-
# Alias for == to ensure Set functionality works correctly
|
|
379
209
|
alias eql? ==
|
|
380
210
|
|
|
381
|
-
#
|
|
211
|
+
# Returns a hash code for the Identifier.
|
|
382
212
|
#
|
|
383
|
-
# @return [Integer]
|
|
213
|
+
# @return [Integer] Hash code
|
|
384
214
|
def hash
|
|
385
|
-
[
|
|
215
|
+
[sin, pin].hash
|
|
386
216
|
end
|
|
387
217
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
# Split QPI string into SIN and PIN components
|
|
218
|
+
# Returns an inspect string for the Identifier.
|
|
391
219
|
#
|
|
392
|
-
# @
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
parts = qpi_string.split(SEPARATOR, 2)
|
|
396
|
-
raise ::ArgumentError, format(ERROR_MISSING_SEPARATOR, qpi_string) unless parts.size == 2
|
|
397
|
-
|
|
398
|
-
parts
|
|
220
|
+
# @return [String] Inspect representation
|
|
221
|
+
def inspect
|
|
222
|
+
"#<#{self.class} #{self}>"
|
|
399
223
|
end
|
|
400
224
|
|
|
401
|
-
|
|
225
|
+
private
|
|
402
226
|
|
|
403
|
-
#
|
|
404
|
-
#
|
|
405
|
-
#
|
|
406
|
-
def validate_semantic_consistency
|
|
407
|
-
expected_side = @sin_identifier.side
|
|
408
|
-
actual_side = @pin_identifier.side
|
|
227
|
+
# ========================================================================
|
|
228
|
+
# Private Validation
|
|
229
|
+
# ========================================================================
|
|
409
230
|
|
|
410
|
-
|
|
231
|
+
def validate_sin!(sin)
|
|
232
|
+
return if sin.is_a?(Sashite::Sin::Identifier)
|
|
411
233
|
|
|
412
|
-
raise ::
|
|
413
|
-
@sin_identifier.letter, expected_side, actual_side)
|
|
234
|
+
raise Errors::Argument, Errors::Argument::Messages::INVALID_SIN
|
|
414
235
|
end
|
|
415
236
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
first_player? ? Pin::Identifier::SECOND_PLAYER : Pin::Identifier::FIRST_PLAYER
|
|
237
|
+
def validate_pin!(pin)
|
|
238
|
+
return if pin.is_a?(Sashite::Pin::Identifier)
|
|
239
|
+
|
|
240
|
+
raise Errors::Argument, Errors::Argument::Messages::INVALID_PIN
|
|
421
241
|
end
|
|
422
242
|
end
|
|
423
243
|
end
|