Qpi.rb
QPI (Qualified Piece Identifier) implementation for the Ruby language.
What is QPI?
QPI (Qualified Piece Identifier) provides a rule-agnostic format for identifying game pieces in abstract strategy board games by combining Style Identifier Notation (SIN) and Piece Identifier Notation (PIN) primitives with a colon separator.
This gem implements the QPI Specification v1.0.0 exactly, providing complete piece identification with all four fundamental attributes: Family, Type, Side, and State.
Installation
# In your Gemfile
gem "sashite-qpi"Or install manually:
gem install sashite-qpiDependencies
QPI builds upon two foundational primitive specifications:
gem "sashite-sin" # Style Identifier Notation
gem "sashite-pin" # Piece Identifier NotationUsage
Basic Operations
require "sashite/qpi"
# Parse QPI strings
identifier = Sashite::Qpi.parse("C:K") # Chess king, first player
identifier.to_s # => "C:K"
# Create identifiers from parameters (strict validation)
identifier = Sashite::Qpi.identifier(:C, :K, :first, :normal)
identifier = Sashite::Qpi::Identifier.new(:S, :R, :first, :enhanced)
# Validate QPI strings
Sashite::Qpi.valid?("C:K") # => true
Sashite::Qpi.valid?("s:+p") # => true
Sashite::Qpi.valid?("C:k") # => false (semantic mismatch)Strict Parameter Validation
Important: QPI enforces the same strict validation as its underlying SIN and PIN primitives:
# ✓ Valid - uppercase symbols only for family and type parameters
Sashite::Qpi.identifier(:C, :K, :first, :normal) # => "C:K"
Sashite::Qpi.identifier(:C, :K, :second, :normal) # => "c:k"
# ✗ Invalid - lowercase symbols rejected with ArgumentError
Sashite::Qpi.identifier(:c, :K, :first, :normal) # => ArgumentError
Sashite::Qpi.identifier(:C, :k, :first, :normal) # => ArgumentErrorKey principle: Input parameters must use uppercase symbols (:A to :Z). The side parameter determines the display case, not the input case.
Attribute Access
identifier = Sashite::Qpi.parse("S:+R")
# Four fundamental piece attributes
identifier.family # => :S
identifier.type # => :R
identifier.side # => :first
identifier.state # => :enhanced
# Component extraction
identifier.to_sin # => "S"
identifier.to_pin # => "+R"
identifier.sin_component # => #<Sashite::Sin::Identifier>
identifier.pin_component # => #<Sashite::Pin::Identifier>Transformations
# All transformations return new immutable instances
identifier = Sashite::Qpi.parse("C:K")
# State transformations
enhanced = identifier.enhance # => "C:+K"
diminished = identifier.diminish # => "C:-K"
normalized = identifier.normalize # => "C:K"
# Attribute transformations
different_type = identifier.with_type(:Q) # => "C:Q"
different_side = identifier.with_side(:second) # => "c:k"
different_state = identifier.with_state(:enhanced) # => "C:+K"
different_family = identifier.with_family(:S) # => "S:K"
# Player assignment flip
flipped = identifier.flip # => "c:k"
# Chain transformations
result = identifier.flip.enhance.with_type(:Q) # => "c:+q"State and Comparison Queries
identifier = Sashite::Qpi.parse("S:+P")
# State queries
identifier.normal? # => false
identifier.enhanced? # => true
identifier.diminished? # => false
identifier.first_player? # => true
identifier.second_player? # => false
# Comparison methods
other = Sashite::Qpi.parse("C:+P")
identifier.same_family?(other) # => false (S vs C)
identifier.same_type?(other) # => true (both P)
identifier.same_side?(other) # => true (both first player)
identifier.same_state?(other) # => true (both enhanced)
identifier.cross_family?(other) # => true (different families)API Reference
Main Module Methods
-
Sashite::Qpi.parse(qpi_string)- Parse QPI string into Identifier object -
Sashite::Qpi.identifier(family, type, side, state = :normal)- Create identifier from parameters (strict validation) -
Sashite::Qpi.valid?(qpi_string)- Check if string is valid QPI notation
Identifier Class
Creation and Parsing
-
Sashite::Qpi::Identifier.new(family, type, side, state = :normal)- Create from parameters (strict validation) -
Sashite::Qpi::Identifier.parse(qpi_string)- Parse QPI string
Parameter Validation
Strict validation enforced:
-
familyparameter: Must be symbol:Ato:Z(uppercase only) -
typeparameter: Must be symbol:Ato:Z(uppercase only) -
sideparameter: Must be:firstor:second -
stateparameter: Must be:normal,:enhanced, or:diminished
Attribute Access
-
#family- Get style family (symbol:Ato:Z) -
#type- Get piece type (symbol:Ato:Z) -
#side- Get player side (:firstor:second) -
#state- Get piece state (:normal,:enhanced, or:diminished) -
#to_s- Convert to QPI string representation
Component Access
-
#to_sin- Get SIN string representation -
#to_pin- Get PIN string representation -
#sin_component- Get SIN identifier object -
#pin_component- Get PIN identifier object
State Queries
-
#normal?- Check if normal state -
#enhanced?- Check if enhanced state -
#diminished?- Check if diminished state -
#first_player?- Check if first player -
#second_player?- Check if second player
Transformations (immutable - return new instances)
-
#enhance- Create enhanced version -
#diminish- Create diminished version -
#normalize- Remove state modifiers -
#with_type(new_type)- Change piece type -
#with_side(new_side)- Change player side -
#with_state(new_state)- Change piece state -
#with_family(new_family)- Change style family -
#flip- Switch player assignment for both components
Comparison Methods
-
#same_family?(other)- Check if same style family -
#same_type?(other)- Check if same piece type -
#same_side?(other)- Check if same player side -
#same_state?(other)- Check if same piece state -
#cross_family?(other)- Check if different style families -
#==(other)- Full equality comparison
Format Specification
Structure
<sin>:<pin>
Grammar (BNF)
<qpi> ::= <uppercase-qpi> | <lowercase-qpi>
<uppercase-qpi> ::= <uppercase-letter> ":" <uppercase-pin>
<lowercase-qpi> ::= <lowercase-letter> ":" <lowercase-pin>
<uppercase-pin> ::= ["+" | "-"] <uppercase-letter>
<lowercase-pin> ::= ["+" | "-"] <lowercase-letter>
Regular Expression
/\A([A-Z]:[-+]?[A-Z]|[a-z]:[-+]?[a-z])\z/Examples
-
C:K- Chess-style king, first player -
c:k- Chess-style king, second player -
S:+R- Shogi-style enhanced rook, first player -
x:-s- Xiangqi-style diminished soldier, second player
Semantic Consistency
QPI enforces semantic consistency: the style and piece components must represent the same player. Both components use case to indicate player assignment, and these must align.
Valid combinations:
Sashite::Qpi.valid?("C:K") # => true (both first player)
Sashite::Qpi.valid?("c:k") # => true (both second player)Invalid combinations:
Sashite::Qpi.valid?("C:k") # => false (family=first, piece=second)
Sashite::Qpi.valid?("c:K") # => false (family=second, piece=first)Parameter Validation
Strict Validation Rules
QPI enforces strict parameter validation consistent with its underlying SIN and PIN primitives:
# ✓ Valid parameter examples
Sashite::Qpi.identifier(:C, :K, :first, :normal) # All uppercase symbols
Sashite::Qpi.identifier(:S, :R, :second, :enhanced) # Display case determined by side
# ✗ Invalid parameter examples (raise ArgumentError)
Sashite::Qpi.identifier(:c, :K, :first, :normal) # Lowercase family rejected
Sashite::Qpi.identifier(:C, :k, :first, :normal) # Lowercase type rejected
Sashite::Qpi.identifier("C", :K, :first, :normal) # String family rejected
Sashite::Qpi.identifier(:C, "K", :first, :normal) # String type rejectedError Handling
QPI delegates validation to its underlying primitives, ensuring consistent error messages:
begin
Sashite::Qpi.identifier(:c, :K, :first, :normal)
rescue ArgumentError => e
# Same error message as Sashite::Sin::Identifier.new(:c, :first)
puts e.message # => "Family must be a symbol from :A to :Z representing Style Family, got: :c"
end
begin
Sashite::Qpi.identifier(:C, :k, :first, :normal)
rescue ArgumentError => e
# Same error message as Sashite::Pin::Identifier.new(:k, :first, :normal)
puts e.message # => "Type must be a symbol from :A to :Z, got: :k"
endDesign Properties
- Rule-agnostic: Independent of specific game mechanics
- Complete identification: All four piece attributes represented
- Cross-style support: Enables multi-tradition gaming
- Semantic validation: Ensures component consistency
- Primitive foundation: Built from SIN and PIN specifications
- Strict validation: Consistent parameter validation with underlying primitives
- Immutable: All instances frozen, transformations return new objects
- Functional: Pure functions with no side effects
Related Specifications
- QPI Specification v1.0.0 - Complete technical specification
- QPI Examples - Practical implementation examples
- SIN Specification v1.0.0 - Style identification component
- PIN Specification v1.0.0 - Piece identification component
- Sashité Protocol - Conceptual foundation
License
Available as open source under the MIT License.
About
Maintained by Sashité — promoting chess variants and sharing the beauty of board game cultures.