Class: Sashite::Qpi::Identifier
- Inherits:
-
Object
- Object
- Sashite::Qpi::Identifier
- Defined in:
- lib/sashite/qpi/identifier.rb
Overview
Represents an identifier in QPI (Qualified Piece Identifier) format.
A QPI identifier combines style and piece attributes into a unified representation:
-
Family: Style family from SIN component (:A to :Z only)
-
Type: Piece type (:A to :Z) from PIN component
-
Side: Player assignment (:first or :second) from both components
-
State: Piece state (:normal, :enhanced, :diminished) from PIN component
-
Semantic constraint: SIN and PIN components must represent the same player
All instances are immutable - transformation methods return new instances. This follows the QPI Specification v1.0.0 with strict parameter validation consistent with the underlying SIN and PIN primitive specifications.
## Strict Parameter Validation
QPI enforces the same strict validation as its underlying primitives:
-
Family parameter must be a symbol from :A to :Z (not :a to :z)
-
Type parameter must be a symbol from :A to :Z (delegated to PIN)
-
Side parameter determines the display case, not the input parameters
This ensures consistency with SIN and PIN behavior where lowercase symbols are rejected with ArgumentError.
Constant Summary collapse
- SEPARATOR =
Component separator for string representation
":"- ERROR_INVALID_QPI =
Error messages
"Invalid QPI string: %s"- ERROR_SEMANTIC_MISMATCH =
"Family and side must represent the same player: family=%s (side=%s), side=%s"- ERROR_MISSING_SEPARATOR =
"QPI string must contain exactly one colon separator: %s"
Class Method Summary collapse
-
.parse(qpi_string) ⇒ Identifier
Parse a QPI string into an Identifier object.
-
.valid?(qpi_string) ⇒ Boolean
Check if a string is a valid QPI notation.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
(also: #eql?)
Custom equality comparison.
-
#cross_family?(other) ⇒ Boolean
Check if this identifier has different family from another.
-
#diminish ⇒ Identifier
Create a new identifier with diminished state.
-
#diminished? ⇒ Boolean
Check if the identifier has diminished state.
-
#enhance ⇒ Identifier
Create a new identifier with enhanced state.
-
#enhanced? ⇒ Boolean
Check if the identifier has enhanced state.
-
#family ⇒ Symbol
The style family (:A to :Z based on SIN component).
-
#first_player? ⇒ Boolean
Check if the identifier belongs to the first player.
-
#flip ⇒ Identifier
Create a new identifier with opposite player assignment.
-
#hash ⇒ Integer
Custom hash implementation for use in collections.
-
#initialize(family, type, side, state = Pin::Identifier::NORMAL_STATE) ⇒ Identifier
constructor
Create a new identifier instance.
-
#normal? ⇒ Boolean
Check if the identifier has normal state.
-
#normalize ⇒ Identifier
Create a new identifier with normal state (no modifiers).
-
#pin_component ⇒ Sashite::Pin::Identifier
Get the parsed PIN identifier object.
-
#same_family?(other) ⇒ Boolean
Check if this identifier has the same family as another.
-
#same_side?(other) ⇒ Boolean
Check if this identifier has the same side as another.
-
#same_state?(other) ⇒ Boolean
Check if this identifier has the same state as another.
-
#same_type?(other) ⇒ Boolean
Check if this identifier has the same type as another.
-
#second_player? ⇒ Boolean
Check if the identifier belongs to the second player.
-
#side ⇒ Symbol
The player side (:first or :second).
-
#sin_component ⇒ Sashite::Sin::Identifier
Get the parsed SIN identifier object.
-
#state ⇒ Symbol
The piece state (:normal, :enhanced, or :diminished).
-
#to_pin ⇒ String
Convert to PIN string representation (piece component only).
-
#to_s ⇒ String
Convert the identifier to its QPI string representation.
-
#to_sin ⇒ String
Convert to SIN string representation (style component only).
-
#type ⇒ Symbol
The piece type (:A to :Z).
-
#with_family(new_family) ⇒ Identifier
Create a new identifier with different family.
-
#with_side(new_side) ⇒ Identifier
Create a new identifier with different side.
-
#with_state(new_state) ⇒ Identifier
Create a new identifier with different state.
-
#with_type(new_type) ⇒ Identifier
Create a new identifier with different piece type.
Constructor Details
#initialize(family, type, side, state = Pin::Identifier::NORMAL_STATE) ⇒ Identifier
Create a new identifier instance
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/sashite/qpi/identifier.rb', line 86 def initialize(family, type, side, state = Pin::Identifier::NORMAL_STATE) # Strict validation - delegate to underlying primitives for consistency Sin::Identifier.validate_family(family) Pin::Identifier.validate_type(type) Pin::Identifier.validate_side(side) Pin::Identifier.validate_state(state) # Create PIN component @pin_identifier = Pin::Identifier.new(type, side, state) # Create SIN component - pass family directly without normalization @sin_identifier = Sin::Identifier.new(family, side) # Validate semantic consistency validate_semantic_consistency freeze end |
Class Method Details
.parse(qpi_string) ⇒ Identifier
Parse a QPI string into an Identifier object
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/sashite/qpi/identifier.rb', line 115 def self.parse(qpi_string) string_value = String(qpi_string) sin_part, pin_part = split_components(string_value) # Parse components sin_identifier = Sin::Identifier.parse(sin_part) pin_identifier = Pin::Identifier.parse(pin_part) # Validate semantic consistency BEFORE creating new instance unless sin_identifier.side == pin_identifier.side raise ::ArgumentError, format(ERROR_SEMANTIC_MISMATCH, sin_part, sin_identifier.side, pin_identifier.side) end # Extract parameters and create new instance new(sin_identifier.family, pin_identifier.type, pin_identifier.side, pin_identifier.state) end |
.valid?(qpi_string) ⇒ Boolean
Check if a string is a valid QPI notation
143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/sashite/qpi/identifier.rb', line 143 def self.valid?(qpi_string) return false unless qpi_string.is_a?(::String) # Split components and validate each part sin_part, pin_part = split_components(qpi_string) return false unless Sashite::Sin.valid?(sin_part) && Sashite::Pin.valid?(pin_part) # Semantic consistency check sin_identifier = Sashite::Sin.parse(sin_part) pin_identifier = Sashite::Pin.parse(pin_part) sin_identifier.side == pin_identifier.side rescue ArgumentError false end |
Instance Method Details
#==(other) ⇒ Boolean Also known as: eql?
Custom equality comparison
372 373 374 375 376 |
# File 'lib/sashite/qpi/identifier.rb', line 372 def ==(other) return false unless other.is_a?(self.class) @sin_identifier == other.sin_component && @pin_identifier == other.pin_component end |
#cross_family?(other) ⇒ Boolean
Check if this identifier has different family from another
332 333 334 335 336 |
# File 'lib/sashite/qpi/identifier.rb', line 332 def cross_family?(other) return false unless other.is_a?(self.class) !same_family?(other) end |
#diminish ⇒ Identifier
Create a new identifier with diminished state
211 212 213 214 215 |
# File 'lib/sashite/qpi/identifier.rb', line 211 def diminish return self if diminished? self.class.new(family, type, side, Pin::Identifier::DIMINISHED_STATE) end |
#diminished? ⇒ Boolean
Check if the identifier has diminished state
300 301 302 |
# File 'lib/sashite/qpi/identifier.rb', line 300 def diminished? @pin_identifier.diminished? end |
#enhance ⇒ Identifier
Create a new identifier with enhanced state
202 203 204 205 206 |
# File 'lib/sashite/qpi/identifier.rb', line 202 def enhance return self if enhanced? self.class.new(family, type, side, Pin::Identifier::ENHANCED_STATE) end |
#enhanced? ⇒ Boolean
Check if the identifier has enhanced state
293 294 295 |
# File 'lib/sashite/qpi/identifier.rb', line 293 def enhanced? @pin_identifier.enhanced? end |
#family ⇒ Symbol
Returns the style family (:A to :Z based on SIN component).
51 52 53 |
# File 'lib/sashite/qpi/identifier.rb', line 51 def family @sin_identifier.family end |
#first_player? ⇒ Boolean
Check if the identifier belongs to the first player
307 308 309 |
# File 'lib/sashite/qpi/identifier.rb', line 307 def first_player? @pin_identifier.first_player? end |
#flip ⇒ Identifier
Create a new identifier with opposite player assignment
Changes the player assignment (side) while preserving the family and piece attributes. This maintains semantic consistency between the components.
279 280 281 |
# File 'lib/sashite/qpi/identifier.rb', line 279 def flip self.class.new(family, type, opposite_side, state) end |
#hash ⇒ Integer
Custom hash implementation for use in collections
384 385 386 |
# File 'lib/sashite/qpi/identifier.rb', line 384 def hash [self.class, @sin_identifier, @pin_identifier].hash end |
#normal? ⇒ Boolean
Check if the identifier has normal state
286 287 288 |
# File 'lib/sashite/qpi/identifier.rb', line 286 def normal? @pin_identifier.normal? end |
#normalize ⇒ Identifier
Create a new identifier with normal state (no modifiers)
220 221 222 223 224 |
# File 'lib/sashite/qpi/identifier.rb', line 220 def normalize return self if normal? self.class.new(family, type, side, Pin::Identifier::NORMAL_STATE) end |
#pin_component ⇒ Sashite::Pin::Identifier
Get the parsed PIN identifier object
195 196 197 |
# File 'lib/sashite/qpi/identifier.rb', line 195 def pin_component @pin_identifier end |
#same_family?(other) ⇒ Boolean
Check if this identifier has the same family as another
322 323 324 325 326 |
# File 'lib/sashite/qpi/identifier.rb', line 322 def same_family?(other) return false unless other.is_a?(self.class) @sin_identifier.same_family?(other.sin_component) end |
#same_side?(other) ⇒ Boolean
Check if this identifier has the same side as another
342 343 344 345 346 |
# File 'lib/sashite/qpi/identifier.rb', line 342 def same_side?(other) return false unless other.is_a?(self.class) @pin_identifier.same_side?(other.pin_component) end |
#same_state?(other) ⇒ Boolean
Check if this identifier has the same state as another
362 363 364 365 366 |
# File 'lib/sashite/qpi/identifier.rb', line 362 def same_state?(other) return false unless other.is_a?(self.class) @pin_identifier.same_state?(other.pin_component) end |
#same_type?(other) ⇒ Boolean
Check if this identifier has the same type as another
352 353 354 355 356 |
# File 'lib/sashite/qpi/identifier.rb', line 352 def same_type?(other) return false unless other.is_a?(self.class) @pin_identifier.same_type?(other.pin_component) end |
#second_player? ⇒ Boolean
Check if the identifier belongs to the second player
314 315 316 |
# File 'lib/sashite/qpi/identifier.rb', line 314 def second_player? @pin_identifier.second_player? end |
#side ⇒ Symbol
Returns the player side (:first or :second).
61 62 63 |
# File 'lib/sashite/qpi/identifier.rb', line 61 def side @pin_identifier.side end |
#sin_component ⇒ Sashite::Sin::Identifier
Get the parsed SIN identifier object
188 189 190 |
# File 'lib/sashite/qpi/identifier.rb', line 188 def sin_component @sin_identifier end |
#state ⇒ Symbol
Returns the piece state (:normal, :enhanced, or :diminished).
66 67 68 |
# File 'lib/sashite/qpi/identifier.rb', line 66 def state @pin_identifier.state end |
#to_pin ⇒ String
Convert to PIN string representation (piece component only)
181 182 183 |
# File 'lib/sashite/qpi/identifier.rb', line 181 def to_pin @pin_identifier.to_s end |
#to_s ⇒ String
Convert the identifier to its QPI string representation
163 164 165 |
# File 'lib/sashite/qpi/identifier.rb', line 163 def to_s "#{@sin_identifier}#{SEPARATOR}#{@pin_identifier}" end |
#to_sin ⇒ String
Convert to SIN string representation (style component only)
172 173 174 |
# File 'lib/sashite/qpi/identifier.rb', line 172 def to_sin @sin_identifier.to_s end |
#type ⇒ Symbol
Returns the piece type (:A to :Z).
56 57 58 |
# File 'lib/sashite/qpi/identifier.rb', line 56 def type @pin_identifier.type end |
#with_family(new_family) ⇒ Identifier
Create a new identifier with different family
260 261 262 263 264 |
# File 'lib/sashite/qpi/identifier.rb', line 260 def with_family(new_family) return self if family == new_family self.class.new(new_family, type, side, state) end |
#with_side(new_side) ⇒ Identifier
Create a new identifier with different side
240 241 242 243 244 |
# File 'lib/sashite/qpi/identifier.rb', line 240 def with_side(new_side) return self if side == new_side self.class.new(family, type, new_side, state) end |
#with_state(new_state) ⇒ Identifier
Create a new identifier with different state
250 251 252 253 254 |
# File 'lib/sashite/qpi/identifier.rb', line 250 def with_state(new_state) return self if state == new_state self.class.new(family, type, side, new_state) end |
#with_type(new_type) ⇒ Identifier
Create a new identifier with different piece type
230 231 232 233 234 |
# File 'lib/sashite/qpi/identifier.rb', line 230 def with_type(new_type) return self if type == new_type self.class.new(family, new_type, side, state) end |