sashite-ggn 0.6.0 → 0.8.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 +431 -83
- data/lib/sashite/ggn/ruleset/source/destination/engine.rb +120 -309
- data/lib/sashite/ggn/ruleset/source/destination.rb +46 -84
- data/lib/sashite/ggn/ruleset/source.rb +40 -73
- data/lib/sashite/ggn/ruleset.rb +191 -253
- data/lib/sashite/ggn.rb +47 -312
- data/lib/sashite-ggn.rb +8 -120
- metadata +96 -16
- data/lib/sashite/ggn/move_validator.rb +0 -208
- data/lib/sashite/ggn/ruleset/source/destination/engine/transition.rb +0 -81
- data/lib/sashite/ggn/schema.rb +0 -171
- data/lib/sashite/ggn/validation_error.rb +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 71b56f3fe3aa4fe158bc428cb92880751133bfc769ac9a300f78026e51c95ef6
|
4
|
+
data.tar.gz: 779187aa54e75e9ef0d679bc0617b4a6230052e5ab24018409932654cd351be7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94c8dd3916c7032479547f949e75a6b1865f95a19d1baba421947931b7bb2dd700dc8672b0297734c677ae02152dfd7526eec1a6cfed549d6f484cc3d97dbba3
|
7
|
+
data.tar.gz: 818b73b8a42e82e968e02aac3b9e8cade1f46a59765e56d19f5f3e8fe68478395a8d3134a074e0037890a61eadbc39989a406e7ff86a580f8defda5abb0d1d0d
|
data/README.md
CHANGED
@@ -1,152 +1,500 @@
|
|
1
1
|
# Ggn.rb
|
2
2
|
|
3
|
-
|
3
|
+
[](https://github.com/sashite/ggn.rb/tags)
|
4
|
+
[](https://rubydoc.info/github/sashite/ggn.rb/main)
|
5
|
+

|
6
|
+
[](https://github.com/sashite/ggn.rb/raw/main/LICENSE.md)
|
4
7
|
|
5
|
-
|
6
|
-
|
8
|
+
> **GGN** (General Gameplay Notation) implementation for Ruby — a pure, functional library for evaluating **movement possibilities** in abstract strategy board games.
|
9
|
+
|
10
|
+
---
|
7
11
|
|
8
12
|
## What is GGN?
|
9
13
|
|
10
|
-
GGN (General Gameplay Notation) is a rule-agnostic
|
14
|
+
GGN (General Gameplay Notation) is a rule-agnostic format for describing **pseudo-legal moves** in abstract strategy board games. GGN serves as a **movement possibility oracle**: given a movement context (piece and source location) plus a destination location, it determines if the movement is feasible under specified pre-conditions.
|
15
|
+
|
16
|
+
This gem implements the [GGN Specification v1.0.0](https://sashite.dev/specs/ggn/1.0.0/), providing complete movement possibility evaluation with environmental constraint checking.
|
17
|
+
|
18
|
+
### Core Philosophy
|
11
19
|
|
12
|
-
GGN
|
20
|
+
GGN answers the fundamental question:
|
13
21
|
|
14
|
-
|
15
|
-
- Hybrid games combining elements from different chess variants
|
16
|
-
- Database systems requiring piece disambiguation across game types
|
17
|
-
- Performance-critical applications with pre-computed move libraries
|
22
|
+
> **Can this piece, currently at this location, reach that location?**
|
18
23
|
|
19
|
-
|
24
|
+
It encodes:
|
25
|
+
- **Which piece** (via QPI format)
|
26
|
+
- **From where** (source location using CELL or HAND)
|
27
|
+
- **To where** (destination location using CELL or HAND)
|
28
|
+
- **Which environmental pre-conditions** must hold (`must`)
|
29
|
+
- **Which environmental pre-conditions** must not hold (`deny`)
|
30
|
+
- **What changes occur** if executed (`diff` in STN format)
|
31
|
+
|
32
|
+
---
|
20
33
|
|
21
34
|
## Installation
|
22
35
|
|
23
36
|
```ruby
|
24
|
-
|
37
|
+
# In your Gemfile
|
38
|
+
gem "sashite-ggn"
|
25
39
|
```
|
26
40
|
|
27
|
-
|
41
|
+
Or install manually:
|
42
|
+
|
43
|
+
```sh
|
44
|
+
gem install sashite-ggn
|
45
|
+
```
|
28
46
|
|
29
|
-
|
47
|
+
---
|
48
|
+
|
49
|
+
## Dependencies
|
50
|
+
|
51
|
+
GGN builds upon foundational Sashité specifications:
|
30
52
|
|
31
53
|
```ruby
|
32
|
-
|
54
|
+
gem "sashite-cell" # Coordinate Encoding for Layered Locations
|
55
|
+
gem "sashite-feen" # Forsyth–Edwards Enhanced Notation
|
56
|
+
gem "sashite-hand" # Hold And Notation Designator
|
57
|
+
gem "sashite-lcn" # Location Condition Notation
|
58
|
+
gem "sashite-qpi" # Qualified Piece Identifier
|
59
|
+
gem "sashite-stn" # State Transition Notation
|
60
|
+
```
|
33
61
|
|
34
|
-
|
35
|
-
ruleset = Sashite::Ggn.load_file("chess_moves.json")
|
62
|
+
---
|
36
63
|
|
37
|
-
|
38
|
-
|
39
|
-
|
64
|
+
## Quick Start
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
require "sashite/ggn"
|
68
|
+
|
69
|
+
# Parse GGN data structure
|
70
|
+
ggn_data = {
|
71
|
+
"C:P" => {
|
72
|
+
"e2" => {
|
73
|
+
"e4" => [
|
74
|
+
{
|
75
|
+
"must" => { "e3" => "empty", "e4" => "empty" },
|
76
|
+
"deny" => {},
|
77
|
+
"diff" => {
|
78
|
+
"board" => { "e2" => nil, "e4" => "C:P" },
|
79
|
+
"toggle" => true
|
80
|
+
}
|
81
|
+
}
|
82
|
+
]
|
83
|
+
}
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
ruleset = Sashite::Ggn.parse(ggn_data)
|
88
|
+
|
89
|
+
# Query movement possibility through method chaining
|
90
|
+
feen = "+rnbq+kbn+r/+p+p+p+p+p+p+p+p/8/8/8/8/+P+P+P+P+P+P+P+P/+RNBQ+KBN+R / C/c"
|
91
|
+
transitions = ruleset.select("C:P").from("e2").to("e4").where(feen)
|
92
|
+
|
93
|
+
transitions.any? # => true
|
40
94
|
```
|
41
95
|
|
42
|
-
|
96
|
+
---
|
97
|
+
|
98
|
+
## API Reference
|
99
|
+
|
100
|
+
### Module Functions
|
101
|
+
|
102
|
+
#### `Sashite::Ggn.parse(data) → Ruleset`
|
103
|
+
|
104
|
+
Parses GGN data structure into an immutable Ruleset object.
|
43
105
|
|
44
106
|
```ruby
|
45
|
-
|
46
|
-
|
107
|
+
ruleset = Sashite::Ggn.parse(ggn_data)
|
108
|
+
```
|
47
109
|
|
48
|
-
|
49
|
-
|
110
|
+
**Parameters:**
|
111
|
+
- `data` (Hash): GGN data structure conforming to specification
|
50
112
|
|
51
|
-
|
52
|
-
|
53
|
-
|
113
|
+
**Returns:** `Ruleset` — Immutable ruleset object
|
114
|
+
|
115
|
+
**Raises:** `ArgumentError` — If data structure is invalid
|
116
|
+
|
117
|
+
---
|
118
|
+
|
119
|
+
#### `Sashite::Ggn.valid?(data) → Boolean`
|
120
|
+
|
121
|
+
Validates GGN data structure against specification.
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
Sashite::Ggn.valid?(ggn_data) # => true
|
54
125
|
```
|
55
126
|
|
56
|
-
|
127
|
+
**Parameters:**
|
128
|
+
- `data` (Hash): Data structure to validate
|
129
|
+
|
130
|
+
**Returns:** `Boolean` — True if valid, false otherwise
|
131
|
+
|
132
|
+
---
|
133
|
+
|
134
|
+
### `Sashite::Ggn::Ruleset` Class
|
135
|
+
|
136
|
+
Immutable container for GGN movement rules.
|
137
|
+
|
138
|
+
#### `#select(piece) → Source`
|
139
|
+
|
140
|
+
Selects movement rules for a specific piece type.
|
57
141
|
|
58
142
|
```ruby
|
59
|
-
|
60
|
-
|
143
|
+
source = ruleset.select("C:K")
|
144
|
+
```
|
61
145
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
146
|
+
**Parameters:**
|
147
|
+
- `piece` (String): QPI piece identifier
|
148
|
+
|
149
|
+
**Returns:** `Source` — Source selector object
|
150
|
+
|
151
|
+
**Raises:** `KeyError` — If piece not found in ruleset
|
152
|
+
|
153
|
+
---
|
154
|
+
|
155
|
+
#### `#pseudo_legal_transitions(feen) → Array<Array>`
|
156
|
+
|
157
|
+
Generates all pseudo-legal moves for the given position.
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
moves = ruleset.pseudo_legal_transitions(feen)
|
161
|
+
# => [["C:P", "e2", "e4", [#<Transition...>]], ...]
|
66
162
|
```
|
67
163
|
|
68
|
-
|
164
|
+
**Parameters:**
|
165
|
+
- `feen` (String): Position in FEEN format
|
166
|
+
|
167
|
+
**Returns:** `Array<Array>` — Array of `[piece, source, destination, transitions]` tuples
|
168
|
+
|
169
|
+
---
|
69
170
|
|
70
|
-
|
171
|
+
#### `#piece?(piece) → Boolean`
|
172
|
+
|
173
|
+
Checks if ruleset contains movement rules for specified piece.
|
71
174
|
|
72
175
|
```ruby
|
73
|
-
#
|
74
|
-
|
75
|
-
transitions = engine.where({"e7" => "CHESS:P", "e8" => nil}, "CHESS")
|
176
|
+
ruleset.piece?("C:K") # => true
|
177
|
+
```
|
76
178
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
179
|
+
**Parameters:**
|
180
|
+
- `piece` (String): QPI piece identifier
|
181
|
+
|
182
|
+
**Returns:** `Boolean`
|
183
|
+
|
184
|
+
---
|
185
|
+
|
186
|
+
#### `#pieces → Array<String>`
|
187
|
+
|
188
|
+
Returns all piece identifiers in ruleset.
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
ruleset.pieces # => ["C:K", "C:Q", "C:P", ...]
|
82
192
|
```
|
83
193
|
|
84
|
-
|
194
|
+
**Returns:** `Array<String>` — QPI piece identifiers
|
195
|
+
|
196
|
+
---
|
197
|
+
|
198
|
+
#### `#to_h → Hash`
|
85
199
|
|
86
|
-
|
200
|
+
Converts ruleset to hash representation.
|
87
201
|
|
88
202
|
```ruby
|
89
|
-
#
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
203
|
+
ruleset.to_h # => { "C:K" => { "e1" => { "e2" => [...] } } }
|
204
|
+
```
|
205
|
+
|
206
|
+
**Returns:** `Hash` — GGN data structure
|
207
|
+
|
208
|
+
---
|
209
|
+
|
210
|
+
### `Sashite::Ggn::Ruleset::Source` Class
|
211
|
+
|
212
|
+
Represents movement possibilities for a piece type.
|
213
|
+
|
214
|
+
#### `#from(source) → Destination`
|
215
|
+
|
216
|
+
Specifies the source location for the piece.
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
destination = source.from("e1")
|
220
|
+
```
|
221
|
+
|
222
|
+
**Parameters:**
|
223
|
+
- `source` (String): Source location (CELL coordinate or HAND "*")
|
224
|
+
|
225
|
+
**Returns:** `Destination` — Destination selector object
|
226
|
+
|
227
|
+
**Raises:** `KeyError` — If source not found for this piece
|
228
|
+
|
229
|
+
---
|
230
|
+
|
231
|
+
#### `#sources → Array<String>`
|
232
|
+
|
233
|
+
Returns all valid source locations for this piece.
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
source.sources # => ["e1", "d1", "*"]
|
237
|
+
```
|
238
|
+
|
239
|
+
**Returns:** `Array<String>` — Source locations
|
240
|
+
|
241
|
+
---
|
242
|
+
|
243
|
+
#### `#source?(location) → Boolean`
|
244
|
+
|
245
|
+
Checks if location is a valid source for this piece.
|
246
|
+
|
247
|
+
```ruby
|
248
|
+
source.source?("e1") # => true
|
249
|
+
```
|
94
250
|
|
95
|
-
|
96
|
-
|
251
|
+
**Parameters:**
|
252
|
+
- `location` (String): Source location
|
253
|
+
|
254
|
+
**Returns:** `Boolean`
|
255
|
+
|
256
|
+
---
|
257
|
+
|
258
|
+
### `Sashite::Ggn::Ruleset::Source::Destination` Class
|
259
|
+
|
260
|
+
Represents movement possibilities from a specific source.
|
261
|
+
|
262
|
+
#### `#to(destination) → Engine`
|
263
|
+
|
264
|
+
Specifies the destination location.
|
265
|
+
|
266
|
+
```ruby
|
267
|
+
engine = destination.to("e2")
|
268
|
+
```
|
269
|
+
|
270
|
+
**Parameters:**
|
271
|
+
- `destination` (String): Destination location (CELL coordinate or HAND "*")
|
272
|
+
|
273
|
+
**Returns:** `Engine` — Movement evaluation engine
|
274
|
+
|
275
|
+
**Raises:** `KeyError` — If destination not found from this source
|
276
|
+
|
277
|
+
---
|
278
|
+
|
279
|
+
#### `#destinations → Array<String>`
|
280
|
+
|
281
|
+
Returns all valid destinations from this source.
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
destination.destinations # => ["d1", "d2", "e2", "f2", "f1"]
|
285
|
+
```
|
286
|
+
|
287
|
+
**Returns:** `Array<String>` — Destination locations
|
288
|
+
|
289
|
+
---
|
290
|
+
|
291
|
+
#### `#destination?(location) → Boolean`
|
292
|
+
|
293
|
+
Checks if location is a valid destination from this source.
|
294
|
+
|
295
|
+
```ruby
|
296
|
+
destination.destination?("e2") # => true
|
297
|
+
```
|
298
|
+
|
299
|
+
**Parameters:**
|
300
|
+
- `location` (String): Destination location
|
301
|
+
|
302
|
+
**Returns:** `Boolean`
|
303
|
+
|
304
|
+
---
|
305
|
+
|
306
|
+
### `Sashite::Ggn::Ruleset::Source::Destination::Engine` Class
|
307
|
+
|
308
|
+
Evaluates movement possibility under given position conditions.
|
309
|
+
|
310
|
+
#### `#where(feen) → Array<Transition>`
|
311
|
+
|
312
|
+
Evaluates movement against position and returns valid transitions.
|
313
|
+
|
314
|
+
```ruby
|
315
|
+
transitions = engine.where(feen)
|
316
|
+
```
|
317
|
+
|
318
|
+
**Parameters:**
|
319
|
+
- `feen` (String): Position in FEEN format
|
320
|
+
|
321
|
+
**Returns:** `Array<Sashite::Stn::Transition>` — Valid state transitions (may be empty)
|
322
|
+
|
323
|
+
---
|
324
|
+
|
325
|
+
#### `#possibilities → Array<Hash>`
|
326
|
+
|
327
|
+
Returns raw movement possibility rules.
|
328
|
+
|
329
|
+
```ruby
|
330
|
+
engine.possibilities
|
331
|
+
# => [{ "must" => {...}, "deny" => {...}, "diff" => {...} }]
|
97
332
|
```
|
98
333
|
|
99
|
-
|
334
|
+
**Returns:** `Array<Hash>` — Movement possibility specifications
|
335
|
+
|
336
|
+
---
|
337
|
+
|
338
|
+
## GGN Format
|
100
339
|
|
101
|
-
|
340
|
+
### Structure
|
102
341
|
|
103
342
|
```ruby
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
343
|
+
{
|
344
|
+
"<qpi-piece>" => {
|
345
|
+
"<source-location>" => {
|
346
|
+
"<destination-location>" => [
|
347
|
+
{
|
348
|
+
"must" => { /* LCN format */ },
|
349
|
+
"deny" => { /* LCN format */ },
|
350
|
+
"diff" => { /* STN format */ }
|
351
|
+
}
|
352
|
+
]
|
353
|
+
}
|
354
|
+
}
|
108
355
|
}
|
356
|
+
```
|
357
|
+
|
358
|
+
### Field Specifications
|
359
|
+
|
360
|
+
| Field | Type | Description |
|
361
|
+
|-------|------|-------------|
|
362
|
+
| **Piece** | String (QPI) | Piece identifier (e.g., `"C:K"`, `"s:+p"`) |
|
363
|
+
| **Source** | String (CELL/HAND) | Origin location (e.g., `"e2"`, `"*"`) |
|
364
|
+
| **Destination** | String (CELL/HAND) | Target location (e.g., `"e4"`, `"*"`) |
|
365
|
+
| **must** | Hash (LCN) | Pre-conditions that must be satisfied |
|
366
|
+
| **deny** | Hash (LCN) | Pre-conditions that must not be satisfied |
|
367
|
+
| **diff** | Hash (STN) | State transition specification |
|
368
|
+
|
369
|
+
---
|
370
|
+
|
371
|
+
## Usage Examples
|
372
|
+
|
373
|
+
### Method Chaining
|
374
|
+
|
375
|
+
```ruby
|
376
|
+
# Query specific movement
|
377
|
+
feen = "+rnbq+kbn+r/+p+p+p+p+p+p+p+p/8/8/8/8/+P+P+P+P+P+P+P+P/+RNBQ+KBN+R / C/c"
|
109
378
|
|
110
|
-
transitions =
|
111
|
-
|
379
|
+
transitions = ruleset
|
380
|
+
.select("C:P")
|
381
|
+
.from("e2")
|
382
|
+
.to("e4")
|
383
|
+
.where(feen)
|
384
|
+
|
385
|
+
transitions.size # => 1
|
386
|
+
transitions.first.board_changes # => { "e2" => nil, "e4" => "C:P" }
|
112
387
|
```
|
113
388
|
|
114
|
-
|
389
|
+
### Generate All Pseudo-Legal Moves
|
115
390
|
|
116
391
|
```ruby
|
117
|
-
|
118
|
-
|
119
|
-
|
392
|
+
feen = "+rnbq+kbn+r/+p+p+p+p+p+p+p+p/8/8/8/8/+P+P+P+P+P+P+P+P/+RNBQ+KBN+R / C/c"
|
393
|
+
|
394
|
+
all_moves = ruleset.pseudo_legal_transitions(feen)
|
395
|
+
|
396
|
+
all_moves.each do |piece, source, destination, transitions|
|
397
|
+
puts "#{piece}: #{source} → #{destination} (#{transitions.size} variants)"
|
398
|
+
end
|
120
399
|
```
|
121
400
|
|
122
|
-
|
401
|
+
### Existence Checks
|
123
402
|
|
124
|
-
|
403
|
+
```ruby
|
404
|
+
# Check if piece exists in ruleset
|
405
|
+
ruleset.piece?("C:K") # => true
|
406
|
+
|
407
|
+
# Check valid sources
|
408
|
+
source = ruleset.select("C:K")
|
409
|
+
source.source?("e1") # => true
|
125
410
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
- `Sashite::Ggn::Ruleset::Source::Destination::Engine::Transition` - Move result
|
411
|
+
# Check valid destinations
|
412
|
+
destination = source.from("e1")
|
413
|
+
destination.destination?("e2") # => true
|
414
|
+
```
|
131
415
|
|
132
|
-
###
|
416
|
+
### Introspection
|
417
|
+
|
418
|
+
```ruby
|
419
|
+
# List all pieces
|
420
|
+
ruleset.pieces # => ["C:K", "C:Q", "C:R", ...]
|
133
421
|
|
134
|
-
|
135
|
-
|
136
|
-
|
422
|
+
# List sources for a piece
|
423
|
+
source.sources # => ["e1", "d1", "f1", ...]
|
424
|
+
|
425
|
+
# List destinations from a source
|
426
|
+
destination.destinations # => ["d1", "d2", "e2", "f2", "f1"]
|
427
|
+
|
428
|
+
# Access raw possibilities
|
429
|
+
engine.possibilities
|
430
|
+
# => [{ "must" => {...}, "deny" => {...}, "diff" => {...} }]
|
431
|
+
```
|
432
|
+
|
433
|
+
---
|
434
|
+
|
435
|
+
## Design Properties
|
436
|
+
|
437
|
+
- **Functional**: Pure functions with no side effects
|
438
|
+
- **Immutable**: All data structures frozen and unchangeable
|
439
|
+
- **Composable**: Clean method chaining for natural query flow
|
440
|
+
- **Type-safe**: Strict validation of all inputs
|
441
|
+
- **Delegative**: Leverages CELL, FEEN, HAND, LCN, QPI, STN specifications
|
442
|
+
- **Spec-compliant**: Strictly follows GGN v1.0.0 specification
|
443
|
+
|
444
|
+
---
|
445
|
+
|
446
|
+
## Error Handling
|
447
|
+
|
448
|
+
```ruby
|
449
|
+
# Handle missing piece
|
450
|
+
begin
|
451
|
+
source = ruleset.select("INVALID:X")
|
452
|
+
rescue KeyError => e
|
453
|
+
puts "Piece not found: #{e.message}"
|
454
|
+
end
|
455
|
+
|
456
|
+
# Handle missing source
|
457
|
+
begin
|
458
|
+
destination = source.from("z9")
|
459
|
+
rescue KeyError => e
|
460
|
+
puts "Source not found: #{e.message}"
|
461
|
+
end
|
462
|
+
|
463
|
+
# Handle missing destination
|
464
|
+
begin
|
465
|
+
engine = destination.to("z9")
|
466
|
+
rescue KeyError => e
|
467
|
+
puts "Destination not found: #{e.message}"
|
468
|
+
end
|
469
|
+
|
470
|
+
# Safe validation before parsing
|
471
|
+
if Sashite::Ggn.valid?(data)
|
472
|
+
ruleset = Sashite::Ggn.parse(data)
|
473
|
+
else
|
474
|
+
puts "Invalid GGN structure"
|
475
|
+
end
|
476
|
+
```
|
477
|
+
|
478
|
+
---
|
137
479
|
|
138
480
|
## Related Specifications
|
139
481
|
|
140
|
-
GGN
|
482
|
+
- [GGN v1.0.0](https://sashite.dev/specs/ggn/1.0.0/) — General Gameplay Notation specification
|
483
|
+
- [CELL v1.0.0](https://sashite.dev/specs/cell/1.0.0/) — Coordinate encoding
|
484
|
+
- [FEEN v1.0.0](https://sashite.dev/specs/feen/1.0.0/) — Position notation
|
485
|
+
- [HAND v1.0.0](https://sashite.dev/specs/hand/1.0.0/) — Reserve notation
|
486
|
+
- [LCN v1.0.0](https://sashite.dev/specs/lcn/1.0.0/) — Location conditions
|
487
|
+
- [QPI v1.0.0](https://sashite.dev/specs/qpi/1.0.0/) — Piece identification
|
488
|
+
- [STN v1.0.0](https://sashite.dev/specs/stn/1.0.0/) — State transitions
|
141
489
|
|
142
|
-
|
143
|
-
- [FEEN](https://sashite.dev/documents/feen/1.0.0/) - Board position representation
|
144
|
-
- [PMN](https://sashite.dev/documents/pmn/1.0.0/) - Move sequence representation
|
490
|
+
---
|
145
491
|
|
146
492
|
## License
|
147
493
|
|
148
|
-
|
494
|
+
Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
|
495
|
+
|
496
|
+
---
|
149
497
|
|
150
|
-
## About
|
498
|
+
## About
|
151
499
|
|
152
|
-
|
500
|
+
Maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of board game cultures.
|