sashite-pin 3.2.0 → 4.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/LICENSE +201 -0
- data/README.md +197 -425
- data/lib/sashite/pin/constants.rb +35 -0
- data/lib/sashite/pin/errors/argument/messages.rb +28 -0
- data/lib/sashite/pin/errors/argument.rb +16 -0
- data/lib/sashite/pin/errors.rb +3 -0
- data/lib/sashite/pin/identifier.rb +294 -271
- data/lib/sashite/pin/parser.rb +170 -0
- data/lib/sashite/pin.rb +72 -46
- metadata +14 -13
- data/LICENSE.md +0 -22
data/README.md
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# pin.rb
|
|
2
2
|
|
|
3
3
|
[](https://github.com/sashite/pin.rb/tags)
|
|
4
4
|
[](https://rubydoc.info/github/sashite/pin.rb/main)
|
|
5
|
-
](https://github.com/sashite/pin.rb/actions)
|
|
6
|
+
[](https://github.com/sashite/pin.rb/blob/main/LICENSE)
|
|
7
7
|
|
|
8
|
-
> **PIN** (Piece Identifier Notation) implementation for
|
|
8
|
+
> **PIN** (Piece Identifier Notation) implementation for Ruby.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Overview
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
This gem implements the [PIN Specification v1.0.0](https://sashite.dev/specs/pin/1.0.0/), providing a modern Ruby interface with immutable identifier objects and functional programming principles.
|
|
12
|
+
This library implements the [PIN Specification v1.0.0](https://sashite.dev/specs/pin/1.0.0/).
|
|
15
13
|
|
|
16
14
|
## Installation
|
|
17
15
|
|
|
@@ -28,482 +26,256 @@ gem install sashite-pin
|
|
|
28
26
|
|
|
29
27
|
## Usage
|
|
30
28
|
|
|
29
|
+
### Parsing (String → Identifier)
|
|
30
|
+
|
|
31
|
+
Convert a PIN string into an `Identifier` object.
|
|
32
|
+
|
|
31
33
|
```ruby
|
|
32
34
|
require "sashite/pin"
|
|
33
35
|
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
identifier.terminal? # => false
|
|
41
|
-
|
|
42
|
-
# Parse terminal pieces (e.g., kings in chess)
|
|
43
|
-
terminal_king = Sashite::Pin.parse("K^") # => #<Pin::Identifier type=:K side=:first state=:normal terminal=true>
|
|
44
|
-
terminal_king.to_s # => "K^"
|
|
45
|
-
terminal_king.terminal? # => true
|
|
46
|
-
|
|
47
|
-
# Create identifiers directly
|
|
48
|
-
identifier = Sashite::Pin.identifier(:K, :first, :normal) # => #<Pin::Identifier type=:K side=:first state=:normal>
|
|
49
|
-
terminal_king = Sashite::Pin.identifier(:K, :first, :normal, terminal: true) # => #<Pin::Identifier type=:K side=:first state=:normal terminal=true>
|
|
50
|
-
identifier = Sashite::Pin::Identifier.new(:R, :second, :enhanced) # => #<Pin::Identifier type=:R side=:second state=:enhanced>
|
|
51
|
-
|
|
52
|
-
# Validate PIN strings
|
|
53
|
-
Sashite::Pin.valid?("K") # => true
|
|
54
|
-
Sashite::Pin.valid?("+R") # => true
|
|
55
|
-
Sashite::Pin.valid?("K^") # => true
|
|
56
|
-
Sashite::Pin.valid?("+K^") # => true
|
|
57
|
-
Sashite::Pin.valid?("invalid") # => false
|
|
58
|
-
|
|
59
|
-
# State manipulation (returns new immutable instances)
|
|
60
|
-
enhanced = identifier.enhance # => #<Pin::Identifier type=:K side=:first state=:enhanced>
|
|
61
|
-
enhanced.to_s # => "+K"
|
|
62
|
-
diminished = identifier.diminish # => #<Pin::Identifier type=:K side=:first state=:diminished>
|
|
63
|
-
diminished.to_s # => "-K"
|
|
64
|
-
|
|
65
|
-
# Terminal marker manipulation
|
|
66
|
-
normal_king = Sashite::Pin.parse("K")
|
|
67
|
-
terminal_king = normal_king.mark_terminal # => "K^"
|
|
68
|
-
back_to_normal = terminal_king.unmark_terminal # => "K"
|
|
69
|
-
|
|
70
|
-
# Side manipulation
|
|
71
|
-
flipped = identifier.flip # => #<Pin::Identifier type=:K side=:second state=:normal>
|
|
72
|
-
flipped.to_s # => "k"
|
|
73
|
-
|
|
74
|
-
# Type manipulation
|
|
75
|
-
queen = identifier.with_type(:Q) # => #<Pin::Identifier type=:Q side=:first state=:normal>
|
|
76
|
-
queen.to_s # => "Q"
|
|
36
|
+
# Standard parsing (raises on error)
|
|
37
|
+
pin = Sashite::Pin.parse("K")
|
|
38
|
+
pin.type # => :K
|
|
39
|
+
pin.side # => :first
|
|
40
|
+
pin.state # => :normal
|
|
41
|
+
pin.terminal? # => false
|
|
77
42
|
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
diminished.diminished? # => true
|
|
43
|
+
# With state modifier
|
|
44
|
+
pin = Sashite::Pin.parse("+R")
|
|
45
|
+
pin.state # => :enhanced
|
|
82
46
|
|
|
83
|
-
#
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
# Attribute access
|
|
88
|
-
identifier.letter # => "K"
|
|
89
|
-
enhanced.prefix # => "+"
|
|
90
|
-
identifier.prefix # => ""
|
|
91
|
-
terminal_king.suffix # => "^"
|
|
92
|
-
|
|
93
|
-
# Type and side comparison
|
|
94
|
-
king1 = Sashite::Pin.parse("K")
|
|
95
|
-
king2 = Sashite::Pin.parse("k")
|
|
96
|
-
queen = Sashite::Pin.parse("Q")
|
|
97
|
-
|
|
98
|
-
king1.same_type?(king2) # => true (both kings)
|
|
99
|
-
king1.same_side?(queen) # => true (both first player)
|
|
100
|
-
king1.same_type?(queen) # => false (different types)
|
|
101
|
-
|
|
102
|
-
# Functional transformations can be chained
|
|
103
|
-
pawn = Sashite::Pin.parse("P")
|
|
104
|
-
enemy_promoted = pawn.flip.enhance # => "+p" (second player promoted pawn)
|
|
105
|
-
|
|
106
|
-
# Transformations preserve terminal status
|
|
107
|
-
terminal_piece = Sashite::Pin.parse("K^")
|
|
108
|
-
enhanced_terminal = terminal_piece.enhance # => "+K^"
|
|
109
|
-
```
|
|
47
|
+
# With terminal marker
|
|
48
|
+
pin = Sashite::Pin.parse("K^")
|
|
49
|
+
pin.terminal? # => true
|
|
110
50
|
|
|
111
|
-
|
|
51
|
+
# Combined
|
|
52
|
+
pin = Sashite::Pin.parse("+K^")
|
|
53
|
+
pin.state # => :enhanced
|
|
54
|
+
pin.terminal? # => true
|
|
112
55
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
```
|
|
116
|
-
[<state-modifier>]<letter>[<terminal-marker>]
|
|
56
|
+
# Invalid input raises ArgumentError
|
|
57
|
+
Sashite::Pin.parse("invalid") # => raises ArgumentError
|
|
117
58
|
```
|
|
118
59
|
|
|
119
|
-
###
|
|
60
|
+
### Formatting (Identifier → String)
|
|
120
61
|
|
|
121
|
-
|
|
62
|
+
Convert an `Identifier` back to a PIN string.
|
|
122
63
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
64
|
+
```ruby
|
|
65
|
+
# From Identifier object
|
|
66
|
+
pin = Sashite::Pin::Identifier.new(:K, :first)
|
|
67
|
+
pin.to_s # => "K"
|
|
126
68
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
* **Terminal Marker** (optional suffix):
|
|
69
|
+
# With attributes
|
|
70
|
+
pin = Sashite::Pin::Identifier.new(:R, :second, :enhanced)
|
|
71
|
+
pin.to_s # => "+r"
|
|
131
72
|
|
|
132
|
-
|
|
133
|
-
|
|
73
|
+
pin = Sashite::Pin::Identifier.new(:K, :first, :normal, terminal: true)
|
|
74
|
+
pin.to_s # => "K^"
|
|
75
|
+
```
|
|
134
76
|
|
|
135
|
-
###
|
|
77
|
+
### Validation
|
|
136
78
|
|
|
137
79
|
```ruby
|
|
138
|
-
|
|
80
|
+
# Boolean check
|
|
81
|
+
Sashite::Pin.valid?("K") # => true
|
|
82
|
+
Sashite::Pin.valid?("+R") # => true
|
|
83
|
+
Sashite::Pin.valid?("K^") # => true
|
|
84
|
+
Sashite::Pin.valid?("invalid") # => false
|
|
139
85
|
```
|
|
140
86
|
|
|
141
|
-
###
|
|
142
|
-
|
|
143
|
-
* `K` - First player king (normal state)
|
|
144
|
-
* `K^` - First player king (normal state, terminal)
|
|
145
|
-
* `k` - Second player king (normal state)
|
|
146
|
-
* `k^` - Second player king (normal state, terminal)
|
|
147
|
-
* `+R` - First player rook (enhanced state)
|
|
148
|
-
* `+R^` - First player rook (enhanced state, terminal)
|
|
149
|
-
* `-p` - Second player pawn (diminished state)
|
|
150
|
-
|
|
151
|
-
## API Reference
|
|
152
|
-
|
|
153
|
-
### Main Module Methods
|
|
154
|
-
|
|
155
|
-
* `Sashite::Pin.valid?(pin_string)` - Check if string is valid PIN notation
|
|
156
|
-
* `Sashite::Pin.parse(pin_string)` - Parse PIN string into Identifier object
|
|
157
|
-
* `Sashite::Pin.identifier(type, side, state = :normal, terminal: false)` - Create identifier instance directly
|
|
158
|
-
|
|
159
|
-
### Identifier Class
|
|
160
|
-
|
|
161
|
-
#### Creation and Parsing
|
|
162
|
-
|
|
163
|
-
* `Sashite::Pin::Identifier.new(type, side, state = :normal, terminal: false)` - Create identifier instance
|
|
164
|
-
* `Sashite::Pin::Identifier.parse(pin_string)` - Parse PIN string (same as module method)
|
|
165
|
-
* `Sashite::Pin::Identifier.valid?(pin_string)` - Validate PIN string (class method)
|
|
166
|
-
|
|
167
|
-
#### Attribute Access
|
|
168
|
-
|
|
169
|
-
* `#type` - Get piece type (symbol \:A to \:Z, always uppercase)
|
|
170
|
-
* `#side` - Get player side (\:first or \:second)
|
|
171
|
-
* `#state` - Get state (\:normal, \:enhanced, or \:diminished)
|
|
172
|
-
* `#terminal` - Get terminal status (Boolean)
|
|
173
|
-
* `#letter` - Get letter representation (string, case determined by side)
|
|
174
|
-
* `#prefix` - Get state prefix (string: "+", "-", or "")
|
|
175
|
-
* `#suffix` - Get terminal marker (string: "^" or "")
|
|
176
|
-
* `#to_s` - Convert to PIN string representation
|
|
177
|
-
|
|
178
|
-
#### Type and Case Handling
|
|
179
|
-
|
|
180
|
-
**Important**: The `type` attribute is always stored as an uppercase symbol (`:A` to `:Z`), regardless of the input case when parsing. The display case in `#letter` and `#to_s` is determined by the `side` attribute:
|
|
87
|
+
### Accessing Identifier Data
|
|
181
88
|
|
|
182
89
|
```ruby
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
90
|
+
pin = Sashite::Pin.parse("+K^")
|
|
91
|
+
|
|
92
|
+
# Get attributes
|
|
93
|
+
pin.type # => :K
|
|
94
|
+
pin.side # => :first
|
|
95
|
+
pin.state # => :enhanced
|
|
96
|
+
pin.terminal? # => true
|
|
97
|
+
|
|
98
|
+
# Get string components
|
|
99
|
+
pin.letter # => "K"
|
|
100
|
+
pin.prefix # => "+"
|
|
101
|
+
pin.suffix # => "^"
|
|
192
102
|
```
|
|
193
103
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
* `#normal?` - Check if normal state (no modifiers)
|
|
197
|
-
* `#enhanced?` - Check if enhanced state
|
|
198
|
-
* `#diminished?` - Check if diminished state
|
|
199
|
-
|
|
200
|
-
#### Side Queries
|
|
201
|
-
|
|
202
|
-
* `#first_player?` - Check if first player identifier
|
|
203
|
-
* `#second_player?` - Check if second player identifier
|
|
204
|
-
|
|
205
|
-
#### Terminal Queries
|
|
206
|
-
|
|
207
|
-
* `#terminal?` - Check if terminal piece
|
|
208
|
-
|
|
209
|
-
#### State Transformations (immutable - return new instances)
|
|
210
|
-
|
|
211
|
-
* `#enhance` - Create enhanced version
|
|
212
|
-
* `#unenhance` - Remove enhanced state
|
|
213
|
-
* `#diminish` - Create diminished version
|
|
214
|
-
* `#undiminish` - Remove diminished state
|
|
215
|
-
* `#normalize` - Remove all state modifiers
|
|
216
|
-
* `#flip` - Switch player (change side)
|
|
217
|
-
|
|
218
|
-
#### Attribute Transformations (immutable - return new instances)
|
|
219
|
-
|
|
220
|
-
* `#with_type(new_type)` - Create identifier with different type
|
|
221
|
-
* `#with_side(new_side)` - Create identifier with different side
|
|
222
|
-
* `#with_state(new_state)` - Create identifier with different state
|
|
223
|
-
|
|
224
|
-
#### Terminal Transformations (immutable - return new instances)
|
|
225
|
-
|
|
226
|
-
* `#mark_terminal` - Create terminal version
|
|
227
|
-
* `#unmark_terminal` - Create non-terminal version
|
|
228
|
-
* `#with_terminal(boolean)` - Create identifier with specified terminal status
|
|
229
|
-
|
|
230
|
-
#### Comparison Methods
|
|
231
|
-
|
|
232
|
-
* `#same_type?(other)` - Check if same piece type
|
|
233
|
-
* `#same_side?(other)` - Check if same side
|
|
234
|
-
* `#same_state?(other)` - Check if same state
|
|
235
|
-
* `#same_terminal?(other)` - Check if same terminal status
|
|
236
|
-
* `#==(other)` - Full equality comparison
|
|
237
|
-
|
|
238
|
-
### Constants
|
|
239
|
-
|
|
240
|
-
* `Sashite::Pin::Identifier::PIN_PATTERN` - Regular expression for PIN validation (internal use)
|
|
241
|
-
* `Sashite::Pin::Identifier::TERMINAL_MARKER` - Terminal marker character (`"^"`)
|
|
242
|
-
|
|
243
|
-
## Advanced Usage
|
|
104
|
+
### Transformations
|
|
244
105
|
|
|
245
|
-
|
|
106
|
+
All transformations return new immutable instances.
|
|
246
107
|
|
|
247
108
|
```ruby
|
|
248
|
-
|
|
249
|
-
white_king = Sashite::Pin.parse("K")
|
|
250
|
-
black_king = Sashite::Pin.parse("k")
|
|
109
|
+
pin = Sashite::Pin.parse("K")
|
|
251
110
|
|
|
252
|
-
#
|
|
253
|
-
|
|
254
|
-
|
|
111
|
+
# State transformations
|
|
112
|
+
pin.enhance.to_s # => "+K"
|
|
113
|
+
pin.diminish.to_s # => "-K"
|
|
114
|
+
pin.normalize.to_s # => "K"
|
|
255
115
|
|
|
256
|
-
#
|
|
257
|
-
|
|
258
|
-
black_king.side # => :second
|
|
116
|
+
# Side transformation
|
|
117
|
+
pin.flip.to_s # => "k"
|
|
259
118
|
|
|
260
|
-
#
|
|
261
|
-
|
|
262
|
-
|
|
119
|
+
# Terminal transformations
|
|
120
|
+
pin.mark_terminal.to_s # => "K^"
|
|
121
|
+
pin.unmark_terminal.to_s # => "K"
|
|
263
122
|
|
|
264
|
-
#
|
|
265
|
-
|
|
266
|
-
|
|
123
|
+
# Attribute changes
|
|
124
|
+
pin.with_type(:Q).to_s # => "Q"
|
|
125
|
+
pin.with_side(:second).to_s # => "k"
|
|
126
|
+
pin.with_state(:enhanced).to_s # => "+K"
|
|
127
|
+
pin.with_terminal(true).to_s # => "K^"
|
|
267
128
|
```
|
|
268
129
|
|
|
269
|
-
###
|
|
270
|
-
```ruby
|
|
271
|
-
# All transformations return new instances
|
|
272
|
-
original = Sashite::Pin.identifier(:K, :first, :normal)
|
|
273
|
-
enhanced = original.enhance
|
|
274
|
-
diminished = original.diminish
|
|
275
|
-
|
|
276
|
-
# Original piece is never modified
|
|
277
|
-
puts original.to_s # => "K"
|
|
278
|
-
puts enhanced.to_s # => "+K"
|
|
279
|
-
puts diminished.to_s # => "-K"
|
|
280
|
-
|
|
281
|
-
# Transformations can be chained
|
|
282
|
-
result = original.flip.enhance.with_type(:Q)
|
|
283
|
-
puts result.to_s # => "+q"
|
|
284
|
-
|
|
285
|
-
# Terminal status is preserved through transformations
|
|
286
|
-
terminal_king = Sashite::Pin.parse("K^")
|
|
287
|
-
enhanced_terminal = terminal_king.enhance
|
|
288
|
-
puts enhanced_terminal.to_s # => "+K^"
|
|
289
|
-
```
|
|
130
|
+
### Queries
|
|
290
131
|
|
|
291
|
-
### Game State Management
|
|
292
132
|
```ruby
|
|
293
|
-
|
|
294
|
-
def initialize
|
|
295
|
-
@pieces = {}
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
def place(square, piece)
|
|
299
|
-
@pieces[square] = piece
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
def promote(square, new_type = :Q)
|
|
303
|
-
piece = @pieces[square]
|
|
304
|
-
return nil unless piece&.normal? # Can only promote normal pieces
|
|
305
|
-
|
|
306
|
-
@pieces[square] = piece.with_type(new_type).enhance
|
|
307
|
-
end
|
|
308
|
-
|
|
309
|
-
def capture(from_square, to_square)
|
|
310
|
-
captured = @pieces[to_square]
|
|
311
|
-
@pieces[to_square] = @pieces.delete(from_square)
|
|
312
|
-
captured
|
|
313
|
-
end
|
|
314
|
-
|
|
315
|
-
def pieces_by_side(side)
|
|
316
|
-
@pieces.select { |_, piece| piece.side == side }
|
|
317
|
-
end
|
|
318
|
-
|
|
319
|
-
def promoted_pieces
|
|
320
|
-
@pieces.select { |_, piece| piece.enhanced? }
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
def terminal_pieces
|
|
324
|
-
@pieces.select { |_, piece| piece.terminal? }
|
|
325
|
-
end
|
|
326
|
-
end
|
|
133
|
+
pin = Sashite::Pin.parse("+K^")
|
|
327
134
|
|
|
328
|
-
#
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
board.place("a7", Sashite::Pin.identifier(:P, :first, :normal))
|
|
135
|
+
# State queries
|
|
136
|
+
pin.normal? # => false
|
|
137
|
+
pin.enhanced? # => true
|
|
138
|
+
pin.diminished? # => false
|
|
333
139
|
|
|
334
|
-
#
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
140
|
+
# Side queries
|
|
141
|
+
pin.first_player? # => true
|
|
142
|
+
pin.second_player? # => false
|
|
143
|
+
|
|
144
|
+
# Terminal query
|
|
145
|
+
pin.terminal? # => true
|
|
146
|
+
|
|
147
|
+
# Comparison queries
|
|
148
|
+
other = Sashite::Pin.parse("k")
|
|
149
|
+
pin.same_type?(other) # => true
|
|
150
|
+
pin.same_side?(other) # => false
|
|
151
|
+
pin.same_state?(other) # => false
|
|
152
|
+
pin.same_terminal?(other) # => false
|
|
338
153
|
```
|
|
339
154
|
|
|
340
|
-
|
|
341
|
-
```ruby
|
|
342
|
-
def analyze_pieces(pins)
|
|
343
|
-
pieces = pins.map { |pin| Sashite::Pin.parse(pin) }
|
|
344
|
-
|
|
345
|
-
{
|
|
346
|
-
total: pieces.size,
|
|
347
|
-
by_side: pieces.group_by(&:side),
|
|
348
|
-
by_type: pieces.group_by(&:type),
|
|
349
|
-
by_state: pieces.group_by(&:state),
|
|
350
|
-
promoted: pieces.count(&:enhanced?),
|
|
351
|
-
weakened: pieces.count(&:diminished?),
|
|
352
|
-
terminal: pieces.count(&:terminal?)
|
|
353
|
-
}
|
|
354
|
-
end
|
|
155
|
+
## API Reference
|
|
355
156
|
|
|
356
|
-
|
|
357
|
-
analysis = analyze_pieces(pins)
|
|
358
|
-
puts analysis[:by_side][:first].size # => 6
|
|
359
|
-
puts analysis[:promoted] # => 2
|
|
360
|
-
puts analysis[:terminal] # => 2
|
|
361
|
-
```
|
|
157
|
+
### Types
|
|
362
158
|
|
|
363
|
-
### Move Validation Example
|
|
364
159
|
```ruby
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
160
|
+
# Identifier represents a parsed PIN with all attributes.
|
|
161
|
+
class Sashite::Pin::Identifier
|
|
162
|
+
# Creates an Identifier from attributes.
|
|
163
|
+
# Raises ArgumentError if attributes are invalid.
|
|
164
|
+
#
|
|
165
|
+
# @param type [Symbol] Piece type (:A to :Z)
|
|
166
|
+
# @param side [Symbol] Player side (:first or :second)
|
|
167
|
+
# @param state [Symbol] Piece state (:normal, :enhanced, or :diminished)
|
|
168
|
+
# @param terminal [Boolean] Terminal status
|
|
169
|
+
# @return [Identifier]
|
|
170
|
+
def initialize(type, side, state = :normal, terminal: false)
|
|
171
|
+
|
|
172
|
+
# Returns the piece type (always uppercase symbol).
|
|
173
|
+
#
|
|
174
|
+
# @return [Symbol]
|
|
175
|
+
def type
|
|
176
|
+
|
|
177
|
+
# Returns the player side.
|
|
178
|
+
#
|
|
179
|
+
# @return [Symbol] :first or :second
|
|
180
|
+
def side
|
|
181
|
+
|
|
182
|
+
# Returns the piece state.
|
|
183
|
+
#
|
|
184
|
+
# @return [Symbol] :normal, :enhanced, or :diminished
|
|
185
|
+
def state
|
|
186
|
+
|
|
187
|
+
# Returns the terminal status.
|
|
188
|
+
#
|
|
189
|
+
# @return [Boolean]
|
|
190
|
+
def terminal?
|
|
191
|
+
|
|
192
|
+
# Returns the PIN string representation.
|
|
193
|
+
#
|
|
194
|
+
# @return [String]
|
|
195
|
+
def to_s
|
|
377
196
|
end
|
|
378
|
-
|
|
379
|
-
pawn = Sashite::Pin.identifier(:P, :first, :normal)
|
|
380
|
-
puts can_promote?(pawn, 8) # => true
|
|
381
|
-
|
|
382
|
-
promoted_pawn = pawn.enhance
|
|
383
|
-
puts can_promote?(promoted_pawn, 8) # => false (already promoted)
|
|
384
197
|
```
|
|
385
198
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
Following the [Game Protocol](https://sashite.dev/game-protocol/):
|
|
389
|
-
|
|
390
|
-
| Protocol Attribute | PIN Encoding | Examples | Notes |
|
|
391
|
-
|-------------------|--------------|----------|-------|
|
|
392
|
-
| **Piece Name** | ASCII letter choice | `K`/`k` = King, `P`/`p` = Pawn | Type is always stored as uppercase symbol (`:K`, `:P`) |
|
|
393
|
-
| **Piece Side** | Letter case in display | `K` = First player, `k` = Second player | Case is determined by side during rendering |
|
|
394
|
-
| **Piece State** | Optional prefix | `+K` = Enhanced, `-K` = Diminished, `K` = Normal | |
|
|
395
|
-
| **Terminal Status** | Optional suffix | `K^` = Terminal, `K` = Non-terminal | Identifies pieces critical to match continuation |
|
|
396
|
-
|
|
397
|
-
**Type Convention**: All piece types are internally represented as uppercase symbols (`:A` to `:Z`). The display case is determined by the `side` attribute: first player pieces display as uppercase, second player pieces as lowercase.
|
|
398
|
-
|
|
399
|
-
**Canonical principle**: Identical pieces must have identical PIN representations.
|
|
400
|
-
|
|
401
|
-
**Note**: PIN does not represent the **Style** attribute from the Game Protocol. For style-aware piece notation, see [Piece Name Notation (PNN)](https://sashite.dev/specs/pnn/).
|
|
402
|
-
|
|
403
|
-
## Properties
|
|
404
|
-
|
|
405
|
-
* **ASCII Compatible**: Maximum portability across systems
|
|
406
|
-
* **Rule-Agnostic**: Independent of specific game mechanics
|
|
407
|
-
* **Compact Format**: 1-3 characters per piece
|
|
408
|
-
* **Visual Distinction**: Clear player differentiation through case
|
|
409
|
-
* **Type Normalization**: Consistent uppercase type representation internally
|
|
410
|
-
* **Terminal Marker**: Explicit identification of pieces critical to match continuation
|
|
411
|
-
* **Protocol Compliant**: Direct implementation of Sashité piece attributes
|
|
412
|
-
* **Immutable**: All piece instances are frozen and transformations return new objects
|
|
413
|
-
* **Functional**: Pure functions with no side effects
|
|
414
|
-
|
|
415
|
-
## Implementation Notes
|
|
416
|
-
|
|
417
|
-
### Type Normalization Convention
|
|
418
|
-
|
|
419
|
-
PIN follows a strict type normalization convention:
|
|
420
|
-
|
|
421
|
-
1. **Internal Storage**: All piece types are stored as uppercase symbols (`:A` to `:Z`)
|
|
422
|
-
2. **Input Flexibility**: Both `"K"` and `"k"` are valid input during parsing
|
|
423
|
-
3. **Case Semantics**: Input case determines the `side` attribute, not the `type`
|
|
424
|
-
4. **Display Logic**: Output case is computed from `side` during rendering
|
|
425
|
-
|
|
426
|
-
This design ensures:
|
|
427
|
-
- Consistent internal representation regardless of input format
|
|
428
|
-
- Clear separation between piece identity (type) and ownership (side)
|
|
429
|
-
- Predictable behavior when comparing pieces of the same type
|
|
430
|
-
|
|
431
|
-
### Terminal Marker Convention
|
|
432
|
-
|
|
433
|
-
The terminal marker (`^`) identifies pieces whose presence, condition, or capacity for action determines whether the match can continue:
|
|
434
|
-
|
|
435
|
-
1. **Suffix Position**: Always appears as the last character (`K^`, `+K^`, `-k^`)
|
|
436
|
-
2. **Preservation**: Terminal status is preserved through all transformations
|
|
437
|
-
3. **Equality**: Two pieces are equal only if they have the same terminal status
|
|
438
|
-
4. **Independence**: Terminal status is independent of state (normal/enhanced/diminished)
|
|
439
|
-
|
|
440
|
-
### Example Flow
|
|
199
|
+
### Constants
|
|
441
200
|
|
|
442
201
|
```ruby
|
|
443
|
-
#
|
|
444
|
-
#
|
|
445
|
-
#
|
|
446
|
-
#
|
|
447
|
-
# ↓ Display
|
|
448
|
-
# letter: "k" (computed from type + side)
|
|
449
|
-
# PIN: "k" (final representation)
|
|
202
|
+
Sashite::Pin::Constants::VALID_TYPES # => [:A, :B, ..., :Z]
|
|
203
|
+
Sashite::Pin::Constants::VALID_SIDES # => [:first, :second]
|
|
204
|
+
Sashite::Pin::Constants::VALID_STATES # => [:normal, :enhanced, :diminished]
|
|
205
|
+
Sashite::Pin::Constants::MAX_STRING_LENGTH # => 3
|
|
450
206
|
```
|
|
451
207
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
## System Constraints
|
|
208
|
+
### Parsing
|
|
455
209
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
210
|
+
```ruby
|
|
211
|
+
# Parses a PIN string into an Identifier.
|
|
212
|
+
# Raises ArgumentError if the string is not valid.
|
|
213
|
+
#
|
|
214
|
+
# @param string [String] PIN string
|
|
215
|
+
# @return [Identifier]
|
|
216
|
+
# @raise [ArgumentError] if invalid
|
|
217
|
+
def Sashite::Pin.parse(string)
|
|
218
|
+
```
|
|
460
219
|
|
|
461
|
-
|
|
220
|
+
### Validation
|
|
462
221
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
222
|
+
```ruby
|
|
223
|
+
# Reports whether string is a valid PIN.
|
|
224
|
+
#
|
|
225
|
+
# @param string [String] PIN string
|
|
226
|
+
# @return [Boolean]
|
|
227
|
+
def Sashite::Pin.valid?(string)
|
|
228
|
+
```
|
|
468
229
|
|
|
469
|
-
|
|
230
|
+
### Transformations
|
|
470
231
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
232
|
+
```ruby
|
|
233
|
+
# State transformations (return new Identifier)
|
|
234
|
+
def enhance # => Identifier with :enhanced state
|
|
235
|
+
def diminish # => Identifier with :diminished state
|
|
236
|
+
def normalize # => Identifier with :normal state
|
|
237
|
+
|
|
238
|
+
# Side transformation
|
|
239
|
+
def flip # => Identifier with opposite side
|
|
240
|
+
|
|
241
|
+
# Terminal transformations
|
|
242
|
+
def mark_terminal # => Identifier with terminal: true
|
|
243
|
+
def unmark_terminal # => Identifier with terminal: false
|
|
244
|
+
|
|
245
|
+
# Attribute changes
|
|
246
|
+
def with_type(new_type) # => Identifier with different type
|
|
247
|
+
def with_side(new_side) # => Identifier with different side
|
|
248
|
+
def with_state(new_state) # => Identifier with different state
|
|
249
|
+
def with_terminal(new_terminal) # => Identifier with specified terminal status
|
|
250
|
+
```
|
|
475
251
|
|
|
476
|
-
|
|
252
|
+
### Errors
|
|
477
253
|
|
|
478
|
-
|
|
479
|
-
# Clone the repository
|
|
480
|
-
git clone https://github.com/sashite/pin.rb.git
|
|
481
|
-
cd pin.rb
|
|
254
|
+
All parsing and validation errors raise `ArgumentError` with descriptive messages:
|
|
482
255
|
|
|
483
|
-
|
|
484
|
-
|
|
256
|
+
| Message | Cause |
|
|
257
|
+
|---------|-------|
|
|
258
|
+
| `"empty input"` | String length is 0 |
|
|
259
|
+
| `"input exceeds 3 characters"` | String too long |
|
|
260
|
+
| `"must contain exactly one letter"` | Missing or multiple letters |
|
|
261
|
+
| `"invalid state modifier"` | Invalid prefix character |
|
|
262
|
+
| `"invalid terminal marker"` | Invalid suffix character |
|
|
485
263
|
|
|
486
|
-
|
|
487
|
-
ruby test.rb
|
|
264
|
+
## Design Principles
|
|
488
265
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
266
|
+
- **Bounded values**: Explicit validation of types, sides, states
|
|
267
|
+
- **Object-oriented**: `Identifier` class enables methods and encapsulation
|
|
268
|
+
- **Ruby idioms**: `valid?` predicate, `to_s` conversion, `ArgumentError` for invalid input
|
|
269
|
+
- **Immutable identifiers**: Frozen instances prevent mutation
|
|
270
|
+
- **Transformation methods**: Return new instances for state changes
|
|
271
|
+
- **No dependencies**: Pure Ruby standard library only
|
|
492
272
|
|
|
493
|
-
##
|
|
273
|
+
## Related Specifications
|
|
494
274
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
4. Ensure all tests pass (`ruby test.rb`)
|
|
499
|
-
5. Commit your changes (`git commit -am 'Add new feature'`)
|
|
500
|
-
6. Push to the branch (`git push origin feature/new-feature`)
|
|
501
|
-
7. Create a Pull Request
|
|
275
|
+
- [Game Protocol](https://sashite.dev/game-protocol/) — Conceptual foundation
|
|
276
|
+
- [PIN Specification](https://sashite.dev/specs/pin/1.0.0/) — Official specification
|
|
277
|
+
- [PIN Examples](https://sashite.dev/specs/pin/1.0.0/examples/) — Usage examples
|
|
502
278
|
|
|
503
279
|
## License
|
|
504
280
|
|
|
505
|
-
Available as open source under the [
|
|
506
|
-
|
|
507
|
-
## About
|
|
508
|
-
|
|
509
|
-
Maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of board game cultures.
|
|
281
|
+
Available as open source under the [Apache License 2.0](https://opensource.org/licenses/Apache-2.0).
|