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.
- checksums.yaml +4 -4
- data/README.md +502 -169
- data/lib/sashite/qpi/identifier.rb +115 -298
- data/lib/sashite/qpi.rb +73 -158
- metadata +6 -6
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
|
|
9
|
-
#
|
|
10
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
17
|
+
# QPI is pure composition:
|
|
21
18
|
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
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
|
-
#
|
|
26
|
+
# All piece attributes come from the components.
|
|
29
27
|
#
|
|
30
|
-
#
|
|
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
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
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
|
-
#
|
|
48
|
+
# Regular Expression: `/\A([A-Z]:[-+]?[A-Z]\^?|[a-z]:[-+]?[a-z]\^?)\z/`
|
|
71
49
|
#
|
|
72
|
-
#
|
|
50
|
+
# ## Semantic Constraint
|
|
73
51
|
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
#
|
|
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
|
-
#
|
|
91
|
-
#
|
|
92
|
-
# #
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
# #
|
|
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
|
-
# #
|
|
103
|
-
#
|
|
104
|
-
#
|
|
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
|
-
# #
|
|
107
|
-
#
|
|
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
|
-
#
|
|
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
|
|
131
|
-
# - **
|
|
132
|
-
# - **
|
|
133
|
-
# - **
|
|
134
|
-
# - **
|
|
135
|
-
# - **
|
|
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
|
|
155
|
-
# Sashite::Qpi.valid?("C:K")
|
|
156
|
-
# Sashite::Qpi.valid?("
|
|
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]
|
|
174
|
-
# @raise [ArgumentError] if
|
|
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
|
|
183
|
-
#
|
|
184
|
-
#
|
|
185
|
-
#
|
|
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
|
|
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
|
-
# @
|
|
210
|
-
#
|
|
211
|
-
#
|
|
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
|
-
#
|
|
214
|
-
#
|
|
215
|
-
#
|
|
216
|
-
|
|
217
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|
|
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
|