sashite-pnn 1.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 +7 -0
- data/LICENSE.md +21 -0
- data/README.md +350 -0
- data/lib/sashite/pnn/piece.rb +234 -0
- data/lib/sashite/pnn.rb +60 -0
- data/lib/sashite-pnn.rb +21 -0
- metadata +70 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7647996be4986860b8e395fe0ad419a2ecc9677c43752535b54ccbbf8c046b7b
|
|
4
|
+
data.tar.gz: 3d5566a65d929b5f60169496ecaac21f41855551d415024638ba38d6e941709c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 72f349c71ce7301eb86351cbab1b83fa5c4b501528a9b28c3ddae4f24209a048a049fcd67aba4966d7184007cf228630d23e513effeccb576a8845a062dc6b39
|
|
7
|
+
data.tar.gz: 7bdc2954d3f0aa39da0b59bab6dff52967219c368559ef78c86193bb52ec89007ed85463ba4a5cbf2eb882adae10da4369c8725d2e983d5eed41090aa4dc32a9
|
data/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# The MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sashité
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# Pnn.rb
|
|
2
|
+
|
|
3
|
+
[](https://github.com/sashite/pnn.rb/tags)
|
|
4
|
+
[](https://rubydoc.info/github/sashite/pnn.rb/main)
|
|
5
|
+

|
|
6
|
+
[](https://github.com/sashite/pnn.rb/raw/main/LICENSE.md)
|
|
7
|
+
|
|
8
|
+
> **PNN** (Piece Name Notation) support for the Ruby language.
|
|
9
|
+
|
|
10
|
+
## What is PNN?
|
|
11
|
+
|
|
12
|
+
PNN (Piece Name Notation) extends [PIN (Piece Identifier Notation)](https://sashite.dev/specs/pin/1.0.0/) to provide style-aware piece representation in abstract strategy board games. It adds a derivation marker that distinguishes pieces by their style origin, enabling cross-style game scenarios and piece origin tracking.
|
|
13
|
+
|
|
14
|
+
This gem implements the [PNN Specification v1.0.0](https://sashite.dev/specs/pnn/1.0.0/), providing a Ruby interface for working with style-aware piece representations through an intuitive object-oriented API.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
# In your Gemfile
|
|
20
|
+
gem "sashite-pnn"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or install manually:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
gem install sashite-pnn
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## PNN Format
|
|
30
|
+
|
|
31
|
+
A PNN record consists of a [PIN](https://sashite.dev/specs/pin/1.0.0/) string with an optional derivation suffix:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
<pin>[<suffix>]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Where:
|
|
38
|
+
- `<pin>` is a valid PIN string (`[<state>]<letter>`)
|
|
39
|
+
- `<suffix>` is an optional derivation marker (`'` for foreign style)
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
- `K` - First player king with native style
|
|
43
|
+
- `K'` - First player king with foreign style
|
|
44
|
+
- `+R` - First player rook with enhanced state and native style
|
|
45
|
+
- `+R'` - First player rook with enhanced state and foreign style
|
|
46
|
+
|
|
47
|
+
## Basic Usage
|
|
48
|
+
|
|
49
|
+
### Creating Piece Objects
|
|
50
|
+
|
|
51
|
+
The primary interface is the `Sashite::Pnn::Piece` class, which represents a single piece in PNN format:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
require "sashite/pnn"
|
|
55
|
+
|
|
56
|
+
# Parse a PNN string into a piece object
|
|
57
|
+
piece = Sashite::Pnn::Piece.parse("k")
|
|
58
|
+
# => #<Sashite::Pnn::Piece letter="k" native=true>
|
|
59
|
+
|
|
60
|
+
# With derivation marker
|
|
61
|
+
foreign_piece = Sashite::Pnn::Piece.parse("k'")
|
|
62
|
+
# => #<Sashite::Pnn::Piece letter="k" native=false>
|
|
63
|
+
|
|
64
|
+
# With state modifiers and derivation
|
|
65
|
+
enhanced_foreign = Sashite::Pnn::Piece.parse("+k'")
|
|
66
|
+
# => #<Sashite::Pnn::Piece letter="k" enhanced=true native=false>
|
|
67
|
+
|
|
68
|
+
# Create directly with constructor
|
|
69
|
+
piece = Sashite::Pnn::Piece.new("k")
|
|
70
|
+
foreign_piece = Sashite::Pnn::Piece.new("k", native: false)
|
|
71
|
+
enhanced_piece = Sashite::Pnn::Piece.new("k", enhanced: true, native: false)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Converting to PNN String
|
|
75
|
+
|
|
76
|
+
Convert a piece object back to its PNN string representation:
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
piece = Sashite::Pnn::Piece.parse("k")
|
|
80
|
+
piece.to_s
|
|
81
|
+
# => "k"
|
|
82
|
+
|
|
83
|
+
foreign_piece = Sashite::Pnn::Piece.parse("k'")
|
|
84
|
+
foreign_piece.to_s
|
|
85
|
+
# => "k'"
|
|
86
|
+
|
|
87
|
+
enhanced_foreign = Sashite::Pnn::Piece.parse("+k'")
|
|
88
|
+
enhanced_foreign.to_s
|
|
89
|
+
# => "+k'"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Style Manipulation
|
|
93
|
+
|
|
94
|
+
Create new piece instances with different styles:
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
piece = Sashite::Pnn::Piece.parse("k")
|
|
98
|
+
|
|
99
|
+
# Convert to foreign style
|
|
100
|
+
foreign = piece.foreignize
|
|
101
|
+
foreign.to_s # => "k'"
|
|
102
|
+
|
|
103
|
+
# Convert to native style
|
|
104
|
+
native = foreign.nativize
|
|
105
|
+
native.to_s # => "k"
|
|
106
|
+
|
|
107
|
+
# Toggle style
|
|
108
|
+
toggled = piece.toggle_style
|
|
109
|
+
toggled.to_s # => "k'"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### State Manipulation (inherited from PIN)
|
|
113
|
+
|
|
114
|
+
All PIN state manipulation methods are available:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
piece = Sashite::Pnn::Piece.parse("k")
|
|
118
|
+
|
|
119
|
+
# Enhanced state (+ prefix)
|
|
120
|
+
enhanced = piece.enhance
|
|
121
|
+
enhanced.to_s # => "+k"
|
|
122
|
+
|
|
123
|
+
# Diminished state (- prefix)
|
|
124
|
+
diminished = piece.diminish
|
|
125
|
+
diminished.to_s # => "-k"
|
|
126
|
+
|
|
127
|
+
# Combine with style changes
|
|
128
|
+
enhanced_foreign = piece.enhance.foreignize
|
|
129
|
+
enhanced_foreign.to_s # => "+k'"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Ownership Changes
|
|
133
|
+
|
|
134
|
+
Change piece ownership (case conversion):
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
white_king = Sashite::Pnn::Piece.parse("K")
|
|
138
|
+
black_king = white_king.flip
|
|
139
|
+
black_king.to_s # => "k"
|
|
140
|
+
|
|
141
|
+
# Works with foreign pieces too
|
|
142
|
+
foreign_white = Sashite::Pnn::Piece.parse("K'")
|
|
143
|
+
foreign_black = foreign_white.flip
|
|
144
|
+
foreign_black.to_s # => "k'"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Cross-Style Game Examples
|
|
148
|
+
|
|
149
|
+
### Chess vs. Shōgi Match
|
|
150
|
+
|
|
151
|
+
In a hybrid game where the first player uses Chess pieces and the second player uses Shōgi pieces:
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
# First player (Chess style is native)
|
|
155
|
+
white_pawn = Sashite::Pnn::Piece.parse("P") # Chess pawn (native)
|
|
156
|
+
white_shogi_pawn = Sashite::Pnn::Piece.parse("P'") # Shōgi pawn (foreign)
|
|
157
|
+
|
|
158
|
+
# Second player (Shōgi style is native)
|
|
159
|
+
black_pawn = Sashite::Pnn::Piece.parse("p") # Shōgi pawn (native)
|
|
160
|
+
black_chess_pawn = Sashite::Pnn::Piece.parse("p'") # Chess pawn (foreign)
|
|
161
|
+
|
|
162
|
+
# Promoted pieces with style
|
|
163
|
+
promoted_shogi = Sashite::Pnn::Piece.parse("+P'") # Promoted Shōgi pawn (foreign to first player)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Style Conversions During Gameplay
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
# Capture and style conversion
|
|
170
|
+
enemy_piece = Sashite::Pnn::Piece.parse("p'") # Enemy's foreign piece
|
|
171
|
+
captured = enemy_piece.flip.nativize # Convert to our side with native style
|
|
172
|
+
captured.to_s # => "P"
|
|
173
|
+
|
|
174
|
+
# Promotion with style preservation
|
|
175
|
+
pawn = Sashite::Pnn::Piece.parse("p'") # Foreign pawn
|
|
176
|
+
promoted = pawn.enhance # Promote while keeping foreign style
|
|
177
|
+
promoted.to_s # => "+p'"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Style Logic
|
|
181
|
+
|
|
182
|
+
### Native vs Foreign Style
|
|
183
|
+
|
|
184
|
+
- **No suffix**: Piece has the **native style** of its current side
|
|
185
|
+
- **Apostrophe suffix (`'`)**: Piece has the **foreign style** (opposite side's native style)
|
|
186
|
+
|
|
187
|
+
### Style Assignment
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
piece = Sashite::Pnn::Piece.parse("K")
|
|
191
|
+
|
|
192
|
+
# Check style
|
|
193
|
+
piece.native? # => true
|
|
194
|
+
piece.foreign? # => false
|
|
195
|
+
|
|
196
|
+
# Convert styles
|
|
197
|
+
foreign = piece.foreignize
|
|
198
|
+
foreign.native? # => false
|
|
199
|
+
foreign.foreign? # => true
|
|
200
|
+
|
|
201
|
+
native = foreign.nativize
|
|
202
|
+
native.native? # => true
|
|
203
|
+
native.foreign? # => false
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## State Modifier Methods
|
|
207
|
+
|
|
208
|
+
The `Sashite::Pnn::Piece` class inherits all PIN functionality and adds style methods:
|
|
209
|
+
|
|
210
|
+
### Inherited from PIN
|
|
211
|
+
| Method | Description | Example |
|
|
212
|
+
|--------|-------------|---------|
|
|
213
|
+
| `enhance` | Add Enhanced state (`+` prefix) | `k` → `+k` |
|
|
214
|
+
| `unenhance` | Remove Enhanced state | `+k` → `k` |
|
|
215
|
+
| `diminish` | Add Diminished state (`-` prefix) | `k` → `-k` |
|
|
216
|
+
| `undiminish` | Remove Diminished state | `-k` → `k` |
|
|
217
|
+
| `normalize` | Remove all state modifiers | `+k` → `k` |
|
|
218
|
+
| `flip` | Change ownership (case) | `K` → `k`, `k` → `K` |
|
|
219
|
+
|
|
220
|
+
### PNN-Specific Style Methods
|
|
221
|
+
| Method | Description | Example |
|
|
222
|
+
|--------|-------------|---------|
|
|
223
|
+
| `foreignize` | Convert to foreign style | `k` → `k'` |
|
|
224
|
+
| `nativize` | Convert to native style | `k'` → `k` |
|
|
225
|
+
| `toggle_style` | Toggle between native/foreign | `k` → `k'`, `k'` → `k` |
|
|
226
|
+
|
|
227
|
+
All methods return new `Sashite::Pnn::Piece` instances, leaving the original unchanged (immutable design).
|
|
228
|
+
|
|
229
|
+
## API Reference
|
|
230
|
+
|
|
231
|
+
### Module Methods
|
|
232
|
+
|
|
233
|
+
- `Sashite::Pnn.valid?(pnn_string)` - Check if a string is valid PNN notation
|
|
234
|
+
- `Sashite::Pnn.parse(pnn_string)` - Parse a PNN string into a piece object
|
|
235
|
+
|
|
236
|
+
### Sashite::Pnn::Piece Class Methods
|
|
237
|
+
|
|
238
|
+
- `Sashite::Pnn::Piece.parse(pnn_string)` - Parse a PNN string into a piece object
|
|
239
|
+
- `Sashite::Pnn::Piece.new(letter, **options)` - Create a new piece instance
|
|
240
|
+
|
|
241
|
+
### Instance Methods
|
|
242
|
+
|
|
243
|
+
#### Style Queries
|
|
244
|
+
- `#native?` - Check if piece has native style
|
|
245
|
+
- `#foreign?` - Check if piece has foreign style
|
|
246
|
+
|
|
247
|
+
#### Style Manipulation
|
|
248
|
+
- `#foreignize` - Convert to foreign style
|
|
249
|
+
- `#nativize` - Convert to native style
|
|
250
|
+
- `#toggle_style` - Toggle between native/foreign style
|
|
251
|
+
|
|
252
|
+
#### Inherited PIN Methods
|
|
253
|
+
All methods from `Sashite::Pin::Piece` are available, including state queries, state manipulation, and conversion methods.
|
|
254
|
+
|
|
255
|
+
#### Conversion
|
|
256
|
+
- `#to_s` - Convert to PNN string representation
|
|
257
|
+
- `#to_pin` - Convert to underlying PIN representation
|
|
258
|
+
- `#inspect` - Detailed string representation for debugging
|
|
259
|
+
|
|
260
|
+
## Examples in Common Games
|
|
261
|
+
|
|
262
|
+
### Single-Style Games
|
|
263
|
+
|
|
264
|
+
```ruby
|
|
265
|
+
# Western Chess (both players use Chess style)
|
|
266
|
+
white_king = Sashite::Pnn::Piece.parse("K") # White king
|
|
267
|
+
black_king = Sashite::Pnn::Piece.parse("k") # Black king
|
|
268
|
+
castling_rook = Sashite::Pnn::Piece.parse("+R") # Rook that can castle
|
|
269
|
+
|
|
270
|
+
# Japanese Shōgi (both players use Shōgi style)
|
|
271
|
+
white_king = Sashite::Pnn::Piece.parse("K") # White king
|
|
272
|
+
promoted_pawn = Sashite::Pnn::Piece.parse("+P") # Promoted pawn (tokin)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Cross-Style Games
|
|
276
|
+
|
|
277
|
+
```ruby
|
|
278
|
+
# Chess vs Shōgi hybrid
|
|
279
|
+
chess_queen = Sashite::Pnn::Piece.parse("Q") # Chess queen (native to first player)
|
|
280
|
+
shogi_gold = Sashite::Pnn::Piece.parse("G'") # Shōgi gold (foreign to first player)
|
|
281
|
+
shogi_king = Sashite::Pnn::Piece.parse("k") # Shōgi king (native to second player)
|
|
282
|
+
chess_knight = Sashite::Pnn::Piece.parse("n'") # Chess knight (foreign to second player)
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Advanced Usage
|
|
286
|
+
|
|
287
|
+
### Chaining Operations
|
|
288
|
+
|
|
289
|
+
```ruby
|
|
290
|
+
piece = Sashite::Pnn::Piece.parse("k")
|
|
291
|
+
|
|
292
|
+
# Chain multiple operations
|
|
293
|
+
result = piece.enhance.foreignize.flip
|
|
294
|
+
result.to_s # => "+K'"
|
|
295
|
+
|
|
296
|
+
# Complex transformations
|
|
297
|
+
captured_and_converted = piece.flip.nativize.enhance
|
|
298
|
+
captured_and_converted.to_s # => "+K"
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Validation
|
|
302
|
+
|
|
303
|
+
All parsing automatically validates input according to the PNN specification:
|
|
304
|
+
|
|
305
|
+
```ruby
|
|
306
|
+
# Valid PNN strings
|
|
307
|
+
Sashite::Pnn::Piece.parse("k") # ✓
|
|
308
|
+
Sashite::Pnn::Piece.parse("k'") # ✓
|
|
309
|
+
Sashite::Pnn::Piece.parse("+p") # ✓
|
|
310
|
+
Sashite::Pnn::Piece.parse("+p'") # ✓
|
|
311
|
+
|
|
312
|
+
# Check validity
|
|
313
|
+
Sashite::Pnn.valid?("k'") # => true
|
|
314
|
+
Sashite::Pnn.valid?("invalid") # => false
|
|
315
|
+
|
|
316
|
+
# Invalid PNN strings raise ArgumentError
|
|
317
|
+
Sashite::Pnn::Piece.parse("") # ✗ ArgumentError
|
|
318
|
+
Sashite::Pnn::Piece.parse("k''") # ✗ ArgumentError
|
|
319
|
+
Sashite::Pnn::Piece.parse("++k") # ✗ ArgumentError
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Properties of PNN
|
|
323
|
+
|
|
324
|
+
* **PIN compatibility**: All valid PIN strings are valid PNN strings
|
|
325
|
+
* **Style awareness**: Distinguishes pieces by their style origin
|
|
326
|
+
* **Rule-agnostic**: PNN does not encode legality, validity, or game-specific conditions
|
|
327
|
+
* **Cross-tradition support**: Enables hybrid game scenarios
|
|
328
|
+
* **Immutable objects**: All operations return new instances, ensuring thread safety
|
|
329
|
+
* **Compact format**: Minimal overhead (single character suffix for style)
|
|
330
|
+
|
|
331
|
+
## Constraints
|
|
332
|
+
|
|
333
|
+
* PNN inherits all PIN constraints (two players, 26 piece types maximum)
|
|
334
|
+
* Each side must have a defined native style
|
|
335
|
+
* Style assignment is rule-dependent and remains fixed throughout the match
|
|
336
|
+
* Foreign style pieces represent adoption of the opponent's style system
|
|
337
|
+
|
|
338
|
+
## Documentation
|
|
339
|
+
|
|
340
|
+
- [Official PNN Specification](https://sashite.dev/specs/pnn/1.0.0/)
|
|
341
|
+
- [PIN Specification](https://sashite.dev/specs/pin/1.0.0/)
|
|
342
|
+
- [API Documentation](https://rubydoc.info/github/sashite/pnn.rb/main)
|
|
343
|
+
|
|
344
|
+
## License
|
|
345
|
+
|
|
346
|
+
Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
|
|
347
|
+
|
|
348
|
+
## About
|
|
349
|
+
|
|
350
|
+
Maintained by [Sashité](https://sashite.com/) — promoting chess variants and sharing the beauty of board game cultures.
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sashite/pin"
|
|
4
|
+
|
|
5
|
+
module Sashite
|
|
6
|
+
module Pnn
|
|
7
|
+
# Represents a piece in PNN (Piece Name Notation) format.
|
|
8
|
+
#
|
|
9
|
+
# Extends PIN::Piece to add style awareness through derivation markers.
|
|
10
|
+
# A PNN piece consists of a PIN string with an optional derivation suffix:
|
|
11
|
+
# - No suffix: native style
|
|
12
|
+
# - Apostrophe suffix ('): foreign style
|
|
13
|
+
#
|
|
14
|
+
# All instances are immutable - style manipulation methods return new instances.
|
|
15
|
+
class Piece < ::Sashite::Pin::Piece
|
|
16
|
+
# PNN validation pattern extending PIN
|
|
17
|
+
PNN_PATTERN = /\A(?<prefix>[-+])?(?<letter>[a-zA-Z])(?<suffix>'?)\z/
|
|
18
|
+
|
|
19
|
+
# Derivation marker for foreign style
|
|
20
|
+
FOREIGN_SUFFIX = "'"
|
|
21
|
+
|
|
22
|
+
# Error messages
|
|
23
|
+
ERROR_INVALID_PNN = "Invalid PNN string: %s"
|
|
24
|
+
ERROR_INVALID_NATIVE = "Native must be true or false: %s"
|
|
25
|
+
|
|
26
|
+
# @return [Boolean] whether the piece has native style
|
|
27
|
+
attr_reader :native
|
|
28
|
+
alias native? native
|
|
29
|
+
|
|
30
|
+
# Create a new piece instance with style information
|
|
31
|
+
#
|
|
32
|
+
# @param letter [String] single ASCII letter (a-z or A-Z)
|
|
33
|
+
# @param native [Boolean] whether the piece has native style
|
|
34
|
+
# @raise [ArgumentError] if parameters are invalid
|
|
35
|
+
def initialize(letter, native: true, **)
|
|
36
|
+
raise ::ArgumentError, format(ERROR_INVALID_NATIVE, native) unless [true, false].include?(native)
|
|
37
|
+
|
|
38
|
+
@native = native
|
|
39
|
+
super(letter, **)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Parse a PNN string into a Piece object
|
|
43
|
+
#
|
|
44
|
+
# @param pnn_string [String] PNN notation string
|
|
45
|
+
# @return [Piece] new piece instance
|
|
46
|
+
# @raise [ArgumentError] if the PNN string is invalid
|
|
47
|
+
# @example
|
|
48
|
+
# Pnn::Piece.parse("k") # => #<Pnn::Piece letter="k" native=true>
|
|
49
|
+
# Pnn::Piece.parse("k'") # => #<Pnn::Piece letter="k" native=false>
|
|
50
|
+
# Pnn::Piece.parse("+R'") # => #<Pnn::Piece letter="R" enhanced=true native=false>
|
|
51
|
+
def self.parse(pnn_string)
|
|
52
|
+
string_value = String(pnn_string)
|
|
53
|
+
matches = match_pattern(string_value)
|
|
54
|
+
|
|
55
|
+
letter = matches[:letter]
|
|
56
|
+
enhanced = matches[:prefix] == ENHANCED_PREFIX
|
|
57
|
+
diminished = matches[:prefix] == DIMINISHED_PREFIX
|
|
58
|
+
native = matches[:suffix] != FOREIGN_SUFFIX
|
|
59
|
+
|
|
60
|
+
new(
|
|
61
|
+
letter,
|
|
62
|
+
native: native,
|
|
63
|
+
enhanced: enhanced,
|
|
64
|
+
diminished: diminished
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Convert the piece to its PNN string representation
|
|
69
|
+
#
|
|
70
|
+
# @return [String] PNN notation string
|
|
71
|
+
# @example
|
|
72
|
+
# piece.to_s # => "k"
|
|
73
|
+
# piece.to_s # => "k'"
|
|
74
|
+
# piece.to_s # => "+R'"
|
|
75
|
+
def to_s
|
|
76
|
+
pin_string = super
|
|
77
|
+
suffix = native? ? "" : FOREIGN_SUFFIX
|
|
78
|
+
"#{pin_string}#{suffix}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Convert the piece to its underlying PIN representation
|
|
82
|
+
#
|
|
83
|
+
# @return [String] PIN notation string without style information
|
|
84
|
+
def to_pin
|
|
85
|
+
nativize.to_s
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Check if the piece has foreign style
|
|
89
|
+
#
|
|
90
|
+
# @return [Boolean] true if foreign style
|
|
91
|
+
def foreign?
|
|
92
|
+
!native?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Create a new piece with native style
|
|
96
|
+
#
|
|
97
|
+
# @return [Piece] new piece instance with native style
|
|
98
|
+
# @example
|
|
99
|
+
# piece.nativize # k' => k
|
|
100
|
+
def nativize
|
|
101
|
+
return self if native?
|
|
102
|
+
|
|
103
|
+
self.class.new(
|
|
104
|
+
letter,
|
|
105
|
+
native: true,
|
|
106
|
+
enhanced: enhanced?,
|
|
107
|
+
diminished: diminished?
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Create a new piece with foreign style
|
|
112
|
+
#
|
|
113
|
+
# @return [Piece] new piece instance with foreign style
|
|
114
|
+
# @example
|
|
115
|
+
# piece.foreignize # k => k'
|
|
116
|
+
def foreignize
|
|
117
|
+
return self if foreign?
|
|
118
|
+
|
|
119
|
+
self.class.new(
|
|
120
|
+
letter,
|
|
121
|
+
native: false,
|
|
122
|
+
enhanced: enhanced?,
|
|
123
|
+
diminished: diminished?
|
|
124
|
+
)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Create a new piece with toggled style
|
|
128
|
+
#
|
|
129
|
+
# @return [Piece] new piece instance with opposite style
|
|
130
|
+
# @example
|
|
131
|
+
# piece.toggle_style # k => k', k' => k
|
|
132
|
+
def toggle_style
|
|
133
|
+
native? ? foreignize : nativize
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Override parent methods to maintain PNN type in return values
|
|
137
|
+
|
|
138
|
+
def enhance
|
|
139
|
+
return self if enhanced?
|
|
140
|
+
|
|
141
|
+
self.class.new(
|
|
142
|
+
letter,
|
|
143
|
+
native: native?,
|
|
144
|
+
enhanced: true,
|
|
145
|
+
diminished: false
|
|
146
|
+
)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def unenhance
|
|
150
|
+
return self unless enhanced?
|
|
151
|
+
|
|
152
|
+
self.class.new(
|
|
153
|
+
letter,
|
|
154
|
+
native: native?,
|
|
155
|
+
enhanced: false,
|
|
156
|
+
diminished: diminished?
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def diminish
|
|
161
|
+
return self if diminished?
|
|
162
|
+
|
|
163
|
+
self.class.new(
|
|
164
|
+
letter,
|
|
165
|
+
native: native?,
|
|
166
|
+
enhanced: false,
|
|
167
|
+
diminished: true
|
|
168
|
+
)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def undiminish
|
|
172
|
+
return self unless diminished?
|
|
173
|
+
|
|
174
|
+
self.class.new(
|
|
175
|
+
letter,
|
|
176
|
+
native: native?,
|
|
177
|
+
enhanced: enhanced?,
|
|
178
|
+
diminished: false
|
|
179
|
+
)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def normalize
|
|
183
|
+
return self if normal?
|
|
184
|
+
|
|
185
|
+
self.class.new(
|
|
186
|
+
letter,
|
|
187
|
+
native: native?
|
|
188
|
+
)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def flip
|
|
192
|
+
flipped_letter = letter.swapcase
|
|
193
|
+
|
|
194
|
+
self.class.new(
|
|
195
|
+
flipped_letter,
|
|
196
|
+
native: native?,
|
|
197
|
+
enhanced: enhanced?,
|
|
198
|
+
diminished: diminished?
|
|
199
|
+
)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Custom equality comparison including style
|
|
203
|
+
#
|
|
204
|
+
# @param other [Object] object to compare with
|
|
205
|
+
# @return [Boolean] true if pieces are equal
|
|
206
|
+
def ==(other)
|
|
207
|
+
return false unless other.is_a?(self.class)
|
|
208
|
+
|
|
209
|
+
super && native? == other.native?
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Custom hash implementation including style
|
|
213
|
+
#
|
|
214
|
+
# @return [Integer] hash value
|
|
215
|
+
def hash
|
|
216
|
+
[super, @native].hash
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Match PNN pattern against string
|
|
220
|
+
#
|
|
221
|
+
# @param string [String] string to match
|
|
222
|
+
# @return [MatchData] match data
|
|
223
|
+
# @raise [ArgumentError] if string doesn't match
|
|
224
|
+
def self.match_pattern(string)
|
|
225
|
+
matches = PNN_PATTERN.match(string)
|
|
226
|
+
return matches if matches
|
|
227
|
+
|
|
228
|
+
raise ::ArgumentError, format(ERROR_INVALID_PNN, string)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
private_class_method :match_pattern
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
data/lib/sashite/pnn.rb
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "pnn/piece"
|
|
4
|
+
|
|
5
|
+
module Sashite
|
|
6
|
+
# PNN (Piece Name Notation) implementation for Ruby
|
|
7
|
+
#
|
|
8
|
+
# Extends PIN to provide style-aware piece representation in abstract strategy board games.
|
|
9
|
+
# PNN adds a derivation marker that distinguishes pieces by their style origin, enabling
|
|
10
|
+
# cross-style game scenarios and piece origin tracking.
|
|
11
|
+
#
|
|
12
|
+
# Format: <pin>[<suffix>]
|
|
13
|
+
# - PIN component: [<state>]<letter> (state modifier + letter)
|
|
14
|
+
# - Suffix: "'" for foreign style, none for native style
|
|
15
|
+
#
|
|
16
|
+
# Examples:
|
|
17
|
+
# "K" - First player king (native style)
|
|
18
|
+
# "K'" - First player king (foreign style)
|
|
19
|
+
# "+R" - First player rook, enhanced state (native style)
|
|
20
|
+
# "+R'" - First player rook, enhanced state (foreign style)
|
|
21
|
+
# "-p'" - Second player pawn, diminished state (foreign style)
|
|
22
|
+
#
|
|
23
|
+
# See: https://sashite.dev/specs/pnn/1.0.0/
|
|
24
|
+
module Pnn
|
|
25
|
+
# Regular expression for PNN validation
|
|
26
|
+
# Matches: optional state modifier, letter, optional derivation marker
|
|
27
|
+
PNN_REGEX = /\A[-+]?[A-Za-z]'?\z/
|
|
28
|
+
|
|
29
|
+
# Check if a string is a valid PNN notation
|
|
30
|
+
#
|
|
31
|
+
# @param pnn [String] The string to validate
|
|
32
|
+
# @return [Boolean] true if valid PNN, false otherwise
|
|
33
|
+
#
|
|
34
|
+
# @example
|
|
35
|
+
# Sashite::Pnn.valid?("K") # => true
|
|
36
|
+
# Sashite::Pnn.valid?("K'") # => true
|
|
37
|
+
# Sashite::Pnn.valid?("+R'") # => true
|
|
38
|
+
# Sashite::Pnn.valid?("-p'") # => true
|
|
39
|
+
# Sashite::Pnn.valid?("K''") # => false
|
|
40
|
+
# Sashite::Pnn.valid?("++K'") # => false
|
|
41
|
+
def self.valid?(pnn)
|
|
42
|
+
return false unless pnn.is_a?(::String)
|
|
43
|
+
|
|
44
|
+
pnn.match?(PNN_REGEX)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Parse a PNN string into a Piece object
|
|
48
|
+
#
|
|
49
|
+
# @param pnn_string [String] PNN notation string
|
|
50
|
+
# @return [Pnn::Piece] new piece instance
|
|
51
|
+
# @raise [ArgumentError] if the PNN string is invalid
|
|
52
|
+
# @example
|
|
53
|
+
# Sashite::Pnn.parse("K") # => #<Pnn::Piece letter="K" native=true>
|
|
54
|
+
# Sashite::Pnn.parse("K'") # => #<Pnn::Piece letter="K" native=false>
|
|
55
|
+
# Sashite::Pnn.parse("+R'") # => #<Pnn::Piece letter="R" enhanced=true native=false>
|
|
56
|
+
def self.parse(pnn_string)
|
|
57
|
+
Piece.parse(pnn_string)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/sashite-pnn.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Sashité namespace for board game notation libraries
|
|
4
|
+
module Sashite
|
|
5
|
+
# Piece Name Notation (PNN) implementation for Ruby
|
|
6
|
+
#
|
|
7
|
+
# PNN extends PIN (Piece Identifier Notation) to provide style-aware piece
|
|
8
|
+
# representation in abstract strategy board games. PNN adds a derivation marker
|
|
9
|
+
# that distinguishes pieces by their style origin, enabling cross-style game
|
|
10
|
+
# scenarios and piece origin tracking.
|
|
11
|
+
#
|
|
12
|
+
# Format: <pin>[<suffix>]
|
|
13
|
+
# - PIN component: [<state>]<letter> (from PIN specification)
|
|
14
|
+
# - Suffix: "'" for foreign style, none for native style
|
|
15
|
+
#
|
|
16
|
+
# @see https://sashite.dev/specs/pnn/1.0.0/ PNN Specification v1.0.0
|
|
17
|
+
# @see https://sashite.dev/specs/pin/1.0.0/ PIN Specification v1.0.0
|
|
18
|
+
# @author Sashité
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
require_relative "sashite/pnn"
|
metadata
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sashite-pnn
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Cyril Kato
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: sashite-pin
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 1.0.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 1.0.0
|
|
26
|
+
description: A clean, immutable Ruby interface for working with piece identifiers
|
|
27
|
+
in PNN format. PNN extends PIN notation to provide style-aware piece representation
|
|
28
|
+
with derivation markers for cross-style games. Features include all four fundamental
|
|
29
|
+
piece attributes (type, side, state, style), enabling hybrid game scenarios and
|
|
30
|
+
piece origin tracking. Perfect for game engines, analysis tools, and cross-tradition
|
|
31
|
+
board game applications.
|
|
32
|
+
email: contact@cyril.email
|
|
33
|
+
executables: []
|
|
34
|
+
extensions: []
|
|
35
|
+
extra_rdoc_files: []
|
|
36
|
+
files:
|
|
37
|
+
- LICENSE.md
|
|
38
|
+
- README.md
|
|
39
|
+
- lib/sashite-pnn.rb
|
|
40
|
+
- lib/sashite/pnn.rb
|
|
41
|
+
- lib/sashite/pnn/piece.rb
|
|
42
|
+
homepage: https://github.com/sashite/pnn.rb
|
|
43
|
+
licenses:
|
|
44
|
+
- MIT
|
|
45
|
+
metadata:
|
|
46
|
+
bug_tracker_uri: https://github.com/sashite/pnn.rb/issues
|
|
47
|
+
documentation_uri: https://rubydoc.info/github/sashite/pnn.rb/main
|
|
48
|
+
homepage_uri: https://github.com/sashite/pnn.rb
|
|
49
|
+
source_code_uri: https://github.com/sashite/pnn.rb
|
|
50
|
+
specification_uri: https://sashite.dev/specs/pnn/1.0.0/
|
|
51
|
+
rubygems_mfa_required: 'true'
|
|
52
|
+
rdoc_options: []
|
|
53
|
+
require_paths:
|
|
54
|
+
- lib
|
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: 3.2.0
|
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
|
+
requirements:
|
|
62
|
+
- - ">="
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
version: '0'
|
|
65
|
+
requirements: []
|
|
66
|
+
rubygems_version: 3.6.9
|
|
67
|
+
specification_version: 4
|
|
68
|
+
summary: Modern Ruby implementation of Piece Name Notation (PNN) for abstract strategy
|
|
69
|
+
games.
|
|
70
|
+
test_files: []
|