sashite-gan 2.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.md +1 -1
- data/README.md +398 -165
- data/lib/sashite/gan/actor.rb +185 -0
- data/lib/sashite/gan.rb +70 -31
- data/lib/sashite-gan.rb +15 -3
- metadata +27 -101
- data/lib/sashite/gan/abbr.rb +0 -76
- data/lib/sashite/gan/error/string.rb +0 -10
- data/lib/sashite/gan/error/style.rb +0 -12
- data/lib/sashite/gan/error/type.rb +0 -12
- data/lib/sashite/gan/error.rb +0 -12
- data/lib/sashite/gan/parser.rb +0 -31
- data/lib/sashite/gan/piece.rb +0 -150
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5469e58632f24a93eea22a4dd6d48c27028f84a2137469164eae9684274a7af
|
4
|
+
data.tar.gz: fa8fe045e1f5ced019b9bdf33369bc457e94187d7703ed13c7189c040e430502
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91d7fbc665bce2be60a37921398dea166684efa68ce55e22fd740d7d85907599d8812a491dbd561b73c1bb8aa1c625d453590842adb3edfb86b686e69c1f8ed8
|
7
|
+
data.tar.gz: 7761db8067e89586009fe4c52f06e632e7c153c5a7f370c613abd3066691d3d9b3fe214f71db1de1988a4a0d02df1d841b03a70b9aadb49ad89dcf3b940ef8bd
|
data/LICENSE.md
CHANGED
data/README.md
CHANGED
@@ -1,181 +1,414 @@
|
|
1
|
-
#
|
1
|
+
# Gan.rb
|
2
2
|
|
3
|
-
[](https://github.com/sashite/gan.rb/tags)
|
4
|
+
[](https://rubydoc.info/github/sashite/gan.rb/main)
|
5
|
+

|
6
|
+
[](https://github.com/sashite/gan.rb/raw/main/LICENSE.md)
|
4
7
|
|
5
|
-
>
|
8
|
+
> **GAN** (General Actor Notation) support for the Ruby language.
|
9
|
+
|
10
|
+
## What is GAN?
|
11
|
+
|
12
|
+
GAN (General Actor Notation) defines a consistent and rule-agnostic format for identifying game actors in abstract strategy board games. GAN provides unambiguous identification of pieces by combining Style Name Notation (SNN) with Piece Name Notation (PNN), eliminating collision problems when multiple piece styles are present in the same context.
|
13
|
+
|
14
|
+
This gem implements the [GAN Specification v1.0.0](https://sashite.dev/documents/gan/1.0.0/), providing a Ruby interface for working with game actors through a clean and modular API that builds upon the existing [sashite-snn](https://rubygems.org/gems/sashite-snn) and [pnn](https://rubygems.org/gems/pnn) gems.
|
6
15
|
|
7
16
|
## Installation
|
8
17
|
|
9
|
-
|
18
|
+
```ruby
|
19
|
+
# In your Gemfile
|
20
|
+
gem "sashite-gan"
|
21
|
+
```
|
22
|
+
|
23
|
+
Or install manually:
|
24
|
+
|
25
|
+
```sh
|
26
|
+
gem install sashite-gan
|
27
|
+
```
|
28
|
+
|
29
|
+
## GAN Format
|
30
|
+
|
31
|
+
A GAN record consists of a style identifier (SNN format), followed by a colon separator, followed by a piece identifier (PNN format):
|
32
|
+
|
33
|
+
```
|
34
|
+
<style-id>:<piece-id>
|
35
|
+
```
|
36
|
+
|
37
|
+
Where:
|
38
|
+
- `<style-id>` is a Style Name Notation (SNN) identifier conforming to SNN specification
|
39
|
+
- `:` is a literal colon character serving as a separator
|
40
|
+
- `<piece-id>` is a Piece Name Notation (PNN) identifier conforming to PNN specification
|
10
41
|
|
11
|
-
|
12
|
-
gem 'sashite-gan'
|
13
|
-
```
|
42
|
+
## Basic Usage
|
14
43
|
|
15
|
-
|
44
|
+
### Creating Actor Objects
|
16
45
|
|
17
|
-
|
46
|
+
The primary interface is the `Sashite::Gan::Actor` class, which represents a game actor in GAN format:
|
18
47
|
|
19
48
|
```ruby
|
20
|
-
require
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
piece.abbr.to_s # => "-k"
|
42
|
-
piece.style # => "c"
|
43
|
-
piece.topside? # => true
|
44
|
-
piece.bottomside? # => false
|
45
|
-
piece.to_s # => "c:-k"
|
46
|
-
piece.topside.to_s # => "c:-k"
|
47
|
-
piece.bottomside.to_s # => "C:-K"
|
48
|
-
piece.oppositeside.to_s # => "C:-K"
|
49
|
-
piece.promote.to_s # => "c:+-k"
|
50
|
-
piece.unpromote.to_s # => "c:-k"
|
51
|
-
|
52
|
-
|
53
|
-
# Makruk (Thai chess)'s Bishop, White
|
54
|
-
piece = Sashite::GAN.parse("M:B")
|
55
|
-
|
56
|
-
piece.abbr.to_s # => "B"
|
57
|
-
piece.style # => "M"
|
58
|
-
piece.topside? # => false
|
59
|
-
piece.bottomside? # => true
|
60
|
-
piece.to_s # => "M:B"
|
61
|
-
piece.topside.to_s # => "m:b"
|
62
|
-
piece.bottomside.to_s # => "M:B"
|
63
|
-
piece.oppositeside.to_s # => "m:b"
|
64
|
-
piece.promote.to_s # => "M:+B"
|
65
|
-
piece.unpromote.to_s # => "M:B"
|
66
|
-
|
67
|
-
|
68
|
-
# Shogi (Japanese chess)'s King, Gote
|
69
|
-
piece = Sashite::GAN.parse("s:-k")
|
70
|
-
|
71
|
-
piece.abbr.to_s # => "-k"
|
72
|
-
piece.style # => "s"
|
73
|
-
piece.topside? # => true
|
74
|
-
piece.bottomside? # => false
|
75
|
-
piece.to_s # => "s:-k"
|
76
|
-
piece.topside.to_s # => "s:-k"
|
77
|
-
piece.bottomside.to_s # => "S:-K"
|
78
|
-
piece.oppositeside.to_s # => "S:-K"
|
79
|
-
piece.promote.to_s # => "s:+-k"
|
80
|
-
piece.unpromote.to_s # => "s:-k"
|
81
|
-
|
82
|
-
|
83
|
-
# Shogi (Japanese chess)'s King, Sente
|
84
|
-
piece = Sashite::GAN.parse("S:-K")
|
85
|
-
|
86
|
-
piece.abbr.to_s # => "-K"
|
87
|
-
piece.style # => "S"
|
88
|
-
piece.topside? # => false
|
89
|
-
piece.bottomside? # => true
|
90
|
-
piece.to_s # => "S:-K"
|
91
|
-
piece.topside.to_s # => "s:-k"
|
92
|
-
piece.bottomside.to_s # => "S:-K"
|
93
|
-
piece.oppositeside.to_s # => "s:-k"
|
94
|
-
piece.promote.to_s # => "S:+-K"
|
95
|
-
piece.unpromote.to_s # => "S:-K"
|
96
|
-
|
97
|
-
|
98
|
-
# Shogi (Japanese chess)'s promoted Pawn, Sente
|
99
|
-
piece = Sashite::GAN.parse("S:+P")
|
100
|
-
|
101
|
-
piece.abbr.to_s # => "+P"
|
102
|
-
piece.style # => "S"
|
103
|
-
piece.topside? # => false
|
104
|
-
piece.bottomside? # => true
|
105
|
-
piece.to_s # => "S:+P"
|
106
|
-
piece.topside.to_s # => "s:+p"
|
107
|
-
piece.bottomside.to_s # => "S:+P"
|
108
|
-
piece.oppositeside.to_s # => "s:+p"
|
109
|
-
piece.promote.to_s # => "S:+P"
|
110
|
-
piece.unpromote.to_s # => "S:P"
|
111
|
-
|
112
|
-
|
113
|
-
# Xiangqi (Chinese chess)'s General, Red
|
114
|
-
piece = Sashite::GAN.parse("X:-G")
|
115
|
-
|
116
|
-
piece.abbr.to_s # => "-G"
|
117
|
-
piece.style # => "X"
|
118
|
-
piece.topside? # => false
|
119
|
-
piece.bottomside? # => true
|
120
|
-
piece.to_s # => "X:-G"
|
121
|
-
piece.topside.to_s # => "x:-g"
|
122
|
-
piece.bottomside.to_s # => "X:-G"
|
123
|
-
piece.oppositeside.to_s # => "x:-g"
|
124
|
-
piece.promote.to_s # => "X:+-G"
|
125
|
-
piece.unpromote.to_s # => "X:-G"
|
126
|
-
|
127
|
-
|
128
|
-
# Xiangqi (Chinese chess)'s Flying General, Red
|
129
|
-
piece = Sashite::GAN.parse("X:+-G")
|
130
|
-
|
131
|
-
piece.abbr.to_s # => "+-G"
|
132
|
-
piece.style # => "X"
|
133
|
-
piece.topside? # => false
|
134
|
-
piece.bottomside? # => true
|
135
|
-
piece.to_s # => "X:+-G"
|
136
|
-
piece.topside.to_s # => "x:+-g"
|
137
|
-
piece.bottomside.to_s # => "X:+-G"
|
138
|
-
piece.oppositeside.to_s # => "x:+-g"
|
139
|
-
piece.promote.to_s # => "X:+-G"
|
140
|
-
piece.unpromote.to_s # => "X:-G"
|
141
|
-
|
142
|
-
|
143
|
-
# Dai Dai Shogi (huge Japanese chess)'s Phoenix, Sente
|
144
|
-
piece = Sashite::GAN.parse("DAI_DAI_SHOGI:PH")
|
145
|
-
|
146
|
-
piece.abbr.to_s # => "PH"
|
147
|
-
piece.style # => "DAI_DAI_SHOGI"
|
148
|
-
piece.topside? # => false
|
149
|
-
piece.bottomside? # => true
|
150
|
-
piece.to_s # => "DAI_DAI_SHOGI:PH"
|
151
|
-
piece.topside.to_s # => "dai_dai_shogi:ph"
|
152
|
-
piece.bottomside.to_s # => "DAI_DAI_SHOGI:PH"
|
153
|
-
piece.oppositeside.to_s # => "dai_dai_shogi:ph"
|
154
|
-
piece.promote.to_s # => "DAI_DAI_SHOGI:+PH"
|
155
|
-
piece.unpromote.to_s # => "DAI_DAI_SHOGI:PH"
|
156
|
-
|
157
|
-
|
158
|
-
# A random FOO chess variant's promoted Z piece, Bottom-side
|
159
|
-
piece = Sashite::GAN.parse("FOO:+Z")
|
160
|
-
|
161
|
-
piece.abbr.to_s # => "+Z"
|
162
|
-
piece.style # => "FOO"
|
163
|
-
piece.topside? # => false
|
164
|
-
piece.bottomside? # => true
|
165
|
-
piece.to_s # => "FOO:+Z"
|
166
|
-
piece.topside.to_s # => "foo:+z"
|
167
|
-
piece.bottomside.to_s # => "FOO:+Z"
|
168
|
-
piece.oppositeside.to_s # => "foo:+z"
|
169
|
-
piece.promote.to_s # => "FOO:+Z"
|
170
|
-
piece.unpromote.to_s # => "FOO:Z"
|
49
|
+
require "sashite/gan"
|
50
|
+
|
51
|
+
# Parse a GAN string into an actor object
|
52
|
+
actor = Sashite::Gan::Actor.parse("CHESS:K")
|
53
|
+
# => #<Sashite::Gan::Actor:0x... @style="CHESS" @piece="K">
|
54
|
+
|
55
|
+
# With piece modifiers
|
56
|
+
enhanced_actor = Sashite::Gan::Actor.parse("SHOGI:+P")
|
57
|
+
# => #<Sashite::Gan::Actor:0x... @style="SHOGI" @piece="+P">
|
58
|
+
|
59
|
+
# Create directly with constructor
|
60
|
+
actor = Sashite::Gan::Actor.new("CHESS", "K")
|
61
|
+
enhanced_actor = Sashite::Gan::Actor.new("SHOGI", "+P")
|
62
|
+
|
63
|
+
# Create with style and piece objects
|
64
|
+
style = Sashite::Snn::Style.new("CHESS")
|
65
|
+
piece = Pnn::Piece.new("K")
|
66
|
+
actor = Sashite::Gan::Actor.new(style, piece)
|
67
|
+
|
68
|
+
# Convenience method
|
69
|
+
actor = Sashite::Gan.actor("CHESS", "K")
|
171
70
|
```
|
172
71
|
|
173
|
-
|
72
|
+
### Converting to GAN String
|
73
|
+
|
74
|
+
Convert an actor object back to its GAN string representation:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
actor = Sashite::Gan::Actor.parse("CHESS:K")
|
78
|
+
actor.to_s
|
79
|
+
# => "CHESS:K"
|
80
|
+
|
81
|
+
enhanced_actor = Sashite::Gan::Actor.parse("SHOGI:+p'")
|
82
|
+
enhanced_actor.to_s
|
83
|
+
# => "SHOGI:+p'"
|
84
|
+
```
|
85
|
+
|
86
|
+
### Accessing Components
|
87
|
+
|
88
|
+
Access the style and piece components of an actor:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
actor = Sashite::Gan::Actor.parse("CHESS:K")
|
92
|
+
|
93
|
+
# Access as strings
|
94
|
+
actor.style_name # => "CHESS"
|
95
|
+
actor.piece_name # => "K"
|
96
|
+
|
97
|
+
# Access as objects
|
98
|
+
actor.style # => #<Sashite::Snn::Style:0x... @identifier="CHESS">
|
99
|
+
actor.piece # => #<Pnn::Piece:0x... @letter="K">
|
100
|
+
|
101
|
+
# Check player associations
|
102
|
+
actor.style.first_player? # => true
|
103
|
+
actor.piece.uppercase? # => true
|
104
|
+
```
|
105
|
+
|
106
|
+
## Casing Combinations and Player Association
|
107
|
+
|
108
|
+
GAN allows all four combinations of case between style and piece identifiers to support dynamic ownership changes:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
# First player's style, first player's piece
|
112
|
+
actor1 = Sashite::Gan::Actor.parse("CHESS:K")
|
113
|
+
actor1.style.first_player? # => true
|
114
|
+
actor1.piece.uppercase? # => true
|
115
|
+
|
116
|
+
# First player's style, second player's piece (piece was captured and converted)
|
117
|
+
actor2 = Sashite::Gan::Actor.parse("CHESS:k")
|
118
|
+
actor2.style.first_player? # => true
|
119
|
+
actor2.piece.lowercase? # => true
|
120
|
+
|
121
|
+
# Second player's style, first player's piece (piece was captured and converted)
|
122
|
+
actor3 = Sashite::Gan::Actor.parse("chess:K")
|
123
|
+
actor3.style.second_player? # => true
|
124
|
+
actor3.piece.uppercase? # => true
|
125
|
+
|
126
|
+
# Second player's style, second player's piece
|
127
|
+
actor4 = Sashite::Gan::Actor.parse("chess:k")
|
128
|
+
actor4.style.second_player? # => true
|
129
|
+
actor4.piece.lowercase? # => true
|
130
|
+
```
|
131
|
+
|
132
|
+
## Dynamic Ownership Changes
|
133
|
+
|
134
|
+
While style assignment remains fixed throughout a game, piece ownership may change during gameplay:
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
# Original piece owned by first player
|
138
|
+
original = Sashite::Gan::Actor.parse("SHOGI:P")
|
139
|
+
|
140
|
+
# After capture by second player (modifiers preserved by default)
|
141
|
+
captured = original.change_piece_ownership
|
142
|
+
captured.to_s # => "SHOGI:p"
|
143
|
+
|
144
|
+
# Or create the captured version directly
|
145
|
+
captured = Sashite::Gan::Actor.new(original.style, "p")
|
146
|
+
|
147
|
+
# Example with enhanced piece - modifiers are preserved
|
148
|
+
enhanced = Sashite::Gan::Actor.parse("SHOGI:+P")
|
149
|
+
captured_enhanced = enhanced.change_piece_ownership
|
150
|
+
captured_enhanced.to_s # => "SHOGI:+p" (modifiers preserved)
|
151
|
+
|
152
|
+
# To remove modifiers explicitly (if game rules require it):
|
153
|
+
bare_captured = enhanced.bare_piece.change_piece_ownership
|
154
|
+
bare_captured.to_s # => "SHOGI:p" (modifiers removed)
|
155
|
+
```
|
156
|
+
|
157
|
+
## Traditional Same-Style Games
|
158
|
+
|
159
|
+
In traditional games where both players use the same piece style:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
# Chess pieces
|
163
|
+
white_king = Sashite::Gan::Actor.parse("CHESS:K")
|
164
|
+
black_king = Sashite::Gan::Actor.parse("chess:k")
|
165
|
+
|
166
|
+
white_queen = Sashite::Gan::Actor.parse("CHESS:Q")
|
167
|
+
black_queen = Sashite::Gan::Actor.parse("chess:q")
|
168
|
+
|
169
|
+
# Shogi pieces
|
170
|
+
first_king = Sashite::Gan::Actor.parse("SHOGI:K")
|
171
|
+
second_king = Sashite::Gan::Actor.parse("shogi:k")
|
172
|
+
|
173
|
+
first_gold = Sashite::Gan::Actor.parse("SHOGI:G")
|
174
|
+
second_gold = Sashite::Gan::Actor.parse("shogi:g")
|
175
|
+
```
|
176
|
+
|
177
|
+
## Cross-Style Games
|
178
|
+
|
179
|
+
In games where players use different piece styles:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
# Chess vs Makruk
|
183
|
+
chess_king = Sashite::Gan::Actor.parse("CHESS:K")
|
184
|
+
makruk_king = Sashite::Gan::Actor.parse("makruk:k")
|
185
|
+
|
186
|
+
chess_queen = Sashite::Gan::Actor.parse("CHESS:Q")
|
187
|
+
makruk_queen = Sashite::Gan::Actor.parse("makruk:q")
|
188
|
+
|
189
|
+
# Shogi vs Xiangqi
|
190
|
+
shogi_king = Sashite::Gan::Actor.parse("SHOGI:K")
|
191
|
+
xiangqi_general = Sashite::Gan::Actor.parse("xiangqi:g")
|
192
|
+
|
193
|
+
shogi_gold = Sashite::Gan::Actor.parse("SHOGI:G")
|
194
|
+
xiangqi_advisor = Sashite::Gan::Actor.parse("xiangqi:a")
|
195
|
+
```
|
196
|
+
|
197
|
+
## Pieces with States and Ownership Changes
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
# Original enhanced piece
|
201
|
+
original = Sashite::Gan::Actor.parse("CHESS:R'")
|
202
|
+
|
203
|
+
# After capture (modifiers preserved by default)
|
204
|
+
captured = original.change_piece_ownership
|
205
|
+
captured.to_s # => "chess:R'"
|
206
|
+
|
207
|
+
# If game rules require modifier removal during capture:
|
208
|
+
captured_bare = original.bare_piece.change_piece_ownership
|
209
|
+
captured_bare.to_s # => "chess:R"
|
210
|
+
|
211
|
+
# Promoted shogi piece captured
|
212
|
+
promoted_pawn = Sashite::Gan::Actor.parse("shogi:+p")
|
213
|
+
captured_promoted = promoted_pawn.change_piece_ownership
|
214
|
+
captured_promoted.to_s # => "SHOGI:+p" (modifiers preserved)
|
215
|
+
|
216
|
+
# With explicit modifier removal:
|
217
|
+
captured_demoted = promoted_pawn.bare_piece.change_piece_ownership
|
218
|
+
captured_demoted.to_s # => "SHOGI:p"
|
219
|
+
```
|
220
|
+
|
221
|
+
## Collision Resolution
|
222
|
+
|
223
|
+
GAN resolves naming conflicts between different styles:
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
# All different actors despite similar piece types
|
227
|
+
chess_rook = Sashite::Gan::Actor.parse("CHESS:R")
|
228
|
+
shogi_rook = Sashite::Gan::Actor.parse("SHOGI:R")
|
229
|
+
makruk_rook = Sashite::Gan::Actor.parse("MAKRUK:R")
|
230
|
+
xiangqi_chariot = Sashite::Gan::Actor.parse("xiangqi:r")
|
231
|
+
|
232
|
+
# They can all coexist in the same context
|
233
|
+
pieces = [chess_rook, shogi_rook, makruk_rook, xiangqi_chariot]
|
234
|
+
puts pieces.map(&:to_s)
|
235
|
+
# => ["CHESS:R", "SHOGI:R", "MAKRUK:R", "xiangqi:r"]
|
236
|
+
```
|
237
|
+
|
238
|
+
## Advanced Usage
|
239
|
+
|
240
|
+
### Working with Collections
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
# Group actors by style
|
244
|
+
actors = [
|
245
|
+
Sashite::Gan::Actor.parse("CHESS:K"),
|
246
|
+
Sashite::Gan::Actor.parse("CHESS:Q"),
|
247
|
+
Sashite::Gan::Actor.parse("shogi:k"),
|
248
|
+
Sashite::Gan::Actor.parse("shogi:g")
|
249
|
+
]
|
250
|
+
|
251
|
+
grouped = actors.group_by { |actor| actor.style_name.downcase }
|
252
|
+
# => {"chess" => [...], "shogi" => [...]}
|
253
|
+
|
254
|
+
# Filter by player
|
255
|
+
first_player_actors = actors.select { |actor| actor.style.first_player? }
|
256
|
+
second_player_actors = actors.select { |actor| actor.style.second_player? }
|
257
|
+
|
258
|
+
# Find actors by piece type
|
259
|
+
kings = actors.select { |actor| actor.piece_name.downcase == "k" }
|
260
|
+
```
|
261
|
+
|
262
|
+
### State Manipulation
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
actor = Sashite::Gan::Actor.parse("SHOGI:P")
|
266
|
+
|
267
|
+
# Enhance the piece
|
268
|
+
enhanced = actor.enhance_piece
|
269
|
+
enhanced.to_s # => "SHOGI:+P"
|
270
|
+
|
271
|
+
# Add intermediate state
|
272
|
+
intermediate = actor.set_piece_intermediate
|
273
|
+
intermediate.to_s # => "SHOGI:P'"
|
274
|
+
|
275
|
+
# Chain operations
|
276
|
+
complex = actor.enhance_piece.set_piece_intermediate
|
277
|
+
complex.to_s # => "SHOGI:+P'"
|
278
|
+
|
279
|
+
# Remove all modifiers
|
280
|
+
bare = complex.bare_piece
|
281
|
+
bare.to_s # => "SHOGI:P"
|
282
|
+
```
|
283
|
+
|
284
|
+
### Validation
|
285
|
+
|
286
|
+
All parsing automatically validates input according to the GAN specification:
|
287
|
+
|
288
|
+
```ruby
|
289
|
+
# Valid GAN strings
|
290
|
+
Sashite::Gan::Actor.parse("CHESS:K") # ✓
|
291
|
+
Sashite::Gan::Actor.parse("shogi:+p") # ✓
|
292
|
+
Sashite::Gan::Actor.parse("XIANGQI:r'") # ✓
|
293
|
+
|
294
|
+
# Valid constructor calls
|
295
|
+
Sashite::Gan::Actor.new("CHESS", "K") # ✓
|
296
|
+
Sashite::Gan::Actor.new("shogi", "+p") # ✓
|
297
|
+
|
298
|
+
# Convenience method
|
299
|
+
Sashite::Gan.actor("MAKRUK", "Q") # ✓
|
300
|
+
|
301
|
+
# Check validity
|
302
|
+
Sashite::Gan.valid?("CHESS:K") # => true
|
303
|
+
Sashite::Gan.valid?("Chess:K") # => false (mixed case in style)
|
304
|
+
Sashite::Gan.valid?("CHESS") # => false (missing piece)
|
305
|
+
Sashite::Gan.valid?("") # => false (empty string)
|
306
|
+
|
307
|
+
# Invalid GAN strings raise ArgumentError
|
308
|
+
Sashite::Gan::Actor.parse("") # ✗ ArgumentError
|
309
|
+
Sashite::Gan::Actor.parse("Chess:K") # ✗ ArgumentError (mixed case)
|
310
|
+
Sashite::Gan::Actor.parse("CHESS") # ✗ ArgumentError (missing piece)
|
311
|
+
Sashite::Gan::Actor.parse("CHESS:++K") # ✗ ArgumentError (invalid piece)
|
312
|
+
```
|
313
|
+
|
314
|
+
### Inspection and Debugging
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
actor = Sashite::Gan::Actor.parse("SHOGI:+p'")
|
318
|
+
|
319
|
+
# Get detailed information
|
320
|
+
actor.inspect
|
321
|
+
# => "#<Sashite::Gan::Actor:0x... style=\"SHOGI\" piece=\"+p'\">"
|
322
|
+
|
323
|
+
# Check components
|
324
|
+
actor.style_name # => "SHOGI"
|
325
|
+
actor.piece_name # => "+p'"
|
326
|
+
actor.piece.enhanced? # => true
|
327
|
+
actor.piece.intermediate? # => true
|
328
|
+
```
|
329
|
+
|
330
|
+
## API Reference
|
331
|
+
|
332
|
+
### Module Methods
|
333
|
+
|
334
|
+
- `Sashite::Gan.valid?(gan_string)` - Check if a string is valid GAN notation
|
335
|
+
- `Sashite::Gan.actor(style, piece)` - Convenience method to create actors
|
336
|
+
|
337
|
+
### Sashite::Gan::Actor Class Methods
|
338
|
+
|
339
|
+
- `Sashite::Gan::Actor.parse(gan_string)` - Parse a GAN string into an actor object
|
340
|
+
- `Sashite::Gan::Actor.new(style, piece)` - Create a new actor instance
|
341
|
+
|
342
|
+
### Instance Methods
|
343
|
+
|
344
|
+
#### Component Access
|
345
|
+
- `#style` - Get the style object (Sashite::Snn::Style)
|
346
|
+
- `#piece` - Get the piece object (Pnn::Piece)
|
347
|
+
- `#style_name` - Get the style name as string
|
348
|
+
- `#piece_name` - Get the piece name as string
|
349
|
+
|
350
|
+
#### Piece State Manipulation
|
351
|
+
- `#enhance_piece` - Create actor with enhanced piece
|
352
|
+
- `#diminish_piece` - Create actor with diminished piece
|
353
|
+
- `#set_piece_intermediate` - Create actor with intermediate piece state
|
354
|
+
- `#bare_piece` - Create actor with piece without modifiers
|
355
|
+
- `#change_piece_ownership` - Create actor with piece ownership flipped
|
356
|
+
|
357
|
+
#### Conversion
|
358
|
+
- `#to_s` - Convert to GAN string representation
|
359
|
+
- `#inspect` - Detailed string representation for debugging
|
174
360
|
|
175
|
-
|
361
|
+
## Properties of GAN
|
362
|
+
|
363
|
+
* **Rule-agnostic**: GAN does not encode game states, legality, validity, or game-specific conditions
|
364
|
+
* **Unambiguous identification**: Different piece styles can coexist without naming conflicts
|
365
|
+
* **Canonical representation**: Equivalent actors yield identical strings
|
366
|
+
* **Cross-style support**: Enables games where pieces from multiple traditions may be present
|
367
|
+
* **Dynamic ownership**: Supports games where piece ownership can change during gameplay
|
368
|
+
* **Compositional architecture**: Built on independent SNN and PNN specifications
|
369
|
+
|
370
|
+
## Constraints
|
371
|
+
|
372
|
+
* GAN supports exactly **two players**
|
373
|
+
* Players are distinguished through the combination of SNN and PNN casing
|
374
|
+
* Style assignment to players remains **fixed throughout a game**
|
375
|
+
* Piece ownership may change during gameplay through casing changes
|
376
|
+
* Both style and piece identifiers must conform to their respective specifications
|
377
|
+
|
378
|
+
## Use Cases
|
379
|
+
|
380
|
+
GAN is particularly useful in the following scenarios:
|
381
|
+
|
382
|
+
1. **Multi-style environments**: When positions or analyses involve pieces from multiple style traditions
|
383
|
+
2. **Game engine development**: When implementing engines that need to distinguish between similar pieces from different styles while tracking ownership changes
|
384
|
+
3. **Hybrid games**: When creating or analyzing positions from games that combine elements from different piece traditions
|
385
|
+
4. **Database systems**: When storing game data that must avoid naming conflicts between similar pieces from different styles
|
386
|
+
5. **Cross-style analysis**: When comparing or analyzing strategic elements across different piece traditions
|
387
|
+
6. **Capture-conversion games**: When implementing games like shōgi where pieces change ownership and require clear ownership tracking
|
388
|
+
|
389
|
+
## Dependencies
|
390
|
+
|
391
|
+
This gem depends on:
|
392
|
+
|
393
|
+
- [sashite-snn](https://github.com/sashite/snn.rb) (~> 1.0.0) - Style Name Notation implementation
|
394
|
+
- [pnn](https://github.com/sashite/pnn.rb) (~> 2.0.0) - Piece Name Notation implementation
|
395
|
+
|
396
|
+
## Specification
|
397
|
+
|
398
|
+
- [GAN Specification](https://sashite.dev/documents/gan/1.0.0/)
|
399
|
+
- [SNN Specification](https://sashite.dev/documents/snn/1.0.0/)
|
400
|
+
- [PNN Specification](https://sashite.dev/documents/pnn/1.0.0/)
|
401
|
+
|
402
|
+
## Documentation
|
403
|
+
|
404
|
+
- [GAN Documentation](https://rubydoc.info/github/sashite/gan.rb/main)
|
405
|
+
- [SNN Documentation](https://rubydoc.info/github/sashite/snn.rb/main)
|
406
|
+
- [PNN Documentation](https://rubydoc.info/github/sashite/pnn.rb/main)
|
407
|
+
|
408
|
+
## License
|
176
409
|
|
177
|
-
|
410
|
+
The [gem](https://rubygems.org/gems/sashite-gan) is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
178
411
|
|
179
|
-
|
412
|
+
## About Sashité
|
180
413
|
|
181
|
-
|
414
|
+
This project is maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of Chinese, Japanese, and Western chess cultures.
|