sashite-epin 1.2.0 → 2.1.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 +299 -420
- data/lib/sashite/epin.rb +274 -56
- metadata +6 -11
- data/lib/sashite/epin/identifier.rb +0 -588
data/lib/sashite/epin.rb
CHANGED
|
@@ -1,83 +1,301 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require "sashite/pin"
|
|
4
4
|
|
|
5
5
|
module Sashite
|
|
6
|
-
# EPIN (Extended Piece Identifier Notation) implementation for Ruby
|
|
6
|
+
# EPIN (Extended Piece Identifier Notation) implementation for Ruby.
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# enabling cross-style game scenarios and piece origin tracking.
|
|
8
|
+
# EPIN extends PIN by adding a **derivation marker** to track piece style
|
|
9
|
+
# in cross-style games.
|
|
11
10
|
#
|
|
12
|
-
#
|
|
13
|
-
# - State modifier: "+" (enhanced), "-" (diminished), or none (normal)
|
|
14
|
-
# - Letter: A-Z (first player), a-z (second player)
|
|
15
|
-
# - Terminal marker: "^" (terminal piece)
|
|
16
|
-
# - Derivation marker: "'" (foreign style), or none (native style)
|
|
11
|
+
# **EPIN is simply: PIN + optional style derivation marker (`'`)**
|
|
17
12
|
#
|
|
18
|
-
#
|
|
19
|
-
# "K" - First player king (native style, normal state, non-terminal)
|
|
20
|
-
# "K^" - First player king (native style, normal state, terminal)
|
|
21
|
-
# "k'" - Second player king (foreign style, normal state, non-terminal)
|
|
22
|
-
# "k^'" - Second player king (foreign style, normal state, terminal)
|
|
23
|
-
# "+R'" - First player rook (foreign style, enhanced state, non-terminal)
|
|
24
|
-
# "+K^'" - First player king (foreign style, enhanced state, terminal)
|
|
25
|
-
# "-p" - Second player pawn (native style, diminished state, non-terminal)
|
|
13
|
+
# == Format
|
|
26
14
|
#
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
|
|
15
|
+
# <pin-token>[<derivation-marker>]
|
|
16
|
+
#
|
|
17
|
+
# Where +<pin-token>+ is a valid PIN token and +<derivation-marker>+ is
|
|
18
|
+
# an optional trailing apostrophe (<tt>'</tt>).
|
|
19
|
+
#
|
|
20
|
+
# == Five Fundamental Attributes
|
|
21
|
+
#
|
|
22
|
+
# EPIN exposes all five attributes from the Sashité Game Protocol:
|
|
23
|
+
#
|
|
24
|
+
# - *Piece Name* â†' +epin.pin.type+
|
|
25
|
+
# - *Piece Side* â†' +epin.pin.side+
|
|
26
|
+
# - *Piece State* â†' +epin.pin.state+
|
|
27
|
+
# - *Terminal Status* â†' +epin.pin.terminal+
|
|
28
|
+
# - *Piece Style* â†' +epin.derived+ (native vs derived)
|
|
29
|
+
#
|
|
30
|
+
# == Examples
|
|
31
|
+
#
|
|
32
|
+
# epin = Sashite::Epin.parse("K^'")
|
|
33
|
+
# epin.pin.type # => :K
|
|
34
|
+
# epin.pin.terminal # => true
|
|
35
|
+
# epin.derived # => true
|
|
36
|
+
#
|
|
37
|
+
# pin = Sashite::Pin.parse("K^")
|
|
38
|
+
# epin = Sashite::Epin.new(pin, derived: true)
|
|
39
|
+
# epin.to_s # => "K^'"
|
|
40
|
+
#
|
|
41
|
+
# Sashite::Epin.valid?("K^'") # => true
|
|
42
|
+
# Sashite::Epin.valid?("K'^") # => false
|
|
43
|
+
#
|
|
44
|
+
# See the EPIN Specification (https://sashite.dev/specs/epin/1.0.0/) for details.
|
|
45
|
+
class Epin
|
|
46
|
+
# Pattern for validating EPIN strings
|
|
47
|
+
EPIN_PATTERN = /\A(?<pin>[-+]?[A-Za-z]\^?)(?<derived>')?\z/
|
|
48
|
+
|
|
49
|
+
# @return [Sashite::Pin] The underlying PIN component
|
|
50
|
+
attr_reader :pin
|
|
51
|
+
|
|
52
|
+
# @return [Boolean] Derivation status (true = derived, false = native)
|
|
53
|
+
attr_reader :derived
|
|
54
|
+
|
|
55
|
+
# ========================================================================
|
|
56
|
+
# Creation and Parsing
|
|
57
|
+
# ========================================================================
|
|
58
|
+
|
|
59
|
+
# Creates a new EPIN instance from a PIN component.
|
|
60
|
+
#
|
|
61
|
+
# @param pin [Sashite::Pin] The underlying PIN instance
|
|
62
|
+
# @param derived [Boolean] Derivation status (default: false)
|
|
63
|
+
# @return [Epin] A new frozen Epin instance
|
|
64
|
+
#
|
|
65
|
+
# @example
|
|
66
|
+
# pin = Sashite::Pin.parse("K^")
|
|
67
|
+
# Sashite::Epin.new(pin)
|
|
68
|
+
# # => #<Sashite::Epin K^>
|
|
69
|
+
#
|
|
70
|
+
# Sashite::Epin.new(pin, derived: true)
|
|
71
|
+
# # => #<Sashite::Epin K^'>
|
|
72
|
+
def initialize(pin, derived: false)
|
|
73
|
+
raise ArgumentError, "Expected a Sashite::Pin instance, got: #{pin.inspect}" unless pin.is_a?(Pin)
|
|
74
|
+
|
|
75
|
+
@pin = pin
|
|
76
|
+
@derived = !!derived
|
|
77
|
+
|
|
78
|
+
freeze
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Parses an EPIN string into an Epin instance.
|
|
82
|
+
#
|
|
83
|
+
# @param epin_string [String] The EPIN string to parse
|
|
84
|
+
# @return [Epin] A new Epin instance
|
|
85
|
+
# @raise [ArgumentError] If the string is not a valid EPIN
|
|
86
|
+
#
|
|
87
|
+
# @example
|
|
88
|
+
# Sashite::Epin.parse("K")
|
|
89
|
+
# # => #<Sashite::Epin K>
|
|
90
|
+
#
|
|
91
|
+
# Sashite::Epin.parse("K'")
|
|
92
|
+
# # => #<Sashite::Epin K'>
|
|
93
|
+
#
|
|
94
|
+
# Sashite::Epin.parse("+R^'")
|
|
95
|
+
# # => #<Sashite::Epin +R^'>
|
|
96
|
+
#
|
|
97
|
+
# Sashite::Epin.parse("invalid")
|
|
98
|
+
# # => ArgumentError: Invalid EPIN string: invalid
|
|
99
|
+
def self.parse(epin_string)
|
|
100
|
+
raise ArgumentError, "Invalid EPIN string: #{epin_string.inspect}" unless epin_string.is_a?(String)
|
|
101
|
+
|
|
102
|
+
match = EPIN_PATTERN.match(epin_string)
|
|
103
|
+
raise ArgumentError, "Invalid EPIN string: #{epin_string}" unless match
|
|
104
|
+
|
|
105
|
+
pin_string = match[:pin]
|
|
106
|
+
derived_marker = match[:derived]
|
|
107
|
+
|
|
108
|
+
pin = Pin.parse(pin_string)
|
|
109
|
+
derived = derived_marker == "'"
|
|
110
|
+
|
|
111
|
+
new(pin, derived: derived)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Checks if a string is a valid EPIN notation.
|
|
30
115
|
#
|
|
31
116
|
# @param epin_string [String] The string to validate
|
|
32
|
-
# @return [Boolean] true if valid
|
|
117
|
+
# @return [Boolean] true if valid, false otherwise
|
|
33
118
|
#
|
|
34
119
|
# @example
|
|
35
120
|
# Sashite::Epin.valid?("K") # => true
|
|
36
|
-
# Sashite::Epin.valid?("K
|
|
37
|
-
# Sashite::Epin.valid?("+R'")
|
|
38
|
-
# Sashite::Epin.valid?("
|
|
39
|
-
# Sashite::Epin.valid?("
|
|
40
|
-
# Sashite::Epin.valid?("
|
|
41
|
-
# Sashite::Epin.valid?("++K") # => false
|
|
42
|
-
# Sashite::Epin.valid?("K'^") # => false (wrong order)
|
|
121
|
+
# Sashite::Epin.valid?("K'") # => true
|
|
122
|
+
# Sashite::Epin.valid?("+R^'") # => true
|
|
123
|
+
# Sashite::Epin.valid?("K'^") # => false
|
|
124
|
+
# Sashite::Epin.valid?("K''") # => false
|
|
125
|
+
# Sashite::Epin.valid?("invalid") # => false
|
|
43
126
|
def self.valid?(epin_string)
|
|
44
|
-
|
|
127
|
+
return false unless epin_string.is_a?(String)
|
|
128
|
+
|
|
129
|
+
EPIN_PATTERN.match?(epin_string)
|
|
45
130
|
end
|
|
46
131
|
|
|
47
|
-
#
|
|
132
|
+
# ========================================================================
|
|
133
|
+
# Conversion
|
|
134
|
+
# ========================================================================
|
|
135
|
+
|
|
136
|
+
# Converts the Epin to its string representation.
|
|
48
137
|
#
|
|
49
|
-
# @
|
|
50
|
-
# @return [Epin::Identifier] new identifier instance
|
|
51
|
-
# @raise [ArgumentError] if the EPIN string is invalid
|
|
138
|
+
# @return [String] The EPIN string
|
|
52
139
|
#
|
|
53
140
|
# @example
|
|
54
|
-
# Sashite::
|
|
55
|
-
# Sashite::Epin.
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
# Sashite::Epin.
|
|
59
|
-
|
|
60
|
-
|
|
141
|
+
# pin = Sashite::Pin.parse("K^")
|
|
142
|
+
# Sashite::Epin.new(pin).to_s
|
|
143
|
+
# # => "K^"
|
|
144
|
+
#
|
|
145
|
+
# Sashite::Epin.new(pin, derived: true).to_s
|
|
146
|
+
# # => "K^'"
|
|
147
|
+
def to_s
|
|
148
|
+
"#{pin}#{derivation_suffix}"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# ========================================================================
|
|
152
|
+
# Transformations
|
|
153
|
+
# ========================================================================
|
|
154
|
+
|
|
155
|
+
# Returns a new Epin with a different PIN component.
|
|
156
|
+
#
|
|
157
|
+
# @param new_pin [Sashite::Pin] The new PIN component
|
|
158
|
+
# @return [Epin] A new Epin with the specified PIN
|
|
159
|
+
#
|
|
160
|
+
# @example
|
|
161
|
+
# epin = Sashite::Epin.parse("K^'")
|
|
162
|
+
# new_pin = epin.pin.with_type(:Q)
|
|
163
|
+
# epin.with_pin(new_pin).to_s
|
|
164
|
+
# # => "Q^'"
|
|
165
|
+
def with_pin(new_pin)
|
|
166
|
+
return self if pin == new_pin
|
|
167
|
+
|
|
168
|
+
self.class.new(new_pin, derived: derived)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Returns a new Epin with a different derivation status.
|
|
172
|
+
#
|
|
173
|
+
# @param new_derived [Boolean] The new derivation status
|
|
174
|
+
# @return [Epin] A new Epin with the specified derivation status
|
|
175
|
+
#
|
|
176
|
+
# @example
|
|
177
|
+
# epin = Sashite::Epin.parse("K^")
|
|
178
|
+
# epin.with_derived(true).to_s
|
|
179
|
+
# # => "K^'"
|
|
180
|
+
#
|
|
181
|
+
# epin = Sashite::Epin.parse("K^'")
|
|
182
|
+
# epin.with_derived(false).to_s
|
|
183
|
+
# # => "K^"
|
|
184
|
+
def with_derived(new_derived)
|
|
185
|
+
return self if derived == !!new_derived
|
|
186
|
+
|
|
187
|
+
self.class.new(pin, derived: !!new_derived)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Returns a new Epin marked as derived.
|
|
191
|
+
#
|
|
192
|
+
# @return [Epin] A new Epin with derived: true
|
|
193
|
+
#
|
|
194
|
+
# @example
|
|
195
|
+
# epin = Sashite::Epin.parse("K^")
|
|
196
|
+
# epin.mark_derived.derived
|
|
197
|
+
# # => true
|
|
198
|
+
def mark_derived
|
|
199
|
+
return self if derived
|
|
200
|
+
|
|
201
|
+
self.class.new(pin, derived: true)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Returns a new Epin marked as native (not derived).
|
|
205
|
+
#
|
|
206
|
+
# @return [Epin] A new Epin with derived: false
|
|
207
|
+
#
|
|
208
|
+
# @example
|
|
209
|
+
# epin = Sashite::Epin.parse("K^'")
|
|
210
|
+
# epin.unmark_derived.derived
|
|
211
|
+
# # => false
|
|
212
|
+
def unmark_derived
|
|
213
|
+
return self unless derived
|
|
214
|
+
|
|
215
|
+
self.class.new(pin, derived: false)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# ========================================================================
|
|
219
|
+
# Queries
|
|
220
|
+
# ========================================================================
|
|
221
|
+
|
|
222
|
+
# Checks if the Epin is derived (uses opponent's style).
|
|
223
|
+
#
|
|
224
|
+
# @return [Boolean] true if derived
|
|
225
|
+
#
|
|
226
|
+
# @example
|
|
227
|
+
# Sashite::Epin.parse("K^'").derived? # => true
|
|
228
|
+
# Sashite::Epin.parse("K^").derived? # => false
|
|
229
|
+
def derived?
|
|
230
|
+
derived
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Checks if the Epin is native (uses own side's style).
|
|
234
|
+
#
|
|
235
|
+
# @return [Boolean] true if native
|
|
236
|
+
#
|
|
237
|
+
# @example
|
|
238
|
+
# Sashite::Epin.parse("K^").native? # => true
|
|
239
|
+
# Sashite::Epin.parse("K^'").native? # => false
|
|
240
|
+
def native?
|
|
241
|
+
!derived
|
|
61
242
|
end
|
|
62
243
|
|
|
63
|
-
#
|
|
244
|
+
# Checks if two Epins have the same derivation status.
|
|
64
245
|
#
|
|
65
|
-
# @param
|
|
66
|
-
# @
|
|
67
|
-
# @param state [Symbol] piece state (:normal, :enhanced, or :diminished)
|
|
68
|
-
# @param native [Boolean] style derivation (true for native, false for foreign)
|
|
69
|
-
# @param terminal [Boolean] whether the piece is a terminal piece
|
|
70
|
-
# @return [Epin::Identifier] new identifier instance
|
|
71
|
-
# @raise [ArgumentError] if parameters are invalid
|
|
246
|
+
# @param other [Epin] The other Epin to compare
|
|
247
|
+
# @return [Boolean] true if same derivation status
|
|
72
248
|
#
|
|
73
249
|
# @example
|
|
74
|
-
# Sashite::Epin.
|
|
75
|
-
# Sashite::Epin.
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
250
|
+
# epin1 = Sashite::Epin.parse("K^'")
|
|
251
|
+
# epin2 = Sashite::Epin.parse("Q'")
|
|
252
|
+
# epin1.same_derived?(epin2)
|
|
253
|
+
# # => true
|
|
254
|
+
#
|
|
255
|
+
# epin3 = Sashite::Epin.parse("K^")
|
|
256
|
+
# epin1.same_derived?(epin3)
|
|
257
|
+
# # => false
|
|
258
|
+
def same_derived?(other)
|
|
259
|
+
derived == other.derived
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# ========================================================================
|
|
263
|
+
# Comparison
|
|
264
|
+
# ========================================================================
|
|
265
|
+
|
|
266
|
+
# Checks equality with another Epin.
|
|
267
|
+
#
|
|
268
|
+
# @param other [Object] The object to compare
|
|
269
|
+
# @return [Boolean] true if equal
|
|
270
|
+
def ==(other)
|
|
271
|
+
return false unless other.is_a?(self.class)
|
|
272
|
+
|
|
273
|
+
pin == other.pin && derived == other.derived
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
alias eql? ==
|
|
277
|
+
|
|
278
|
+
# Returns a hash code for the Epin.
|
|
279
|
+
#
|
|
280
|
+
# @return [Integer] Hash code
|
|
281
|
+
def hash
|
|
282
|
+
[pin, derived].hash
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Returns an inspect string for the Epin.
|
|
286
|
+
#
|
|
287
|
+
# @return [String] Inspect representation
|
|
288
|
+
def inspect
|
|
289
|
+
"#<#{self.class} #{self}>"
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
private
|
|
293
|
+
|
|
294
|
+
# Returns the derivation suffix for string representation.
|
|
295
|
+
#
|
|
296
|
+
# @return [String] "'" if derived, "" otherwise
|
|
297
|
+
def derivation_suffix
|
|
298
|
+
derived ? "'" : ""
|
|
81
299
|
end
|
|
82
300
|
end
|
|
83
301
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sashite-epin
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cyril Kato
|
|
@@ -15,22 +15,18 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 3.
|
|
18
|
+
version: 3.3.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.
|
|
25
|
+
version: 3.3.0
|
|
26
26
|
description: |
|
|
27
|
-
EPIN (Extended Piece Identifier Notation)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
principles. EPIN adds derivation markers to PIN that distinguish pieces by their style
|
|
31
|
-
origin, enabling cross-style game scenarios and piece origin tracking. Represents all
|
|
32
|
-
four Game Protocol piece attributes with full PIN backward compatibility. Perfect for
|
|
33
|
-
game engines, cross-tradition tournaments, and hybrid board game environments.
|
|
27
|
+
EPIN (Extended Piece Identifier Notation) implementation for Ruby.
|
|
28
|
+
Extends PIN by adding a derivation marker to track piece style in cross-style
|
|
29
|
+
abstract strategy board games with a minimal compositional API.
|
|
34
30
|
email: contact@cyril.email
|
|
35
31
|
executables: []
|
|
36
32
|
extensions: []
|
|
@@ -40,7 +36,6 @@ files:
|
|
|
40
36
|
- README.md
|
|
41
37
|
- lib/sashite-epin.rb
|
|
42
38
|
- lib/sashite/epin.rb
|
|
43
|
-
- lib/sashite/epin/identifier.rb
|
|
44
39
|
homepage: https://github.com/sashite/epin.rb
|
|
45
40
|
licenses:
|
|
46
41
|
- MIT
|