sashite-qpi 1.0.0 → 2.0.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.
data/lib/sashite/qpi.rb CHANGED
@@ -5,54 +5,34 @@ require_relative "qpi/identifier"
5
5
  module Sashite
6
6
  # QPI (Qualified Piece Identifier) implementation for Ruby
7
7
  #
8
- # Provides a rule-agnostic format for identifying game pieces in abstract strategy board games
9
- # by combining Style Identifier Notation (SIN) and Piece Identifier Notation (PIN) primitives
10
- # with a colon separator. This combination enables complete piece identification across different
11
- # game styles and contexts.
8
+ # Provides complete piece identification by combining two primitive notations:
9
+ # - SIN (Style Identifier Notation) identifies the piece style
10
+ # - PIN (Piece Identifier Notation) identifies the piece attributes
12
11
  #
13
- # ## Concept
12
+ # A QPI identifier is simply a pair of (SIN, PIN) with one constraint:
13
+ # both components must represent the same player.
14
14
  #
15
- # QPI addresses the fundamental need to uniquely identify game pieces across different style
16
- # systems while maintaining complete attribute information. By combining SIN and PIN primitives,
17
- # QPI provides explicit representation of all four fundamental piece attributes from the
18
- # Sashité Protocol.
15
+ # ## Core Concept
19
16
  #
20
- # ## Four Fundamental Attributes
17
+ # QPI is pure composition:
21
18
  #
22
- # QPI represents all four piece attributes through primitive combination:
23
- # - **Family**: Style identification from SIN component
24
- # - **Type**: Piece type from PIN component
25
- # - **Side**: Player assignment from both components (must be consistent)
26
- # - **State**: Piece state from PIN component
19
+ # sin = Sashite::Sin.parse("C")
20
+ # pin = Sashite::Pin.parse("K^")
21
+ # qpi = Sashite::Qpi.new(sin, pin)
22
+ # qpi.to_s # => "C:K^"
23
+ # qpi.sin # => SIN::Identifier instance
24
+ # qpi.pin # => PIN::Identifier instance
27
25
  #
28
- # ## Format Structure
26
+ # All piece attributes come from the components.
29
27
  #
30
- # A QPI identifier consists of two primitive components separated by a colon:
31
- # - **SIN component**: Style identification with player assignment
32
- # - **PIN component**: Piece identification with type, side, and state
33
- # - **Separator**: Colon (:) provides clear delimitation
28
+ # ## Five Fundamental Attributes
34
29
  #
35
- # The components must maintain semantic consistency: both SIN and PIN must represent
36
- # the same player (first or second) through their respective case encodings.
37
- #
38
- # ## Semantic Consistency Constraint
39
- #
40
- # QPI enforces a critical constraint: the style identified by the SIN component must be
41
- # associated with the same player as indicated by the PIN component. This ensures that
42
- # piece ownership and style ownership remain aligned, preventing impossible combinations
43
- # like a first player style with a second player piece.
44
- #
45
- # Examples of semantic consistency:
46
- # - SIN "C" (first player) + PIN "K" (first player) = Valid
47
- # - SIN "c" (second player) + PIN "k" (second player) = Valid
48
- # - SIN "C" (first player) + PIN "k" (second player) = Invalid
49
- # - SIN "c" (second player) + PIN "K" (first player) = Invalid
50
- #
51
- # ## Cross-Style Gaming Support
52
- #
53
- # QPI enables cross-style gaming scenarios where different players use different game
54
- # traditions. The explicit style identification allows pieces from different systems
55
- # to coexist while maintaining clear attribution to their respective players.
30
+ # QPI exposes all five attributes from the Sashité Game Protocol:
31
+ # - **Piece Style** via qpi.sin.family
32
+ # - **Piece Name** — via qpi.pin.type
33
+ # - **Piece Side** — via qpi.sin.side or qpi.pin.side
34
+ # - **Piece State** — via qpi.pin.state
35
+ # - **Terminal Status** via qpi.pin.terminal?
56
36
  #
57
37
  # ## Format Specification
58
38
  #
@@ -62,159 +42,94 @@ module Sashite
62
42
  # <qpi> ::= <uppercase-qpi> | <lowercase-qpi>
63
43
  # <uppercase-qpi> ::= <uppercase-letter> ":" <uppercase-pin>
64
44
  # <lowercase-qpi> ::= <lowercase-letter> ":" <lowercase-pin>
65
- # <uppercase-pin> ::= ["+" | "-"] <uppercase-letter>
66
- # <lowercase-pin> ::= ["+" | "-"] <lowercase-letter>
67
- #
68
- # Regular Expression: `/\A([A-Z]:[-+]?[A-Z]|[a-z]:[-+]?[a-z])\z/`
45
+ # <uppercase-pin> ::= ["+" | "-"] <uppercase-letter> ["^"]
46
+ # <lowercase-pin> ::= ["+" | "-"] <lowercase-letter> ["^"]
69
47
  #
70
- # ## Attribute Mapping
48
+ # Regular Expression: `/\A([A-Z]:[-+]?[A-Z]\^?|[a-z]:[-+]?[a-z]\^?)\z/`
71
49
  #
72
- # QPI encodes piece attributes through primitive combination:
50
+ # ## Semantic Constraint
73
51
  #
74
- # | Piece Attribute | QPI Encoding | Examples |
75
- # |-----------------|--------------|----------|
76
- # | **Family** | SIN component | `C:K` = Chess family, `O:K` = Ogi family |
77
- # | **Type** | PIN letter choice | `C:K` = King, `C:P` = Pawn |
78
- # | **Side** | Component cases | `C:K` = First player, `c:k` = Second player |
79
- # | **State** | PIN prefix modifier | `O:+P` = Enhanced, `C:-P` = Diminished |
80
- #
81
- # ## System Constraints
82
- #
83
- # - **Semantic Consistency**: SIN and PIN components must represent the same player
84
- # - **Component Validation**: Each component must be valid according to its specification
85
- # - **Complete Attribution**: All four fundamental piece attributes explicitly represented
86
- # - **Cross-Style Support**: Enables multi-tradition gaming environments
52
+ # The SIN and PIN components must represent the same player:
53
+ # - Valid: "C:K" (both first player), "c:k" (both second player)
54
+ # - Invalid: "C:k" (side mismatch), "c:K" (side mismatch)
87
55
  #
88
56
  # ## Examples
89
57
  #
90
- # ### Single-Style Games
91
- #
92
- # # Chess (both players use Chess style)
93
- # white_king = Sashite::Qpi.parse("C:K") # Chess king, first player
94
- # black_king = Sashite::Qpi.parse("c:k") # Chess king, second player
95
- #
96
- # # Ogi (both players use Ogi style)
97
- # sente_king = Sashite::Qpi.parse("O:K") # Ogi king, first player (sente)
98
- # gote_rook = Sashite::Qpi.parse("o:+r") # Ogi promoted rook, second player (gote)
99
- #
100
- # ### Cross-Style Games
58
+ # # Parse QPI string
59
+ # qpi = Sashite::Qpi.parse("C:K^")
60
+ # qpi.sin.family # => :C (Piece Style)
61
+ # qpi.pin.type # => :K (Piece Name)
62
+ # qpi.sin.side # => :first (Piece Side)
63
+ # qpi.pin.state # => :normal (Piece State)
64
+ # qpi.pin.terminal? # => true (Terminal Status)
101
65
  #
102
- # # Chess vs. Ogi match
103
- # chess_player = Sashite::Qpi.parse("C:K") # First player uses Chess
104
- # ogi_player = Sashite::Qpi.parse("o:k") # Second player uses Ogi
66
+ # # Create from components
67
+ # sin = Sashite::Sin.parse("S")
68
+ # pin = Sashite::Pin.parse("+R^")
69
+ # qpi = Sashite::Qpi.new(sin, pin)
70
+ # qpi.to_s # => "S:+R^"
105
71
  #
106
- # # Verify cross-style scenario
107
- # chess_player.cross_family?(ogi_player) # => true
72
+ # # Transform via components
73
+ # qpi.with_sin(qpi.sin.with_family(:C)) # => "C:+R^"
74
+ # qpi.with_pin(qpi.pin.with_type(:B)) # => "S:+B^"
108
75
  #
109
- # ### Attribute Access and Manipulation
110
- #
111
- # identifier = Sashite::Qpi.parse("O:+R")
112
- #
113
- # # Four fundamental attributes
114
- # identifier.family # => :O
115
- # identifier.type # => :R
116
- # identifier.side # => :first
117
- # identifier.state # => :enhanced
118
- #
119
- # # Component extraction
120
- # identifier.to_sin # => "O"
121
- # identifier.to_pin # => "+R"
122
- #
123
- # # Immutable transformations
124
- # flipped = identifier.flip # => "o:+r"
125
- # different_type = identifier.with_type(:Q) # => "O:+Q"
126
- # different_family = identifier.with_family(:C) # => "C:+R"
76
+ # # Flip both components (only convenience method)
77
+ # qpi.flip # => "s:+r^"
127
78
  #
128
79
  # ## Design Properties
129
80
  #
130
- # - **Rule-agnostic**: Independent of specific game mechanics
131
- # - **Complete identification**: Explicit representation of all four piece attributes
132
- # - **Cross-style support**: Enables multi-tradition gaming environments
133
- # - **Semantic validation**: Ensures consistency between style and piece ownership
134
- # - **Primitive foundation**: Built from foundational SIN and PIN building blocks
135
- # - **Extension-ready**: Can be enhanced by human-readable naming systems
136
- # - **Context-flexible**: Adaptable to various identification needs
137
- # - **Immutable**: All instances are frozen and transformations return new objects
138
- # - **Functional**: Pure functions with no side effects
81
+ # - **Rule-agnostic**: Independent of game mechanics
82
+ # - **Pure composition**: Zero feature duplication
83
+ # - **Minimal API**: Only 5 core methods
84
+ # - **Component transparency**: Direct primitive access
85
+ # - **Immutable**: Frozen instances
86
+ # - **Semantic validation**: Automatic side consistency
139
87
  #
140
88
  # @see https://sashite.dev/specs/qpi/1.0.0/ QPI Specification v1.0.0
141
- # @see https://sashite.dev/specs/qpi/1.0.0/examples/ QPI Examples
142
89
  # @see https://sashite.dev/specs/sin/1.0.0/ Style Identifier Notation (SIN)
143
90
  # @see https://sashite.dev/specs/pin/1.0.0/ Piece Identifier Notation (PIN)
144
91
  module Qpi
145
92
  # Check if a string is a valid QPI notation
146
93
  #
147
- # Validates the string format and semantic consistency between SIN and PIN components.
148
- # Both components must be individually valid and represent the same player through
149
- # their respective case encodings.
150
- #
151
94
  # @param qpi_string [String] the string to validate
152
95
  # @return [Boolean] true if valid QPI, false otherwise
153
96
  #
154
- # @example Validate various QPI formats
155
- # Sashite::Qpi.valid?("C:K") # => true (Chess king, first player)
156
- # Sashite::Qpi.valid?("c:k") # => true (Chess king, second player)
157
- # Sashite::Qpi.valid?("O:+P") # => true (Ogi enhanced pawn, first player)
158
- # Sashite::Qpi.valid?("o:-r") # => true (Ogi diminished rook, second player)
159
- # Sashite::Qpi.valid?("C:k") # => false (semantic mismatch: first player style, second player piece)
160
- # Sashite::Qpi.valid?("c:K") # => false (semantic mismatch: second player style, first player piece)
161
- # Sashite::Qpi.valid?("CHESS:K") # => false (multi-character SIN component)
162
- # Sashite::Qpi.valid?("C") # => false (missing PIN component)
97
+ # @example
98
+ # Sashite::Qpi.valid?("C:K^") # => true
99
+ # Sashite::Qpi.valid?("C:k") # => false (side mismatch)
163
100
  def self.valid?(qpi_string)
164
101
  Identifier.valid?(qpi_string)
165
102
  end
166
103
 
167
104
  # Parse a QPI string into an Identifier object
168
105
  #
169
- # Creates a new QPI identifier by parsing the string into SIN and PIN components,
170
- # validating each component, and ensuring semantic consistency between them.
171
- #
172
106
  # @param qpi_string [String] QPI notation string (format: sin:pin)
173
- # @return [Qpi::Identifier] parsed identifier object with family, type, side, and state attributes
174
- # @raise [ArgumentError] if the QPI string is invalid or semantically inconsistent
175
- #
176
- # @example Parse different QPI formats with complete attribute access
177
- # Sashite::Qpi.parse("C:K") # => #<Qpi::Identifier family=:C type=:K side=:first state=:normal>
178
- # Sashite::Qpi.parse("c:k") # => #<Qpi::Identifier family=:C type=:K side=:second state=:normal>
179
- # Sashite::Qpi.parse("O:+R") # => #<Qpi::Identifier family=:O type=:R side=:first state=:enhanced>
180
- # Sashite::Qpi.parse("x:-s") # => #<Qpi::Identifier family=:X type=:S side=:second state=:diminished>
107
+ # @return [Qpi::Identifier] identifier with sin and pin components
108
+ # @raise [ArgumentError] if invalid or semantically inconsistent
181
109
  #
182
- # @example Traditional game styles
183
- # chess_king = Sashite::Qpi.parse("C:K") # Chess king, first player
184
- # ogi_rook = Sashite::Qpi.parse("o:+r") # Ogi promoted rook, second player
185
- # xiongqi_king = Sashite::Qpi.parse("X:K") # Xiongqi king, first player
110
+ # @example
111
+ # qpi = Sashite::Qpi.parse("C:K^")
112
+ # qpi.sin.family # => :C
113
+ # qpi.pin.type # => :K
114
+ # qpi.pin.terminal? # => true
186
115
  def self.parse(qpi_string)
187
116
  Identifier.parse(qpi_string)
188
117
  end
189
118
 
190
- # Create a new identifier instance with explicit parameters
191
- #
192
- # Constructs a QPI identifier by directly specifying all four fundamental attributes.
193
- # This method provides parameter-based construction as an alternative to string parsing,
194
- # enabling immediate validation and clearer API usage.
195
- #
196
- # @param family [Symbol] style family identifier (single ASCII letter as symbol)
197
- # @param type [Symbol] piece type (:A to :Z)
198
- # @param side [Symbol] player side (:first or :second)
199
- # @param state [Symbol] piece state (:normal, :enhanced, or :diminished)
200
- # @return [Qpi::Identifier] new immutable identifier instance
201
- # @raise [ArgumentError] if parameters are invalid or semantically inconsistent
202
- #
203
- # @example Create identifiers with explicit parameters
204
- # Sashite::Qpi.identifier(:C, :K, :first, :normal) # => "C:K"
205
- # Sashite::Qpi.identifier(:c, :K, :second, :normal) # => "c:k"
206
- # Sashite::Qpi.identifier(:O, :R, :first, :enhanced) # => "O:+R"
207
- # Sashite::Qpi.identifier(:x, :S, :second, :diminished) # => "x:-s"
119
+ # Create a new identifier from SIN and PIN components
208
120
  #
209
- # @example Cross-style game setup
210
- # chess_player = Sashite::Qpi.identifier(:C, :K, :first, :normal) # Chess king, first player
211
- # ogi_player = Sashite::Qpi.identifier(:o, :K, :second, :normal) # Ogi king, second player
121
+ # @param sin [Sin::Identifier] SIN component
122
+ # @param pin [Pin::Identifier] PIN component
123
+ # @return [Qpi::Identifier] new identifier instance
124
+ # @raise [ArgumentError] if components have different sides
212
125
  #
213
- # chess_player.cross_family?(ogi_player) # => true (different families)
214
- # chess_player.same_type?(ogi_player) # => true (both kings)
215
- # chess_player.same_side?(ogi_player) # => false (different players)
216
- def self.identifier(family, type, side, state = Pin::Identifier::NORMAL_STATE)
217
- Identifier.new(family, type, side, state)
126
+ # @example
127
+ # sin = Sashite::Sin.parse("C")
128
+ # pin = Sashite::Pin.parse("K^")
129
+ # qpi = Sashite::Qpi.new(sin, pin)
130
+ # qpi.to_s # => "C:K^"
131
+ def self.new(sin, pin)
132
+ Identifier.new(sin, pin)
218
133
  end
219
134
  end
220
135
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sashite-qpi
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '3.1'
18
+ version: 3.2.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '3.1'
25
+ version: 3.2.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: sashite-sin
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '2.1'
32
+ version: 2.1.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '2.1'
39
+ version: 2.1.0
40
40
  description: |
41
41
  QPI (Qualified Piece Identifier) provides a rule-agnostic format for identifying game pieces
42
42
  in abstract strategy board games by combining Style Identifier Notation (SIN) and Piece
@@ -80,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
80
  - !ruby/object:Gem::Version
81
81
  version: '0'
82
82
  requirements: []
83
- rubygems_version: 3.7.1
83
+ rubygems_version: 3.7.2
84
84
  specification_version: 4
85
85
  summary: QPI (Qualified Piece Identifier) implementation for Ruby with immutable identifier
86
86
  objects