sashite-pnn 1.0.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc91e11f8d339cc5fbad7f63a992fe3673aca84de09823f57b3bac30f787d115
4
- data.tar.gz: 3d5566a65d929b5f60169496ecaac21f41855551d415024638ba38d6e941709c
3
+ metadata.gz: 4d74e4979082abf26a0ec1d7e871f9d3a31834cbbca683e73952f789f88b50c8
4
+ data.tar.gz: 5587bdfc31daba336a8c50be37ee8f342ab90c10b4f1d61b8b73426f1a9c071e
5
5
  SHA512:
6
- metadata.gz: 995dfb0e2966e9fec73b675b4c313d4817f87ad09bce6ec8a1b977f2144dfd6cb0260cc8ee27beb4fcfc4db5dd9acb0e88d648bd9015697a5f11b47103550f48
7
- data.tar.gz: 7bdc2954d3f0aa39da0b59bab6dff52967219c368559ef78c86193bb52ec89007ed85463ba4a5cbf2eb882adae10da4369c8725d2e983d5eed41090aa4dc32a9
6
+ metadata.gz: '04313675987fd9ec5b2262ce3aeea7263e0a0cbd863a4a894def608a6b6b716c8991447227c9194260e248a0bde325958459eea80e1480117435f0b3ff903ea4'
7
+ data.tar.gz: 618b5df20b597a34228458430f88b9cf4134b8997348e7f2691a3411534001ea74358b62cd914b66338c6bd6a6e047ec43077775d9af6d3971ed227696e804f8
data/LICENSE.md CHANGED
@@ -1,21 +1,22 @@
1
- # The MIT License
1
+ Copyright (c) 2025 Cyril Kato
2
2
 
3
- Copyright (c) 2025 Sashité
3
+ MIT License
4
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:
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
11
12
 
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
14
15
 
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.
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -5,13 +5,13 @@
5
5
  ![Ruby](https://github.com/sashite/pnn.rb/actions/workflows/main.yml/badge.svg?branch=main)
6
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
7
 
8
- > **PNN** (Piece Name Notation) support for the Ruby language.
8
+ > **PNN** (Piece Name Notation) implementation for the Ruby language.
9
9
 
10
10
  ## What is PNN?
11
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.
12
+ PNN (Piece Name Notation) is a formal, rule-agnostic naming system for identifying **pieces** in abstract strategy board games such as chess, shÅgi, xiangqi, and their many variants. Each piece is represented by a canonical, human-readable ASCII name with optional state modifiers (e.g., `"KING"`, `"queen"`, `"+ROOK"`, `"-pawn"`).
13
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.
14
+ This gem implements the [PNN Specification v1.0.0](https://sashite.dev/specs/pnn/1.0.0/), supporting validation, parsing, and comparison of piece names with integrated state management.
15
15
 
16
16
  ## Installation
17
17
 
@@ -26,320 +26,213 @@ Or install manually:
26
26
  gem install sashite-pnn
27
27
  ```
28
28
 
29
- ## PNN Format
29
+ ## Usage
30
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:
31
+ ### Basic Operations
52
32
 
53
33
  ```ruby
54
34
  require "sashite/pnn"
55
35
 
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>
36
+ # Parse PNN strings into piece name objects
37
+ name = Sashite::Pnn.parse("KING") # => #<Pnn::Name value="KING">
38
+ name.to_s # => "KING"
39
+ name.value # => "KING"
63
40
 
64
- # With state modifiers and derivation
65
- enhanced_foreign = Sashite::Pnn::Piece.parse("+k'")
66
- # => #<Sashite::Pnn::Piece letter="k" enhanced=true native=false>
41
+ # Create from string or symbol
42
+ name = Sashite::Pnn.name("queen") # => #<Pnn::Name value="queen">
43
+ name = Sashite::Pnn::Name.new(:ROOK) # => #<Pnn::Name value="ROOK">
67
44
 
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)
45
+ # Validate PNN strings
46
+ Sashite::Pnn.valid?("BISHOP") # => true
47
+ Sashite::Pnn.valid?("King") # => false (mixed case not allowed)
48
+ Sashite::Pnn.valid?("+ROOK") # => true (enhanced state)
49
+ Sashite::Pnn.valid?("-pawn") # => true (diminished state)
72
50
  ```
73
51
 
74
- ### Converting to PNN String
75
-
76
- Convert a piece object back to its PNN string representation:
52
+ ### State Modifiers
77
53
 
78
54
  ```ruby
79
- piece = Sashite::Pnn::Piece.parse("k")
80
- piece.to_s
81
- # => "k"
55
+ # Enhanced pieces (+ prefix)
56
+ enhanced = Sashite::Pnn.parse("+QUEEN")
57
+ enhanced.enhanced? # => true
58
+ enhanced.normal? # => false
59
+ enhanced.base_name # => "QUEEN"
82
60
 
83
- foreign_piece = Sashite::Pnn::Piece.parse("k'")
84
- foreign_piece.to_s
85
- # => "k'"
61
+ # Diminished pieces (- prefix)
62
+ diminished = Sashite::Pnn.parse("-pawn")
63
+ diminished.diminished? # => true
64
+ diminished.base_name # => "pawn"
86
65
 
87
- enhanced_foreign = Sashite::Pnn::Piece.parse("+k'")
88
- enhanced_foreign.to_s
89
- # => "+k'"
66
+ # Normal pieces (no prefix)
67
+ normal = Sashite::Pnn.parse("KNIGHT")
68
+ normal.normal? # => true
69
+ normal.enhanced? # => false
70
+ normal.diminished? # => false
90
71
  ```
91
72
 
92
- ### Style Manipulation
93
-
94
- Create new piece instances with different styles:
73
+ ### Player Assignment
95
74
 
96
75
  ```ruby
97
- piece = Sashite::Pnn::Piece.parse("k")
98
-
99
- # Convert to foreign style
100
- foreign = piece.foreignize
101
- foreign.to_s # => "k'"
76
+ # First player pieces (uppercase)
77
+ first_player = Sashite::Pnn.parse("KING")
78
+ first_player.first_player? # => true
79
+ first_player.second_player? # => false
102
80
 
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'"
81
+ # Second player pieces (lowercase)
82
+ second_player = Sashite::Pnn.parse("king")
83
+ second_player.first_player? # => false
84
+ second_player.second_player? # => true
110
85
  ```
111
86
 
112
- ### State Manipulation (inherited from PIN)
113
-
114
- All PIN state manipulation methods are available:
87
+ ### Normalization and Comparison
115
88
 
116
89
  ```ruby
117
- piece = Sashite::Pnn::Piece.parse("k")
118
-
119
- # Enhanced state (+ prefix)
120
- enhanced = piece.enhance
121
- enhanced.to_s # => "+k"
90
+ a = Sashite::Pnn.parse("ROOK")
91
+ b = Sashite::Pnn.parse("ROOK")
122
92
 
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'"
93
+ a == b # => true
94
+ a.same_base_name?(Sashite::Pnn.parse("rook")) # => true (same piece, different player)
95
+ a.to_s # => "ROOK"
130
96
  ```
131
97
 
132
- ### Ownership Changes
133
-
134
- Change piece ownership (case conversion):
98
+ ### Collections and Filtering
135
99
 
136
100
  ```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
- ```
101
+ pieces = %w[KING queen +ROOK -pawn BISHOP knight].map { |n| Sashite::Pnn.parse(n) }
146
102
 
147
- ## Cross-Style Game Examples
103
+ # Filter by player
104
+ first_player_pieces = pieces.select(&:first_player?).map(&:to_s)
105
+ # => ["KING", "+ROOK", "BISHOP"]
148
106
 
149
- ### Chess vs. Shōgi Match
107
+ # Filter by state
108
+ enhanced_pieces = pieces.select(&:enhanced?).map(&:to_s)
109
+ # => ["+ROOK"]
150
110
 
151
- In a hybrid game where the first player uses Chess pieces and the second player uses Shōgi pieces:
111
+ diminished_pieces = pieces.select(&:diminished?).map(&:to_s)
112
+ # => ["-pawn"]
113
+ ```
152
114
 
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)
115
+ ## Format Specification
157
116
 
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)
117
+ ### Structure
161
118
 
162
- # Promoted pieces with style
163
- promoted_shogi = Sashite::Pnn::Piece.parse("+P'") # Promoted Shōgi pawn (foreign to first player)
164
119
  ```
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'"
120
+ <state-modifier>?<piece-name>
178
121
  ```
179
122
 
180
- ## Style Logic
181
-
182
- ### Native vs Foreign Style
123
+ Where:
124
+ - `<state-modifier>` is optional `+` (enhanced) or `-` (diminished)
125
+ - `<piece-name>` is case-consistent alphabetic characters
183
126
 
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)
127
+ ### Grammar (BNF)
186
128
 
187
- ### Style Assignment
129
+ ```bnf
130
+ <pnn> ::= <state-modifier> <name-body>
131
+ | <name-body>
188
132
 
189
- ```ruby
190
- piece = Sashite::Pnn::Piece.parse("K")
133
+ <state-modifier> ::= "+" | "-"
191
134
 
192
- # Check style
193
- piece.native? # => true
194
- piece.foreign? # => false
135
+ <name-body> ::= <uppercase-name> | <lowercase-name>
195
136
 
196
- # Convert styles
197
- foreign = piece.foreignize
198
- foreign.native? # => false
199
- foreign.foreign? # => true
137
+ <uppercase-name> ::= <uppercase-letter>+
138
+ <lowercase-name> ::= <lowercase-letter>+
200
139
 
201
- native = foreign.nativize
202
- native.native? # => true
203
- native.foreign? # => false
140
+ <uppercase-letter> ::= "A" | "B" | "C" | ... | "Z"
141
+ <lowercase-letter> ::= "a" | "b" | "c" | ... | "z"
204
142
  ```
205
143
 
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).
144
+ ### Regular Expression
228
145
 
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
146
+ ```ruby
147
+ /\A[+-]?([A-Z]+|[a-z]+)\z/
148
+ ```
240
149
 
241
- ### Instance Methods
150
+ ## Design Principles
242
151
 
243
- #### Style Queries
244
- - `#native?` - Check if piece has native style
245
- - `#foreign?` - Check if piece has foreign style
152
+ * **Human-readable**: Names like `"KING"` or `"queen"` are intuitive and descriptive.
153
+ * **State-aware**: Integrated state management through `+` and `-` modifiers.
154
+ * **Rule-agnostic**: Independent of specific game mechanics.
155
+ * **Case-consistent**: Visual distinction between players through case.
156
+ * **Canonical**: One valid name per piece state within a given context.
157
+ * **ASCII-only**: Compatible with all systems.
246
158
 
247
- #### Style Manipulation
248
- - `#foreignize` - Convert to foreign style
249
- - `#nativize` - Convert to native style
250
- - `#toggle_style` - Toggle between native/foreign style
159
+ ## Integration with PIN
251
160
 
252
- #### Inherited PIN Methods
253
- All methods from `Sashite::Pin::Piece` are available, including state queries, state manipulation, and conversion methods.
161
+ PNN names serve as the formal source for PIN character identifiers. For example:
254
162
 
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
163
+ | PNN | PIN | Description |
164
+ | --------- | ------- | ----------- |
165
+ | `KING` | `K` | First player king |
166
+ | `king` | `k` | Second player king |
167
+ | `+ROOK` | `+R` | Enhanced first player rook |
168
+ | `-pawn` | `-p` | Diminished second player pawn |
259
169
 
260
- ## Examples in Common Games
170
+ Multiple PNN names may map to the same PIN character (e.g., `"KING"` and `"KHAN"` both â†' `K`), but PNN provides unambiguous naming within broader contexts.
261
171
 
262
- ### Single-Style Games
172
+ ## Examples
263
173
 
264
174
  ```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
- ```
175
+ # Traditional pieces
176
+ Sashite::Pnn.parse("KING") # => #<Pnn::Name value="KING">
177
+ Sashite::Pnn.parse("queen") # => #<Pnn::Name value="queen">
274
178
 
275
- ### Cross-Style Games
179
+ # State modifiers
180
+ Sashite::Pnn.parse("+ROOK") # => #<Pnn::Name value="+ROOK">
181
+ Sashite::Pnn.parse("-pawn") # => #<Pnn::Name value="-pawn">
276
182
 
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)
183
+ # Validation
184
+ Sashite::Pnn.valid?("BISHOP") # => true
185
+ Sashite::Pnn.valid?("Bishop") # => false (mixed case)
186
+ Sashite::Pnn.valid?("KING1") # => false (no digits allowed)
283
187
  ```
284
188
 
285
- ## Advanced Usage
286
-
287
- ### Chaining Operations
288
-
289
- ```ruby
290
- piece = Sashite::Pnn::Piece.parse("k")
189
+ ## API Reference
291
190
 
292
- # Chain multiple operations
293
- result = piece.enhance.foreignize.flip
294
- result.to_s # => "+K'"
191
+ ### Main Module
295
192
 
296
- # Complex transformations
297
- captured_and_converted = piece.flip.nativize.enhance
298
- captured_and_converted.to_s # => "+K"
299
- ```
193
+ * `Sashite::Pnn.valid?(str)` â€" Returns `true` if the string is valid PNN.
194
+ * `Sashite::Pnn.parse(str)` â€" Returns a `Sashite::Pnn::Name` object.
195
+ * `Sashite::Pnn.name(sym_or_str)` â€" Alias for constructing a name.
300
196
 
301
- ### Validation
197
+ ### `Sashite::Pnn::Name`
302
198
 
303
- All parsing automatically validates input according to the PNN specification:
199
+ * `#value` â€" Returns the canonical string value.
200
+ * `#to_s` â€" Returns the string representation.
201
+ * `#base_name` â€" Returns the name without state modifier.
202
+ * `#enhanced?` â€" Returns `true` if piece has enhanced state (`+` prefix).
203
+ * `#diminished?` â€" Returns `true` if piece has diminished state (`-` prefix).
204
+ * `#normal?` â€" Returns `true` if piece has normal state (no prefix).
205
+ * `#first_player?` â€" Returns `true` if piece belongs to first player (uppercase).
206
+ * `#second_player?` â€" Returns `true` if piece belongs to second player (lowercase).
207
+ * `#same_base_name?(other)` â€" Returns `true` if both pieces have same base name.
208
+ * `#==`, `#eql?`, `#hash` â€" Value-based equality.
304
209
 
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
- ```
210
+ ## Development
321
211
 
322
- ## Properties of PNN
212
+ ```sh
213
+ # Clone the repository
214
+ git clone https://github.com/sashite/pnn.rb.git
215
+ cd pnn.rb
323
216
 
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)
217
+ # Install dependencies
218
+ bundle install
330
219
 
331
- ## Constraints
220
+ # Run tests
221
+ ruby test.rb
332
222
 
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
223
+ # Generate documentation
224
+ yard doc
225
+ ```
337
226
 
338
- ## Documentation
227
+ ## Contributing
339
228
 
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)
229
+ 1. Fork the repository
230
+ 2. Create a feature branch (`git checkout -b feature/new-feature`)
231
+ 3. Add tests for your changes
232
+ 4. Ensure all tests pass (`ruby test.rb`)
233
+ 5. Commit your changes (`git commit -am 'Add new feature'`)
234
+ 6. Push to the branch (`git push origin feature/new-feature`)
235
+ 7. Create a Pull Request
343
236
 
344
237
  ## License
345
238
 
@@ -347,4 +240,4 @@ Available as open source under the [MIT License](https://opensource.org/licenses
347
240
 
348
241
  ## About
349
242
 
350
- Maintained by [Sashité](https://sashite.com/) promoting chess variants and sharing the beauty of board game cultures.
243
+ Maintained by [Sashité](https://sashite.com/) â€" promoting chess variants and sharing the beauty of board game cultures.
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sashite
4
+ module Pnn
5
+ # Represents a piece name in PNN (Piece Name Notation) format.
6
+ #
7
+ # PNN provides a canonical naming system for abstract strategy game pieces.
8
+ # Each name consists of an optional state modifier (+ or -) followed by a
9
+ # case-consistent alphabetic name, encoding piece identity, player assignment,
10
+ # and state in a human-readable format.
11
+ #
12
+ # All instances are immutable.
13
+ class Name
14
+ # PNN validation pattern matching the specification
15
+ PNN_PATTERN = /\A([+-]?)([A-Z]+|[a-z]+)\z/
16
+
17
+ # Error messages
18
+ ERROR_INVALID_NAME = "Invalid PNN string: %s"
19
+
20
+ # @return [String] the canonical piece name
21
+ attr_reader :value
22
+
23
+ # Create a new piece name instance
24
+ #
25
+ # @param name [String, Symbol] the piece name (e.g., "KING", :queen, "+ROOK", "-pawn")
26
+ # @raise [ArgumentError] if the name does not match PNN pattern
27
+ def initialize(name)
28
+ string_value = name.to_s
29
+ self.class.validate_format(string_value)
30
+
31
+ @value = string_value.freeze
32
+ @parsed = parse_components(string_value)
33
+ freeze
34
+ end
35
+
36
+ # Parse a PNN string into a Name object
37
+ #
38
+ # @param string [String] the PNN-formatted piece name
39
+ # @return [Name] a new Name instance
40
+ # @raise [ArgumentError] if the string is invalid
41
+ #
42
+ # @example
43
+ # Sashite::Pnn::Name.parse("KING") # => #<Pnn::Name value="KING">
44
+ # Sashite::Pnn::Name.parse("+queen") # => #<Pnn::Name value="+queen">
45
+ def self.parse(string)
46
+ new(string)
47
+ end
48
+
49
+ # Check whether the given string is a valid PNN name
50
+ #
51
+ # @param string [String] input string to validate
52
+ # @return [Boolean] true if valid, false otherwise
53
+ #
54
+ # @example
55
+ # Sashite::Pnn::Name.valid?("KING") # => true
56
+ # Sashite::Pnn::Name.valid?("King") # => false (mixed case)
57
+ # Sashite::Pnn::Name.valid?("+queen") # => true
58
+ # Sashite::Pnn::Name.valid?("KING1") # => false (contains digit)
59
+ def self.valid?(string)
60
+ string.is_a?(::String) && string.match?(PNN_PATTERN)
61
+ end
62
+
63
+ # Validate that the string is in proper PNN format
64
+ #
65
+ # @param str [String]
66
+ # @raise [ArgumentError] if invalid
67
+ def self.validate_format(str)
68
+ raise ::ArgumentError, format(ERROR_INVALID_NAME, str.inspect) unless str.match?(PNN_PATTERN)
69
+ end
70
+
71
+ # Returns the string representation of the name
72
+ #
73
+ # @return [String]
74
+ def to_s
75
+ value
76
+ end
77
+
78
+ # Returns the base name without state modifier
79
+ #
80
+ # @return [String] the piece name without + or - prefix
81
+ #
82
+ # @example
83
+ # Sashite::Pnn::Name.parse("KING").base_name # => "KING"
84
+ # Sashite::Pnn::Name.parse("+queen").base_name # => "queen"
85
+ # Sashite::Pnn::Name.parse("-ROOK").base_name # => "ROOK"
86
+ def base_name
87
+ @parsed[:base_name]
88
+ end
89
+
90
+ # Check if the piece has enhanced state (+)
91
+ #
92
+ # @return [Boolean] true if enhanced, false otherwise
93
+ #
94
+ # @example
95
+ # Sashite::Pnn::Name.parse("+KING").enhanced? # => true
96
+ # Sashite::Pnn::Name.parse("KING").enhanced? # => false
97
+ def enhanced?
98
+ @parsed[:state_modifier] == "+"
99
+ end
100
+
101
+ # Check if the piece has diminished state (-)
102
+ #
103
+ # @return [Boolean] true if diminished, false otherwise
104
+ #
105
+ # @example
106
+ # Sashite::Pnn::Name.parse("-pawn").diminished? # => true
107
+ # Sashite::Pnn::Name.parse("pawn").diminished? # => false
108
+ def diminished?
109
+ @parsed[:state_modifier] == "-"
110
+ end
111
+
112
+ # Check if the piece has normal state (no modifier)
113
+ #
114
+ # @return [Boolean] true if normal, false otherwise
115
+ #
116
+ # @example
117
+ # Sashite::Pnn::Name.parse("KING").normal? # => true
118
+ # Sashite::Pnn::Name.parse("+KING").normal? # => false
119
+ def normal?
120
+ @parsed[:state_modifier].empty?
121
+ end
122
+
123
+ # Check if the piece belongs to the first player (uppercase)
124
+ #
125
+ # @return [Boolean] true if first player, false otherwise
126
+ #
127
+ # @example
128
+ # Sashite::Pnn::Name.parse("KING").first_player? # => true
129
+ # Sashite::Pnn::Name.parse("queen").first_player? # => false
130
+ def first_player?
131
+ @parsed[:base_name] == @parsed[:base_name].upcase
132
+ end
133
+
134
+ # Check if the piece belongs to the second player (lowercase)
135
+ #
136
+ # @return [Boolean] true if second player, false otherwise
137
+ #
138
+ # @example
139
+ # Sashite::Pnn::Name.parse("king").second_player? # => true
140
+ # Sashite::Pnn::Name.parse("QUEEN").second_player? # => false
141
+ def second_player?
142
+ @parsed[:base_name] == @parsed[:base_name].downcase
143
+ end
144
+
145
+ # Check if another piece has the same base name (ignoring case and state)
146
+ #
147
+ # @param other [Name] another piece name to compare
148
+ # @return [Boolean] true if same base name, false otherwise
149
+ #
150
+ # @example
151
+ # king = Sashite::Pnn::Name.parse("KING")
152
+ # queen = Sashite::Pnn::Name.parse("king")
153
+ # enhanced = Sashite::Pnn::Name.parse("+KING")
154
+ #
155
+ # king.same_base_name?(queen) # => true (same piece, different player)
156
+ # king.same_base_name?(enhanced) # => true (same piece, different state)
157
+ def same_base_name?(other)
158
+ return false unless other.is_a?(self.class)
159
+
160
+ base_name.downcase == other.base_name.downcase
161
+ end
162
+
163
+ # Equality based on normalized string value
164
+ #
165
+ # @param other [Object]
166
+ # @return [Boolean]
167
+ def ==(other)
168
+ other.is_a?(self.class) && value == other.value
169
+ end
170
+
171
+ # Required for correct Set/hash behavior
172
+ alias eql? ==
173
+
174
+ # Hash based on class and value
175
+ #
176
+ # @return [Integer]
177
+ def hash
178
+ [self.class, value].hash
179
+ end
180
+
181
+ private
182
+
183
+ # Parse the components of a PNN string
184
+ #
185
+ # @param str [String] the PNN string
186
+ # @return [Hash] parsed components
187
+ def parse_components(str)
188
+ match = str.match(PNN_PATTERN)
189
+ {
190
+ state_modifier: match[1],
191
+ base_name: match[2]
192
+ }
193
+ end
194
+ end
195
+ end
196
+ end
data/lib/sashite/pnn.rb CHANGED
@@ -1,60 +1,66 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "pnn/piece"
3
+ require_relative "pnn/name"
4
4
 
5
5
  module Sashite
6
6
  # PNN (Piece Name Notation) implementation for Ruby
7
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.
8
+ # Provides a formal naming system for identifying pieces in abstract strategy board games.
9
+ # PNN uses canonical, human-readable ASCII names with optional state modifiers and case
10
+ # encoding for player assignment. It supports unlimited unique piece identifiers with
11
+ # consistent, rule-agnostic semantics.
11
12
  #
12
- # Format: <pin>[<suffix>]
13
- # - PIN component: [<state>]<letter> (state modifier + letter)
14
- # - Suffix: "'" for foreign style, none for native style
13
+ # Format: [<state-modifier>]<case-consistent-name>
15
14
  #
16
15
  # 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)
16
+ # "KING" - First player king (normal state)
17
+ # "queen" - Second player queen (normal state)
18
+ # "+ROOK" - First player rook (enhanced state)
19
+ # "-pawn" - Second player pawn (diminished state)
20
+ # "BISHOP" - First player bishop (normal state)
22
21
  #
23
22
  # See: https://sashite.dev/specs/pnn/1.0.0/
24
23
  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
24
+ # Check if a string is valid PNN notation
30
25
  #
31
- # @param pnn [String] The string to validate
26
+ # @param pnn_string [String] the string to validate
32
27
  # @return [Boolean] true if valid PNN, false otherwise
33
28
  #
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)
29
+ # @example Validate PNN strings
30
+ # Sashite::Pnn.valid?("KING") # => true
31
+ # Sashite::Pnn.valid?("queen") # => true
32
+ # Sashite::Pnn.valid?("+ROOK") # => true
33
+ # Sashite::Pnn.valid?("-pawn") # => true
34
+ # Sashite::Pnn.valid?("King") # => false (mixed case)
35
+ # Sashite::Pnn.valid?("KING1") # => false (contains digit)
36
+ def self.valid?(pnn_string)
37
+ Name.valid?(pnn_string)
38
+ end
43
39
 
44
- pnn.match?(PNN_REGEX)
40
+ # Parse a PNN string into a Name object
41
+ #
42
+ # @param pnn_string [String] the piece name string
43
+ # @return [Pnn::Name] a parsed name object
44
+ # @raise [ArgumentError] if the name is invalid
45
+ #
46
+ # @example Parse valid PNN names
47
+ # Sashite::Pnn.parse("KING") # => #<Pnn::Name value="KING">
48
+ # Sashite::Pnn.parse("+queen") # => #<Pnn::Name value="+queen">
49
+ def self.parse(pnn_string)
50
+ Name.parse(pnn_string)
45
51
  end
46
52
 
47
- # Parse a PNN string into a Piece object
53
+ # Create a new Name instance directly
54
+ #
55
+ # @param value [String, Symbol] piece name to construct
56
+ # @return [Pnn::Name] new name instance
57
+ # @raise [ArgumentError] if name format is invalid
48
58
  #
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
59
  # @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)
60
+ # Sashite::Pnn.name("BISHOP") # => #<Pnn::Name value="BISHOP">
61
+ # Sashite::Pnn.name(:queen) # => #<Pnn::Name value="queen">
62
+ def self.name(value)
63
+ Name.new(value)
58
64
  end
59
65
  end
60
66
  end
data/lib/sashite-pnn.rb CHANGED
@@ -1,21 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "sashite/pnn"
4
+
3
5
  # Sashité namespace for board game notation libraries
6
+ #
7
+ # Sashité provides a collection of libraries for representing and manipulating
8
+ # board game concepts according to the Sashité Protocol specifications.
9
+ #
10
+ # @see https://sashite.dev/protocol/ Sashité Protocol
11
+ # @see https://sashite.dev/specs/ Sashité Specifications
12
+ # @author Sashité
4
13
  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
14
  end
20
-
21
- require_relative "sashite/pnn"
metadata CHANGED
@@ -1,34 +1,21 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sashite-pnn
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
8
8
  bindir: bin
9
9
  cert_chain: []
10
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.1.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.1.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.
11
+ dependencies: []
12
+ description: |
13
+ PNN (Piece Name Notation) provides a rule-agnostic, scalable naming system for identifying
14
+ abstract strategy board game pieces. This gem implements the PNN Specification v1.0.0 with
15
+ a modern Ruby interface featuring immutable piece name objects and functional programming
16
+ principles. PNN uses canonical ASCII names with optional state modifiers (e.g., "KING", "queen",
17
+ "+ROOK", "-pawn") to unambiguously refer to game pieces across variants and traditions.
18
+ Ideal for engines, protocols, and tools that need clear and extensible piece identifiers.
32
19
  email: contact@cyril.email
33
20
  executables: []
34
21
  extensions: []
@@ -38,7 +25,7 @@ files:
38
25
  - README.md
39
26
  - lib/sashite-pnn.rb
40
27
  - lib/sashite/pnn.rb
41
- - lib/sashite/pnn/piece.rb
28
+ - lib/sashite/pnn/name.rb
42
29
  homepage: https://github.com/sashite/pnn.rb
43
30
  licenses:
44
31
  - MIT
@@ -63,8 +50,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
63
50
  - !ruby/object:Gem::Version
64
51
  version: '0'
65
52
  requirements: []
66
- rubygems_version: 3.6.9
53
+ rubygems_version: 3.7.1
67
54
  specification_version: 4
68
- summary: Modern Ruby implementation of Piece Name Notation (PNN) for abstract strategy
69
- games.
55
+ summary: PNN (Piece Name Notation) implementation for Ruby with immutable piece name
56
+ objects
70
57
  test_files: []
@@ -1,234 +0,0 @@
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