sashite-pnn 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +430 -231
- data/lib/sashite/pnn/piece.rb +339 -132
- data/lib/sashite/pnn.rb +38 -29
- data/lib/sashite-pnn.rb +9 -16
- metadata +13 -11
data/lib/sashite/pnn/piece.rb
CHANGED
|
@@ -6,37 +6,75 @@ module Sashite
|
|
|
6
6
|
module Pnn
|
|
7
7
|
# Represents a piece in PNN (Piece Name Notation) format.
|
|
8
8
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# -
|
|
12
|
-
# - Apostrophe suffix ('): foreign style
|
|
9
|
+
# A piece consists of a PIN component with an optional derivation marker:
|
|
10
|
+
# - PIN component: [<state>]<letter> (from PIN specification)
|
|
11
|
+
# - Derivation marker: "'" (foreign style) or none (native style)
|
|
13
12
|
#
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
# The case of the letter determines ownership:
|
|
14
|
+
# - Uppercase (A-Z): first player
|
|
15
|
+
# - Lowercase (a-z): second player
|
|
16
|
+
#
|
|
17
|
+
# Style derivation logic:
|
|
18
|
+
# - No suffix: piece has the native style of its current side
|
|
19
|
+
# - Apostrophe suffix: piece has the foreign style (opposite side's native style)
|
|
20
|
+
#
|
|
21
|
+
# All instances are immutable - state manipulation methods return new instances.
|
|
22
|
+
# This extends the Game Protocol's piece model with Style support through derivation.
|
|
23
|
+
class Piece
|
|
24
|
+
# Valid derivation suffixes
|
|
20
25
|
FOREIGN_SUFFIX = "'"
|
|
26
|
+
NATIVE_SUFFIX = ""
|
|
27
|
+
|
|
28
|
+
# Derivation constants
|
|
29
|
+
NATIVE = true
|
|
30
|
+
FOREIGN = false
|
|
31
|
+
|
|
32
|
+
# Valid derivations
|
|
33
|
+
VALID_DERIVATIONS = [NATIVE, FOREIGN].freeze
|
|
21
34
|
|
|
22
35
|
# Error messages
|
|
23
36
|
ERROR_INVALID_PNN = "Invalid PNN string: %s"
|
|
24
|
-
|
|
37
|
+
ERROR_INVALID_DERIVATION = "Derivation must be true (native) or false (foreign), got: %s"
|
|
38
|
+
|
|
39
|
+
# @return [Symbol] the piece type (:A to :Z)
|
|
40
|
+
def type
|
|
41
|
+
@piece.type
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @return [Symbol] the player side (:first or :second)
|
|
45
|
+
def side
|
|
46
|
+
@piece.side
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [Symbol] the piece state (:normal, :enhanced, or :diminished)
|
|
50
|
+
def state
|
|
51
|
+
@piece.state
|
|
52
|
+
end
|
|
25
53
|
|
|
26
|
-
# @return [Boolean]
|
|
54
|
+
# @return [Boolean] the style derivation (true for native, false for foreign)
|
|
27
55
|
attr_reader :native
|
|
28
|
-
alias native? native
|
|
29
56
|
|
|
30
|
-
# Create a new piece instance
|
|
57
|
+
# Create a new piece instance
|
|
31
58
|
#
|
|
32
|
-
# @param
|
|
33
|
-
# @param
|
|
59
|
+
# @param type [Symbol] piece type (:A to :Z)
|
|
60
|
+
# @param side [Symbol] player side (:first or :second)
|
|
61
|
+
# @param state [Symbol] piece state (:normal, :enhanced, or :diminished)
|
|
62
|
+
# @param native [Boolean] style derivation (true for native, false for foreign)
|
|
34
63
|
# @raise [ArgumentError] if parameters are invalid
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
64
|
+
# @example
|
|
65
|
+
# Piece.new(:K, :first, :normal, true)
|
|
66
|
+
# Piece.new(:P, :second, :enhanced, false)
|
|
67
|
+
def initialize(type, side, state = Pin::Piece::NORMAL_STATE, native = NATIVE)
|
|
68
|
+
# Validate using PIN class methods for type, side, and state
|
|
69
|
+
Pin::Piece.validate_type(type)
|
|
70
|
+
Pin::Piece.validate_side(side)
|
|
71
|
+
Pin::Piece.validate_state(state)
|
|
72
|
+
self.class.validate_derivation(native)
|
|
73
|
+
|
|
74
|
+
@piece = Pin::Piece.new(type, side, state)
|
|
38
75
|
@native = native
|
|
39
|
-
|
|
76
|
+
|
|
77
|
+
freeze
|
|
40
78
|
end
|
|
41
79
|
|
|
42
80
|
# Parse a PNN string into a Piece object
|
|
@@ -45,190 +83,359 @@ module Sashite
|
|
|
45
83
|
# @return [Piece] new piece instance
|
|
46
84
|
# @raise [ArgumentError] if the PNN string is invalid
|
|
47
85
|
# @example
|
|
48
|
-
# Pnn::Piece.parse("k") # => #<Pnn::Piece
|
|
49
|
-
# Pnn::Piece.parse("
|
|
50
|
-
# Pnn::Piece.parse("
|
|
86
|
+
# Pnn::Piece.parse("k") # => #<Pnn::Piece type=:K side=:second state=:normal native=true>
|
|
87
|
+
# Pnn::Piece.parse("+R'") # => #<Pnn::Piece type=:R side=:first state=:enhanced native=false>
|
|
88
|
+
# Pnn::Piece.parse("-p") # => #<Pnn::Piece type=:P side=:second state=:diminished native=true>
|
|
51
89
|
def self.parse(pnn_string)
|
|
52
90
|
string_value = String(pnn_string)
|
|
53
|
-
matches = match_pattern(string_value)
|
|
54
91
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
92
|
+
# Check for derivation suffix
|
|
93
|
+
if string_value.end_with?(FOREIGN_SUFFIX)
|
|
94
|
+
pin_part = string_value[0...-1] # Remove the apostrophe
|
|
95
|
+
foreign = true
|
|
96
|
+
else
|
|
97
|
+
pin_part = string_value
|
|
98
|
+
foreign = false
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Validate and parse the PIN part using existing PIN logic
|
|
102
|
+
raise ::ArgumentError, format(ERROR_INVALID_PNN, string_value) unless Pin::Piece.valid?(pin_part)
|
|
103
|
+
|
|
104
|
+
pin_piece = Pin::Piece.parse(pin_part)
|
|
105
|
+
piece_native = !foreign
|
|
59
106
|
|
|
60
|
-
new(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
107
|
+
new(pin_piece.type, pin_piece.side, pin_piece.state, piece_native)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Check if a string is a valid PNN notation
|
|
111
|
+
#
|
|
112
|
+
# @param pnn_string [String] The string to validate
|
|
113
|
+
# @return [Boolean] true if valid PNN, false otherwise
|
|
114
|
+
#
|
|
115
|
+
# @example
|
|
116
|
+
# Sashite::Pnn::Piece.valid?("K") # => true
|
|
117
|
+
# Sashite::Pnn::Piece.valid?("+R'") # => true
|
|
118
|
+
# Sashite::Pnn::Piece.valid?("-p") # => true
|
|
119
|
+
# Sashite::Pnn::Piece.valid?("KK") # => false
|
|
120
|
+
# Sashite::Pnn::Piece.valid?("++K") # => false
|
|
121
|
+
def self.valid?(pnn_string)
|
|
122
|
+
return false unless pnn_string.is_a?(::String)
|
|
123
|
+
return false if pnn_string.empty?
|
|
124
|
+
|
|
125
|
+
# Check for derivation suffix
|
|
126
|
+
if pnn_string.end_with?(FOREIGN_SUFFIX)
|
|
127
|
+
pin_part = pnn_string[0...-1] # Remove the apostrophe
|
|
128
|
+
return false if pin_part.empty? # Can't have just an apostrophe
|
|
129
|
+
else
|
|
130
|
+
pin_part = pnn_string
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Validate the PIN part using existing PIN validation
|
|
134
|
+
Pin::Piece.valid?(pin_part)
|
|
66
135
|
end
|
|
67
136
|
|
|
68
137
|
# Convert the piece to its PNN string representation
|
|
69
138
|
#
|
|
70
139
|
# @return [String] PNN notation string
|
|
71
140
|
# @example
|
|
72
|
-
# piece.to_s # => "k"
|
|
73
|
-
# piece.to_s # => "k'"
|
|
74
141
|
# piece.to_s # => "+R'"
|
|
142
|
+
# piece.to_s # => "-p"
|
|
143
|
+
# piece.to_s # => "K"
|
|
75
144
|
def to_s
|
|
76
|
-
|
|
77
|
-
suffix = native? ? "" : FOREIGN_SUFFIX
|
|
78
|
-
"#{pin_string}#{suffix}"
|
|
145
|
+
"#{prefix}#{letter}#{suffix}"
|
|
79
146
|
end
|
|
80
147
|
|
|
81
|
-
#
|
|
148
|
+
# Get the letter representation (inherited from PIN logic)
|
|
82
149
|
#
|
|
83
|
-
# @return [String]
|
|
84
|
-
def
|
|
85
|
-
|
|
150
|
+
# @return [String] letter representation combining type and side
|
|
151
|
+
def letter
|
|
152
|
+
@piece.letter
|
|
86
153
|
end
|
|
87
154
|
|
|
88
|
-
#
|
|
155
|
+
# Get the prefix representation (inherited from PIN logic)
|
|
89
156
|
#
|
|
90
|
-
# @return [
|
|
91
|
-
def
|
|
92
|
-
|
|
157
|
+
# @return [String] prefix representing the state
|
|
158
|
+
def prefix
|
|
159
|
+
@piece.prefix
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Get the suffix representation
|
|
163
|
+
#
|
|
164
|
+
# @return [String] suffix representing the derivation
|
|
165
|
+
def suffix
|
|
166
|
+
native? ? NATIVE_SUFFIX : FOREIGN_SUFFIX
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Create a new piece with enhanced state
|
|
170
|
+
#
|
|
171
|
+
# @return [Piece] new piece instance with enhanced state
|
|
172
|
+
# @example
|
|
173
|
+
# piece.enhance # (:K, :first, :normal, true) => (:K, :first, :enhanced, true)
|
|
174
|
+
def enhance
|
|
175
|
+
return self if enhanced?
|
|
176
|
+
|
|
177
|
+
self.class.new(type, side, Pin::Piece::ENHANCED_STATE, native)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Create a new piece without enhanced state
|
|
181
|
+
#
|
|
182
|
+
# @return [Piece] new piece instance without enhanced state
|
|
183
|
+
# @example
|
|
184
|
+
# piece.unenhance # (:K, :first, :enhanced, true) => (:K, :first, :normal, true)
|
|
185
|
+
def unenhance
|
|
186
|
+
return self unless enhanced?
|
|
187
|
+
|
|
188
|
+
self.class.new(type, side, Pin::Piece::NORMAL_STATE, native)
|
|
93
189
|
end
|
|
94
190
|
|
|
95
|
-
# Create a new piece with
|
|
191
|
+
# Create a new piece with diminished state
|
|
192
|
+
#
|
|
193
|
+
# @return [Piece] new piece instance with diminished state
|
|
194
|
+
# @example
|
|
195
|
+
# piece.diminish # (:K, :first, :normal, true) => (:K, :first, :diminished, true)
|
|
196
|
+
def diminish
|
|
197
|
+
return self if diminished?
|
|
198
|
+
|
|
199
|
+
self.class.new(type, side, Pin::Piece::DIMINISHED_STATE, native)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Create a new piece without diminished state
|
|
203
|
+
#
|
|
204
|
+
# @return [Piece] new piece instance without diminished state
|
|
205
|
+
# @example
|
|
206
|
+
# piece.undiminish # (:K, :first, :diminished, true) => (:K, :first, :normal, true)
|
|
207
|
+
def undiminish
|
|
208
|
+
return self unless diminished?
|
|
209
|
+
|
|
210
|
+
self.class.new(type, side, Pin::Piece::NORMAL_STATE, native)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Create a new piece with normal state (no modifiers)
|
|
214
|
+
#
|
|
215
|
+
# @return [Piece] new piece instance with normal state
|
|
216
|
+
# @example
|
|
217
|
+
# piece.normalize # (:K, :first, :enhanced, true) => (:K, :first, :normal, true)
|
|
218
|
+
def normalize
|
|
219
|
+
return self if normal?
|
|
220
|
+
|
|
221
|
+
self.class.new(type, side, Pin::Piece::NORMAL_STATE, native)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Create a new piece with opposite side
|
|
225
|
+
#
|
|
226
|
+
# @return [Piece] new piece instance with opposite side
|
|
227
|
+
# @example
|
|
228
|
+
# piece.flip # (:K, :first, :normal, true) => (:K, :second, :normal, true)
|
|
229
|
+
def flip
|
|
230
|
+
self.class.new(type, opposite_side, state, native)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Create a new piece with foreign style (derivation marker)
|
|
234
|
+
#
|
|
235
|
+
# @return [Piece] new piece instance with foreign style
|
|
236
|
+
# @example
|
|
237
|
+
# piece.derive # (:K, :first, :normal, true) => (:K, :first, :normal, false)
|
|
238
|
+
def derive
|
|
239
|
+
return self if derived?
|
|
240
|
+
|
|
241
|
+
self.class.new(type, side, state, FOREIGN)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Create a new piece with native style (no derivation marker)
|
|
96
245
|
#
|
|
97
246
|
# @return [Piece] new piece instance with native style
|
|
98
247
|
# @example
|
|
99
|
-
# piece.
|
|
100
|
-
def
|
|
248
|
+
# piece.underive # (:K, :first, :normal, false) => (:K, :first, :normal, true)
|
|
249
|
+
def underive
|
|
101
250
|
return self if native?
|
|
102
251
|
|
|
103
|
-
self.class.new(
|
|
104
|
-
letter,
|
|
105
|
-
native: true,
|
|
106
|
-
enhanced: enhanced?,
|
|
107
|
-
diminished: diminished?
|
|
108
|
-
)
|
|
252
|
+
self.class.new(type, side, state, NATIVE)
|
|
109
253
|
end
|
|
110
254
|
|
|
111
|
-
# Create a new piece with
|
|
255
|
+
# Create a new piece with a different type (keeping same side, state, and derivation)
|
|
112
256
|
#
|
|
113
|
-
# @
|
|
257
|
+
# @param new_type [Symbol] new type (:A to :Z)
|
|
258
|
+
# @return [Piece] new piece instance with different type
|
|
114
259
|
# @example
|
|
115
|
-
# piece.
|
|
116
|
-
def
|
|
117
|
-
|
|
260
|
+
# piece.with_type(:Q) # (:K, :first, :normal, true) => (:Q, :first, :normal, true)
|
|
261
|
+
def with_type(new_type)
|
|
262
|
+
Pin::Piece.validate_type(new_type)
|
|
263
|
+
return self if type == new_type
|
|
118
264
|
|
|
119
|
-
self.class.new(
|
|
120
|
-
letter,
|
|
121
|
-
native: false,
|
|
122
|
-
enhanced: enhanced?,
|
|
123
|
-
diminished: diminished?
|
|
124
|
-
)
|
|
265
|
+
self.class.new(new_type, side, state, native)
|
|
125
266
|
end
|
|
126
267
|
|
|
127
|
-
# Create a new piece with
|
|
268
|
+
# Create a new piece with a different side (keeping same type, state, and derivation)
|
|
128
269
|
#
|
|
129
|
-
# @
|
|
270
|
+
# @param new_side [Symbol] :first or :second
|
|
271
|
+
# @return [Piece] new piece instance with different side
|
|
130
272
|
# @example
|
|
131
|
-
# piece.
|
|
132
|
-
def
|
|
133
|
-
|
|
273
|
+
# piece.with_side(:second) # (:K, :first, :normal, true) => (:K, :second, :normal, true)
|
|
274
|
+
def with_side(new_side)
|
|
275
|
+
Pin::Piece.validate_side(new_side)
|
|
276
|
+
return self if side == new_side
|
|
277
|
+
|
|
278
|
+
self.class.new(type, new_side, state, native)
|
|
134
279
|
end
|
|
135
280
|
|
|
136
|
-
#
|
|
281
|
+
# Create a new piece with a different state (keeping same type, side, and derivation)
|
|
282
|
+
#
|
|
283
|
+
# @param new_state [Symbol] :normal, :enhanced, or :diminished
|
|
284
|
+
# @return [Piece] new piece instance with different state
|
|
285
|
+
# @example
|
|
286
|
+
# piece.with_state(:enhanced) # (:K, :first, :normal, true) => (:K, :first, :enhanced, true)
|
|
287
|
+
def with_state(new_state)
|
|
288
|
+
Pin::Piece.validate_state(new_state)
|
|
289
|
+
return self if state == new_state
|
|
137
290
|
|
|
138
|
-
|
|
139
|
-
|
|
291
|
+
self.class.new(type, side, new_state, native)
|
|
292
|
+
end
|
|
140
293
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
294
|
+
# Create a new piece with a different derivation (keeping same type, side, and state)
|
|
295
|
+
#
|
|
296
|
+
# @param new_native [Boolean] true for native, false for foreign
|
|
297
|
+
# @return [Piece] new piece instance with different derivation
|
|
298
|
+
# @example
|
|
299
|
+
# piece.with_derivation(false) # (:K, :first, :normal, true) => (:K, :first, :normal, false)
|
|
300
|
+
def with_derivation(new_native)
|
|
301
|
+
self.class.validate_derivation(new_native)
|
|
302
|
+
return self if native == new_native
|
|
303
|
+
|
|
304
|
+
self.class.new(type, side, state, new_native)
|
|
147
305
|
end
|
|
148
306
|
|
|
149
|
-
|
|
150
|
-
|
|
307
|
+
# Check if the piece has enhanced state
|
|
308
|
+
#
|
|
309
|
+
# @return [Boolean] true if enhanced
|
|
310
|
+
def enhanced?
|
|
311
|
+
@piece.enhanced?
|
|
312
|
+
end
|
|
151
313
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
)
|
|
314
|
+
# Check if the piece has diminished state
|
|
315
|
+
#
|
|
316
|
+
# @return [Boolean] true if diminished
|
|
317
|
+
def diminished?
|
|
318
|
+
@piece.diminished?
|
|
158
319
|
end
|
|
159
320
|
|
|
160
|
-
|
|
161
|
-
|
|
321
|
+
# Check if the piece has normal state (no modifiers)
|
|
322
|
+
#
|
|
323
|
+
# @return [Boolean] true if no modifiers are present
|
|
324
|
+
def normal?
|
|
325
|
+
@piece.normal?
|
|
326
|
+
end
|
|
162
327
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
)
|
|
328
|
+
# Check if the piece belongs to the first player
|
|
329
|
+
#
|
|
330
|
+
# @return [Boolean] true if first player
|
|
331
|
+
def first_player?
|
|
332
|
+
@piece.first_player?
|
|
169
333
|
end
|
|
170
334
|
|
|
171
|
-
|
|
172
|
-
|
|
335
|
+
# Check if the piece belongs to the second player
|
|
336
|
+
#
|
|
337
|
+
# @return [Boolean] true if second player
|
|
338
|
+
def second_player?
|
|
339
|
+
@piece.second_player?
|
|
340
|
+
end
|
|
173
341
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
)
|
|
342
|
+
# Check if the piece has native style (no derivation marker)
|
|
343
|
+
#
|
|
344
|
+
# @return [Boolean] true if native style
|
|
345
|
+
def native?
|
|
346
|
+
native == NATIVE
|
|
180
347
|
end
|
|
181
348
|
|
|
182
|
-
|
|
183
|
-
|
|
349
|
+
# Check if the piece has foreign style (derivation marker)
|
|
350
|
+
#
|
|
351
|
+
# @return [Boolean] true if foreign style
|
|
352
|
+
def derived?
|
|
353
|
+
native == FOREIGN
|
|
354
|
+
end
|
|
184
355
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
356
|
+
# Alias for derived? to match the specification terminology
|
|
357
|
+
alias foreign? derived?
|
|
358
|
+
|
|
359
|
+
# Check if this piece is the same type as another (ignoring side, state, and derivation)
|
|
360
|
+
#
|
|
361
|
+
# @param other [Piece] piece to compare with
|
|
362
|
+
# @return [Boolean] true if same type
|
|
363
|
+
# @example
|
|
364
|
+
# king1.same_type?(king2) # (:K, :first, :normal, true) and (:K, :second, :enhanced, false) => true
|
|
365
|
+
def same_type?(other)
|
|
366
|
+
return false unless other.is_a?(self.class)
|
|
367
|
+
|
|
368
|
+
@piece.same_type?(other.instance_variable_get(:@piece))
|
|
189
369
|
end
|
|
190
370
|
|
|
191
|
-
|
|
192
|
-
|
|
371
|
+
# Check if this piece belongs to the same side as another
|
|
372
|
+
#
|
|
373
|
+
# @param other [Piece] piece to compare with
|
|
374
|
+
# @return [Boolean] true if same side
|
|
375
|
+
def same_side?(other)
|
|
376
|
+
return false unless other.is_a?(self.class)
|
|
377
|
+
|
|
378
|
+
@piece.same_side?(other.instance_variable_get(:@piece))
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# Check if this piece has the same state as another
|
|
382
|
+
#
|
|
383
|
+
# @param other [Piece] piece to compare with
|
|
384
|
+
# @return [Boolean] true if same state
|
|
385
|
+
def same_state?(other)
|
|
386
|
+
return false unless other.is_a?(self.class)
|
|
193
387
|
|
|
194
|
-
|
|
195
|
-
flipped_letter,
|
|
196
|
-
native: native?,
|
|
197
|
-
enhanced: enhanced?,
|
|
198
|
-
diminished: diminished?
|
|
199
|
-
)
|
|
388
|
+
@piece.same_state?(other.instance_variable_get(:@piece))
|
|
200
389
|
end
|
|
201
390
|
|
|
202
|
-
#
|
|
391
|
+
# Check if this piece has the same style derivation as another
|
|
392
|
+
#
|
|
393
|
+
# @param other [Piece] piece to compare with
|
|
394
|
+
# @return [Boolean] true if same style derivation
|
|
395
|
+
def same_style?(other)
|
|
396
|
+
return false unless other.is_a?(self.class)
|
|
397
|
+
|
|
398
|
+
native == other.native
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# Custom equality comparison
|
|
203
402
|
#
|
|
204
403
|
# @param other [Object] object to compare with
|
|
205
404
|
# @return [Boolean] true if pieces are equal
|
|
206
405
|
def ==(other)
|
|
207
406
|
return false unless other.is_a?(self.class)
|
|
208
407
|
|
|
209
|
-
|
|
408
|
+
@piece == other.instance_variable_get(:@piece) && native == other.native
|
|
210
409
|
end
|
|
211
410
|
|
|
212
|
-
#
|
|
411
|
+
# Alias for == to ensure Set functionality works correctly
|
|
412
|
+
alias eql? ==
|
|
413
|
+
|
|
414
|
+
# Custom hash implementation for use in collections
|
|
213
415
|
#
|
|
214
416
|
# @return [Integer] hash value
|
|
215
417
|
def hash
|
|
216
|
-
[
|
|
418
|
+
[self.class, @piece, native].hash
|
|
217
419
|
end
|
|
218
420
|
|
|
219
|
-
#
|
|
421
|
+
# Validate that the derivation is a valid boolean
|
|
220
422
|
#
|
|
221
|
-
# @param
|
|
222
|
-
# @
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
matches = PNN_PATTERN.match(string)
|
|
226
|
-
return matches if matches
|
|
423
|
+
# @param derivation [Boolean] the derivation to validate
|
|
424
|
+
# @raise [ArgumentError] if invalid
|
|
425
|
+
def self.validate_derivation(derivation)
|
|
426
|
+
return if VALID_DERIVATIONS.include?(derivation)
|
|
227
427
|
|
|
228
|
-
raise ::ArgumentError, format(
|
|
428
|
+
raise ::ArgumentError, format(ERROR_INVALID_DERIVATION, derivation.inspect)
|
|
229
429
|
end
|
|
230
430
|
|
|
231
|
-
|
|
431
|
+
private
|
|
432
|
+
|
|
433
|
+
# Get the opposite side of the current piece
|
|
434
|
+
#
|
|
435
|
+
# @return [Symbol] :first if current side is :second, :second if current side is :first
|
|
436
|
+
def opposite_side
|
|
437
|
+
@piece.send(:opposite_side)
|
|
438
|
+
end
|
|
232
439
|
end
|
|
233
440
|
end
|
|
234
441
|
end
|
data/lib/sashite/pnn.rb
CHANGED
|
@@ -5,43 +5,36 @@ require_relative "pnn/piece"
|
|
|
5
5
|
module Sashite
|
|
6
6
|
# PNN (Piece Name Notation) implementation for Ruby
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
# PNN
|
|
10
|
-
# cross-style game scenarios and piece origin tracking.
|
|
8
|
+
# Provides style-aware ASCII-based format for representing pieces in abstract strategy board games.
|
|
9
|
+
# PNN extends PIN by adding derivation markers that distinguish pieces by their style origin,
|
|
10
|
+
# enabling cross-style game scenarios and piece origin tracking.
|
|
11
11
|
#
|
|
12
|
-
# Format: <
|
|
13
|
-
# -
|
|
14
|
-
# -
|
|
12
|
+
# Format: [<state>]<letter>[<derivation>]
|
|
13
|
+
# - State modifier: "+" (enhanced), "-" (diminished), or none (normal)
|
|
14
|
+
# - Letter: A-Z (first player), a-z (second player)
|
|
15
|
+
# - Derivation marker: "'" (foreign style), or none (native style)
|
|
15
16
|
#
|
|
16
17
|
# Examples:
|
|
17
|
-
# "K" - First player king (native style)
|
|
18
|
-
# "
|
|
19
|
-
# "+R"
|
|
20
|
-
# "
|
|
21
|
-
# "-p'" - Second player pawn, diminished state (foreign style)
|
|
18
|
+
# "K" - First player king (native style, normal state)
|
|
19
|
+
# "k'" - Second player king (foreign style, normal state)
|
|
20
|
+
# "+R'" - First player rook (foreign style, enhanced state)
|
|
21
|
+
# "-p" - Second player pawn (native style, diminished state)
|
|
22
22
|
#
|
|
23
23
|
# See: https://sashite.dev/specs/pnn/1.0.0/
|
|
24
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
25
|
# Check if a string is a valid PNN notation
|
|
30
26
|
#
|
|
31
|
-
# @param
|
|
27
|
+
# @param pnn_string [String] The string to validate
|
|
32
28
|
# @return [Boolean] true if valid PNN, false otherwise
|
|
33
29
|
#
|
|
34
30
|
# @example
|
|
35
|
-
# Sashite::Pnn.valid?("K")
|
|
36
|
-
# Sashite::Pnn.valid?("
|
|
37
|
-
# Sashite::Pnn.valid?("
|
|
38
|
-
# Sashite::Pnn.valid?("
|
|
39
|
-
# Sashite::Pnn.valid?("K
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return false unless pnn.is_a?(::String)
|
|
43
|
-
|
|
44
|
-
pnn.match?(PNN_REGEX)
|
|
31
|
+
# Sashite::Pnn.valid?("K") # => true
|
|
32
|
+
# Sashite::Pnn.valid?("+R'") # => true
|
|
33
|
+
# Sashite::Pnn.valid?("-p") # => true
|
|
34
|
+
# Sashite::Pnn.valid?("KK") # => false
|
|
35
|
+
# Sashite::Pnn.valid?("++K") # => false
|
|
36
|
+
def self.valid?(pnn_string)
|
|
37
|
+
Piece.valid?(pnn_string)
|
|
45
38
|
end
|
|
46
39
|
|
|
47
40
|
# Parse a PNN string into a Piece object
|
|
@@ -50,11 +43,27 @@ module Sashite
|
|
|
50
43
|
# @return [Pnn::Piece] new piece instance
|
|
51
44
|
# @raise [ArgumentError] if the PNN string is invalid
|
|
52
45
|
# @example
|
|
53
|
-
# Sashite::Pnn.parse("K") # => #<Pnn::Piece
|
|
54
|
-
# Sashite::Pnn.parse("
|
|
55
|
-
# Sashite::Pnn.parse("
|
|
46
|
+
# Sashite::Pnn.parse("K") # => #<Pnn::Piece type=:K side=:first state=:normal native=true>
|
|
47
|
+
# Sashite::Pnn.parse("+R'") # => #<Pnn::Piece type=:R side=:first state=:enhanced native=false>
|
|
48
|
+
# Sashite::Pnn.parse("-p") # => #<Pnn::Piece type=:P side=:second state=:diminished native=true>
|
|
56
49
|
def self.parse(pnn_string)
|
|
57
50
|
Piece.parse(pnn_string)
|
|
58
51
|
end
|
|
52
|
+
|
|
53
|
+
# Create a new piece instance
|
|
54
|
+
#
|
|
55
|
+
# @param type [Symbol] piece type (:A to :Z)
|
|
56
|
+
# @param side [Symbol] player side (:first or :second)
|
|
57
|
+
# @param state [Symbol] piece state (:normal, :enhanced, or :diminished)
|
|
58
|
+
# @param native [Boolean] style derivation (true for native, false for foreign)
|
|
59
|
+
# @return [Pnn::Piece] new piece instance
|
|
60
|
+
# @raise [ArgumentError] if parameters are invalid
|
|
61
|
+
# @example
|
|
62
|
+
# Sashite::Pnn.piece(:K, :first, :normal, true) # => #<Pnn::Piece type=:K side=:first state=:normal native=true>
|
|
63
|
+
# Sashite::Pnn.piece(:R, :first, :enhanced, false) # => #<Pnn::Piece type=:R side=:first state=:enhanced native=false>
|
|
64
|
+
# Sashite::Pnn.piece(:P, :second, :diminished, true) # => #<Pnn::Piece type=:P side=:second state=:diminished native=true>
|
|
65
|
+
def self.piece(type, side, state = Sashite::Pin::Piece::NORMAL_STATE, native = Piece::NATIVE)
|
|
66
|
+
Piece.new(type, side, state, native)
|
|
67
|
+
end
|
|
59
68
|
end
|
|
60
69
|
end
|