QPI (Qualified Piece Identifier) implementation for the Ruby language.
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.
# In your Gemfile
gem "sashite-qpi"
Or install manually:
gem install sashite-qpi
QPI builds upon two foundational primitive specifications:
gem "sashite-sin" # Style Identifier Notation
gem "sashite-pin" # Piece Identifier Notation
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)
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) # => ArgumentError
Key principle: Input parameters must use uppercase symbols (:A
to :Z
). The side
parameter determines the display case, not the input case.
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>
# 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"
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)
Sashite::Qpi.parse(qpi_string)
- Parse QPI string into Identifier objectSashite::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
Sashite::Qpi::Identifier.new(family, type, side, state = :normal)
- Create from parameters (strict validation)Sashite::Qpi::Identifier.parse(qpi_string)
- Parse QPI string
Strict validation enforced:
family
parameter: Must be symbol:A
to:Z
(uppercase only)type
parameter: Must be symbol:A
to:Z
(uppercase only)side
parameter: Must be:first
or:second
state
parameter: Must be:normal
,:enhanced
, or:diminished
#family
- Get style family (symbol:A
to:Z
)#type
- Get piece type (symbol:A
to:Z
)#side
- Get player side (:first
or:second
)#state
- Get piece state (:normal
,:enhanced
, or:diminished
)#to_s
- Convert to QPI string representation
#to_sin
- Get SIN string representation#to_pin
- Get PIN string representation#sin_component
- Get SIN identifier object#pin_component
- Get PIN identifier object
#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
#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
#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
<sin>:<pin>
<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>
/\A([A-Z]:[-+]?[A-Z]|[a-z]:[-+]?[a-z])\z/
C:K
- Chess-style king, first playerc:k
- Chess-style king, second playerS:+R
- Shogi-style enhanced rook, first playerx:-s
- Xiangqi-style diminished soldier, second player
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)
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 rejected
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"
end
- 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
- 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
Available as open source under the MIT License.
Maintained by Sashité — promoting chess variants and sharing the beauty of board game cultures.