sashite-pnn 3.0.0 → 3.1.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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +94 -41
  3. data/lib/sashite/pnn/name.rb +53 -26
  4. metadata +6 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d74e4979082abf26a0ec1d7e871f9d3a31834cbbca683e73952f789f88b50c8
4
- data.tar.gz: 5587bdfc31daba336a8c50be37ee8f342ab90c10b4f1d61b8b73426f1a9c071e
3
+ metadata.gz: 85b86d223876b87d9bdcf29bc1484a32b667038bc378b1b9188aea72f4f2f66d
4
+ data.tar.gz: 27793a4f9a79241ffedbcf69f3ed4c3f6dc0293928318c308331d21eafb7df0c
5
5
  SHA512:
6
- metadata.gz: '04313675987fd9ec5b2262ce3aeea7263e0a0cbd863a4a894def608a6b6b716c8991447227c9194260e248a0bde325958459eea80e1480117435f0b3ff903ea4'
7
- data.tar.gz: 618b5df20b597a34228458430f88b9cf4134b8997348e7f2691a3411534001ea74358b62cd914b66338c6bd6a6e047ec43077775d9af6d3971ed227696e804f8
6
+ metadata.gz: 13c0726586676a5ecf632126879761198c9ca7be438aaf58720c5e469b3283c3782bc51a4e8315ee91f14fc57501c705860d7fc225de175b43526014d1464239
7
+ data.tar.gz: d654455e116883dfed83ac75ca22906ae42480eafa656f1f39f908ee0d5a79da7974ac2b421c0e8bdcaa8615650666fa1de1c39c9e149a7da079a7c127931be8
data/README.md CHANGED
@@ -9,19 +9,17 @@
9
9
 
10
10
  ## What is PNN?
11
11
 
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"`).
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 and optional terminal markers (e.g., `"KING"`, `"queen"`, `"+ROOK"`, `"-pawn"`, `"KING^"`).
13
13
 
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.
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 and terminal piece identification.
15
15
 
16
16
  ## Installation
17
-
18
17
  ```ruby
19
18
  # In your Gemfile
20
19
  gem "sashite-pnn"
21
20
  ```
22
21
 
23
22
  Or install manually:
24
-
25
23
  ```sh
26
24
  gem install sashite-pnn
27
25
  ```
@@ -29,7 +27,6 @@ gem install sashite-pnn
29
27
  ## Usage
30
28
 
31
29
  ### Basic Operations
32
-
33
30
  ```ruby
34
31
  require "sashite/pnn"
35
32
 
@@ -47,10 +44,11 @@ Sashite::Pnn.valid?("BISHOP") # => true
47
44
  Sashite::Pnn.valid?("King") # => false (mixed case not allowed)
48
45
  Sashite::Pnn.valid?("+ROOK") # => true (enhanced state)
49
46
  Sashite::Pnn.valid?("-pawn") # => true (diminished state)
47
+ Sashite::Pnn.valid?("KING^") # => true (terminal piece)
48
+ Sashite::Pnn.valid?("+KING^") # => true (enhanced terminal piece)
50
49
  ```
51
50
 
52
51
  ### State Modifiers
53
-
54
52
  ```ruby
55
53
  # Enhanced pieces (+ prefix)
56
54
  enhanced = Sashite::Pnn.parse("+QUEEN")
@@ -70,39 +68,69 @@ normal.enhanced? # => false
70
68
  normal.diminished? # => false
71
69
  ```
72
70
 
73
- ### Player Assignment
71
+ ### Terminal Markers
72
+ ```ruby
73
+ # Terminal pieces (^ suffix)
74
+ terminal = Sashite::Pnn.parse("KING^")
75
+ terminal.terminal? # => true
76
+ terminal.base_name # => "KING"
77
+
78
+ # Non-terminal pieces (no suffix)
79
+ non_terminal = Sashite::Pnn.parse("PAWN")
80
+ non_terminal.terminal? # => false
81
+
82
+ # Combined state and terminal marker
83
+ enhanced_terminal = Sashite::Pnn.parse("+ROOK^")
84
+ enhanced_terminal.enhanced? # => true
85
+ enhanced_terminal.terminal? # => true
86
+ enhanced_terminal.base_name # => "ROOK"
87
+
88
+ diminished_terminal = Sashite::Pnn.parse("-king^")
89
+ diminished_terminal.diminished? # => true
90
+ diminished_terminal.terminal? # => true
91
+ diminished_terminal.base_name # => "king"
92
+ ```
74
93
 
94
+ ### Player Assignment
75
95
  ```ruby
76
96
  # First player pieces (uppercase)
77
97
  first_player = Sashite::Pnn.parse("KING")
78
98
  first_player.first_player? # => true
79
99
  first_player.second_player? # => false
80
100
 
101
+ first_player_terminal = Sashite::Pnn.parse("KING^")
102
+ first_player_terminal.first_player? # => true
103
+ first_player_terminal.terminal? # => true
104
+
81
105
  # Second player pieces (lowercase)
82
106
  second_player = Sashite::Pnn.parse("king")
83
107
  second_player.first_player? # => false
84
108
  second_player.second_player? # => true
109
+
110
+ second_player_terminal = Sashite::Pnn.parse("king^")
111
+ second_player_terminal.second_player? # => true
112
+ second_player_terminal.terminal? # => true
85
113
  ```
86
114
 
87
115
  ### Normalization and Comparison
88
-
89
116
  ```ruby
90
117
  a = Sashite::Pnn.parse("ROOK")
91
118
  b = Sashite::Pnn.parse("ROOK")
92
119
 
93
120
  a == b # => true
94
121
  a.same_base_name?(Sashite::Pnn.parse("rook")) # => true (same piece, different player)
95
- a.to_s # => "ROOK"
122
+ a.same_base_name?(Sashite::Pnn.parse("ROOK^")) # => true (same piece, terminal marker)
123
+ a.same_base_name?(Sashite::Pnn.parse("+rook")) # => true (same piece, different state)
124
+ a.to_s # => "ROOK"
96
125
  ```
97
126
 
98
127
  ### Collections and Filtering
99
-
100
128
  ```ruby
101
- pieces = %w[KING queen +ROOK -pawn BISHOP knight].map { |n| Sashite::Pnn.parse(n) }
129
+ pieces = %w[KING^ queen +ROOK -pawn BISHOP knight^ GENERAL^].map { |n| Sashite::Pnn.parse(n) }
102
130
 
103
131
  # Filter by player
104
132
  first_player_pieces = pieces.select(&:first_player?).map(&:to_s)
105
- # => ["KING", "+ROOK", "BISHOP"]
133
+ # => ["KING^", "+ROOK", "BISHOP", "GENERAL^"]
106
134
 
107
135
  # Filter by state
108
136
  enhanced_pieces = pieces.select(&:enhanced?).map(&:to_s)
@@ -110,24 +138,33 @@ enhanced_pieces = pieces.select(&:enhanced?).map(&:to_s)
110
138
 
111
139
  diminished_pieces = pieces.select(&:diminished?).map(&:to_s)
112
140
  # => ["-pawn"]
141
+
142
+ # Filter by terminal status
143
+ terminal_pieces = pieces.select(&:terminal?).map(&:to_s)
144
+ # => ["KING^", "knight^", "GENERAL^"]
145
+
146
+ # Combine filters
147
+ first_player_terminals = pieces.select { |p| p.first_player? && p.terminal? }.map(&:to_s)
148
+ # => ["KING^", "GENERAL^"]
113
149
  ```
114
150
 
115
151
  ## Format Specification
116
152
 
117
153
  ### Structure
118
-
119
154
  ```
120
- <state-modifier>?<piece-name>
155
+ [<state-modifier>]<piece-name>[<terminal-marker>]
121
156
  ```
122
157
 
123
158
  Where:
124
159
  - `<state-modifier>` is optional `+` (enhanced) or `-` (diminished)
125
160
  - `<piece-name>` is case-consistent alphabetic characters
161
+ - `<terminal-marker>` is optional `^` (terminal piece)
126
162
 
127
163
  ### Grammar (BNF)
128
-
129
164
  ```bnf
130
- <pnn> ::= <state-modifier> <name-body>
165
+ <pnn> ::= <state-modifier> <name-body> <terminal-marker>
166
+ | <state-modifier> <name-body>
167
+ | <name-body> <terminal-marker>
131
168
  | <name-body>
132
169
 
133
170
  <state-modifier> ::= "+" | "-"
@@ -137,20 +174,22 @@ Where:
137
174
  <uppercase-name> ::= <uppercase-letter>+
138
175
  <lowercase-name> ::= <lowercase-letter>+
139
176
 
177
+ <terminal-marker> ::= "^"
178
+
140
179
  <uppercase-letter> ::= "A" | "B" | "C" | ... | "Z"
141
180
  <lowercase-letter> ::= "a" | "b" | "c" | ... | "z"
142
181
  ```
143
182
 
144
183
  ### Regular Expression
145
-
146
184
  ```ruby
147
- /\A[+-]?([A-Z]+|[a-z]+)\z/
185
+ /\A[+-]?([A-Z]+|[a-z]+)\^?\z/
148
186
  ```
149
187
 
150
188
  ## Design Principles
151
189
 
152
190
  * **Human-readable**: Names like `"KING"` or `"queen"` are intuitive and descriptive.
153
191
  * **State-aware**: Integrated state management through `+` and `-` modifiers.
192
+ * **Terminal-aware**: Explicit identification of terminal pieces through `^` marker.
154
193
  * **Rule-agnostic**: Independent of specific game mechanics.
155
194
  * **Case-consistent**: Visual distinction between players through case.
156
195
  * **Canonical**: One valid name per piece state within a given context.
@@ -160,55 +199,69 @@ Where:
160
199
 
161
200
  PNN names serve as the formal source for PIN character identifiers. For example:
162
201
 
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 |
202
+ | PNN | PIN | Description |
203
+ | ---------- | -------- | ----------- |
204
+ | `KING` | `K` | First player king |
205
+ | `king` | `k` | Second player king |
206
+ | `KING^` | `K^` | Terminal first player king |
207
+ | `king^` | `k^` | Terminal second player king |
208
+ | `+ROOK` | `+R` | Enhanced first player rook |
209
+ | `+ROOK^` | `+R^` | Enhanced terminal first player rook |
210
+ | `-pawn` | `-p` | Diminished second player pawn |
211
+ | `-pawn^` | `-p^` | Diminished terminal second player pawn |
169
212
 
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.
213
+ 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.
171
214
 
172
215
  ## Examples
173
-
174
216
  ```ruby
175
217
  # Traditional pieces
176
218
  Sashite::Pnn.parse("KING") # => #<Pnn::Name value="KING">
177
219
  Sashite::Pnn.parse("queen") # => #<Pnn::Name value="queen">
178
220
 
221
+ # Terminal pieces
222
+ Sashite::Pnn.parse("KING^") # => #<Pnn::Name value="KING^">
223
+ Sashite::Pnn.parse("general^") # => #<Pnn::Name value="general^">
224
+
179
225
  # State modifiers
180
226
  Sashite::Pnn.parse("+ROOK") # => #<Pnn::Name value="+ROOK">
181
227
  Sashite::Pnn.parse("-pawn") # => #<Pnn::Name value="-pawn">
182
228
 
229
+ # Combined modifiers
230
+ Sashite::Pnn.parse("+KING^") # => #<Pnn::Name value="+KING^">
231
+ Sashite::Pnn.parse("-pawn^") # => #<Pnn::Name value="-pawn^">
232
+
183
233
  # Validation
184
234
  Sashite::Pnn.valid?("BISHOP") # => true
235
+ Sashite::Pnn.valid?("KING^") # => true
236
+ Sashite::Pnn.valid?("+ROOK^") # => true
185
237
  Sashite::Pnn.valid?("Bishop") # => false (mixed case)
186
238
  Sashite::Pnn.valid?("KING1") # => false (no digits allowed)
239
+ Sashite::Pnn.valid?("^KING") # => false (terminal marker must be suffix)
187
240
  ```
188
241
 
189
242
  ## API Reference
190
243
 
191
244
  ### Main Module
192
245
 
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.
246
+ * `Sashite::Pnn.valid?(str)` Returns `true` if the string is valid PNN.
247
+ * `Sashite::Pnn.parse(str)` Returns a `Sashite::Pnn::Name` object.
248
+ * `Sashite::Pnn.name(sym_or_str)` Alias for constructing a name.
196
249
 
197
250
  ### `Sashite::Pnn::Name`
198
251
 
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.
252
+ * `#value` Returns the canonical string value.
253
+ * `#to_s` Returns the string representation.
254
+ * `#base_name` Returns the name without state modifier or terminal marker.
255
+ * `#enhanced?` Returns `true` if piece has enhanced state (`+` prefix).
256
+ * `#diminished?` Returns `true` if piece has diminished state (`-` prefix).
257
+ * `#normal?` Returns `true` if piece has normal state (no prefix).
258
+ * `#terminal?` Returns `true` if piece is a terminal piece (`^` suffix).
259
+ * `#first_player?` Returns `true` if piece belongs to first player (uppercase).
260
+ * `#second_player?` Returns `true` if piece belongs to second player (lowercase).
261
+ * `#same_base_name?(other)` — Returns `true` if both pieces have same base name.
262
+ * `#==`, `#eql?`, `#hash` — Value-based equality.
209
263
 
210
264
  ## Development
211
-
212
265
  ```sh
213
266
  # Clone the repository
214
267
  git clone https://github.com/sashite/pnn.rb.git
@@ -240,4 +293,4 @@ Available as open source under the [MIT License](https://opensource.org/licenses
240
293
 
241
294
  ## About
242
295
 
243
- Maintained by [Sashité](https://sashite.com/) â€" promoting chess variants and sharing the beauty of board game cultures.
296
+ Maintained by [Sashité](https://sashite.com/) promoting chess variants and sharing the beauty of board game cultures.
@@ -6,13 +6,14 @@ module Sashite
6
6
  #
7
7
  # PNN provides a canonical naming system for abstract strategy game pieces.
8
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.
9
+ # case-consistent alphabetic name and an optional terminal marker (^),
10
+ # encoding piece identity, player assignment, state, and terminal status
11
+ # in a human-readable format.
11
12
  #
12
13
  # All instances are immutable.
13
14
  class Name
14
15
  # PNN validation pattern matching the specification
15
- PNN_PATTERN = /\A([+-]?)([A-Z]+|[a-z]+)\z/
16
+ PNN_PATTERN = /\A([+-]?)([A-Z]+|[a-z]+)(\^?)\z/
16
17
 
17
18
  # Error messages
18
19
  ERROR_INVALID_NAME = "Invalid PNN string: %s"
@@ -22,7 +23,7 @@ module Sashite
22
23
 
23
24
  # Create a new piece name instance
24
25
  #
25
- # @param name [String, Symbol] the piece name (e.g., "KING", :queen, "+ROOK", "-pawn")
26
+ # @param name [String, Symbol] the piece name (e.g., "KING", :queen, "+ROOK", "-pawn", "KING^")
26
27
  # @raise [ArgumentError] if the name does not match PNN pattern
27
28
  def initialize(name)
28
29
  string_value = name.to_s
@@ -40,8 +41,10 @@ module Sashite
40
41
  # @raise [ArgumentError] if the string is invalid
41
42
  #
42
43
  # @example
43
- # Sashite::Pnn::Name.parse("KING") # => #<Pnn::Name value="KING">
44
- # Sashite::Pnn::Name.parse("+queen") # => #<Pnn::Name value="+queen">
44
+ # Sashite::Pnn::Name.parse("KING") # => #<Pnn::Name value="KING">
45
+ # Sashite::Pnn::Name.parse("+queen") # => #<Pnn::Name value="+queen">
46
+ # Sashite::Pnn::Name.parse("KING^") # => #<Pnn::Name value="KING^">
47
+ # Sashite::Pnn::Name.parse("+ROOK^") # => #<Pnn::Name value="+ROOK^">
45
48
  def self.parse(string)
46
49
  new(string)
47
50
  end
@@ -55,6 +58,7 @@ module Sashite
55
58
  # Sashite::Pnn::Name.valid?("KING") # => true
56
59
  # Sashite::Pnn::Name.valid?("King") # => false (mixed case)
57
60
  # Sashite::Pnn::Name.valid?("+queen") # => true
61
+ # Sashite::Pnn::Name.valid?("KING^") # => true
58
62
  # Sashite::Pnn::Name.valid?("KING1") # => false (contains digit)
59
63
  def self.valid?(string)
60
64
  string.is_a?(::String) && string.match?(PNN_PATTERN)
@@ -75,14 +79,16 @@ module Sashite
75
79
  value
76
80
  end
77
81
 
78
- # Returns the base name without state modifier
82
+ # Returns the base name without state modifier or terminal marker
79
83
  #
80
- # @return [String] the piece name without + or - prefix
84
+ # @return [String] the piece name without + or - prefix and without ^ suffix
81
85
  #
82
86
  # @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"
87
+ # Sashite::Pnn::Name.parse("KING").base_name # => "KING"
88
+ # Sashite::Pnn::Name.parse("+queen").base_name # => "queen"
89
+ # Sashite::Pnn::Name.parse("-ROOK").base_name # => "ROOK"
90
+ # Sashite::Pnn::Name.parse("KING^").base_name # => "KING"
91
+ # Sashite::Pnn::Name.parse("+ROOK^").base_name # => "ROOK"
86
92
  def base_name
87
93
  @parsed[:base_name]
88
94
  end
@@ -92,8 +98,9 @@ module Sashite
92
98
  # @return [Boolean] true if enhanced, false otherwise
93
99
  #
94
100
  # @example
95
- # Sashite::Pnn::Name.parse("+KING").enhanced? # => true
96
- # Sashite::Pnn::Name.parse("KING").enhanced? # => false
101
+ # Sashite::Pnn::Name.parse("+KING").enhanced? # => true
102
+ # Sashite::Pnn::Name.parse("KING").enhanced? # => false
103
+ # Sashite::Pnn::Name.parse("+KING^").enhanced? # => true
97
104
  def enhanced?
98
105
  @parsed[:state_modifier] == "+"
99
106
  end
@@ -103,8 +110,9 @@ module Sashite
103
110
  # @return [Boolean] true if diminished, false otherwise
104
111
  #
105
112
  # @example
106
- # Sashite::Pnn::Name.parse("-pawn").diminished? # => true
107
- # Sashite::Pnn::Name.parse("pawn").diminished? # => false
113
+ # Sashite::Pnn::Name.parse("-pawn").diminished? # => true
114
+ # Sashite::Pnn::Name.parse("pawn").diminished? # => false
115
+ # Sashite::Pnn::Name.parse("-pawn^").diminished? # => true
108
116
  def diminished?
109
117
  @parsed[:state_modifier] == "-"
110
118
  end
@@ -114,19 +122,34 @@ module Sashite
114
122
  # @return [Boolean] true if normal, false otherwise
115
123
  #
116
124
  # @example
117
- # Sashite::Pnn::Name.parse("KING").normal? # => true
118
- # Sashite::Pnn::Name.parse("+KING").normal? # => false
125
+ # Sashite::Pnn::Name.parse("KING").normal? # => true
126
+ # Sashite::Pnn::Name.parse("+KING").normal? # => false
127
+ # Sashite::Pnn::Name.parse("KING^").normal? # => true
119
128
  def normal?
120
129
  @parsed[:state_modifier].empty?
121
130
  end
122
131
 
132
+ # Check if the piece is a terminal piece (has ^ marker)
133
+ #
134
+ # @return [Boolean] true if terminal, false otherwise
135
+ #
136
+ # @example
137
+ # Sashite::Pnn::Name.parse("KING^").terminal? # => true
138
+ # Sashite::Pnn::Name.parse("KING").terminal? # => false
139
+ # Sashite::Pnn::Name.parse("+KING^").terminal? # => true
140
+ # Sashite::Pnn::Name.parse("-pawn^").terminal? # => true
141
+ def terminal?
142
+ @parsed[:terminal_marker] == "^"
143
+ end
144
+
123
145
  # Check if the piece belongs to the first player (uppercase)
124
146
  #
125
147
  # @return [Boolean] true if first player, false otherwise
126
148
  #
127
149
  # @example
128
- # Sashite::Pnn::Name.parse("KING").first_player? # => true
129
- # Sashite::Pnn::Name.parse("queen").first_player? # => false
150
+ # Sashite::Pnn::Name.parse("KING").first_player? # => true
151
+ # Sashite::Pnn::Name.parse("queen").first_player? # => false
152
+ # Sashite::Pnn::Name.parse("KING^").first_player? # => true
130
153
  def first_player?
131
154
  @parsed[:base_name] == @parsed[:base_name].upcase
132
155
  end
@@ -136,13 +159,14 @@ module Sashite
136
159
  # @return [Boolean] true if second player, false otherwise
137
160
  #
138
161
  # @example
139
- # Sashite::Pnn::Name.parse("king").second_player? # => true
140
- # Sashite::Pnn::Name.parse("QUEEN").second_player? # => false
162
+ # Sashite::Pnn::Name.parse("king").second_player? # => true
163
+ # Sashite::Pnn::Name.parse("QUEEN").second_player? # => false
164
+ # Sashite::Pnn::Name.parse("king^").second_player? # => true
141
165
  def second_player?
142
166
  @parsed[:base_name] == @parsed[:base_name].downcase
143
167
  end
144
168
 
145
- # Check if another piece has the same base name (ignoring case and state)
169
+ # Check if another piece has the same base name (ignoring case, state, and terminal marker)
146
170
  #
147
171
  # @param other [Name] another piece name to compare
148
172
  # @return [Boolean] true if same base name, false otherwise
@@ -151,9 +175,11 @@ module Sashite
151
175
  # king = Sashite::Pnn::Name.parse("KING")
152
176
  # queen = Sashite::Pnn::Name.parse("king")
153
177
  # enhanced = Sashite::Pnn::Name.parse("+KING")
178
+ # terminal = Sashite::Pnn::Name.parse("KING^")
154
179
  #
155
- # king.same_base_name?(queen) # => true (same piece, different player)
156
- # king.same_base_name?(enhanced) # => true (same piece, different state)
180
+ # king.same_base_name?(queen) # => true (same piece, different player)
181
+ # king.same_base_name?(enhanced) # => true (same piece, different state)
182
+ # king.same_base_name?(terminal) # => true (same piece, terminal marker)
157
183
  def same_base_name?(other)
158
184
  return false unless other.is_a?(self.class)
159
185
 
@@ -187,8 +213,9 @@ module Sashite
187
213
  def parse_components(str)
188
214
  match = str.match(PNN_PATTERN)
189
215
  {
190
- state_modifier: match[1],
191
- base_name: match[2]
216
+ state_modifier: match[1],
217
+ base_name: match[2],
218
+ terminal_marker: match[3]
192
219
  }
193
220
  end
194
221
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sashite-pnn
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
@@ -13,9 +13,10 @@ description: |
13
13
  PNN (Piece Name Notation) provides a rule-agnostic, scalable naming system for identifying
14
14
  abstract strategy board game pieces. This gem implements the PNN Specification v1.0.0 with
15
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.
16
+ principles. PNN uses canonical ASCII names with optional state modifiers and optional terminal
17
+ markers (e.g., "KING", "queen", "+ROOK", "-pawn", "KING^", "+GENERAL^") to unambiguously
18
+ refer to game pieces across variants and traditions. Ideal for engines, protocols, and tools
19
+ that need clear and extensible piece identifiers.
19
20
  email: contact@cyril.email
20
21
  executables: []
21
22
  extensions: []
@@ -50,7 +51,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
50
51
  - !ruby/object:Gem::Version
51
52
  version: '0'
52
53
  requirements: []
53
- rubygems_version: 3.7.1
54
+ rubygems_version: 3.7.2
54
55
  specification_version: 4
55
56
  summary: PNN (Piece Name Notation) implementation for Ruby with immutable piece name
56
57
  objects