sashite-epin 2.1.0 → 2.2.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 +151 -298
- data/lib/sashite/epin/constants.rb +15 -0
- data/lib/sashite/epin/errors/argument/messages.rb +25 -0
- data/lib/sashite/epin/errors/argument.rb +16 -0
- data/lib/sashite/epin/errors.rb +3 -0
- data/lib/sashite/epin/identifier.rb +200 -0
- data/lib/sashite/epin/parser.rb +101 -0
- data/lib/sashite/epin.rb +45 -266
- data/lib/sashite-epin.rb +0 -11
- metadata +10 -5
- data/LICENSE.md +0 -21
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1490d9f28fb1193e779995a461a8af0c569077070fa4d84e698fe368dace28e7
|
|
4
|
+
data.tar.gz: cd58beb135dd350afc08ab316a9249dad8ba26a5a293a4ce092c705ffe621b94
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b811b8c087a3e98f0f960a316a293c239ebbb423401a33b3b5c45f1ba2533ff9625d5adcf304bba96c220febea73ef0ce139fd2554d1ce2f9555083dfee483b7
|
|
7
|
+
data.tar.gz: 3366a948b81091c255daa9111de74d6bc1e52a2e2597bd239ca0c834706b8fdc4695d833c5272074835bea397213eee999326cb62768692b6e9df824f0833510
|
data/README.md
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# epin.rb
|
|
2
2
|
|
|
3
3
|
[](https://github.com/sashite/epin.rb/tags)
|
|
4
4
|
[](https://rubydoc.info/github/sashite/epin.rb/main)
|
|
5
|
-
](https://github.com/sashite/epin.rb/raw/main/LICENSE
|
|
5
|
+
[](https://github.com/sashite/epin.rb/actions)
|
|
6
|
+
[](https://github.com/sashite/epin.rb/raw/main/LICENSE)
|
|
7
7
|
|
|
8
8
|
> **EPIN** (Extended Piece Identifier Notation) implementation for Ruby.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Overview
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
This library implements the [EPIN Specification v1.0.0](https://sashite.dev/specs/epin/1.0.0/).
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
This gem implements the [EPIN Specification v1.0.0](https://sashite.dev/specs/epin/1.0.0/) with a minimal compositional API.
|
|
14
|
+
EPIN extends [PIN](https://sashite.dev/specs/pin/1.0.0/) with an optional derivation marker (`'`) that flags whether a piece uses a native or derived style.
|
|
17
15
|
|
|
18
16
|
## Installation
|
|
19
17
|
|
|
@@ -28,67 +26,67 @@ Or install manually:
|
|
|
28
26
|
gem install sashite-epin
|
|
29
27
|
```
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
## Core Concept
|
|
29
|
+
## Dependencies
|
|
34
30
|
|
|
35
31
|
```ruby
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# EPIN is just PIN + derived flag
|
|
39
|
-
pin = Sashite::Pin.parse("K^")
|
|
40
|
-
epin = Sashite::Epin.new(pin)
|
|
41
|
-
|
|
42
|
-
epin.to_s # => "K^" (native)
|
|
43
|
-
epin.pin # => #<Sashite::Pin K^>
|
|
44
|
-
epin.derived # => false
|
|
45
|
-
|
|
46
|
-
# Mark as derived
|
|
47
|
-
derived_epin = epin.mark_derived
|
|
48
|
-
derived_epin.to_s # => "K^'" (derived from opposite side's style)
|
|
32
|
+
gem "sashite-pin" # Piece Identifier Notation
|
|
49
33
|
```
|
|
50
34
|
|
|
51
|
-
**That's it.** All piece attributes come from the PIN component.
|
|
52
|
-
|
|
53
35
|
## Usage
|
|
54
36
|
|
|
37
|
+
### Parsing (String → Identifier)
|
|
38
|
+
|
|
39
|
+
Convert an EPIN string into an `Identifier` object.
|
|
40
|
+
|
|
55
41
|
```ruby
|
|
56
42
|
require "sashite/epin"
|
|
57
43
|
|
|
58
|
-
#
|
|
44
|
+
# Standard parsing (raises on error)
|
|
59
45
|
epin = Sashite::Epin.parse("K^'")
|
|
60
|
-
epin.to_s
|
|
46
|
+
epin.to_s # => "K^'"
|
|
47
|
+
|
|
48
|
+
# Access PIN attributes through the component
|
|
49
|
+
epin.pin.abbr # => :K
|
|
50
|
+
epin.pin.side # => :first
|
|
51
|
+
epin.pin.state # => :normal
|
|
52
|
+
epin.pin.terminal? # => true
|
|
61
53
|
|
|
62
|
-
# Access
|
|
63
|
-
epin.
|
|
64
|
-
epin.
|
|
65
|
-
epin.pin.state # => :normal (Piece State)
|
|
66
|
-
epin.pin.terminal # => true (Terminal Status)
|
|
67
|
-
epin.derived # => true (Piece Style: derived vs native)
|
|
54
|
+
# Access derivation status
|
|
55
|
+
epin.derived? # => true
|
|
56
|
+
epin.native? # => false
|
|
68
57
|
|
|
69
|
-
# PIN component is a full Sashite::Pin instance
|
|
58
|
+
# PIN component is a full Sashite::Pin::Identifier instance
|
|
70
59
|
epin.pin.enhanced? # => false
|
|
71
|
-
epin.pin.letter # => "K"
|
|
72
60
|
epin.pin.first_player? # => true
|
|
61
|
+
|
|
62
|
+
# Invalid input raises ArgumentError
|
|
63
|
+
Sashite::Epin.parse("invalid") # => raises ArgumentError
|
|
73
64
|
```
|
|
74
65
|
|
|
75
|
-
###
|
|
66
|
+
### Formatting (Identifier → String)
|
|
76
67
|
|
|
77
|
-
|
|
78
|
-
# Parse from string
|
|
79
|
-
epin = Sashite::Epin.parse("K^") # Native
|
|
80
|
-
epin = Sashite::Epin.parse("K^'") # Derived
|
|
68
|
+
Convert an `Identifier` back to an EPIN string.
|
|
81
69
|
|
|
82
|
-
|
|
70
|
+
```ruby
|
|
71
|
+
# From PIN component
|
|
83
72
|
pin = Sashite::Pin.parse("K^")
|
|
84
|
-
epin = Sashite::Epin.new(pin)
|
|
85
|
-
epin
|
|
86
|
-
|
|
87
|
-
#
|
|
88
|
-
Sashite::Epin.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
73
|
+
epin = Sashite::Epin::Identifier.new(pin)
|
|
74
|
+
epin.to_s # => "K^"
|
|
75
|
+
|
|
76
|
+
# With derivation
|
|
77
|
+
epin = Sashite::Epin::Identifier.new(pin, derived: true)
|
|
78
|
+
epin.to_s # => "K^'"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Validation
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
# Boolean check
|
|
85
|
+
Sashite::Epin.valid?("K") # => true
|
|
86
|
+
Sashite::Epin.valid?("+R^'") # => true
|
|
87
|
+
Sashite::Epin.valid?("invalid") # => false
|
|
88
|
+
Sashite::Epin.valid?("K''") # => false
|
|
89
|
+
Sashite::Epin.valid?("K'^") # => false
|
|
92
90
|
```
|
|
93
91
|
|
|
94
92
|
### Accessing Components
|
|
@@ -97,16 +95,15 @@ Sashite::Epin.valid?("K'^") # => false (wrong order)
|
|
|
97
95
|
epin = Sashite::Epin.parse("+R^'")
|
|
98
96
|
|
|
99
97
|
# Get PIN component
|
|
100
|
-
epin.pin
|
|
101
|
-
epin.pin.to_s
|
|
98
|
+
epin.pin # => #<Sashite::Pin::Identifier +R^>
|
|
99
|
+
epin.pin.to_s # => "+R^"
|
|
102
100
|
|
|
103
101
|
# Check derivation
|
|
104
|
-
epin.derived
|
|
105
|
-
epin.
|
|
106
|
-
epin.native? # => false
|
|
102
|
+
epin.derived? # => true
|
|
103
|
+
epin.native? # => false
|
|
107
104
|
|
|
108
105
|
# Serialize
|
|
109
|
-
epin.to_s
|
|
106
|
+
epin.to_s # => "+R^'"
|
|
110
107
|
```
|
|
111
108
|
|
|
112
109
|
### Transformations
|
|
@@ -116,17 +113,13 @@ All transformations return new immutable instances.
|
|
|
116
113
|
```ruby
|
|
117
114
|
epin = Sashite::Epin.parse("K^")
|
|
118
115
|
|
|
119
|
-
#
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
# Mark as native
|
|
124
|
-
native = derived.unmark_derived
|
|
125
|
-
native.to_s # => "K^"
|
|
116
|
+
# Derivation transformations
|
|
117
|
+
epin.derive.to_s # => "K^'"
|
|
118
|
+
epin.native.to_s # => "K^"
|
|
126
119
|
|
|
127
|
-
#
|
|
128
|
-
|
|
129
|
-
|
|
120
|
+
# Replace PIN component
|
|
121
|
+
new_pin = Sashite::Pin.parse("+Q^")
|
|
122
|
+
epin.with_pin(new_pin).to_s # => "+Q^"
|
|
130
123
|
```
|
|
131
124
|
|
|
132
125
|
### Transform via PIN Component
|
|
@@ -134,33 +127,17 @@ toggled.to_s # => "K^'"
|
|
|
134
127
|
```ruby
|
|
135
128
|
epin = Sashite::Epin.parse("K^'")
|
|
136
129
|
|
|
137
|
-
#
|
|
138
|
-
|
|
139
|
-
epin.with_pin(new_pin).to_s # => "Q^'"
|
|
130
|
+
# Change abbr
|
|
131
|
+
epin.with_pin(epin.pin.with_abbr(:Q)).to_s # => "Q^'"
|
|
140
132
|
|
|
141
133
|
# Change state
|
|
142
|
-
|
|
143
|
-
epin.with_pin(new_pin).to_s # => "+K^'"
|
|
144
|
-
|
|
145
|
-
# Remove terminal marker
|
|
146
|
-
new_pin = epin.pin.unmark_terminal
|
|
147
|
-
epin.with_pin(new_pin).to_s # => "K'"
|
|
134
|
+
epin.with_pin(epin.pin.enhance).to_s # => "+K^'"
|
|
148
135
|
|
|
149
136
|
# Change side
|
|
150
|
-
|
|
151
|
-
epin.with_pin(new_pin).to_s # => "k^'"
|
|
152
|
-
```
|
|
137
|
+
epin.with_pin(epin.pin.flip).to_s # => "k^'"
|
|
153
138
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
```ruby
|
|
157
|
-
epin = Sashite::Epin.parse("K^")
|
|
158
|
-
|
|
159
|
-
# Transform PIN and derivation
|
|
160
|
-
new_pin = epin.pin.with_type(:Q).enhance
|
|
161
|
-
transformed = epin.with_pin(new_pin).mark_derived
|
|
162
|
-
|
|
163
|
-
transformed.to_s # => "+Q^'"
|
|
139
|
+
# Remove terminal
|
|
140
|
+
epin.with_pin(epin.pin.non_terminal).to_s # => "K'"
|
|
164
141
|
```
|
|
165
142
|
|
|
166
143
|
### Component Queries
|
|
@@ -170,255 +147,131 @@ Use the PIN API directly:
|
|
|
170
147
|
```ruby
|
|
171
148
|
epin = Sashite::Epin.parse("+P^'")
|
|
172
149
|
|
|
173
|
-
# PIN queries
|
|
174
|
-
epin.pin.
|
|
175
|
-
epin.pin.side
|
|
176
|
-
epin.pin.state
|
|
177
|
-
epin.pin.terminal
|
|
178
|
-
epin.pin.first_player?
|
|
179
|
-
epin.pin.enhanced?
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
epin.
|
|
183
|
-
|
|
184
|
-
# EPIN queries (style)
|
|
185
|
-
epin.derived? # => true
|
|
186
|
-
epin.native? # => false
|
|
150
|
+
# PIN queries
|
|
151
|
+
epin.pin.abbr # => :P
|
|
152
|
+
epin.pin.side # => :first
|
|
153
|
+
epin.pin.state # => :enhanced
|
|
154
|
+
epin.pin.terminal? # => true
|
|
155
|
+
epin.pin.first_player? # => true
|
|
156
|
+
epin.pin.enhanced? # => true
|
|
157
|
+
|
|
158
|
+
# EPIN queries
|
|
159
|
+
epin.derived? # => true
|
|
160
|
+
epin.native? # => false
|
|
187
161
|
|
|
188
162
|
# Compare EPINs
|
|
189
163
|
other = Sashite::Epin.parse("+P^")
|
|
190
|
-
epin.pin.
|
|
191
|
-
epin.pin.same_state?(other.pin) # => true
|
|
192
|
-
epin.same_derived?(other) # => false
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
## Five Fundamental Attributes
|
|
196
|
-
|
|
197
|
-
EPIN exposes all five attributes from the [Sashité Game Protocol](https://sashite.dev/game-protocol/):
|
|
198
|
-
|
|
199
|
-
| Protocol Attribute | EPIN Access | Example |
|
|
200
|
-
|-------------------|-------------|---------|
|
|
201
|
-
| **Piece Name** | `epin.pin.type` | `:K` (King), `:R` (Rook) |
|
|
202
|
-
| **Piece Side** | `epin.pin.side` | `:first`, `:second` |
|
|
203
|
-
| **Piece State** | `epin.pin.state` | `:normal`, `:enhanced`, `:diminished` |
|
|
204
|
-
| **Terminal Status** | `epin.pin.terminal` | `true`, `false` |
|
|
205
|
-
| **Piece Style** | `epin.derived` | `false` (native), `true` (derived) |
|
|
206
|
-
|
|
207
|
-
## Format Specification
|
|
208
|
-
|
|
209
|
-
### Structure
|
|
210
|
-
|
|
211
|
-
```
|
|
212
|
-
<pin>[']
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
Where:
|
|
216
|
-
- `<pin>` is any valid PIN token
|
|
217
|
-
- `'` is the optional derivation marker
|
|
218
|
-
|
|
219
|
-
### Grammar (EBNF)
|
|
220
|
-
|
|
221
|
-
```ebnf
|
|
222
|
-
epin ::= pin | pin "'"
|
|
223
|
-
pin ::= ["+" | "-"] letter ["^"]
|
|
224
|
-
letter ::= "A" | ... | "Z" | "a" | ... | "z"
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### Regular Expression
|
|
228
|
-
|
|
229
|
-
```ruby
|
|
230
|
-
/\A[-+]?[A-Za-z]\^?'?\z/
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
### Examples
|
|
234
|
-
|
|
235
|
-
| EPIN | Side | State | Terminal | Derived | Description |
|
|
236
|
-
|------|------|-------|----------|---------|-------------|
|
|
237
|
-
| `K` | First | Normal | No | No | Standard native king |
|
|
238
|
-
| `K'` | First | Normal | No | Yes | Derived king |
|
|
239
|
-
| `K^` | First | Normal | Yes | No | Terminal native king |
|
|
240
|
-
| `K^'` | First | Normal | Yes | Yes | Terminal derived king |
|
|
241
|
-
| `+R'` | First | Enhanced | No | Yes | Enhanced derived rook |
|
|
242
|
-
| `-p` | Second | Diminished | No | No | Diminished native pawn |
|
|
243
|
-
|
|
244
|
-
## Cross-Style Game Example
|
|
245
|
-
|
|
246
|
-
In a chess-vs-makruk cross-style match where:
|
|
247
|
-
- First side native style = chess
|
|
248
|
-
- Second side native style = makruk
|
|
249
|
-
|
|
250
|
-
```ruby
|
|
251
|
-
# First player pieces
|
|
252
|
-
chess_king = Sashite::Epin.parse("K^") # Native Chess king
|
|
253
|
-
makruk_pawn = Sashite::Epin.parse("P'") # Derived Makruk pawn (foreign)
|
|
254
|
-
|
|
255
|
-
chess_king.native? # => true (uses own style)
|
|
256
|
-
makruk_pawn.derived? # => true (uses opponent's style)
|
|
257
|
-
|
|
258
|
-
# Second player pieces
|
|
259
|
-
makruk_king = Sashite::Epin.parse("k^") # Native Makruk king
|
|
260
|
-
chess_pawn = Sashite::Epin.parse("p'") # Derived Chess pawn (foreign)
|
|
261
|
-
|
|
262
|
-
makruk_king.native? # => true
|
|
263
|
-
chess_pawn.derived? # => true
|
|
164
|
+
epin.pin.same_abbr?(other.pin) # => true
|
|
165
|
+
epin.pin.same_state?(other.pin) # => true
|
|
166
|
+
epin.same_derived?(other) # => false
|
|
264
167
|
```
|
|
265
168
|
|
|
266
169
|
## API Reference
|
|
267
170
|
|
|
268
|
-
###
|
|
171
|
+
### Types
|
|
269
172
|
|
|
270
173
|
```ruby
|
|
271
|
-
|
|
272
|
-
Sashite::Epin
|
|
174
|
+
# Identifier represents a parsed EPIN combining PIN with derivation status.
|
|
175
|
+
class Sashite::Epin::Identifier
|
|
176
|
+
# Creates an Identifier from a PIN component.
|
|
177
|
+
# Raises ArgumentError if the PIN is invalid.
|
|
178
|
+
#
|
|
179
|
+
# @param pin [Sashite::Pin::Identifier] PIN component
|
|
180
|
+
# @param derived [Boolean] Derived status
|
|
181
|
+
# @return [Identifier]
|
|
182
|
+
def initialize(pin, derived: false)
|
|
183
|
+
|
|
184
|
+
# Returns the PIN component.
|
|
185
|
+
#
|
|
186
|
+
# @return [Sashite::Pin::Identifier]
|
|
187
|
+
def pin
|
|
188
|
+
|
|
189
|
+
# Returns true if derived style.
|
|
190
|
+
#
|
|
191
|
+
# @return [Boolean]
|
|
192
|
+
def derived?
|
|
193
|
+
|
|
194
|
+
# Returns true if native style.
|
|
195
|
+
#
|
|
196
|
+
# @return [Boolean]
|
|
197
|
+
def native?
|
|
198
|
+
|
|
199
|
+
# Returns the EPIN string representation.
|
|
200
|
+
#
|
|
201
|
+
# @return [String]
|
|
202
|
+
def to_s
|
|
203
|
+
end
|
|
273
204
|
```
|
|
274
205
|
|
|
275
|
-
###
|
|
206
|
+
### Parsing
|
|
276
207
|
|
|
277
208
|
```ruby
|
|
278
|
-
|
|
279
|
-
|
|
209
|
+
# Parses an EPIN string into an Identifier.
|
|
210
|
+
# Raises ArgumentError if the string is not valid.
|
|
211
|
+
#
|
|
212
|
+
# @param string [String] EPIN string
|
|
213
|
+
# @return [Identifier]
|
|
214
|
+
# @raise [ArgumentError] if invalid
|
|
215
|
+
def Sashite::Epin.parse(string)
|
|
280
216
|
```
|
|
281
217
|
|
|
282
|
-
###
|
|
218
|
+
### Validation
|
|
283
219
|
|
|
284
220
|
```ruby
|
|
285
|
-
|
|
221
|
+
# Reports whether string is a valid EPIN.
|
|
222
|
+
#
|
|
223
|
+
# @param string [String] EPIN string
|
|
224
|
+
# @return [Boolean]
|
|
225
|
+
def Sashite::Epin.valid?(string)
|
|
286
226
|
```
|
|
287
227
|
|
|
288
228
|
### Transformations
|
|
289
229
|
|
|
290
|
-
All transformations return new `Sashite::Epin` instances:
|
|
291
|
-
|
|
292
230
|
```ruby
|
|
293
|
-
# PIN replacement
|
|
294
|
-
|
|
231
|
+
# PIN replacement (returns new Identifier)
|
|
232
|
+
def with_pin(new_pin) # => Identifier with different PIN
|
|
295
233
|
|
|
296
|
-
# Derivation
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
epin.with_derived(boolean) # => Sashite::Epin with specified derivation
|
|
234
|
+
# Derivation transformations
|
|
235
|
+
def derive # => Identifier with derived: true
|
|
236
|
+
def native # => Identifier with derived: false
|
|
300
237
|
```
|
|
301
238
|
|
|
302
|
-
###
|
|
303
|
-
|
|
304
|
-
```ruby
|
|
305
|
-
# Derivation
|
|
306
|
-
epin.derived? # => true if derived
|
|
307
|
-
epin.native? # => true if not derived
|
|
308
|
-
|
|
309
|
-
# Comparison
|
|
310
|
-
epin.same_derived?(other) # => true if same derivation status
|
|
311
|
-
```
|
|
239
|
+
### Errors
|
|
312
240
|
|
|
313
|
-
|
|
241
|
+
All parsing and validation errors raise `ArgumentError` with descriptive messages:
|
|
314
242
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
```
|
|
243
|
+
| Message | Cause |
|
|
244
|
+
|---------|-------|
|
|
245
|
+
| `"invalid derivation marker"` | Derivation marker misplaced or duplicated |
|
|
246
|
+
| `"invalid PIN component: ..."` | PIN parsing failed |
|
|
320
247
|
|
|
321
|
-
##
|
|
248
|
+
## PIN Compatibility
|
|
322
249
|
|
|
323
|
-
|
|
250
|
+
Every valid PIN is a valid EPIN (native by default):
|
|
324
251
|
|
|
325
252
|
```ruby
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
pin.state # Piece State
|
|
331
|
-
pin.terminal # Terminal Status
|
|
332
|
-
|
|
333
|
-
# EPIN: 5 attributes (PIN + style)
|
|
334
|
-
epin = Sashite::Epin.parse("K^'")
|
|
335
|
-
epin.pin.type # Piece Name
|
|
336
|
-
epin.pin.side # Piece Side
|
|
337
|
-
epin.pin.state # Piece State
|
|
338
|
-
epin.pin.terminal # Terminal Status
|
|
339
|
-
epin.derived # Piece Style (5th attribute)
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
### When to Use EPIN vs PIN
|
|
343
|
-
|
|
344
|
-
**Use PIN when:**
|
|
345
|
-
- Single-style games (both players use same style)
|
|
346
|
-
- Style information not needed
|
|
347
|
-
- Maximum compatibility required
|
|
348
|
-
|
|
349
|
-
**Use EPIN when:**
|
|
350
|
-
- Cross-style games (different styles per player)
|
|
351
|
-
- Pieces can change style (promotion to foreign piece)
|
|
352
|
-
- Need to track native vs derived pieces
|
|
353
|
-
|
|
354
|
-
## Design Principles
|
|
355
|
-
|
|
356
|
-
### 1. Pure Composition
|
|
357
|
-
|
|
358
|
-
EPIN doesn't reimplement PIN features — it extends PIN minimally:
|
|
359
|
-
|
|
360
|
-
```ruby
|
|
361
|
-
def initialize(pin, derived: false)
|
|
362
|
-
@pin = pin
|
|
363
|
-
@derived = !!derived
|
|
364
|
-
freeze
|
|
253
|
+
%w[K +R -p K^ +R^].each do |pin_token|
|
|
254
|
+
epin = Sashite::Epin.parse(pin_token)
|
|
255
|
+
epin.native? # => true
|
|
256
|
+
epin.to_s # => pin_token
|
|
365
257
|
end
|
|
366
258
|
```
|
|
367
259
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
**6 core methods only:**
|
|
371
|
-
1. `new` — create from PIN
|
|
372
|
-
2. `pin` — get PIN component
|
|
373
|
-
3. `derived` / `derived?` — check derivation
|
|
374
|
-
4. `to_s` — serialize
|
|
375
|
-
5. `with_pin` — replace PIN
|
|
376
|
-
6. `with_derived` / `mark_derived` / `unmark_derived` — change derivation
|
|
377
|
-
|
|
378
|
-
Everything else uses the PIN API directly.
|
|
379
|
-
|
|
380
|
-
### 3. Component Transparency
|
|
381
|
-
|
|
382
|
-
Access PIN directly — no wrappers:
|
|
383
|
-
|
|
384
|
-
```ruby
|
|
385
|
-
# Use PIN API directly
|
|
386
|
-
epin.pin.type
|
|
387
|
-
epin.pin.with_type(:Q)
|
|
388
|
-
epin.pin.enhanced?
|
|
389
|
-
epin.pin.flip
|
|
390
|
-
|
|
391
|
-
# No need for wrapper methods like:
|
|
392
|
-
# epin.type
|
|
393
|
-
# epin.with_type(:Q)
|
|
394
|
-
# epin.enhanced?
|
|
395
|
-
# epin.flip
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
### 4. Backward Compatibility
|
|
260
|
+
## Design Principles
|
|
399
261
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
epin = Sashite::Epin.parse(token)
|
|
406
|
-
epin.native? # => true
|
|
407
|
-
epin.to_s # => token
|
|
408
|
-
end
|
|
409
|
-
```
|
|
262
|
+
- **Pure composition**: EPIN composes PIN without reimplementing features
|
|
263
|
+
- **Minimal API**: Core methods (`pin`, `derived?`, `native?`, `to_s`) plus transformations
|
|
264
|
+
- **Component transparency**: Access PIN directly, no wrapper methods
|
|
265
|
+
- **Immutable identifiers**: Frozen instances prevent mutation
|
|
266
|
+
- **Ruby idioms**: `valid?` predicate, `to_s` conversion, `ArgumentError` for invalid input
|
|
410
267
|
|
|
411
268
|
## Related Specifications
|
|
412
269
|
|
|
413
|
-
- [
|
|
270
|
+
- [Game Protocol](https://sashite.dev/game-protocol/) — Conceptual foundation
|
|
271
|
+
- [EPIN Specification](https://sashite.dev/specs/epin/1.0.0/) — Official specification
|
|
414
272
|
- [EPIN Examples](https://sashite.dev/specs/epin/1.0.0/examples/) — Usage examples
|
|
415
|
-
- [PIN Specification
|
|
416
|
-
- [Sashité Game Protocol](https://sashite.dev/game-protocol/) — Foundation
|
|
273
|
+
- [PIN Specification](https://sashite.dev/specs/pin/1.0.0/) — Base component
|
|
417
274
|
|
|
418
275
|
## License
|
|
419
276
|
|
|
420
|
-
Available as open source under the [
|
|
421
|
-
|
|
422
|
-
## About
|
|
423
|
-
|
|
424
|
-
Maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of board game cultures.
|
|
277
|
+
Available as open source under the [Apache License 2.0](https://opensource.org/licenses/Apache-2.0).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sashite
|
|
4
|
+
module Epin
|
|
5
|
+
# Constants for EPIN (Extended Piece Identifier Notation).
|
|
6
|
+
#
|
|
7
|
+
# EPIN extends PIN with a single derivation marker.
|
|
8
|
+
# PIN constants (VALID_TYPES, VALID_SIDES, VALID_STATES, etc.)
|
|
9
|
+
# are accessed through the sashite-pin dependency.
|
|
10
|
+
module Constants
|
|
11
|
+
# Derivation marker suffix.
|
|
12
|
+
DERIVATION_SUFFIX = "'"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sashite
|
|
4
|
+
module Epin
|
|
5
|
+
module Errors
|
|
6
|
+
class Argument < ::ArgumentError
|
|
7
|
+
# Centralized error messages for EPIN parsing and validation.
|
|
8
|
+
#
|
|
9
|
+
# PIN-related errors (empty input, must contain exactly one letter, etc.)
|
|
10
|
+
# are propagated from the sashite-pin dependency.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# raise Errors::Argument, Messages::INVALID_DERIVATION_MARKER
|
|
14
|
+
module Messages
|
|
15
|
+
# Parsing error
|
|
16
|
+
INVALID_DERIVATION_MARKER = "invalid derivation marker"
|
|
17
|
+
|
|
18
|
+
# Validation errors (constructor)
|
|
19
|
+
INVALID_PIN = "pin must be a Sashite::Pin::Identifier"
|
|
20
|
+
INVALID_DERIVED = "derived must be true or false"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "argument/messages"
|
|
4
|
+
|
|
5
|
+
module Sashite
|
|
6
|
+
module Epin
|
|
7
|
+
module Errors
|
|
8
|
+
# Error raised when EPIN parsing or validation fails.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# raise Argument, Argument::Messages::INVALID_DERIVATION_MARKER
|
|
12
|
+
class Argument < ::ArgumentError
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|