sashite-epin 1.2.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 +509 -396
- data/lib/sashite/epin/identifier.rb +168 -463
- data/lib/sashite/epin.rb +195 -54
- metadata +1 -1
data/README.md
CHANGED
|
@@ -9,9 +9,29 @@
|
|
|
9
9
|
|
|
10
10
|
## What is EPIN?
|
|
11
11
|
|
|
12
|
-
EPIN (Extended Piece Identifier Notation) extends [PIN
|
|
12
|
+
EPIN (Extended Piece Identifier Notation) extends [PIN](https://sashite.dev/specs/pin/1.0.0/) by adding a **derivation marker** to track piece style in cross-style games.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
**EPIN is simply: PIN + optional style derivation marker (`'`)**
|
|
15
|
+
|
|
16
|
+
This gem implements the [EPIN Specification v1.0.0](https://sashite.dev/specs/epin/1.0.0/) with a minimal compositional API.
|
|
17
|
+
|
|
18
|
+
## Core Concept
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
# EPIN is just PIN + derived flag
|
|
22
|
+
pin = Sashite::Pin.parse("K^")
|
|
23
|
+
epin = Sashite::Epin.new(pin, derived: false)
|
|
24
|
+
|
|
25
|
+
epin.to_s # => "K^" (native)
|
|
26
|
+
epin.pin # => PIN::Identifier instance
|
|
27
|
+
epin.derived? # => false
|
|
28
|
+
|
|
29
|
+
# Mark as derived
|
|
30
|
+
derived_epin = epin.mark_derived
|
|
31
|
+
derived_epin.to_s # => "K^'" (derived from opposite side's style)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**That's it.** All piece attributes come from the PIN component.
|
|
15
35
|
|
|
16
36
|
## Installation
|
|
17
37
|
|
|
@@ -26,515 +46,608 @@ Or install manually:
|
|
|
26
46
|
gem install sashite-epin
|
|
27
47
|
```
|
|
28
48
|
|
|
29
|
-
##
|
|
49
|
+
## Dependencies
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
gem "sashite-pin" # Piece Identifier Notation
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
30
56
|
|
|
31
57
|
```ruby
|
|
32
58
|
require "sashite/epin"
|
|
33
59
|
|
|
34
|
-
# Parse EPIN
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
identifier.type # => :K
|
|
38
|
-
identifier.side # => :first
|
|
39
|
-
identifier.state # => :normal
|
|
40
|
-
identifier.native? # => true
|
|
41
|
-
|
|
42
|
-
# Create identifiers directly
|
|
43
|
-
identifier = Sashite::Epin::Identifier.new(:R, :second, :enhanced, false) # => #<Epin::Identifier type=:R side=:second state=:enhanced native=false>
|
|
44
|
-
|
|
45
|
-
# Validate EPIN strings
|
|
46
|
-
Sashite::Epin.valid?("K") # => true
|
|
47
|
-
Sashite::Epin.valid?("+R'") # => true
|
|
48
|
-
Sashite::Epin.valid?("invalid") # => false
|
|
49
|
-
|
|
50
|
-
# Style derivation with apostrophe suffix
|
|
51
|
-
native_king = Sashite::Epin.parse("K") # => #<Epin::Identifier type=:K side=:first state=:normal native=true>
|
|
52
|
-
foreign_king = Sashite::Epin.parse("K'") # => #<Epin::Identifier type=:K side=:first state=:normal native=false>
|
|
53
|
-
|
|
54
|
-
native_king.to_s # => "K"
|
|
55
|
-
foreign_king.to_s # => "K'"
|
|
56
|
-
|
|
57
|
-
# State manipulation (returns new immutable instances)
|
|
58
|
-
enhanced = identifier.enhance # => #<Epin::Identifier type=:K side=:first state=:enhanced native=true>
|
|
59
|
-
enhanced.to_s # => "+K"
|
|
60
|
-
diminished = identifier.diminish # => #<Epin::Identifier type=:K side=:first state=:diminished native=true>
|
|
61
|
-
diminished.to_s # => "-K"
|
|
62
|
-
|
|
63
|
-
# Style derivation manipulation
|
|
64
|
-
foreign_piece = identifier.derive # => #<Epin::Identifier type=:K side=:first state=:normal native=false>
|
|
65
|
-
foreign_piece.to_s # => "K'"
|
|
66
|
-
back_to_native = foreign_piece.underive # => #<Epin::Identifier type=:K side=:first state=:normal native=true>
|
|
67
|
-
back_to_native.to_s # => "K"
|
|
68
|
-
|
|
69
|
-
# Side manipulation
|
|
70
|
-
flipped = identifier.flip # => #<Epin::Identifier type=:K side=:second state=:normal native=true>
|
|
71
|
-
flipped.to_s # => "k"
|
|
72
|
-
|
|
73
|
-
# Type manipulation
|
|
74
|
-
queen = identifier.with_type(:Q) # => #<Epin::Identifier type=:Q side=:first state=:normal native=true>
|
|
75
|
-
queen.to_s # => "Q"
|
|
76
|
-
|
|
77
|
-
# Style queries
|
|
78
|
-
identifier.native? # => true
|
|
79
|
-
foreign_king.derived? # => true
|
|
80
|
-
foreign_king.foreign? # => true (alias for derived?)
|
|
81
|
-
|
|
82
|
-
# State queries
|
|
83
|
-
identifier.normal? # => true
|
|
84
|
-
enhanced.enhanced? # => true
|
|
85
|
-
diminished.diminished? # => true
|
|
86
|
-
|
|
87
|
-
# Side queries
|
|
88
|
-
identifier.first_player? # => true
|
|
89
|
-
flipped.second_player? # => true
|
|
90
|
-
|
|
91
|
-
# Attribute access
|
|
92
|
-
identifier.letter # => "K"
|
|
93
|
-
enhanced.prefix # => "+"
|
|
94
|
-
foreign_king.suffix # => "'"
|
|
95
|
-
identifier.suffix # => ""
|
|
96
|
-
|
|
97
|
-
# Type and side comparison
|
|
98
|
-
king1 = Sashite::Epin.parse("K")
|
|
99
|
-
king2 = Sashite::Epin.parse("k")
|
|
100
|
-
queen = Sashite::Epin.parse("Q")
|
|
101
|
-
|
|
102
|
-
king1.same_type?(king2) # => true (both kings)
|
|
103
|
-
king1.same_side?(queen) # => true (both first player)
|
|
104
|
-
king1.same_type?(queen) # => false (different types)
|
|
105
|
-
|
|
106
|
-
# Style comparison
|
|
107
|
-
native_king = Sashite::Epin.parse("K")
|
|
108
|
-
foreign_king = Sashite::Epin.parse("K'")
|
|
109
|
-
|
|
110
|
-
native_king.same_style?(foreign_king) # => false (different derivation)
|
|
111
|
-
|
|
112
|
-
# Functional transformations can be chained
|
|
113
|
-
pawn = Sashite::Epin.parse("P")
|
|
114
|
-
enemy_foreign_promoted = pawn.flip.derive.enhance # => "+p'" (second player foreign promoted pawn)
|
|
115
|
-
```
|
|
60
|
+
# Parse an EPIN string
|
|
61
|
+
epin = Sashite::Epin.parse("K^'")
|
|
62
|
+
epin.to_s # => "K^'"
|
|
116
63
|
|
|
117
|
-
|
|
64
|
+
# Access five fundamental attributes through PIN component + derived flag
|
|
65
|
+
epin.pin.type # => :K (Piece Name)
|
|
66
|
+
epin.pin.side # => :first (Piece Side)
|
|
67
|
+
epin.pin.state # => :normal (Piece State)
|
|
68
|
+
epin.pin.terminal? # => true (Terminal Status)
|
|
69
|
+
epin.derived? # => true (Piece Style: derived vs native)
|
|
118
70
|
|
|
119
|
-
|
|
71
|
+
# PIN component is a full PIN::Identifier
|
|
72
|
+
epin.pin.enhanced? # => false
|
|
73
|
+
epin.pin.letter # => "K"
|
|
120
74
|
```
|
|
121
|
-
|
|
75
|
+
|
|
76
|
+
## Basic Usage
|
|
77
|
+
|
|
78
|
+
### Creating Identifiers
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
# Parse from string
|
|
82
|
+
epin = Sashite::Epin.parse("K^") # Native
|
|
83
|
+
epin = Sashite::Epin.parse("K^'") # Derived
|
|
84
|
+
|
|
85
|
+
# Create from PIN component
|
|
86
|
+
pin = Sashite::Pin.parse("K^")
|
|
87
|
+
epin = Sashite::Epin.new(pin, derived: false) # Native
|
|
88
|
+
epin = Sashite::Epin.new(pin, derived: true) # Derived
|
|
89
|
+
|
|
90
|
+
# Validate
|
|
91
|
+
Sashite::Epin.valid?("K^") # => true
|
|
92
|
+
Sashite::Epin.valid?("K^'") # => true
|
|
93
|
+
Sashite::Epin.valid?("K^''") # => false (multiple markers)
|
|
122
94
|
```
|
|
123
95
|
|
|
124
|
-
|
|
96
|
+
### Accessing Components
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
epin = Sashite::Epin.parse("+R^'")
|
|
100
|
+
|
|
101
|
+
# Get PIN component
|
|
102
|
+
epin.pin # => #<Pin::Identifier type=:R state=:enhanced terminal=true>
|
|
103
|
+
epin.pin.to_s # => "+R^"
|
|
125
104
|
|
|
126
|
-
|
|
105
|
+
# Check derivation
|
|
106
|
+
epin.derived? # => true
|
|
127
107
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
- Lowercase: Second player pieces
|
|
132
|
-
- **State** (optional prefix):
|
|
133
|
-
- `+`: Enhanced state (promoted, upgraded, empowered)
|
|
134
|
-
- `-`: Diminished state (weakened, restricted, temporary)
|
|
135
|
-
- No prefix: Normal state
|
|
108
|
+
# Serialize
|
|
109
|
+
epin.to_s # => "+R^'"
|
|
110
|
+
```
|
|
136
111
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
112
|
+
### Five Fundamental Attributes
|
|
113
|
+
|
|
114
|
+
All attributes accessible via PIN component + derived flag:
|
|
140
115
|
|
|
141
|
-
### Regular Expression
|
|
142
116
|
```ruby
|
|
143
|
-
|
|
117
|
+
epin = Sashite::Epin.parse("+R^'")
|
|
118
|
+
|
|
119
|
+
# From PIN component (4 attributes)
|
|
120
|
+
epin.pin.type # => :R (Piece Name)
|
|
121
|
+
epin.pin.side # => :first (Piece Side)
|
|
122
|
+
epin.pin.state # => :enhanced (Piece State)
|
|
123
|
+
epin.pin.terminal? # => true (Terminal Status)
|
|
124
|
+
|
|
125
|
+
# From EPIN (5th attribute)
|
|
126
|
+
epin.derived? # => true (Piece Style: native vs derived)
|
|
144
127
|
```
|
|
145
128
|
|
|
146
|
-
|
|
147
|
-
- `K` - First player king (native style, normal state)
|
|
148
|
-
- `k'` - Second player king (foreign style, normal state)
|
|
149
|
-
- `+R'` - First player rook (foreign style, enhanced state)
|
|
150
|
-
- `-p` - Second player pawn (native style, diminished state)
|
|
129
|
+
## Transformations
|
|
151
130
|
|
|
152
|
-
|
|
131
|
+
All transformations return new immutable instances:
|
|
153
132
|
|
|
154
|
-
###
|
|
133
|
+
### Change Derivation Status
|
|
155
134
|
|
|
156
135
|
```ruby
|
|
157
|
-
|
|
158
|
-
# Native styles: first=Chess, second=Shōgi
|
|
159
|
-
|
|
160
|
-
# Native pieces (no derivation suffix)
|
|
161
|
-
white_king = Sashite::Epin.identifier(:K, :first, :normal, true) # => "K" (Chess king)
|
|
162
|
-
black_king = Sashite::Epin.identifier(:K, :second, :normal, true) # => "k" (Shōgi king)
|
|
136
|
+
epin = Sashite::Epin.parse("K^")
|
|
163
137
|
|
|
164
|
-
#
|
|
165
|
-
|
|
166
|
-
|
|
138
|
+
# Mark as derived
|
|
139
|
+
derived = epin.mark_derived
|
|
140
|
+
derived.to_s # => "K^'"
|
|
167
141
|
|
|
168
|
-
#
|
|
169
|
-
|
|
170
|
-
|
|
142
|
+
# Mark as native
|
|
143
|
+
native = derived.unmark_native
|
|
144
|
+
native.to_s # => "K^"
|
|
171
145
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
black_promoted_pawn.native? # => true
|
|
146
|
+
# Toggle
|
|
147
|
+
toggled = epin.with_derived(!epin.derived?)
|
|
148
|
+
toggled.to_s # => "K^'"
|
|
176
149
|
```
|
|
177
150
|
|
|
178
|
-
###
|
|
151
|
+
### Transform via PIN Component
|
|
179
152
|
|
|
180
153
|
```ruby
|
|
181
|
-
|
|
182
|
-
# All pieces are native, so EPIN behaves exactly like PIN
|
|
154
|
+
epin = Sashite::Epin.parse("K^'")
|
|
183
155
|
|
|
184
|
-
|
|
185
|
-
|
|
156
|
+
# Replace PIN component
|
|
157
|
+
new_pin = epin.pin.with_type(:Q)
|
|
158
|
+
epin.with_pin(new_pin).to_s # => "Q^'"
|
|
186
159
|
|
|
187
|
-
|
|
188
|
-
|
|
160
|
+
# Change type
|
|
161
|
+
epin.with_pin(epin.pin.with_type(:Q)).to_s # => "Q^'"
|
|
189
162
|
|
|
190
|
-
#
|
|
191
|
-
|
|
192
|
-
|
|
163
|
+
# Change state
|
|
164
|
+
epin.with_pin(epin.pin.with_state(:enhanced)).to_s # => "+K^'"
|
|
165
|
+
|
|
166
|
+
# Remove terminal marker
|
|
167
|
+
epin.with_pin(epin.pin.with_terminal(false)).to_s # => "K'"
|
|
168
|
+
|
|
169
|
+
# Change side
|
|
170
|
+
epin.with_pin(epin.pin.flip).to_s # => "k^'"
|
|
193
171
|
```
|
|
194
172
|
|
|
195
|
-
###
|
|
173
|
+
### Multiple Transformations
|
|
196
174
|
|
|
197
175
|
```ruby
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
176
|
+
epin = Sashite::Epin.parse("K^")
|
|
177
|
+
|
|
178
|
+
# Transform PIN and derivation
|
|
179
|
+
transformed = epin
|
|
180
|
+
.with_pin(epin.pin.with_type(:Q).with_state(:enhanced))
|
|
181
|
+
.mark_derived
|
|
182
|
+
|
|
183
|
+
transformed.to_s # => "+Q^'"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Component Queries
|
|
201
187
|
|
|
202
|
-
|
|
203
|
-
captured.to_s # => "P" (white native pawn)
|
|
188
|
+
Use the PIN component API directly:
|
|
204
189
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
190
|
+
```ruby
|
|
191
|
+
epin = Sashite::Epin.parse("+P^'")
|
|
192
|
+
|
|
193
|
+
# PIN queries (name, side, state, terminal)
|
|
194
|
+
epin.pin.type # => :P
|
|
195
|
+
epin.pin.side # => :first
|
|
196
|
+
epin.pin.state # => :enhanced
|
|
197
|
+
epin.pin.terminal? # => true
|
|
198
|
+
epin.pin.first_player? # => true
|
|
199
|
+
epin.pin.enhanced? # => true
|
|
200
|
+
epin.pin.letter # => "P"
|
|
201
|
+
epin.pin.prefix # => "+"
|
|
202
|
+
epin.pin.suffix # => "^"
|
|
203
|
+
|
|
204
|
+
# EPIN queries (style)
|
|
205
|
+
epin.derived? # => true
|
|
206
|
+
epin.native? # => false
|
|
207
|
+
|
|
208
|
+
# Compare EPINs
|
|
209
|
+
other = Sashite::Epin.parse("+P^")
|
|
210
|
+
epin.pin.same_type?(other.pin) # => true (both P)
|
|
211
|
+
epin.pin.same_state?(other.pin) # => true (both enhanced)
|
|
212
|
+
epin.same_derivation?(other) # => false (different derivation)
|
|
209
213
|
```
|
|
210
214
|
|
|
211
215
|
## API Reference
|
|
212
216
|
|
|
213
|
-
### Main Module
|
|
217
|
+
### Main Module
|
|
218
|
+
|
|
219
|
+
```ruby
|
|
220
|
+
# Parse EPIN string
|
|
221
|
+
Sashite::Epin.parse(epin_string) # => Epin::Identifier
|
|
222
|
+
|
|
223
|
+
# Create from PIN component
|
|
224
|
+
Sashite::Epin.new(pin, derived: false) # => Epin::Identifier
|
|
214
225
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
226
|
+
# Validate string
|
|
227
|
+
Sashite::Epin.valid?(epin_string) # => Boolean
|
|
228
|
+
```
|
|
218
229
|
|
|
219
230
|
### Identifier Class
|
|
220
231
|
|
|
221
|
-
####
|
|
222
|
-
- `Sashite::Epin::Identifier.new(type, side, state = :normal, native = true)` - Create identifier instance
|
|
223
|
-
- `Sashite::Epin::Identifier.parse(epin_string)` - Parse EPIN string (same as module method)
|
|
224
|
-
- `Sashite::Epin::Identifier.valid?(epin_string)` - Validate EPIN string (class method)
|
|
225
|
-
|
|
226
|
-
#### Attribute Access
|
|
227
|
-
- `#type` - Get piece type (symbol :A to :Z, always uppercase)
|
|
228
|
-
- `#side` - Get player side (:first or :second)
|
|
229
|
-
- `#state` - Get state (:normal, :enhanced, or :diminished)
|
|
230
|
-
- `#native` - Get style derivation (true for native, false for foreign)
|
|
231
|
-
- `#letter` - Get letter representation (string, case determined by side)
|
|
232
|
-
- `#prefix` - Get state prefix (string: "+", "-", or "")
|
|
233
|
-
- `#suffix` - Get derivation suffix (string: "'" or "")
|
|
234
|
-
- `#to_s` - Convert to EPIN string representation
|
|
235
|
-
|
|
236
|
-
#### Style Queries
|
|
237
|
-
- `#native?` - Check if native style (current side's native style)
|
|
238
|
-
- `#derived?` - Check if foreign style (opposite side's native style)
|
|
239
|
-
- `#foreign?` - Alias for `#derived?`
|
|
240
|
-
|
|
241
|
-
#### State Queries
|
|
242
|
-
- `#normal?` - Check if normal state (no modifiers)
|
|
243
|
-
- `#enhanced?` - Check if enhanced state
|
|
244
|
-
- `#diminished?` - Check if diminished state
|
|
245
|
-
|
|
246
|
-
#### Side Queries
|
|
247
|
-
- `#first_player?` - Check if first player identifier
|
|
248
|
-
- `#second_player?` - Check if second player identifier
|
|
249
|
-
|
|
250
|
-
#### State Transformations (immutable - return new instances)
|
|
251
|
-
- `#enhance` - Create enhanced version
|
|
252
|
-
- `#unenhance` - Remove enhanced state
|
|
253
|
-
- `#diminish` - Create diminished version
|
|
254
|
-
- `#undiminish` - Remove diminished state
|
|
255
|
-
- `#normalize` - Remove all state modifiers
|
|
256
|
-
|
|
257
|
-
#### Style Transformations (immutable - return new instances)
|
|
258
|
-
- `#derive` - Convert to foreign style (add derivation suffix)
|
|
259
|
-
- `#underive` - Convert to native style (remove derivation suffix)
|
|
260
|
-
- `#flip` - Switch player (change side)
|
|
261
|
-
|
|
262
|
-
#### Attribute Transformations (immutable - return new instances)
|
|
263
|
-
- `#with_type(new_type)` - Create identifier with different type
|
|
264
|
-
- `#with_side(new_side)` - Create identifier with different side
|
|
265
|
-
- `#with_state(new_state)` - Create identifier with different state
|
|
266
|
-
- `#with_derivation(native)` - Create identifier with different derivation
|
|
267
|
-
|
|
268
|
-
#### Comparison Methods
|
|
269
|
-
- `#same_type?(other)` - Check if same piece type
|
|
270
|
-
- `#same_side?(other)` - Check if same side
|
|
271
|
-
- `#same_state?(other)` - Check if same state
|
|
272
|
-
- `#same_style?(other)` - Check if same style derivation
|
|
273
|
-
- `#==(other)` - Full equality comparison
|
|
274
|
-
|
|
275
|
-
### Constants
|
|
276
|
-
- `Sashite::Epin::Identifier::NATIVE` - Constant for native style (`true`)
|
|
277
|
-
- `Sashite::Epin::Identifier::FOREIGN` - Constant for foreign style (`false`)
|
|
278
|
-
- `Sashite::Epin::Identifier::DERIVATION_SUFFIX` - Derivation suffix for foreign pieces (`"'"`)
|
|
279
|
-
|
|
280
|
-
## Advanced Usage
|
|
281
|
-
|
|
282
|
-
### Style Derivation Examples
|
|
232
|
+
#### Core Methods (6 total)
|
|
283
233
|
|
|
284
234
|
```ruby
|
|
285
|
-
#
|
|
286
|
-
#
|
|
287
|
-
|
|
288
|
-
#
|
|
235
|
+
# Creation
|
|
236
|
+
Sashite::Epin.new(pin, derived: false) # Create from PIN + derivation flag
|
|
237
|
+
|
|
238
|
+
# Component access
|
|
239
|
+
epin.pin # => PIN::Identifier
|
|
240
|
+
epin.derived? # => Boolean
|
|
289
241
|
|
|
290
|
-
|
|
291
|
-
|
|
242
|
+
# Serialization
|
|
243
|
+
epin.to_s # => "K^'" or "K^"
|
|
292
244
|
|
|
293
|
-
|
|
294
|
-
|
|
245
|
+
# PIN replacement
|
|
246
|
+
epin.with_pin(new_pin) # New EPIN with different PIN
|
|
295
247
|
|
|
296
|
-
#
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
foreign_chess_king.derived? # => true
|
|
248
|
+
# Derivation transformation
|
|
249
|
+
epin.mark_derived # Mark as derived (add ')
|
|
250
|
+
epin.unmark_native # Mark as native (remove ')
|
|
251
|
+
epin.with_derived(boolean) # Set derivation explicitly
|
|
301
252
|
```
|
|
302
253
|
|
|
303
|
-
|
|
254
|
+
#### Convenience Queries
|
|
255
|
+
|
|
304
256
|
```ruby
|
|
305
|
-
#
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
257
|
+
epin.native? # !derived?
|
|
258
|
+
epin.same_derivation?(other) # Compare derivation status
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### Equality
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
epin1 == epin2 # True if both PIN and derived flag equal
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**That's the entire API.** Everything else uses the PIN component API directly.
|
|
310
268
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
269
|
+
## Format Specification
|
|
270
|
+
|
|
271
|
+
### Structure
|
|
272
|
+
```
|
|
273
|
+
<pin>[']
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Where:
|
|
277
|
+
- `<pin>` is any valid PIN token
|
|
278
|
+
- `'` is the optional derivation marker
|
|
316
279
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
280
|
+
### Grammar (BNF)
|
|
281
|
+
```bnf
|
|
282
|
+
<epin> ::= <pin> | <pin> "'"
|
|
283
|
+
|
|
284
|
+
<pin> ::= ["+" | "-"] <letter> ["^"]
|
|
285
|
+
<letter> ::= "A" | ... | "Z" | "a" | ... | "z"
|
|
320
286
|
```
|
|
321
287
|
|
|
322
|
-
###
|
|
288
|
+
### Regular Expression
|
|
323
289
|
```ruby
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
@first_style = first_style
|
|
327
|
-
@second_style = second_style
|
|
328
|
-
@pieces = {}
|
|
329
|
-
end
|
|
290
|
+
/\A[-+]?[A-Za-z]\^?'?\z/
|
|
291
|
+
```
|
|
330
292
|
|
|
331
|
-
|
|
332
|
-
@pieces[square] = piece
|
|
333
|
-
end
|
|
293
|
+
## Examples
|
|
334
294
|
|
|
335
|
-
|
|
336
|
-
captured = @pieces[to_square]
|
|
337
|
-
capturing = @pieces.delete(from_square)
|
|
295
|
+
### Basic Identifiers
|
|
338
296
|
|
|
339
|
-
|
|
297
|
+
```ruby
|
|
298
|
+
# Native pieces (no derivation marker)
|
|
299
|
+
native_king = Sashite::Epin.parse("K^")
|
|
300
|
+
native_king.pin.type # => :K
|
|
301
|
+
native_king.derived? # => false
|
|
302
|
+
|
|
303
|
+
# Derived pieces (with derivation marker)
|
|
304
|
+
derived_king = Sashite::Epin.parse("K^'")
|
|
305
|
+
derived_king.pin.type # => :K
|
|
306
|
+
derived_king.derived? # => true
|
|
307
|
+
|
|
308
|
+
# Enhanced pieces
|
|
309
|
+
enhanced_rook = Sashite::Epin.parse("+R'")
|
|
310
|
+
enhanced_rook.pin.enhanced? # => true
|
|
311
|
+
enhanced_rook.derived? # => true
|
|
312
|
+
|
|
313
|
+
# Diminished pieces
|
|
314
|
+
diminished_pawn = Sashite::Epin.parse("-p")
|
|
315
|
+
diminished_pawn.pin.diminished? # => true
|
|
316
|
+
diminished_pawn.native? # => true
|
|
317
|
+
```
|
|
340
318
|
|
|
341
|
-
|
|
342
|
-
mutated = captured.flip.underive
|
|
343
|
-
mutated = mutated.with_type(new_type) if new_type
|
|
319
|
+
### Cross-Style Scenarios
|
|
344
320
|
|
|
345
|
-
|
|
346
|
-
mutated # Return mutated captured piece for hand
|
|
347
|
-
end
|
|
321
|
+
Assume first player uses Chess style, second player uses Makruk style:
|
|
348
322
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
323
|
+
```ruby
|
|
324
|
+
# First player pieces
|
|
325
|
+
chess_king = Sashite::Epin.parse("K^") # Native Chess king
|
|
326
|
+
makruk_pawn = Sashite::Epin.parse("P'") # Derived Makruk pawn (foreign)
|
|
327
|
+
|
|
328
|
+
chess_king.native? # => true (uses own style)
|
|
329
|
+
makruk_pawn.derived? # => true (uses opponent's style)
|
|
356
330
|
|
|
357
|
-
#
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
board.place("e8", Sashite::Epin.identifier(:K, :second, :normal, true)) # Shōgi king
|
|
361
|
-
board.place("d4", Sashite::Epin.identifier(:Q, :first, :normal, false)) # Chess queen using Shōgi style
|
|
331
|
+
# Second player pieces
|
|
332
|
+
makruk_king = Sashite::Epin.parse("k^") # Native Makruk king
|
|
333
|
+
chess_pawn = Sashite::Epin.parse("p'") # Derived Chess pawn (foreign)
|
|
362
334
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
puts analysis[:foreign].size # => 1
|
|
335
|
+
makruk_king.native? # => true
|
|
336
|
+
chess_pawn.derived? # => true
|
|
366
337
|
```
|
|
367
338
|
|
|
368
|
-
###
|
|
339
|
+
### Component Manipulation
|
|
340
|
+
|
|
369
341
|
```ruby
|
|
370
|
-
#
|
|
371
|
-
|
|
372
|
-
# All PIN strings are valid EPIN strings (native pieces)
|
|
373
|
-
Sashite::Epin.parse(pin_string)
|
|
374
|
-
end
|
|
342
|
+
# Start with native king
|
|
343
|
+
epin = Sashite::Epin.parse("K^")
|
|
375
344
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
345
|
+
# Convert to derived
|
|
346
|
+
derived = epin.mark_derived
|
|
347
|
+
derived.to_s # => "K^'"
|
|
379
348
|
|
|
380
|
-
|
|
381
|
-
|
|
349
|
+
# Change to queen (keep derivation)
|
|
350
|
+
queen = derived.with_pin(derived.pin.with_type(:Q))
|
|
351
|
+
queen.to_s # => "Q^'"
|
|
352
|
+
|
|
353
|
+
# Enhance (keep derivation)
|
|
354
|
+
enhanced = derived.with_pin(derived.pin.with_state(:enhanced))
|
|
355
|
+
enhanced.to_s # => "+K^'"
|
|
356
|
+
|
|
357
|
+
# Change side (keep derivation)
|
|
358
|
+
opponent = derived.with_pin(derived.pin.flip)
|
|
359
|
+
opponent.to_s # => "k^'"
|
|
360
|
+
|
|
361
|
+
# Back to native
|
|
362
|
+
native = derived.unmark_native
|
|
363
|
+
native.to_s # => "K^"
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Working with PIN Component
|
|
367
|
+
|
|
368
|
+
```ruby
|
|
369
|
+
epin = Sashite::Epin.parse("+R^'")
|
|
370
|
+
|
|
371
|
+
# Extract PIN component
|
|
372
|
+
pin = epin.pin # => "+R^"
|
|
373
|
+
|
|
374
|
+
# Transform PIN
|
|
375
|
+
new_pin = pin.with_type(:B).with_state(:normal) # => "B^"
|
|
376
|
+
|
|
377
|
+
# Create new EPIN with transformed PIN
|
|
378
|
+
new_epin = epin.with_pin(new_pin)
|
|
379
|
+
new_epin.to_s # => "B^'"
|
|
380
|
+
|
|
381
|
+
# Multiple PIN transformations
|
|
382
|
+
complex_pin = pin
|
|
383
|
+
.with_type(:Q)
|
|
384
|
+
.with_state(:diminished)
|
|
385
|
+
.with_terminal(false)
|
|
386
|
+
.flip
|
|
382
387
|
|
|
383
|
-
#
|
|
384
|
-
|
|
385
|
-
|
|
388
|
+
epin.with_pin(complex_pin).to_s # => "-q'"
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Immutability
|
|
392
|
+
|
|
393
|
+
```ruby
|
|
394
|
+
original = Sashite::Epin.parse("K^")
|
|
386
395
|
|
|
387
|
-
|
|
388
|
-
|
|
396
|
+
# All transformations return new instances
|
|
397
|
+
derived = original.mark_derived
|
|
398
|
+
changed_pin = original.with_pin(original.pin.with_type(:Q))
|
|
399
|
+
|
|
400
|
+
# Original unchanged
|
|
401
|
+
original.to_s # => "K^"
|
|
402
|
+
derived.to_s # => "K^'"
|
|
403
|
+
changed_pin.to_s # => "Q^"
|
|
404
|
+
|
|
405
|
+
# Components are immutable
|
|
406
|
+
pin = original.pin
|
|
407
|
+
pin.frozen? # => true
|
|
408
|
+
original.frozen? # => true
|
|
389
409
|
```
|
|
390
410
|
|
|
391
|
-
|
|
411
|
+
## Attribute Mapping
|
|
412
|
+
|
|
413
|
+
EPIN exposes all five fundamental attributes from the Sashité Game Protocol:
|
|
414
|
+
|
|
415
|
+
| Protocol Attribute | EPIN Access | Example |
|
|
416
|
+
|-------------------|-------------|---------|
|
|
417
|
+
| **Piece Name** | `epin.pin.type` | `:K` (King), `:R` (Rook) |
|
|
418
|
+
| **Piece Side** | `epin.pin.side` | `:first`, `:second` |
|
|
419
|
+
| **Piece State** | `epin.pin.state` | `:normal`, `:enhanced`, `:diminished` |
|
|
420
|
+
| **Terminal Status** | `epin.pin.terminal?` | `true`, `false` |
|
|
421
|
+
| **Piece Style** | `epin.derived?` | `false` (native), `true` (derived) |
|
|
422
|
+
|
|
423
|
+
## Design Principles
|
|
424
|
+
|
|
425
|
+
### 1. Pure Composition
|
|
426
|
+
|
|
427
|
+
EPIN doesn't reimplement PIN features — it extends PIN minimally:
|
|
428
|
+
|
|
392
429
|
```ruby
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
(piece.first_player? && target_rank == 8) ||
|
|
399
|
-
(piece.second_player? && target_rank == 1)
|
|
400
|
-
when %i[P shogi] # Shōgi pawn
|
|
401
|
-
(piece.first_player? && target_rank >= 7) ||
|
|
402
|
-
(piece.second_player? && target_rank <= 3)
|
|
403
|
-
when %i[R shogi], %i[B shogi] # Shōgi major pieces
|
|
404
|
-
true
|
|
405
|
-
else
|
|
406
|
-
false
|
|
430
|
+
# EPIN is just PIN + derived flag
|
|
431
|
+
class Identifier
|
|
432
|
+
def initialize(pin, derived: false)
|
|
433
|
+
@pin = pin
|
|
434
|
+
@derived = !!derived
|
|
407
435
|
end
|
|
408
436
|
end
|
|
437
|
+
```
|
|
409
438
|
|
|
410
|
-
|
|
411
|
-
shogi_pawn = Sashite::Epin.identifier(:P, :first, :normal, false)
|
|
439
|
+
### 2. Absolute Minimal API
|
|
412
440
|
|
|
413
|
-
|
|
441
|
+
**6 core methods only:**
|
|
442
|
+
1. `new(pin, derived: false)` — create from PIN
|
|
443
|
+
2. `pin` — get PIN component
|
|
444
|
+
3. `derived?` — check derivation
|
|
445
|
+
4. `to_s` — serialize
|
|
446
|
+
5. `with_pin(new_pin)` — replace PIN
|
|
447
|
+
6. `with_derived(boolean)` — change derivation
|
|
414
448
|
|
|
415
|
-
|
|
416
|
-
|
|
449
|
+
Everything else uses the PIN component API directly.
|
|
450
|
+
|
|
451
|
+
### 3. Component Transparency
|
|
452
|
+
|
|
453
|
+
Access PIN directly — no wrappers:
|
|
454
|
+
|
|
455
|
+
```ruby
|
|
456
|
+
# Use PIN API directly
|
|
457
|
+
epin.pin.type
|
|
458
|
+
epin.pin.with_type(:Q)
|
|
459
|
+
epin.pin.enhanced?
|
|
460
|
+
epin.pin.flip
|
|
461
|
+
|
|
462
|
+
# No need for wrapper methods like:
|
|
463
|
+
# epin.type
|
|
464
|
+
# epin.with_type
|
|
465
|
+
# epin.enhanced?
|
|
466
|
+
# epin.flip
|
|
417
467
|
```
|
|
418
468
|
|
|
419
|
-
|
|
469
|
+
### 4. Backward Compatibility
|
|
420
470
|
|
|
421
|
-
|
|
471
|
+
Every valid PIN is a valid EPIN (without derivation marker):
|
|
422
472
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
473
|
+
```ruby
|
|
474
|
+
# All PIN identifiers work as EPIN
|
|
475
|
+
pin_tokens = ["K", "+R", "-p", "K^", "+R^"]
|
|
476
|
+
pin_tokens.each do |token|
|
|
477
|
+
epin = Sashite::Epin.parse(token)
|
|
478
|
+
epin.native? # => true
|
|
479
|
+
epin.to_s == token # => true
|
|
480
|
+
end
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### 5. Immutability
|
|
427
484
|
|
|
428
|
-
|
|
429
|
-
- **Reliability**: Reuses battle-tested PIN logic
|
|
430
|
-
- **Maintainability**: PIN updates automatically benefit EPIN
|
|
431
|
-
- **Consistency**: PIN and EPIN identifiers behave identically for shared attributes
|
|
432
|
-
- **Performance**: Minimal overhead over pure PIN implementation
|
|
485
|
+
All instances frozen. Transformations return new instances:
|
|
433
486
|
|
|
434
|
-
|
|
487
|
+
```ruby
|
|
488
|
+
epin1 = Sashite::Epin.parse("K^")
|
|
489
|
+
epin2 = epin1.mark_derived
|
|
490
|
+
epin1.frozen? # => true
|
|
491
|
+
epin2.frozen? # => true
|
|
492
|
+
epin1.equal?(epin2) # => false
|
|
493
|
+
```
|
|
435
494
|
|
|
436
|
-
|
|
495
|
+
## Semantics
|
|
437
496
|
|
|
438
|
-
|
|
439
|
-
|-------------------|--------------|----------|-------|
|
|
440
|
-
| **Type** | ASCII letter choice | `K`/`k` = King, `P`/`p` = Pawn | Type is always stored as uppercase symbol (`:K`, `:P`) |
|
|
441
|
-
| **Side** | Letter case in display | `K` = First player, `k` = Second player | Case is determined by side during rendering |
|
|
442
|
-
| **State** | Optional prefix | `+K` = Enhanced, `-K` = Diminished, `K` = Normal | |
|
|
443
|
-
| **Style** | Derivation suffix | `K` = Native style, `K'` = Foreign style | |
|
|
497
|
+
### Native vs Derived
|
|
444
498
|
|
|
445
|
-
|
|
446
|
-
- **
|
|
447
|
-
- **
|
|
499
|
+
In cross-style games:
|
|
500
|
+
- **Native piece**: Uses its own side's style (no `'` marker)
|
|
501
|
+
- **Derived piece**: Uses opponent's style (`'` marker present)
|
|
502
|
+
|
|
503
|
+
```ruby
|
|
504
|
+
# Chess vs Makruk match
|
|
505
|
+
# First player = Chess, Second player = Makruk
|
|
448
506
|
|
|
449
|
-
|
|
507
|
+
"K" # First player king in Chess style (native)
|
|
508
|
+
"K'" # First player king in Makruk style (derived from opponent)
|
|
509
|
+
"k" # Second player king in Makruk style (native)
|
|
510
|
+
"k'" # Second player king in Chess style (derived from opponent)
|
|
511
|
+
```
|
|
450
512
|
|
|
451
|
-
|
|
513
|
+
### Style Derivation Logic
|
|
452
514
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
* **ASCII Compatible**: Maximum portability across systems
|
|
456
|
-
* **Rule-Agnostic**: Independent of specific game mechanics
|
|
457
|
-
* **Compact Format**: Minimal character usage (1-3 characters per piece)
|
|
458
|
-
* **Visual Distinction**: Clear player and style differentiation
|
|
459
|
-
* **Protocol Compliant**: Complete implementation of Sashité piece attributes
|
|
460
|
-
* **Immutable**: All identifier instances are frozen and transformations return new objects
|
|
461
|
-
* **Functional**: Pure functions with no side effects
|
|
515
|
+
```ruby
|
|
516
|
+
# Assume: first=Chess, second=Makruk
|
|
462
517
|
|
|
463
|
-
|
|
518
|
+
epin = Sashite::Epin.parse("P")
|
|
519
|
+
# Piece Side: first
|
|
520
|
+
# Style: Chess (first's native) → Native piece
|
|
464
521
|
|
|
465
|
-
|
|
522
|
+
epin = Sashite::Epin.parse("P'")
|
|
523
|
+
# Piece Side: first
|
|
524
|
+
# Style: Makruk (second's native) → Derived piece
|
|
466
525
|
|
|
467
|
-
|
|
526
|
+
epin = Sashite::Epin.parse("p")
|
|
527
|
+
# Piece Side: second
|
|
528
|
+
# Style: Makruk (second's native) → Native piece
|
|
468
529
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
530
|
+
epin = Sashite::Epin.parse("p'")
|
|
531
|
+
# Piece Side: second
|
|
532
|
+
# Style: Chess (first's native) → Derived piece
|
|
533
|
+
```
|
|
473
534
|
|
|
474
|
-
|
|
535
|
+
## Error Handling
|
|
475
536
|
|
|
476
537
|
```ruby
|
|
477
|
-
#
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
#
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
#
|
|
538
|
+
# Invalid EPIN string
|
|
539
|
+
begin
|
|
540
|
+
Sashite::Epin.parse("invalid")
|
|
541
|
+
rescue ArgumentError => e
|
|
542
|
+
e.message # => "Invalid EPIN string: invalid"
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
# Multiple derivation markers
|
|
546
|
+
begin
|
|
547
|
+
Sashite::Epin.parse("K''")
|
|
548
|
+
rescue ArgumentError => e
|
|
549
|
+
# Invalid format
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
# PIN validation errors delegate
|
|
553
|
+
begin
|
|
554
|
+
Sashite::Epin.parse("KK'")
|
|
555
|
+
rescue ArgumentError => e
|
|
556
|
+
# PIN validation error
|
|
557
|
+
end
|
|
485
558
|
```
|
|
486
559
|
|
|
487
|
-
|
|
560
|
+
## Performance Considerations
|
|
488
561
|
|
|
489
|
-
|
|
562
|
+
### Efficient Composition
|
|
490
563
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
- **Style context dependency**: Requires match-level side-style associations
|
|
564
|
+
```ruby
|
|
565
|
+
# PIN component created once
|
|
566
|
+
pin = Sashite::Pin.parse("K^")
|
|
567
|
+
epin = Sashite::Epin.new(pin, derived: true)
|
|
496
568
|
|
|
497
|
-
|
|
569
|
+
# Accessing components is O(1)
|
|
570
|
+
epin.pin # => direct reference
|
|
571
|
+
epin.derived? # => direct boolean check
|
|
572
|
+
|
|
573
|
+
# No overhead from method delegation
|
|
574
|
+
epin.pin.type # => direct method call on PIN component
|
|
575
|
+
```
|
|
498
576
|
|
|
499
|
-
|
|
500
|
-
- [Game Protocol](https://sashite.dev/game-protocol/) - Conceptual foundation for abstract strategy board games
|
|
501
|
-
- [CELL](https://sashite.dev/specs/cell/) - Board position coordinates
|
|
502
|
-
- [HAND](https://sashite.dev/specs/hand/) - Reserve location notation
|
|
503
|
-
- [PMN](https://sashite.dev/specs/pmn/) - Portable Move Notation
|
|
577
|
+
### Transformation Patterns
|
|
504
578
|
|
|
505
|
-
|
|
579
|
+
```ruby
|
|
580
|
+
epin = Sashite::Epin.parse("K^'")
|
|
506
581
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
- [Game Protocol Foundation](https://sashite.dev/game-protocol/)
|
|
510
|
-
- [API Documentation](https://rubydoc.info/github/sashite/epin.rb/main)
|
|
582
|
+
# Pattern 1: Change derivation only
|
|
583
|
+
epin.unmark_native
|
|
511
584
|
|
|
512
|
-
|
|
585
|
+
# Pattern 2: Change PIN only
|
|
586
|
+
epin.with_pin(epin.pin.with_type(:Q))
|
|
513
587
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
588
|
+
# Pattern 3: Change both
|
|
589
|
+
new_pin = epin.pin.with_type(:Q)
|
|
590
|
+
epin.with_pin(new_pin).unmark_native
|
|
591
|
+
|
|
592
|
+
# Pattern 4: Complex PIN transformation
|
|
593
|
+
new_pin = epin.pin
|
|
594
|
+
.with_type(:Q)
|
|
595
|
+
.with_state(:enhanced)
|
|
596
|
+
.flip
|
|
597
|
+
epin.with_pin(new_pin).mark_derived
|
|
598
|
+
```
|
|
518
599
|
|
|
519
|
-
|
|
520
|
-
bundle install
|
|
600
|
+
## Comparison with PIN
|
|
521
601
|
|
|
522
|
-
|
|
523
|
-
ruby test.rb
|
|
602
|
+
### What EPIN Adds
|
|
524
603
|
|
|
525
|
-
|
|
526
|
-
|
|
604
|
+
```ruby
|
|
605
|
+
# PIN: 4 attributes
|
|
606
|
+
pin = Sashite::Pin.parse("K^")
|
|
607
|
+
pin.type # Piece Name
|
|
608
|
+
pin.side # Piece Side
|
|
609
|
+
pin.state # Piece State
|
|
610
|
+
pin.terminal? # Terminal Status
|
|
611
|
+
|
|
612
|
+
# EPIN: 5 attributes (PIN + style)
|
|
613
|
+
epin = Sashite::Epin.parse("K^'")
|
|
614
|
+
epin.pin.type # Piece Name
|
|
615
|
+
epin.pin.side # Piece Side
|
|
616
|
+
epin.pin.state # Piece State
|
|
617
|
+
epin.pin.terminal? # Terminal Status
|
|
618
|
+
epin.derived? # Piece Style (5th attribute)
|
|
527
619
|
```
|
|
528
620
|
|
|
529
|
-
|
|
621
|
+
### When to Use EPIN vs PIN
|
|
622
|
+
|
|
623
|
+
**Use PIN when:**
|
|
624
|
+
- Single-style games (both players use same style)
|
|
625
|
+
- Style information not needed
|
|
626
|
+
- Maximum compatibility required
|
|
627
|
+
|
|
628
|
+
**Use EPIN when:**
|
|
629
|
+
- Cross-style games (different styles per player)
|
|
630
|
+
- Pieces can change style (promotion to foreign piece)
|
|
631
|
+
- Need to track native vs derived pieces
|
|
632
|
+
|
|
633
|
+
## Design Properties
|
|
634
|
+
|
|
635
|
+
- **Rule-agnostic**: Independent of game mechanics
|
|
636
|
+
- **Pure composition**: Extends PIN minimally
|
|
637
|
+
- **Minimal API**: Only 6 core methods
|
|
638
|
+
- **Component transparency**: Direct PIN access
|
|
639
|
+
- **Backward compatible**: All PIN tokens valid
|
|
640
|
+
- **Immutable**: Frozen instances
|
|
641
|
+
- **Type-safe**: Full PIN type preservation
|
|
642
|
+
- **Style-aware**: Tracks native vs derived
|
|
643
|
+
- **Compact**: Single character overhead for derivation
|
|
644
|
+
|
|
645
|
+
## Related Specifications
|
|
530
646
|
|
|
531
|
-
1.
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
5. Commit your changes (`git commit -am 'Add new feature'`)
|
|
536
|
-
6. Push to the branch (`git push origin feature/new-feature`)
|
|
537
|
-
7. Create a Pull Request
|
|
647
|
+
- [EPIN Specification v1.0.0](https://sashite.dev/specs/epin/1.0.0/) - Technical specification
|
|
648
|
+
- [EPIN Examples](https://sashite.dev/specs/epin/1.0.0/examples/) - Usage examples
|
|
649
|
+
- [PIN Specification v1.0.0](https://sashite.dev/specs/pin/1.0.0/) - Base component
|
|
650
|
+
- [Sashité Game Protocol](https://sashite.dev/game-protocol/) - Foundation
|
|
538
651
|
|
|
539
652
|
## License
|
|
540
653
|
|