Class: Sashite::Qpi::Identifier

Inherits:
Object
  • Object
show all
Defined in:
lib/sashite/qpi/identifier.rb

Overview

Represents an identifier in QPI (Qualified Piece Identifier) format.

QPI is pure composition of SIN and PIN primitives with one constraint: both components must represent the same player (side).

## Minimal API Design

The Identifier class provides only 5 core methods:

  1. new(sin, pin) — create from components with validation

  2. sin — access SIN component

  3. pin — access PIN component

  4. to_s — serialize to QPI string

  5. flip — flip both components (only convenience method)

Additionally, component replacement methods:

  • with_sin(new_sin) — create identifier with different SIN

  • with_pin(new_pin) — create identifier with different PIN

All other operations use the component APIs directly:

  • qpi.sin.family — access Piece Style

  • qpi.sin.side — access Piece Side

  • qpi.pin.type — access Piece Name

  • qpi.pin.state — access Piece State

  • qpi.pin.terminal? — access Terminal Status

## Why Only flip as Convenience?

flip is the ONLY transformation that naturally operates on both SIN and PIN components simultaneously. All other transformations work through component replacement:

qpi.with_sin(qpi.sin.with_family(:S))      # Transform SIN
qpi.with_pin(qpi.pin.with_type(:Q))        # Transform PIN
qpi.with_pin(qpi.pin.with_terminal(true))  # Transform PIN

This avoids arbitrary conveniences and maintains a clear principle.

Examples:

Pure composition

sin = Sashite::Sin.parse("C")
pin = Sashite::Pin.parse("K^")
qpi = Sashite::Qpi::Identifier.new(sin, pin)
qpi.to_s              # => "C:K^"
qpi.sin               # => SIN::Identifier
qpi.pin               # => PIN::Identifier

Access attributes via components

qpi.sin.family        # => :C (Piece Style)
qpi.pin.type          # => :K (Piece Name)
qpi.sin.side          # => :first (Piece Side)
qpi.pin.state         # => :normal (Piece State)
qpi.pin.terminal?     # => true (Terminal Status)

Transform via components

qpi.with_sin(qpi.sin.with_family(:S))     # => "S:K^"
qpi.with_pin(qpi.pin.with_type(:Q))       # => "C:Q^"
qpi.flip                                   # => "c:k^"

See Also:

Constant Summary collapse

SEPARATOR =

Component separator for string representation

":"
ERROR_INVALID_QPI =

Error messages

"Invalid QPI string: %s"
ERROR_SEMANTIC_MISMATCH =
"SIN and PIN components must have same side: sin.side=%s, pin.side=%s"
ERROR_MISSING_SEPARATOR =
"QPI string must contain exactly one colon separator: %s"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(sin, pin) ⇒ Identifier

Create a new identifier from SIN and PIN components

Examples:

sin = Sashite::Sin.parse("C")
pin = Sashite::Pin.parse("K^")
qpi = Sashite::Qpi::Identifier.new(sin, pin)

Parameters:

  • sin (Sin::Identifier)

    SIN component

  • pin (Pin::Identifier)

    PIN component

Raises:

  • (ArgumentError)

    if components have different sides



91
92
93
94
95
96
97
98
# File 'lib/sashite/qpi/identifier.rb', line 91

def initialize(sin, pin)
  validate_semantic_consistency(sin, pin)

  @sin = sin
  @pin = pin

  freeze
end

Instance Attribute Details

#pinPin::Identifier (readonly)

Returns the PIN component.

Returns:

  • (Pin::Identifier)

    the PIN component



79
80
81
# File 'lib/sashite/qpi/identifier.rb', line 79

def pin
  @pin
end

#sinSin::Identifier (readonly)

Returns the SIN component.

Returns:

  • (Sin::Identifier)

    the SIN component



76
77
78
# File 'lib/sashite/qpi/identifier.rb', line 76

def sin
  @sin
end

Class Method Details

.parse(qpi_string) ⇒ Identifier

Parse a QPI string into an Identifier object

Examples:

qpi = Sashite::Qpi::Identifier.parse("C:K^")
qpi.sin.family        # => :C
qpi.pin.type          # => :K

Parameters:

  • qpi_string (String)

    QPI notation string (format: sin:pin)

Returns:

Raises:

  • (ArgumentError)

    if invalid or semantically inconsistent



110
111
112
113
114
115
116
117
118
# File 'lib/sashite/qpi/identifier.rb', line 110

def self.parse(qpi_string)
  string_value = String(qpi_string)
  sin_part, pin_part = split_components(string_value)

  sin_identifier = Sin::Identifier.parse(sin_part)
  pin_identifier = Pin::Identifier.parse(pin_part)

  new(sin_identifier, pin_identifier)
end

.valid?(qpi_string) ⇒ Boolean

Check if a string is a valid QPI notation

Examples:

Sashite::Qpi::Identifier.valid?("C:K^")   # => true
Sashite::Qpi::Identifier.valid?("C:k")    # => false (side mismatch)

Parameters:

  • qpi_string (String)

    the string to validate

Returns:

  • (Boolean)

    true if valid QPI, false otherwise



128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/sashite/qpi/identifier.rb', line 128

def self.valid?(qpi_string)
  return false unless qpi_string.is_a?(::String)

  sin_part, pin_part = split_components(qpi_string)
  return false unless Sashite::Sin.valid?(sin_part) && Sashite::Pin.valid?(pin_part)

  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

Parameters:

  • other (Object)

    object to compare with

Returns:

  • (Boolean)

    true if both SIN and PIN components are equal



197
198
199
200
201
# File 'lib/sashite/qpi/identifier.rb', line 197

def ==(other)
  return false unless other.is_a?(self.class)

  @sin == other.sin && @pin == other.pin
end

#flipIdentifier

Create a new identifier with both components flipped

This is the ONLY convenience method because it’s the only transformation that naturally operates on both components.

Examples:

qpi = Sashite::Qpi.parse("C:K^")
qpi.flip  # => "c:k^"

Returns:

  • (Identifier)

    new identifier with both components flipped



189
190
191
# File 'lib/sashite/qpi/identifier.rb', line 189

def flip
  self.class.new(@sin.flip, @pin.flip)
end

#hashInteger

Custom hash implementation for use in collections

Returns:

  • (Integer)

    hash value



209
210
211
# File 'lib/sashite/qpi/identifier.rb', line 209

def hash
  [self.class, @sin, @pin].hash
end

#to_sString

Convert the identifier to its QPI string representation

Examples:

qpi.to_s  # => "C:K^"

Returns:

  • (String)

    QPI notation string (format: sin:pin)



147
148
149
# File 'lib/sashite/qpi/identifier.rb', line 147

def to_s
  "#{@sin}#{SEPARATOR}#{@pin}"
end

#with_pin(new_pin) ⇒ Identifier

Create a new identifier with different PIN component

Examples:

qpi.with_pin(qpi.pin.with_type(:Q))  # => "C:Q^"

Parameters:

  • new_pin (Pin::Identifier)

    new PIN component

Returns:

Raises:

  • (ArgumentError)

    if new PIN has different side than SIN



173
174
175
176
177
# File 'lib/sashite/qpi/identifier.rb', line 173

def with_pin(new_pin)
  return self if @pin == new_pin

  self.class.new(@sin, new_pin)
end

#with_sin(new_sin) ⇒ Identifier

Create a new identifier with different SIN component

Examples:

qpi.with_sin(qpi.sin.with_family(:S))  # => "S:K^"

Parameters:

  • new_sin (Sin::Identifier)

    new SIN component

Returns:

Raises:

  • (ArgumentError)

    if new SIN has different side than PIN



159
160
161
162
163
# File 'lib/sashite/qpi/identifier.rb', line 159

def with_sin(new_sin)
  return self if @sin == new_sin

  self.class.new(new_sin, @pin)
end