sashite-pnn 2.0.0 → 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 +4 -4
- data/LICENSE.md +18 -17
- data/README.md +122 -428
- data/lib/sashite/pnn/name.rb +196 -0
- data/lib/sashite/pnn.rb +39 -42
- data/lib/sashite-pnn.rb +2 -2
- metadata +12 -27
- data/lib/sashite/pnn/piece.rb +0 -441
data/lib/sashite/pnn/piece.rb
DELETED
|
@@ -1,441 +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
|
-
# 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)
|
|
12
|
-
#
|
|
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
|
|
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
|
|
34
|
-
|
|
35
|
-
# Error messages
|
|
36
|
-
ERROR_INVALID_PNN = "Invalid PNN string: %s"
|
|
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
|
|
53
|
-
|
|
54
|
-
# @return [Boolean] the style derivation (true for native, false for foreign)
|
|
55
|
-
attr_reader :native
|
|
56
|
-
|
|
57
|
-
# Create a new piece instance
|
|
58
|
-
#
|
|
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)
|
|
63
|
-
# @raise [ArgumentError] if parameters are invalid
|
|
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)
|
|
75
|
-
@native = native
|
|
76
|
-
|
|
77
|
-
freeze
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# Parse a PNN string into a Piece object
|
|
81
|
-
#
|
|
82
|
-
# @param pnn_string [String] PNN notation string
|
|
83
|
-
# @return [Piece] new piece instance
|
|
84
|
-
# @raise [ArgumentError] if the PNN string is invalid
|
|
85
|
-
# @example
|
|
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>
|
|
89
|
-
def self.parse(pnn_string)
|
|
90
|
-
string_value = String(pnn_string)
|
|
91
|
-
|
|
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
|
|
106
|
-
|
|
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)
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
# Convert the piece to its PNN string representation
|
|
138
|
-
#
|
|
139
|
-
# @return [String] PNN notation string
|
|
140
|
-
# @example
|
|
141
|
-
# piece.to_s # => "+R'"
|
|
142
|
-
# piece.to_s # => "-p"
|
|
143
|
-
# piece.to_s # => "K"
|
|
144
|
-
def to_s
|
|
145
|
-
"#{prefix}#{letter}#{suffix}"
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
# Get the letter representation (inherited from PIN logic)
|
|
149
|
-
#
|
|
150
|
-
# @return [String] letter representation combining type and side
|
|
151
|
-
def letter
|
|
152
|
-
@piece.letter
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# Get the prefix representation (inherited from PIN logic)
|
|
156
|
-
#
|
|
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)
|
|
189
|
-
end
|
|
190
|
-
|
|
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)
|
|
245
|
-
#
|
|
246
|
-
# @return [Piece] new piece instance with native style
|
|
247
|
-
# @example
|
|
248
|
-
# piece.underive # (:K, :first, :normal, false) => (:K, :first, :normal, true)
|
|
249
|
-
def underive
|
|
250
|
-
return self if native?
|
|
251
|
-
|
|
252
|
-
self.class.new(type, side, state, NATIVE)
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
# Create a new piece with a different type (keeping same side, state, and derivation)
|
|
256
|
-
#
|
|
257
|
-
# @param new_type [Symbol] new type (:A to :Z)
|
|
258
|
-
# @return [Piece] new piece instance with different type
|
|
259
|
-
# @example
|
|
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
|
|
264
|
-
|
|
265
|
-
self.class.new(new_type, side, state, native)
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
# Create a new piece with a different side (keeping same type, state, and derivation)
|
|
269
|
-
#
|
|
270
|
-
# @param new_side [Symbol] :first or :second
|
|
271
|
-
# @return [Piece] new piece instance with different side
|
|
272
|
-
# @example
|
|
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)
|
|
279
|
-
end
|
|
280
|
-
|
|
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
|
|
290
|
-
|
|
291
|
-
self.class.new(type, side, new_state, native)
|
|
292
|
-
end
|
|
293
|
-
|
|
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)
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
# Check if the piece has enhanced state
|
|
308
|
-
#
|
|
309
|
-
# @return [Boolean] true if enhanced
|
|
310
|
-
def enhanced?
|
|
311
|
-
@piece.enhanced?
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
# Check if the piece has diminished state
|
|
315
|
-
#
|
|
316
|
-
# @return [Boolean] true if diminished
|
|
317
|
-
def diminished?
|
|
318
|
-
@piece.diminished?
|
|
319
|
-
end
|
|
320
|
-
|
|
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
|
|
327
|
-
|
|
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?
|
|
333
|
-
end
|
|
334
|
-
|
|
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
|
|
341
|
-
|
|
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
|
|
347
|
-
end
|
|
348
|
-
|
|
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
|
|
355
|
-
|
|
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))
|
|
369
|
-
end
|
|
370
|
-
|
|
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)
|
|
387
|
-
|
|
388
|
-
@piece.same_state?(other.instance_variable_get(:@piece))
|
|
389
|
-
end
|
|
390
|
-
|
|
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
|
|
402
|
-
#
|
|
403
|
-
# @param other [Object] object to compare with
|
|
404
|
-
# @return [Boolean] true if pieces are equal
|
|
405
|
-
def ==(other)
|
|
406
|
-
return false unless other.is_a?(self.class)
|
|
407
|
-
|
|
408
|
-
@piece == other.instance_variable_get(:@piece) && native == other.native
|
|
409
|
-
end
|
|
410
|
-
|
|
411
|
-
# Alias for == to ensure Set functionality works correctly
|
|
412
|
-
alias eql? ==
|
|
413
|
-
|
|
414
|
-
# Custom hash implementation for use in collections
|
|
415
|
-
#
|
|
416
|
-
# @return [Integer] hash value
|
|
417
|
-
def hash
|
|
418
|
-
[self.class, @piece, native].hash
|
|
419
|
-
end
|
|
420
|
-
|
|
421
|
-
# Validate that the derivation is a valid boolean
|
|
422
|
-
#
|
|
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)
|
|
427
|
-
|
|
428
|
-
raise ::ArgumentError, format(ERROR_INVALID_DERIVATION, derivation.inspect)
|
|
429
|
-
end
|
|
430
|
-
|
|
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
|
|
439
|
-
end
|
|
440
|
-
end
|
|
441
|
-
end
|