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 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
+ [![Version](https://img.shields.io/github/v/tag/sashite/pnn.rb?label=Version&logo=github)](https://github.com/sashite/pnn.rb/tags)
4
+ [![Yard documentation](https://img.shields.io/badge/Yard-documentation-blue.svg?logo=github)](https://rubydoc.info/github/sashite/pnn.rb/main)
5
+ ![Ruby](https://github.com/sashite/pnn.rb/actions/workflows/main.yml/badge.svg?branch=main)
6
+ [![License](https://img.shields.io/github/license/sashite/pnn.rb?label=License&logo=github)](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
@@ -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
@@ -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: []