sashite-pan 3.0.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/README.md +506 -157
- data/lib/sashite/pan/action/capture.rb +253 -0
- data/lib/sashite/pan/action/drop.rb +261 -0
- data/lib/sashite/pan/action/drop_capture.rb +261 -0
- data/lib/sashite/pan/action/modify.rb +221 -0
- data/lib/sashite/pan/action/move.rb +253 -0
- data/lib/sashite/pan/action/pass.rb +189 -0
- data/lib/sashite/pan/action/special.rb +255 -0
- data/lib/sashite/pan/action/static_capture.rb +207 -0
- data/lib/sashite/pan/action.rb +180 -0
- data/lib/sashite/pan.rb +28 -149
- data/lib/sashite-pan.rb +9 -10
- metadata +45 -12
- data/lib/sashite/pan/dumper.rb +0 -121
- data/lib/sashite/pan/parser.rb +0 -104
data/README.md
CHANGED
|
@@ -5,19 +5,13 @@
|
|
|
5
5
|

|
|
6
6
|
[](https://github.com/sashite/pan.rb/raw/main/LICENSE.md)
|
|
7
7
|
|
|
8
|
-
> **PAN** (Portable Action Notation)
|
|
8
|
+
> **PAN** (Portable Action Notation) implementation for the Ruby language.
|
|
9
9
|
|
|
10
10
|
## What is PAN?
|
|
11
11
|
|
|
12
|
-
PAN (Portable Action Notation) is a
|
|
12
|
+
PAN (Portable Action Notation) is a human-readable string format for representing atomic actions in abstract strategy board games. PAN provides an intuitive operator-based syntax to describe how pieces move, capture, transform, and interact on game boards.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
This gem implements the [PAN Specification v1.0.0](https://sashite.dev/documents/pan/1.0.0/), providing a Ruby interface for:
|
|
17
|
-
|
|
18
|
-
- Parsing PAN strings into structured move data
|
|
19
|
-
- Validating PAN strings according to the specification
|
|
20
|
-
- Converting between PAN and other move representations
|
|
14
|
+
This gem implements the [PAN Specification v1.0.0](https://sashite.dev/specs/pan/1.0.0/), providing a pure functional Ruby interface with immutable action objects.
|
|
21
15
|
|
|
22
16
|
## Installation
|
|
23
17
|
|
|
@@ -32,252 +26,607 @@ Or install manually:
|
|
|
32
26
|
gem install sashite-pan
|
|
33
27
|
```
|
|
34
28
|
|
|
35
|
-
##
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
require "sashite/pan"
|
|
33
|
+
|
|
34
|
+
# Validate PAN strings
|
|
35
|
+
Sashite::Pan.valid?("e2-e4") # => true
|
|
36
|
+
Sashite::Pan.valid?("d1+f3") # => true
|
|
37
|
+
Sashite::Pan.valid?("...") # => true
|
|
38
|
+
Sashite::Pan.valid?("invalid") # => false
|
|
39
|
+
|
|
40
|
+
# Parse PAN strings into action objects
|
|
41
|
+
action = Sashite::Pan.parse("e2-e4")
|
|
42
|
+
action.type # => :move
|
|
43
|
+
action.source # => "e2"
|
|
44
|
+
action.destination # => "e4"
|
|
45
|
+
action.to_s # => "e2-e4"
|
|
46
|
+
|
|
47
|
+
# Create actions programmatically
|
|
48
|
+
action = Sashite::Pan::Action.move("e2", "e4")
|
|
49
|
+
action.to_s # => "e2-e4"
|
|
50
|
+
|
|
51
|
+
promotion = Sashite::Pan::Action.move("e7", "e8", transformation: "Q")
|
|
52
|
+
promotion.to_s # => "e7-e8=Q"
|
|
53
|
+
|
|
54
|
+
capture = Sashite::Pan::Action.capture("d1", "f3")
|
|
55
|
+
capture.to_s # => "d1+f3"
|
|
56
|
+
|
|
57
|
+
# Drop actions (shogi-style)
|
|
58
|
+
drop = Sashite::Pan::Action.drop("e5", piece: "P")
|
|
59
|
+
drop.to_s # => "P*e5"
|
|
60
|
+
|
|
61
|
+
# Pass action
|
|
62
|
+
pass = Sashite::Pan::Action.pass
|
|
63
|
+
pass.to_s # => "..."
|
|
64
|
+
|
|
65
|
+
# Query action properties
|
|
66
|
+
action.move? # => true
|
|
67
|
+
action.pass? # => false
|
|
68
|
+
capture.capture? # => true
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Format Overview
|
|
72
|
+
|
|
73
|
+
PAN uses six intuitive operators:
|
|
74
|
+
|
|
75
|
+
| Operator | Meaning | Example |
|
|
76
|
+
|----------|---------|---------|
|
|
77
|
+
| `-` | Move to empty square | `e2-e4` |
|
|
78
|
+
| `+` | Capture at destination | `d1+f3` |
|
|
79
|
+
| `~` | Special move with side effects | `e1~g1` (castling) |
|
|
80
|
+
| `*` | Drop to empty square | `P*e5` |
|
|
81
|
+
| `.` | Drop with capture | `L.b4` |
|
|
82
|
+
| `=` | Transform piece | `e4=+P` |
|
|
83
|
+
| `...` | Pass turn | `...` |
|
|
84
|
+
|
|
85
|
+
For complete format details, see the [PAN Specification](https://sashite.dev/specs/pan/1.0.0/).
|
|
86
|
+
|
|
87
|
+
## API Reference
|
|
36
88
|
|
|
37
|
-
|
|
89
|
+
### Module Methods
|
|
38
90
|
|
|
39
|
-
|
|
91
|
+
#### Validation
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
Sashite::Pan.valid?(pan_string)
|
|
40
95
|
```
|
|
41
|
-
|
|
96
|
+
|
|
97
|
+
Check if a string represents a valid PAN action.
|
|
98
|
+
|
|
99
|
+
**Parameters:**
|
|
100
|
+
- `pan_string` [String] - The string to validate
|
|
101
|
+
|
|
102
|
+
**Returns:** [Boolean] - true if valid PAN, false otherwise
|
|
103
|
+
|
|
104
|
+
**Examples:**
|
|
105
|
+
```ruby
|
|
106
|
+
Sashite::Pan.valid?("e2-e4") # => true
|
|
107
|
+
Sashite::Pan.valid?("P*d4") # => true
|
|
108
|
+
Sashite::Pan.valid?("...") # => true
|
|
109
|
+
Sashite::Pan.valid?("invalid") # => false
|
|
42
110
|
```
|
|
43
|
-
**Example**: `e2-e4` - Moves a piece from e2 to e4
|
|
44
111
|
|
|
45
|
-
|
|
112
|
+
#### Parsing
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
Sashite::Pan.parse(pan_string)
|
|
46
116
|
```
|
|
47
|
-
|
|
117
|
+
|
|
118
|
+
Parse a PAN string into an Action object.
|
|
119
|
+
|
|
120
|
+
**Parameters:**
|
|
121
|
+
- `pan_string` [String] - PAN notation string
|
|
122
|
+
|
|
123
|
+
**Returns:** [Pan::Action] - Immutable action object
|
|
124
|
+
|
|
125
|
+
**Raises:** [ArgumentError] - If the PAN string is invalid
|
|
126
|
+
|
|
127
|
+
**Examples:**
|
|
128
|
+
```ruby
|
|
129
|
+
Sashite::Pan.parse("e2-e4") # => #<Pan::Action type=:move ...>
|
|
130
|
+
Sashite::Pan.parse("d1+f3") # => #<Pan::Action type=:capture ...>
|
|
131
|
+
Sashite::Pan.parse("...") # => #<Pan::Action type=:pass>
|
|
48
132
|
```
|
|
49
|
-
**Example**: `e4xd5` - Moves a piece from e4 to d5, capturing the piece at d5
|
|
50
133
|
|
|
51
|
-
###
|
|
134
|
+
### Action Class
|
|
135
|
+
|
|
136
|
+
#### Creation Methods
|
|
137
|
+
|
|
138
|
+
All creation methods return immutable Action objects.
|
|
139
|
+
|
|
140
|
+
##### Pass Action
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
Sashite::Pan::Action.pass
|
|
52
144
|
```
|
|
53
|
-
|
|
145
|
+
|
|
146
|
+
Create a pass action (no move, turn ends).
|
|
147
|
+
|
|
148
|
+
**Returns:** [Action] - Pass action
|
|
149
|
+
|
|
150
|
+
**Example:**
|
|
151
|
+
```ruby
|
|
152
|
+
action = Sashite::Pan::Action.pass
|
|
153
|
+
action.to_s # => "..."
|
|
54
154
|
```
|
|
55
|
-
**Example**: `*e4` - Places a piece at e4 from off-board (hand, reserve, etc.)
|
|
56
155
|
|
|
57
|
-
|
|
156
|
+
##### Movement Actions
|
|
58
157
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
158
|
+
```ruby
|
|
159
|
+
Sashite::Pan::Action.move(source, destination, transformation: nil)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Create a move action to an empty square.
|
|
62
163
|
|
|
63
|
-
|
|
164
|
+
**Parameters:**
|
|
165
|
+
- `source` [String] - Source CELL coordinate
|
|
166
|
+
- `destination` [String] - Destination CELL coordinate
|
|
167
|
+
- `transformation` [String, nil] - Optional EPIN transformation
|
|
64
168
|
|
|
65
|
-
|
|
169
|
+
**Returns:** [Action] - Move action
|
|
66
170
|
|
|
67
|
-
|
|
171
|
+
**Examples:**
|
|
172
|
+
```ruby
|
|
173
|
+
Sashite::Pan::Action.move("e2", "e4")
|
|
174
|
+
# => "e2-e4"
|
|
68
175
|
|
|
69
|
-
|
|
176
|
+
Sashite::Pan::Action.move("e7", "e8", transformation: "Q")
|
|
177
|
+
# => "e7-e8=Q"
|
|
70
178
|
|
|
179
|
+
Sashite::Pan::Action.move("a7", "a8", transformation: "+R")
|
|
180
|
+
# => "a7-a8=+R"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
Sashite::Pan::Action.capture(source, destination, transformation: nil)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Create a capture action at destination.
|
|
190
|
+
|
|
191
|
+
**Parameters:**
|
|
192
|
+
- `source` [String] - Source CELL coordinate
|
|
193
|
+
- `destination` [String] - Destination CELL coordinate (occupied square)
|
|
194
|
+
- `transformation` [String, nil] - Optional EPIN transformation
|
|
195
|
+
|
|
196
|
+
**Returns:** [Action] - Capture action
|
|
197
|
+
|
|
198
|
+
**Examples:**
|
|
71
199
|
```ruby
|
|
72
|
-
|
|
200
|
+
Sashite::Pan::Action.capture("d1", "f3")
|
|
201
|
+
# => "d1+f3"
|
|
73
202
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
203
|
+
Sashite::Pan::Action.capture("b7", "a8", transformation: "R")
|
|
204
|
+
# => "b7+a8=R"
|
|
205
|
+
```
|
|
77
206
|
|
|
78
|
-
|
|
79
|
-
result = Sashite::Pan.parse("e4xd5")
|
|
80
|
-
# => {type: :capture, source: "e4", destination: "d5"}
|
|
207
|
+
---
|
|
81
208
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
# => {type: :drop, destination: "e4"}
|
|
209
|
+
```ruby
|
|
210
|
+
Sashite::Pan::Action.special(source, destination, transformation: nil)
|
|
85
211
|
```
|
|
86
212
|
|
|
87
|
-
|
|
213
|
+
Create a special move action with implicit side effects.
|
|
88
214
|
|
|
89
|
-
|
|
215
|
+
**Parameters:**
|
|
216
|
+
- `source` [String] - Source CELL coordinate
|
|
217
|
+
- `destination` [String] - Destination CELL coordinate
|
|
218
|
+
- `transformation` [String, nil] - Optional EPIN transformation
|
|
90
219
|
|
|
220
|
+
**Returns:** [Action] - Special action
|
|
221
|
+
|
|
222
|
+
**Examples:**
|
|
91
223
|
```ruby
|
|
92
|
-
|
|
224
|
+
Sashite::Pan::Action.special("e1", "g1")
|
|
225
|
+
# => "e1~g1" (castling)
|
|
226
|
+
|
|
227
|
+
Sashite::Pan::Action.special("e5", "f6")
|
|
228
|
+
# => "e5~f6" (en passant)
|
|
229
|
+
```
|
|
93
230
|
|
|
94
|
-
|
|
95
|
-
result = Sashite::Pan.safe_parse("e2-e4")
|
|
96
|
-
# => {type: :move, source: "e2", destination: "e4"}
|
|
231
|
+
##### Static Capture
|
|
97
232
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
# => nil
|
|
233
|
+
```ruby
|
|
234
|
+
Sashite::Pan::Action.static_capture(square)
|
|
101
235
|
```
|
|
102
236
|
|
|
103
|
-
|
|
237
|
+
Create a static capture action (remove piece without movement).
|
|
238
|
+
|
|
239
|
+
**Parameters:**
|
|
240
|
+
- `square` [String] - CELL coordinate of piece to capture
|
|
104
241
|
|
|
105
|
-
|
|
242
|
+
**Returns:** [Action] - Static capture action
|
|
106
243
|
|
|
244
|
+
**Example:**
|
|
107
245
|
```ruby
|
|
108
|
-
|
|
246
|
+
Sashite::Pan::Action.static_capture("d4")
|
|
247
|
+
# => "+d4"
|
|
248
|
+
```
|
|
109
249
|
|
|
110
|
-
|
|
111
|
-
Sashite::Pan.valid?("*e4") # => true
|
|
112
|
-
Sashite::Pan.valid?("e4xd5") # => true
|
|
250
|
+
##### Drop Actions
|
|
113
251
|
|
|
114
|
-
|
|
115
|
-
Sashite::Pan.
|
|
116
|
-
Sashite::Pan.valid?("E2-e4") # => false (uppercase file)
|
|
117
|
-
Sashite::Pan.valid?("e2 - e4") # => false (spaces not allowed)
|
|
252
|
+
```ruby
|
|
253
|
+
Sashite::Pan::Action.drop(destination, piece: nil, transformation: nil)
|
|
118
254
|
```
|
|
119
255
|
|
|
120
|
-
|
|
256
|
+
Create a drop action to empty square.
|
|
257
|
+
|
|
258
|
+
**Parameters:**
|
|
259
|
+
- `destination` [String] - Destination CELL coordinate (empty square)
|
|
260
|
+
- `piece` [String, nil] - Optional EPIN piece identifier
|
|
261
|
+
- `transformation` [String, nil] - Optional EPIN transformation
|
|
262
|
+
|
|
263
|
+
**Returns:** [Action] - Drop action
|
|
121
264
|
|
|
122
|
-
|
|
265
|
+
**Examples:**
|
|
266
|
+
```ruby
|
|
267
|
+
Sashite::Pan::Action.drop("e5", piece: "P")
|
|
268
|
+
# => "P*e5"
|
|
269
|
+
|
|
270
|
+
Sashite::Pan::Action.drop("d4")
|
|
271
|
+
# => "*d4" (piece type inferred from context)
|
|
272
|
+
|
|
273
|
+
Sashite::Pan::Action.drop("c3", piece: "S", transformation: "+S")
|
|
274
|
+
# => "S*c3=+S"
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
123
278
|
|
|
124
279
|
```ruby
|
|
125
|
-
|
|
280
|
+
Sashite::Pan::Action.drop_capture(destination, piece: nil, transformation: nil)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Create a drop action with capture.
|
|
126
284
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
285
|
+
**Parameters:**
|
|
286
|
+
- `destination` [String] - Destination CELL coordinate (occupied square)
|
|
287
|
+
- `piece` [String, nil] - Optional EPIN piece identifier
|
|
288
|
+
- `transformation` [String, nil] - Optional EPIN transformation
|
|
130
289
|
|
|
131
|
-
|
|
132
|
-
Sashite::Pan.parse("exd5")
|
|
133
|
-
# => {type: :capture, source: "e4", destination: "d5"}
|
|
290
|
+
**Returns:** [Action] - Drop capture action
|
|
134
291
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
Sashite::Pan.
|
|
138
|
-
|
|
292
|
+
**Example:**
|
|
293
|
+
```ruby
|
|
294
|
+
Sashite::Pan::Action.drop_capture("b4", piece: "L")
|
|
295
|
+
# => "L.b4"
|
|
139
296
|
```
|
|
140
297
|
|
|
141
|
-
|
|
298
|
+
##### Modification Action
|
|
142
299
|
|
|
143
300
|
```ruby
|
|
144
|
-
|
|
301
|
+
Sashite::Pan::Action.modify(square, piece)
|
|
302
|
+
```
|
|
145
303
|
|
|
146
|
-
|
|
147
|
-
Sashite::Pan.parse("g7-f7")
|
|
148
|
-
# => {type: :move, source: "g7", destination: "f7"}
|
|
304
|
+
Create an in-place transformation action.
|
|
149
305
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
306
|
+
**Parameters:**
|
|
307
|
+
- `square` [String] - CELL coordinate
|
|
308
|
+
- `piece` [String] - EPIN piece identifier (final state)
|
|
153
309
|
|
|
154
|
-
|
|
155
|
-
Sashite::Pan.parse("h2xg2")
|
|
156
|
-
# => {type: :capture, source: "h2", destination: "g2"}
|
|
310
|
+
**Returns:** [Action] - Modification action
|
|
157
311
|
|
|
158
|
-
|
|
159
|
-
|
|
312
|
+
**Examples:**
|
|
313
|
+
```ruby
|
|
314
|
+
Sashite::Pan::Action.modify("e4", "+P")
|
|
315
|
+
# => "e4=+P"
|
|
316
|
+
|
|
317
|
+
Sashite::Pan::Action.modify("c3", "k'")
|
|
318
|
+
# => "c3=k'"
|
|
160
319
|
```
|
|
161
320
|
|
|
162
|
-
|
|
321
|
+
#### Instance Methods
|
|
163
322
|
|
|
164
|
-
|
|
323
|
+
##### Attribute Access
|
|
165
324
|
|
|
166
|
-
|
|
325
|
+
```ruby
|
|
326
|
+
action.type
|
|
327
|
+
```
|
|
167
328
|
|
|
168
|
-
|
|
169
|
-
- **Promotion choices**: Cannot specify what piece a pawn promotes to
|
|
170
|
-
- **Game state**: No encoding of check, checkmate, or game conditions
|
|
171
|
-
- **Complex moves**: Castling requires external representation
|
|
172
|
-
- **Piece identity**: Multiple pieces of the same type making similar moves
|
|
329
|
+
Get the action type.
|
|
173
330
|
|
|
174
|
-
|
|
331
|
+
**Returns:** [Symbol] - One of: `:pass`, `:move`, `:capture`, `:special`, `:static_capture`, `:drop`, `:drop_capture`, `:modify`
|
|
332
|
+
|
|
333
|
+
---
|
|
175
334
|
|
|
176
335
|
```ruby
|
|
177
|
-
|
|
336
|
+
action.source
|
|
337
|
+
```
|
|
178
338
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
339
|
+
Get the source coordinate (for movement actions).
|
|
340
|
+
|
|
341
|
+
**Returns:** [String, nil] - CELL coordinate or nil
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
```ruby
|
|
346
|
+
action.destination
|
|
183
347
|
```
|
|
184
348
|
|
|
185
|
-
|
|
349
|
+
Get the destination coordinate.
|
|
186
350
|
|
|
187
|
-
|
|
188
|
-
- Games requiring promotion choice specification
|
|
189
|
-
- Analysis requiring piece type identification
|
|
190
|
-
- Self-contained game records without context
|
|
351
|
+
**Returns:** [String, nil] - CELL coordinate or nil
|
|
191
352
|
|
|
192
|
-
|
|
353
|
+
---
|
|
193
354
|
|
|
194
|
-
|
|
355
|
+
```ruby
|
|
356
|
+
action.piece
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Get the piece identifier (for drop/modify actions).
|
|
360
|
+
|
|
361
|
+
**Returns:** [String, nil] - EPIN identifier or nil
|
|
362
|
+
|
|
363
|
+
---
|
|
195
364
|
|
|
196
365
|
```ruby
|
|
197
|
-
|
|
366
|
+
action.transformation
|
|
367
|
+
```
|
|
198
368
|
|
|
199
|
-
|
|
200
|
-
Sashite::Pan.parse("e2-e2") # Source equals destination
|
|
201
|
-
rescue Sashite::Pan::Parser::Error => e
|
|
202
|
-
puts e.message # => "Source and destination cannot be identical"
|
|
203
|
-
end
|
|
369
|
+
Get the transformation piece (for actions with `=<piece>`).
|
|
204
370
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
371
|
+
**Returns:** [String, nil] - EPIN identifier or nil
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
```ruby
|
|
376
|
+
action.to_s
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Convert action to PAN string representation.
|
|
380
|
+
|
|
381
|
+
**Returns:** [String] - PAN notation
|
|
382
|
+
|
|
383
|
+
**Examples:**
|
|
384
|
+
```ruby
|
|
385
|
+
Sashite::Pan::Action.move("e2", "e4").to_s
|
|
386
|
+
# => "e2-e4"
|
|
387
|
+
|
|
388
|
+
Sashite::Pan::Action.drop("e5", piece: "P").to_s
|
|
389
|
+
# => "P*e5"
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
##### Type Queries
|
|
393
|
+
|
|
394
|
+
```ruby
|
|
395
|
+
action.pass?
|
|
396
|
+
action.move?
|
|
397
|
+
action.capture?
|
|
398
|
+
action.special?
|
|
399
|
+
action.static_capture?
|
|
400
|
+
action.drop?
|
|
401
|
+
action.drop_capture?
|
|
402
|
+
action.modify?
|
|
403
|
+
action.movement? # true for move, capture, or special
|
|
404
|
+
action.drop_action? # true for drop or drop_capture
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Check action type.
|
|
408
|
+
|
|
409
|
+
**Returns:** [Boolean]
|
|
410
|
+
|
|
411
|
+
**Examples:**
|
|
412
|
+
```ruby
|
|
413
|
+
action = Sashite::Pan.parse("e2-e4")
|
|
414
|
+
action.move? # => true
|
|
415
|
+
action.movement? # => true
|
|
416
|
+
action.pass? # => false
|
|
417
|
+
|
|
418
|
+
pass = Sashite::Pan::Action.pass
|
|
419
|
+
pass.pass? # => true
|
|
420
|
+
|
|
421
|
+
drop = Sashite::Pan.parse("P*e5")
|
|
422
|
+
drop.drop? # => true
|
|
423
|
+
drop.drop_action? # => true
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
##### Comparison
|
|
427
|
+
|
|
428
|
+
```ruby
|
|
429
|
+
action == other
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Check equality between actions.
|
|
433
|
+
|
|
434
|
+
**Parameters:**
|
|
435
|
+
- `other` [Action] - Action to compare with
|
|
436
|
+
|
|
437
|
+
**Returns:** [Boolean] - true if actions are identical
|
|
438
|
+
|
|
439
|
+
**Example:**
|
|
440
|
+
```ruby
|
|
441
|
+
action1 = Sashite::Pan.parse("e2-e4")
|
|
442
|
+
action2 = Sashite::Pan::Action.move("e2", "e4")
|
|
443
|
+
action1 == action2 # => true
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Advanced Usage
|
|
447
|
+
|
|
448
|
+
### Parsing Game Sequences
|
|
449
|
+
|
|
450
|
+
```ruby
|
|
451
|
+
# Parse a sequence of moves
|
|
452
|
+
moves = %w[e2-e4 e7-e5 g1-f3 b8-c6]
|
|
453
|
+
actions = moves.map { |move| Sashite::Pan.parse(move) }
|
|
210
454
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
455
|
+
# Analyze action types
|
|
456
|
+
actions.count(&:move?) # => 4
|
|
457
|
+
actions.all?(&:movement?) # => true
|
|
458
|
+
|
|
459
|
+
# Extract coordinates
|
|
460
|
+
sources = actions.map(&:source)
|
|
461
|
+
destinations = actions.map(&:destination)
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Action Type Detection
|
|
465
|
+
|
|
466
|
+
```ruby
|
|
467
|
+
def describe_action(pan_string)
|
|
468
|
+
action = Sashite::Pan.parse(pan_string)
|
|
469
|
+
|
|
470
|
+
case action.type
|
|
471
|
+
when :pass
|
|
472
|
+
"Player passes"
|
|
473
|
+
when :move
|
|
474
|
+
"Move from #{action.source} to #{action.destination}"
|
|
475
|
+
when :capture
|
|
476
|
+
"Capture at #{action.destination}"
|
|
477
|
+
when :special
|
|
478
|
+
"Special move: #{action.source} to #{action.destination}"
|
|
479
|
+
when :drop
|
|
480
|
+
piece_str = action.piece ? "#{action.piece} " : ""
|
|
481
|
+
"Drop #{piece_str}at #{action.destination}"
|
|
482
|
+
when :modify
|
|
483
|
+
"Transform piece at #{action.square} to #{action.piece}"
|
|
484
|
+
end
|
|
215
485
|
end
|
|
486
|
+
|
|
487
|
+
describe_action("e2-e4") # => "Move from e2 to e4"
|
|
488
|
+
describe_action("d1+f3") # => "Capture at f3"
|
|
489
|
+
describe_action("P*e5") # => "Drop P at e5"
|
|
490
|
+
describe_action("...") # => "Player passes"
|
|
216
491
|
```
|
|
217
492
|
|
|
218
|
-
|
|
493
|
+
### Transformation Detection
|
|
219
494
|
|
|
220
|
-
|
|
495
|
+
```ruby
|
|
496
|
+
def has_promotion?(pan_string)
|
|
497
|
+
action = Sashite::Pan.parse(pan_string)
|
|
498
|
+
!action.transformation.nil?
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
has_promotion?("e2-e4") # => false
|
|
502
|
+
has_promotion?("e7-e8=Q") # => true
|
|
503
|
+
has_promotion?("P*e5") # => false
|
|
504
|
+
has_promotion?("S*c3=+S") # => true
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Building Move Generators
|
|
221
508
|
|
|
222
509
|
```ruby
|
|
223
|
-
|
|
510
|
+
class MoveBuilder
|
|
511
|
+
def initialize(source)
|
|
512
|
+
@source = source
|
|
513
|
+
end
|
|
224
514
|
|
|
225
|
-
def
|
|
226
|
-
|
|
515
|
+
def to(destination)
|
|
516
|
+
Sashite::Pan::Action.move(@source, destination)
|
|
517
|
+
end
|
|
227
518
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
source = string[0..1]
|
|
231
|
-
destination = string[-2..-1]
|
|
232
|
-
return source != destination
|
|
519
|
+
def captures(destination)
|
|
520
|
+
Sashite::Pan::Action.capture(@source, destination)
|
|
233
521
|
end
|
|
234
522
|
|
|
235
|
-
|
|
523
|
+
def to_promoting(destination, piece)
|
|
524
|
+
Sashite::Pan::Action.move(@source, destination, transformation: piece)
|
|
525
|
+
end
|
|
236
526
|
end
|
|
527
|
+
|
|
528
|
+
# Usage
|
|
529
|
+
builder = MoveBuilder.new("e7")
|
|
530
|
+
builder.to("e8").to_s # => "e7-e8"
|
|
531
|
+
builder.to_promoting("e8", "Q").to_s # => "e7-e8=Q"
|
|
532
|
+
builder.captures("d8").to_s # => "e7+d8"
|
|
237
533
|
```
|
|
238
534
|
|
|
239
|
-
|
|
535
|
+
### Validation Before Parsing
|
|
240
536
|
|
|
241
|
-
|
|
537
|
+
```ruby
|
|
538
|
+
def safe_parse(pan_string)
|
|
539
|
+
return nil unless Sashite::Pan.valid?(pan_string)
|
|
540
|
+
|
|
541
|
+
Sashite::Pan.parse(pan_string)
|
|
542
|
+
rescue ArgumentError
|
|
543
|
+
nil
|
|
544
|
+
end
|
|
242
545
|
|
|
243
|
-
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
- **Quick notation**: Manual notation for simple games
|
|
546
|
+
safe_parse("e2-e4") # => #<Pan::Action ...>
|
|
547
|
+
safe_parse("invalid") # => nil
|
|
548
|
+
```
|
|
247
549
|
|
|
248
|
-
###
|
|
550
|
+
### Pattern Matching (Ruby 3.0+)
|
|
249
551
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
552
|
+
```ruby
|
|
553
|
+
def analyze(action)
|
|
554
|
+
case action
|
|
555
|
+
in { type: :move, source:, destination:, transformation: nil }
|
|
556
|
+
"Simple move: #{source} → #{destination}"
|
|
557
|
+
in { type: :move, transformation: piece }
|
|
558
|
+
"Promotion to #{piece}"
|
|
559
|
+
in { type: :capture, source:, destination: }
|
|
560
|
+
"Capture: #{source} takes #{destination}"
|
|
561
|
+
in { type: :drop, piece:, destination: }
|
|
562
|
+
"Drop #{piece} at #{destination}"
|
|
563
|
+
in { type: :pass }
|
|
564
|
+
"Pass"
|
|
565
|
+
else
|
|
566
|
+
"Other action"
|
|
567
|
+
end
|
|
568
|
+
end
|
|
254
569
|
|
|
255
|
-
|
|
570
|
+
action = Sashite::Pan.parse("e7-e8=Q")
|
|
571
|
+
analyze(action) # => "Promotion to Q"
|
|
572
|
+
```
|
|
256
573
|
|
|
257
|
-
|
|
574
|
+
## Properties
|
|
258
575
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
576
|
+
* **Operator-based**: Intuitive symbols for different action types
|
|
577
|
+
* **Compact notation**: Minimal character usage while maintaining readability
|
|
578
|
+
* **Game-agnostic**: Works across chess, shōgi, xiangqi, and other abstract strategy games
|
|
579
|
+
* **CELL integration**: Uses CELL coordinates for board positions
|
|
580
|
+
* **EPIN integration**: Uses EPIN identifiers for piece representation
|
|
581
|
+
* **Immutable**: All action objects are frozen
|
|
582
|
+
* **Functional**: Pure functions with no side effects
|
|
583
|
+
* **Type-safe**: Strong validation and error handling
|
|
263
584
|
|
|
264
|
-
##
|
|
585
|
+
## Related Specifications
|
|
265
586
|
|
|
266
|
-
-
|
|
267
|
-
-
|
|
268
|
-
-
|
|
269
|
-
-
|
|
270
|
-
-
|
|
587
|
+
- [PAN Specification v1.0.0](https://sashite.dev/specs/pan/1.0.0/) - Complete format specification
|
|
588
|
+
- [PAN Examples](https://sashite.dev/specs/pan/1.0.0/examples/) - Usage examples across different games
|
|
589
|
+
- [CELL](https://sashite.dev/specs/cell/) - Coordinate encoding for board positions
|
|
590
|
+
- [EPIN](https://sashite.dev/specs/epin/) - Extended piece identifiers
|
|
591
|
+
- [Game Protocol](https://sashite.dev/protocol/) - Conceptual foundation
|
|
271
592
|
|
|
272
593
|
## Documentation
|
|
273
594
|
|
|
274
|
-
- [Official PAN Specification](https://sashite.dev/
|
|
595
|
+
- [Official PAN Specification v1.0.0](https://sashite.dev/specs/pan/1.0.0/)
|
|
275
596
|
- [API Documentation](https://rubydoc.info/github/sashite/pan.rb/main)
|
|
597
|
+
- [PAN Examples](https://sashite.dev/specs/pan/1.0.0/examples/)
|
|
598
|
+
|
|
599
|
+
## Development
|
|
600
|
+
|
|
601
|
+
```sh
|
|
602
|
+
# Clone the repository
|
|
603
|
+
git clone https://github.com/sashite/pan.rb.git
|
|
604
|
+
cd pan.rb
|
|
605
|
+
|
|
606
|
+
# Install dependencies
|
|
607
|
+
bundle install
|
|
608
|
+
|
|
609
|
+
# Run tests
|
|
610
|
+
ruby test.rb
|
|
611
|
+
|
|
612
|
+
# Generate documentation
|
|
613
|
+
yard doc
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
## Contributing
|
|
617
|
+
|
|
618
|
+
1. Fork the repository
|
|
619
|
+
2. Create a feature branch (`git checkout -b feature/new-feature`)
|
|
620
|
+
3. Add tests for your changes
|
|
621
|
+
4. Ensure all tests pass (`ruby test.rb`)
|
|
622
|
+
5. Commit your changes (`git commit -am 'Add new feature'`)
|
|
623
|
+
6. Push to the branch (`git push origin feature/new-feature`)
|
|
624
|
+
7. Create a Pull Request
|
|
276
625
|
|
|
277
626
|
## License
|
|
278
627
|
|
|
279
|
-
|
|
628
|
+
Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
|
|
280
629
|
|
|
281
|
-
## About
|
|
630
|
+
## About
|
|
282
631
|
|
|
283
|
-
|
|
632
|
+
Maintained by [Sashité](https://sashite.com/) – promoting chess variants and sharing the beauty of board game cultures.
|