sashite-gan 2.1.0 → 3.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 +164 -165
- data/lib/sashite/gan/dumper.rb +94 -0
- data/lib/sashite/gan/parser.rb +47 -20
- data/lib/sashite/gan/validator.rb +23 -0
- data/lib/sashite/gan.rb +63 -36
- data/lib/sashite-gan.rb +4 -3
- metadata +22 -108
- 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/piece.rb +0 -146
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 361cb6527615fdfe0c4246c3f5f603598af97a46e6be9ec0cc300d03651dcb4a
|
4
|
+
data.tar.gz: 50fea949d4a759c2a68792085e1457c16c49966adf075533eb2e5cd1da5f852a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0298e95b598a17d558f85072321ee9006470f83e83aa20fd58235ec5550df98e1ec20f594b715962d8a063f0757ba927c0093577eb9c3c98ec9dd9b5180cdb4a'
|
7
|
+
data.tar.gz: 705ad8e30abe365dedbe108d941625d6c06725d0507c2e853f622bfe9da992137109b9fd4c44a09162cfbe1aed5c1f7bb15c1689977b131123095d69897cbebf
|
data/LICENSE.md
CHANGED
data/README.md
CHANGED
@@ -1,181 +1,180 @@
|
|
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 representing game actors in abstract strategy board games. Building upon Piece Name Notation (PNN), GAN eliminates ambiguity by associating each piece with its originating game, allowing for unambiguous gameplay application and cross-game distinctions.
|
13
|
+
|
14
|
+
This gem implements the [GAN Specification v1.0.0](https://sashite.dev/documents/gan/1.0.0/), providing a Ruby interface for:
|
15
|
+
|
16
|
+
- Serializing game actors to GAN strings
|
17
|
+
- Parsing GAN strings into their component parts
|
18
|
+
- Validating GAN strings according to the specification
|
6
19
|
|
7
20
|
## Installation
|
8
21
|
|
9
|
-
|
22
|
+
```ruby
|
23
|
+
# In your Gemfile
|
24
|
+
gem "sashite-gan"
|
25
|
+
```
|
26
|
+
|
27
|
+
Or install manually:
|
28
|
+
|
29
|
+
```sh
|
30
|
+
gem install sashite-gan
|
31
|
+
```
|
32
|
+
|
33
|
+
## GAN Format
|
34
|
+
|
35
|
+
A GAN record consists of a game identifier, followed by a colon, followed by a piece identifier that follows the PNN specification:
|
36
|
+
|
37
|
+
```
|
38
|
+
<game-id>:<piece-id>
|
39
|
+
```
|
40
|
+
|
41
|
+
Where:
|
10
42
|
|
11
|
-
|
12
|
-
|
13
|
-
|
43
|
+
- `<game-id>` is a sequence of alphabetic characters identifying the game variant.
|
44
|
+
- `:` is a literal colon character, serving as a separator.
|
45
|
+
- `<piece-id>` is a piece representation following the PNN specification: `[<prefix>]<letter>[<suffix>]`.
|
14
46
|
|
15
|
-
|
47
|
+
The casing of the game identifier reflects the player:
|
16
48
|
|
17
|
-
|
49
|
+
- **Uppercase** game identifiers (e.g., `CHESS:`) denote pieces belonging to the first player.
|
50
|
+
- **Lowercase** game identifiers (e.g., `chess:`) denote pieces belonging to the second player.
|
51
|
+
|
52
|
+
## Basic Usage
|
53
|
+
|
54
|
+
### Parsing GAN Strings
|
55
|
+
|
56
|
+
Convert a GAN string into a structured Ruby hash:
|
18
57
|
|
19
58
|
```ruby
|
20
|
-
require
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
piece
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
# Chess (Western chess)'s King, Black
|
39
|
-
piece = Sashite::GAN.parse("c:-k")
|
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"
|
59
|
+
require "sashite-gan"
|
60
|
+
|
61
|
+
# Basic actor
|
62
|
+
result = Sashite::Gan.parse("CHESS:K")
|
63
|
+
# => { game_id: "CHESS", letter: "K" }
|
64
|
+
|
65
|
+
# With piece prefix
|
66
|
+
result = Sashite::Gan.parse("SHOGI:+P")
|
67
|
+
# => { game_id: "SHOGI", letter: "P", prefix: "+" }
|
68
|
+
|
69
|
+
# With piece suffix
|
70
|
+
result = Sashite::Gan.parse("CHESS:K'")
|
71
|
+
# => { game_id: "CHESS", letter: "K", suffix: "'" }
|
72
|
+
|
73
|
+
# With both piece prefix and suffix
|
74
|
+
result = Sashite::Gan.parse("SHOGI:+R'")
|
75
|
+
# => { game_id: "SHOGI", letter: "R", prefix: "+", suffix: "'" }
|
171
76
|
```
|
172
77
|
|
173
|
-
|
78
|
+
### Safe Parsing
|
79
|
+
|
80
|
+
Parse a GAN string without raising exceptions:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
require "sashite-gan"
|
84
|
+
|
85
|
+
# Valid GAN string
|
86
|
+
result = Sashite::Gan.safe_parse("CHESS:K'")
|
87
|
+
# => { game_id: "CHESS", letter: "K", suffix: "'" }
|
88
|
+
|
89
|
+
# Invalid GAN string
|
90
|
+
result = Sashite::Gan.safe_parse("invalid gan string")
|
91
|
+
# => nil
|
92
|
+
```
|
174
93
|
|
175
|
-
|
94
|
+
### Creating GAN Strings
|
95
|
+
|
96
|
+
Convert actor components into a GAN string:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
require "sashite-gan"
|
100
|
+
|
101
|
+
# Basic actor
|
102
|
+
Sashite::Gan.dump(game_id: "CHESS", letter: "K")
|
103
|
+
# => "CHESS:K"
|
104
|
+
|
105
|
+
# With piece prefix
|
106
|
+
Sashite::Gan.dump(game_id: "SHOGI", letter: "P", prefix: "+")
|
107
|
+
# => "SHOGI:+P"
|
108
|
+
|
109
|
+
# With piece suffix
|
110
|
+
Sashite::Gan.dump(game_id: "CHESS", letter: "K", suffix: "'")
|
111
|
+
# => "CHESS:K'"
|
112
|
+
|
113
|
+
# With both piece prefix and suffix
|
114
|
+
Sashite::Gan.dump(game_id: "SHOGI", letter: "R", prefix: "+", suffix: "'")
|
115
|
+
# => "SHOGI:+R'"
|
116
|
+
```
|
117
|
+
|
118
|
+
### Validation
|
119
|
+
|
120
|
+
Check if a string is valid GAN notation:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
require "sashite-gan"
|
124
|
+
|
125
|
+
Sashite::Gan.valid?("CHESS:K") # => true
|
126
|
+
Sashite::Gan.valid?("SHOGI:+P") # => true
|
127
|
+
Sashite::Gan.valid?("CHESS:K'") # => true
|
128
|
+
Sashite::Gan.valid?("chess:k") # => true
|
129
|
+
|
130
|
+
Sashite::Gan.valid?("") # => false
|
131
|
+
Sashite::Gan.valid?("CHESS:k") # => false (mismatched casing)
|
132
|
+
Sashite::Gan.valid?("CHESS::K") # => false
|
133
|
+
Sashite::Gan.valid?("CHESS-K") # => false
|
134
|
+
```
|
135
|
+
|
136
|
+
## Casing Rules
|
137
|
+
|
138
|
+
The casing of the game identifier must match the piece letter casing:
|
139
|
+
|
140
|
+
- **Uppercase** game IDs must have **uppercase** piece letters for the first player
|
141
|
+
- **Lowercase** game IDs must have **lowercase** piece letters for the second player
|
142
|
+
|
143
|
+
This ensures consistency with the FEEN specification's third field.
|
144
|
+
|
145
|
+
## Examples
|
146
|
+
|
147
|
+
### Chess Pieces
|
148
|
+
|
149
|
+
| PNN | GAN (First Player) | GAN (Second Player) |
|
150
|
+
|-------|--------------------|--------------------|
|
151
|
+
| `K'` | `CHESS:K'` | `chess:k'` |
|
152
|
+
| `Q` | `CHESS:Q` | `chess:q` |
|
153
|
+
| `R` | `CHESS:R` | `chess:r` |
|
154
|
+
| `B` | `CHESS:B` | `chess:b` |
|
155
|
+
| `N` | `CHESS:N` | `chess:n` |
|
156
|
+
| `P` | `CHESS:P` | `chess:p` |
|
157
|
+
|
158
|
+
### Disambiguated Collisions
|
159
|
+
|
160
|
+
These examples show how GAN resolves ambiguities between pieces that would have identical PNN representation:
|
161
|
+
|
162
|
+
| Description | PNN | GAN |
|
163
|
+
|-------------|-------|---------------------|
|
164
|
+
| Chess Rook (white) | `R` | `CHESS:R` |
|
165
|
+
| Makruk Rook (white) | `R` | `MAKRUK:R` |
|
166
|
+
| Shogi Rook (sente) | `R` | `SHOGI:R` |
|
167
|
+
| Promoted Shogi Rook (sente) | `+R` | `SHOGI:+R` |
|
168
|
+
|
169
|
+
## Documentation
|
170
|
+
|
171
|
+
- [Official GAN Specification](https://sashite.dev/documents/gan/1.0.0/)
|
172
|
+
- [API Documentation](https://rubydoc.info/github/sashite/gan.rb/main)
|
173
|
+
|
174
|
+
## License
|
176
175
|
|
177
|
-
|
176
|
+
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
177
|
|
179
|
-
|
178
|
+
## About Sashité
|
180
179
|
|
181
|
-
|
180
|
+
This project is maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of Chinese, Japanese, and Western chess cultures.
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pnn"
|
4
|
+
|
5
|
+
module Sashite
|
6
|
+
module Gan
|
7
|
+
# Serializes actor components into GAN (General Actor Notation) strings.
|
8
|
+
#
|
9
|
+
# The dumper transforms piece data and game identifiers into properly
|
10
|
+
# formatted GAN strings, ensuring consistency between game ID casing
|
11
|
+
# and piece letter casing according to the GAN specification.
|
12
|
+
#
|
13
|
+
# According to the specification, game IDs must be either all uppercase
|
14
|
+
# or all lowercase, and their casing must match the casing of the piece letter.
|
15
|
+
class Dumper
|
16
|
+
# Pattern for validating game identifiers - must be all uppercase OR all lowercase
|
17
|
+
GAME_ID_PATTERN = /\A([A-Z]+|[a-z]+)\z/
|
18
|
+
|
19
|
+
# Error message templates
|
20
|
+
INVALID_GAME_ID_ERROR = "Game ID must be a non-empty string containing only ASCII letters and must be either all uppercase or all lowercase: %s"
|
21
|
+
CASING_MISMATCH_ERROR = "Game ID casing (%s) must match piece letter casing (%s)"
|
22
|
+
|
23
|
+
# Serializes actor components into a GAN string
|
24
|
+
#
|
25
|
+
# @param game_id [String] The game identifier (e.g., "CHESS", "shogi")
|
26
|
+
# @param piece_params [Hash] Piece parameters as accepted by Pnn.dump:
|
27
|
+
# @option piece_params [String] :letter The single ASCII letter identifier (required)
|
28
|
+
# @option piece_params [String, nil] :prefix Optional prefix modifier for the piece ("+", "-")
|
29
|
+
# @option piece_params [String, nil] :suffix Optional suffix modifier for the piece ("'")
|
30
|
+
# @return [String] A properly formatted GAN notation string (e.g., "CHESS:K'")
|
31
|
+
# @raise [ArgumentError] If game_id is invalid or casing is inconsistent with piece letter
|
32
|
+
# @example Create a GAN string for a white chess king with castling rights
|
33
|
+
# Dumper.dump(game_id: "CHESS", letter: "K", suffix: "'")
|
34
|
+
# # => "CHESS:K'"
|
35
|
+
# @example Create a GAN string for a promoted shogi pawn
|
36
|
+
# Dumper.dump(game_id: "SHOGI", letter: "P", prefix: "+")
|
37
|
+
# # => "SHOGI:+P"
|
38
|
+
def self.dump(game_id:, **piece_params)
|
39
|
+
game_id = String(game_id)
|
40
|
+
validate_game_id!(game_id)
|
41
|
+
|
42
|
+
# Build the piece string using the PNN gem
|
43
|
+
pnn_string = ::Pnn.dump(**piece_params)
|
44
|
+
|
45
|
+
# Verify casing consistency
|
46
|
+
validate_casing_consistency!(game_id, pnn_string)
|
47
|
+
|
48
|
+
"#{game_id}:#{pnn_string}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# @api private
|
52
|
+
# Validates that the game_id contains only ASCII letters
|
53
|
+
#
|
54
|
+
# @param game_id [String] The game identifier to validate
|
55
|
+
# @return [void]
|
56
|
+
# @raise [ArgumentError] If game_id contains non-letter characters
|
57
|
+
def self.validate_game_id!(game_id)
|
58
|
+
return if game_id.match?(GAME_ID_PATTERN)
|
59
|
+
|
60
|
+
raise ::ArgumentError, format(INVALID_GAME_ID_ERROR, game_id)
|
61
|
+
end
|
62
|
+
private_class_method :validate_game_id!
|
63
|
+
|
64
|
+
# @api private
|
65
|
+
# Validates that the casing of the game_id is consistent with the piece letter
|
66
|
+
#
|
67
|
+
# According to GAN specification, if game_id is uppercase, piece letter must be uppercase,
|
68
|
+
# and if game_id is lowercase, piece letter must be lowercase.
|
69
|
+
#
|
70
|
+
# @param game_id [String] The game identifier
|
71
|
+
# @param pnn_string [String] The PNN string
|
72
|
+
# @return [void]
|
73
|
+
# @raise [ArgumentError] If casing is inconsistent
|
74
|
+
def self.validate_casing_consistency!(game_id, pnn_string)
|
75
|
+
return if casing_consistent?(game_id, pnn_string)
|
76
|
+
|
77
|
+
raise ::ArgumentError, format(CASING_MISMATCH_ERROR, game_id, pnn_string)
|
78
|
+
end
|
79
|
+
private_class_method :validate_casing_consistency!
|
80
|
+
|
81
|
+
# @api private
|
82
|
+
# Verifies that the casing of the game_id matches the casing of the piece letter
|
83
|
+
#
|
84
|
+
# @param game_id [String] The game identifier
|
85
|
+
# @param pnn_string [String] The PNN string
|
86
|
+
# @return [Boolean] True if casing is consistent
|
87
|
+
def self.casing_consistent?(game_id, pnn_string)
|
88
|
+
# Both must be uppercase or both must be lowercase
|
89
|
+
(game_id == game_id.upcase) == (pnn_string == pnn_string.upcase)
|
90
|
+
end
|
91
|
+
private_class_method :casing_consistent?
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/sashite/gan/parser.rb
CHANGED
@@ -1,30 +1,57 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require_relative 'piece'
|
3
|
+
require "pnn"
|
5
4
|
|
6
5
|
module Sashite
|
7
|
-
module
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
)
|
6
|
+
module Gan
|
7
|
+
# Parses GAN strings into their component parts
|
8
|
+
class Parser
|
9
|
+
# GAN regex pattern for parsing
|
10
|
+
PATTERN = /\A(?<game_id>[a-zA-Z]+):(?<pnn_part>[-+]?[a-zA-Z][']?)\z/
|
11
|
+
|
12
|
+
# Parse a GAN string into its components
|
13
|
+
#
|
14
|
+
# @param gan_string [String] The GAN string to parse
|
15
|
+
# @return [Hash] Hash containing the parsed components
|
16
|
+
# @raise [ArgumentError] If the GAN string is invalid
|
17
|
+
def self.parse(gan_string)
|
18
|
+
gan_string = String(gan_string)
|
19
|
+
|
20
|
+
matches = PATTERN.match(gan_string)
|
21
|
+
raise ArgumentError, "Invalid GAN string: #{gan_string}" if matches.nil?
|
22
|
+
|
23
|
+
game_id = matches[:game_id]
|
24
|
+
pnn_part = matches[:pnn_part]
|
25
|
+
|
26
|
+
# Parse the PNN part using the PNN gem
|
27
|
+
pnn_result = Pnn.parse(pnn_part)
|
28
|
+
|
29
|
+
# Verify casing consistency
|
30
|
+
unless casing_consistent?(game_id, pnn_result[:letter])
|
31
|
+
raise ArgumentError, "Game ID casing (#{game_id}) must match piece letter casing (#{pnn_result[:letter]})"
|
32
|
+
end
|
33
|
+
|
34
|
+
# Merge the game_id with the piece parameters for a flatter structure
|
35
|
+
{ game_id: game_id }.merge(pnn_result)
|
22
36
|
end
|
23
37
|
|
24
|
-
|
25
|
-
|
38
|
+
# Safely parse a GAN string without raising exceptions
|
39
|
+
#
|
40
|
+
# @param gan_string [String] The GAN string to parse
|
41
|
+
# @return [Hash, nil] Hash containing the parsed components or nil if invalid
|
42
|
+
def self.safe_parse(gan_string)
|
43
|
+
parse(gan_string)
|
44
|
+
rescue ArgumentError
|
45
|
+
nil
|
46
|
+
end
|
26
47
|
|
27
|
-
|
48
|
+
# Verifies that the casing of the game_id matches the casing of the piece letter
|
49
|
+
#
|
50
|
+
# @param game_id [String] The game identifier
|
51
|
+
# @param letter [String] The piece letter
|
52
|
+
# @return [Boolean] True if casing is consistent
|
53
|
+
def self.casing_consistent?(game_id, letter)
|
54
|
+
(game_id == game_id.upcase) == (letter == letter.upcase)
|
28
55
|
end
|
29
56
|
end
|
30
57
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pnn"
|
4
|
+
|
5
|
+
module Sashite
|
6
|
+
module Gan
|
7
|
+
# Validates GAN strings
|
8
|
+
class Validator
|
9
|
+
# GAN regex pattern for validation
|
10
|
+
PATTERN = /\A([A-Z]+:[-+]?[A-Z][']?|[a-z]+:[-+]?[a-z][']?)\z/
|
11
|
+
|
12
|
+
# Validates if the given string is a valid GAN string
|
13
|
+
#
|
14
|
+
# @param gan_string [String] The GAN string to validate
|
15
|
+
# @return [Boolean] True if the string is a valid GAN string
|
16
|
+
def self.valid?(gan_string)
|
17
|
+
return false unless gan_string.is_a?(String)
|
18
|
+
|
19
|
+
PATTERN.match?(gan_string)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/sashite/gan.rb
CHANGED
@@ -1,49 +1,76 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative File.join("gan", "dumper")
|
4
|
+
require_relative File.join("gan", "parser")
|
5
|
+
require_relative File.join("gan", "validator")
|
4
6
|
|
5
7
|
module Sashite
|
6
|
-
#
|
8
|
+
# This module provides a Ruby interface for serialization and
|
9
|
+
# deserialization of game actors in GAN format.
|
7
10
|
#
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
#
|
15
|
-
# GAN.parse("C:R")
|
16
|
-
#
|
17
|
-
# @example Chess (Western chess)'s King, Black
|
18
|
-
# GAN.parse("c:-k")
|
19
|
-
#
|
20
|
-
# @example Makruk (Thai chess)'s Bishop, White
|
21
|
-
# GAN.parse("M:B")
|
22
|
-
#
|
23
|
-
# @example Shogi (Japanese chess)'s King, Gote
|
24
|
-
# GAN.parse("s:-k")
|
25
|
-
#
|
26
|
-
# @example Shogi (Japanese chess)'s King, Sente
|
27
|
-
# GAN.parse("S:-K")
|
28
|
-
#
|
29
|
-
# @example Shogi (Japanese chess)'s promoted Pawn, Sente
|
30
|
-
# GAN.parse("S:+P")
|
11
|
+
# GAN (General Actor Notation) defines a consistent and rule-agnostic
|
12
|
+
# format for representing game actors in abstract strategy board games,
|
13
|
+
# building upon Piece Name Notation (PNN).
|
14
|
+
#
|
15
|
+
# @see https://sashite.dev/documents/gan/1.0.0/
|
16
|
+
module Gan
|
17
|
+
# Serializes an actor into a GAN string.
|
31
18
|
#
|
32
|
-
# @
|
33
|
-
#
|
19
|
+
# @param game_id [String] The game identifier
|
20
|
+
# @param piece_params [Hash] Piece parameters as accepted by Pnn.dump
|
21
|
+
# @option piece_params [String] :letter The single ASCII letter identifier (required)
|
22
|
+
# @option piece_params [String, nil] :prefix Optional prefix modifier for the piece ("+", "-")
|
23
|
+
# @option piece_params [String, nil] :suffix Optional suffix modifier for the piece ("'")
|
24
|
+
# @return [String] GAN notation string
|
25
|
+
# @raise [ArgumentError] If any parameter is invalid
|
26
|
+
# @example
|
27
|
+
# Sashite::Gan.dump(game_id: "CHESS", letter: "K", suffix: "'")
|
28
|
+
# # => "CHESS:K'"
|
29
|
+
def self.dump(game_id:, **piece_params)
|
30
|
+
Dumper.dump(game_id:, **piece_params)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Parses a GAN string into its component parts.
|
34
34
|
#
|
35
|
-
# @
|
36
|
-
#
|
35
|
+
# @param gan_string [String] GAN notation string
|
36
|
+
# @return [Hash] Hash containing the parsed actor data with the following keys:
|
37
|
+
# - :game_id [String] - The game identifier
|
38
|
+
# - :letter [String] - The base letter identifier
|
39
|
+
# - :prefix [String, nil] - The prefix modifier if present
|
40
|
+
# - :suffix [String, nil] - The suffix modifier if present
|
41
|
+
# @raise [ArgumentError] If the GAN string is invalid
|
42
|
+
# @example
|
43
|
+
# Sashite::Gan.parse("CHESS:K'")
|
44
|
+
# # => { game_id: "CHESS", letter: "K", suffix: "'" }
|
45
|
+
def self.parse(gan_string)
|
46
|
+
Parser.parse(gan_string)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Safely parses a GAN string into its component parts without raising exceptions.
|
37
50
|
#
|
38
|
-
# @
|
39
|
-
#
|
51
|
+
# @param gan_string [String] GAN notation string
|
52
|
+
# @return [Hash, nil] Hash containing the parsed actor data or nil if parsing fails
|
53
|
+
# @example
|
54
|
+
# # Valid GAN string
|
55
|
+
# Sashite::Gan.safe_parse("CHESS:K'")
|
56
|
+
# # => { game_id: "CHESS", letter: "K", suffix: "'" }
|
40
57
|
#
|
41
|
-
#
|
42
|
-
#
|
58
|
+
# # Invalid GAN string
|
59
|
+
# Sashite::Gan.safe_parse("invalid")
|
60
|
+
# # => nil
|
61
|
+
def self.safe_parse(gan_string)
|
62
|
+
Parser.safe_parse(gan_string)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Validates if the given string is a valid GAN string
|
43
66
|
#
|
44
|
-
# @
|
45
|
-
|
46
|
-
|
67
|
+
# @param gan_string [String] GAN string to validate
|
68
|
+
# @return [Boolean] True if the string is a valid GAN string
|
69
|
+
# @example
|
70
|
+
# Sashite::Gan.valid?("CHESS:K'") # => true
|
71
|
+
# Sashite::Gan.valid?("invalid") # => false
|
72
|
+
def self.valid?(gan_string)
|
73
|
+
Validator.valid?(gan_string)
|
47
74
|
end
|
48
75
|
end
|
49
76
|
end
|
data/lib/sashite-gan.rb
CHANGED
metadata
CHANGED
@@ -1,114 +1,32 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sashite-gan
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyril Kato
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
13
|
+
name: pnn
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
16
15
|
requirements:
|
17
|
-
- - "
|
16
|
+
- - "~>"
|
18
17
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
20
|
-
type: :
|
18
|
+
version: 1.1.0
|
19
|
+
type: :runtime
|
21
20
|
prerelease: false
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
23
22
|
requirements:
|
24
|
-
- - "
|
23
|
+
- - "~>"
|
25
24
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rake
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: rubocop-performance
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: rubocop-thread_safety
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: simplecov
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - ">="
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - ">="
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '0'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: yard
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - ">="
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '0'
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - ">="
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '0'
|
111
|
-
description: A Ruby interface for data serialization in GAN format ♟️
|
25
|
+
version: 1.1.0
|
26
|
+
description: A Ruby interface for serialization and deserialization of game actors
|
27
|
+
in GAN format. GAN is a consistent and rule-agnostic format for representing game
|
28
|
+
actors in abstract strategy board games, providing a standardized way to identify
|
29
|
+
pieces with their originating game.
|
112
30
|
email: contact@cyril.email
|
113
31
|
executables: []
|
114
32
|
extensions: []
|
@@ -118,22 +36,19 @@ files:
|
|
118
36
|
- README.md
|
119
37
|
- lib/sashite-gan.rb
|
120
38
|
- lib/sashite/gan.rb
|
121
|
-
- lib/sashite/gan/
|
122
|
-
- lib/sashite/gan/error.rb
|
123
|
-
- lib/sashite/gan/error/string.rb
|
124
|
-
- lib/sashite/gan/error/style.rb
|
125
|
-
- lib/sashite/gan/error/type.rb
|
39
|
+
- lib/sashite/gan/dumper.rb
|
126
40
|
- lib/sashite/gan/parser.rb
|
127
|
-
- lib/sashite/gan/
|
128
|
-
homepage: https://
|
41
|
+
- lib/sashite/gan/validator.rb
|
42
|
+
homepage: https://github.com/sashite/gan.rb
|
129
43
|
licenses:
|
130
44
|
- MIT
|
131
45
|
metadata:
|
132
46
|
bug_tracker_uri: https://github.com/sashite/gan.rb/issues
|
133
|
-
documentation_uri: https://rubydoc.info/
|
47
|
+
documentation_uri: https://rubydoc.info/github/sashite/gan.rb/main
|
48
|
+
homepage_uri: https://github.com/sashite/gan.rb
|
134
49
|
source_code_uri: https://github.com/sashite/gan.rb
|
135
|
-
|
136
|
-
|
50
|
+
specification_uri: https://sashite.dev/documents/gan/1.0.0/
|
51
|
+
rubygems_mfa_required: 'true'
|
137
52
|
rdoc_options: []
|
138
53
|
require_paths:
|
139
54
|
- lib
|
@@ -141,15 +56,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
141
56
|
requirements:
|
142
57
|
- - ">="
|
143
58
|
- !ruby/object:Gem::Version
|
144
|
-
version: 2.
|
59
|
+
version: 3.2.0
|
145
60
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
61
|
requirements:
|
147
62
|
- - ">="
|
148
63
|
- !ruby/object:Gem::Version
|
149
64
|
version: '0'
|
150
65
|
requirements: []
|
151
|
-
rubygems_version: 3.
|
152
|
-
signing_key:
|
66
|
+
rubygems_version: 3.6.7
|
153
67
|
specification_version: 4
|
154
|
-
summary:
|
68
|
+
summary: GAN (General Actor Notation) support for the Ruby language.
|
155
69
|
test_files: []
|
data/lib/sashite/gan/abbr.rb
DELETED
@@ -1,76 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Sashite
|
4
|
-
module GAN
|
5
|
-
# The piece's abbreviation.
|
6
|
-
class Abbr
|
7
|
-
# The piece's type.
|
8
|
-
#
|
9
|
-
# @!attribute [r] type
|
10
|
-
# @return [String] The type of the piece.
|
11
|
-
attr_reader :type
|
12
|
-
|
13
|
-
def initialize(type, is_promoted:, is_king:)
|
14
|
-
@type = TypeString(type)
|
15
|
-
@is_promoted = Boolean(is_promoted)
|
16
|
-
@is_king = Boolean(is_king)
|
17
|
-
|
18
|
-
freeze
|
19
|
-
end
|
20
|
-
|
21
|
-
# @return [Boolean] Is the piece a king?
|
22
|
-
def king?
|
23
|
-
@is_king
|
24
|
-
end
|
25
|
-
|
26
|
-
# @return [Boolean] Is the piece promoted?
|
27
|
-
def promoted?
|
28
|
-
@is_promoted
|
29
|
-
end
|
30
|
-
|
31
|
-
# @return [String] The abbreviation of the piece.
|
32
|
-
def to_s
|
33
|
-
str = type
|
34
|
-
str = "-#{str}" if king?
|
35
|
-
str = "+#{str}" if promoted?
|
36
|
-
str
|
37
|
-
end
|
38
|
-
|
39
|
-
def inspect
|
40
|
-
to_s
|
41
|
-
end
|
42
|
-
|
43
|
-
def ==(other)
|
44
|
-
other.to_s == to_s
|
45
|
-
end
|
46
|
-
|
47
|
-
def eql?(other)
|
48
|
-
self == other
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
# rubocop:disable Naming/MethodName
|
54
|
-
|
55
|
-
# Ensures `arg` is a boolean, and returns it. Otherwise, raises a
|
56
|
-
# `TypeError`.
|
57
|
-
def Boolean(arg)
|
58
|
-
raise ::TypeError, arg.class.inspect unless [false, true].include?(arg)
|
59
|
-
|
60
|
-
arg
|
61
|
-
end
|
62
|
-
|
63
|
-
# Ensures `arg` is a type, and returns it. Otherwise, raises an error.
|
64
|
-
def TypeString(arg)
|
65
|
-
raise ::TypeError, arg.class.inspect unless arg.is_a?(::String)
|
66
|
-
raise Error::Type, arg.inspect unless arg.match?(/\A[a-z]{1,2}\z/i)
|
67
|
-
|
68
|
-
arg
|
69
|
-
end
|
70
|
-
|
71
|
-
# rubocop:enable Naming/MethodName
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
require_relative 'error'
|
data/lib/sashite/gan/error.rb
DELETED
data/lib/sashite/gan/piece.rb
DELETED
@@ -1,146 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Sashite
|
4
|
-
module GAN
|
5
|
-
# A piece abstraction.
|
6
|
-
class Piece
|
7
|
-
# The abbreviation of the piece.
|
8
|
-
#
|
9
|
-
# @!attribute [r] abbr
|
10
|
-
# @return [String] The abbreviation of the piece.
|
11
|
-
attr_reader :abbr
|
12
|
-
|
13
|
-
# The piece's style.
|
14
|
-
#
|
15
|
-
# @!attribute [r] style
|
16
|
-
# @return [String] The piece's style.
|
17
|
-
attr_reader :style
|
18
|
-
|
19
|
-
# Initialize a piece.
|
20
|
-
#
|
21
|
-
# @param type [String] The type of the piece.
|
22
|
-
# @param is_king [Boolean] Is it a King (or a Xiangqi General),
|
23
|
-
# so it could be checkmated?
|
24
|
-
# @param is_promoted [Boolean] Is it promoted?
|
25
|
-
# @param is_topside [Boolean] Is it owned by top-side player?
|
26
|
-
# @param style [String] The piece's style.
|
27
|
-
def initialize(type, is_king:, is_promoted:, is_topside:, style:)
|
28
|
-
@abbr = Abbr.new(type, is_king: is_king, is_promoted: is_promoted)
|
29
|
-
@is_topside = Boolean(is_topside)
|
30
|
-
@style = StyleString(style)
|
31
|
-
|
32
|
-
freeze
|
33
|
-
end
|
34
|
-
|
35
|
-
# Is it owned by top-side player?
|
36
|
-
#
|
37
|
-
# @return [Boolean] Returns `true` if the top-side player own the piece,
|
38
|
-
# `false` otherwise.
|
39
|
-
def topside?
|
40
|
-
@is_topside
|
41
|
-
end
|
42
|
-
|
43
|
-
# Is it owned by bottom-side player?
|
44
|
-
#
|
45
|
-
# @return [Boolean] Returns `true` if the bottom-side player own the
|
46
|
-
# piece, `false` otherwise.
|
47
|
-
def bottomside?
|
48
|
-
!topside?
|
49
|
-
end
|
50
|
-
|
51
|
-
# @see https://developer.sashite.com/specs/general-actor-notation
|
52
|
-
# @return [String] The notation of the piece.
|
53
|
-
def to_s
|
54
|
-
topside? ? raw.downcase : raw.upcase
|
55
|
-
end
|
56
|
-
|
57
|
-
def inspect
|
58
|
-
to_s
|
59
|
-
end
|
60
|
-
|
61
|
-
# @return [Piece] The top-side side version of the piece.
|
62
|
-
def topside
|
63
|
-
topside? ? self : oppositeside
|
64
|
-
end
|
65
|
-
|
66
|
-
# @return [Piece] The bottom-side side version of the piece.
|
67
|
-
def bottomside
|
68
|
-
topside? ? oppositeside : self
|
69
|
-
end
|
70
|
-
|
71
|
-
# @return [Piece] The opposite side version of the piece.
|
72
|
-
def oppositeside
|
73
|
-
self.class.new(abbr.type,
|
74
|
-
is_king: abbr.king?,
|
75
|
-
is_promoted: abbr.promoted?,
|
76
|
-
is_topside: !topside?,
|
77
|
-
style: style
|
78
|
-
)
|
79
|
-
end
|
80
|
-
|
81
|
-
# @return [Piece] The promoted version of the piece.
|
82
|
-
def promote
|
83
|
-
self.class.new(abbr.type,
|
84
|
-
is_king: abbr.king?,
|
85
|
-
is_promoted: true,
|
86
|
-
is_topside: topside?,
|
87
|
-
style: style
|
88
|
-
)
|
89
|
-
end
|
90
|
-
|
91
|
-
# @return [Piece] The unpromoted version of the piece.
|
92
|
-
def unpromote
|
93
|
-
self.class.new(abbr.type,
|
94
|
-
is_king: abbr.king?,
|
95
|
-
is_promoted: false,
|
96
|
-
is_topside: topside?,
|
97
|
-
style: style
|
98
|
-
)
|
99
|
-
end
|
100
|
-
|
101
|
-
def ==(other)
|
102
|
-
other.to_s == to_s
|
103
|
-
end
|
104
|
-
|
105
|
-
def eql?(other)
|
106
|
-
self == other
|
107
|
-
end
|
108
|
-
|
109
|
-
private
|
110
|
-
|
111
|
-
# @return [String] The style and the abbreviation of the piece (without
|
112
|
-
# case).
|
113
|
-
def raw
|
114
|
-
params.join(SEPARATOR_CHAR)
|
115
|
-
end
|
116
|
-
|
117
|
-
# @return [Array] The style and the abbreviation of the piece.
|
118
|
-
def params
|
119
|
-
[style, abbr]
|
120
|
-
end
|
121
|
-
|
122
|
-
# rubocop:disable Naming/MethodName
|
123
|
-
|
124
|
-
# Ensures `arg` is a boolean, and returns it. Otherwise, raises a
|
125
|
-
# `TypeError`.
|
126
|
-
def Boolean(arg)
|
127
|
-
raise ::TypeError, arg.class.inspect unless [false, true].include?(arg)
|
128
|
-
|
129
|
-
arg
|
130
|
-
end
|
131
|
-
|
132
|
-
# Ensures `arg` is a style, and returns it. Otherwise, raises an error.
|
133
|
-
def StyleString(arg)
|
134
|
-
raise ::TypeError, arg.class.inspect unless arg.is_a?(::String)
|
135
|
-
raise Error::Style, arg.inspect unless arg.match?(/\A[a-z_]+\z/i)
|
136
|
-
|
137
|
-
arg
|
138
|
-
end
|
139
|
-
|
140
|
-
# rubocop:enable Naming/MethodName
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
require_relative 'abbr'
|
146
|
-
require_relative 'error'
|