sashite-pan 2.0.0 → 4.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 +507 -171
- data/lib/sashite/pan/action/capture.rb +253 -0
- data/lib/sashite/pan/action/drop.rb +261 -0
- data/lib/sashite/pan/action/drop_capture.rb +261 -0
- data/lib/sashite/pan/action/modify.rb +221 -0
- data/lib/sashite/pan/action/move.rb +253 -0
- data/lib/sashite/pan/action/pass.rb +189 -0
- data/lib/sashite/pan/action/special.rb +255 -0
- data/lib/sashite/pan/action/static_capture.rb +207 -0
- data/lib/sashite/pan/action.rb +180 -0
- data/lib/sashite/pan.rb +30 -47
- data/lib/sashite-pan.rb +10 -3
- metadata +47 -10
- data/lib/sashite/pan/dumper/error.rb +0 -11
- data/lib/sashite/pan/dumper.rb +0 -81
- data/lib/sashite/pan/parser/error.rb +0 -11
- data/lib/sashite/pan/parser.rb +0 -90
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sashite/cell"
|
|
4
|
+
require "sashite/epin"
|
|
5
|
+
|
|
6
|
+
module Sashite
|
|
7
|
+
module Pan
|
|
8
|
+
module Action
|
|
9
|
+
# Drop capture action class
|
|
10
|
+
#
|
|
11
|
+
# Handles drop actions with capture - placing a piece from reserve
|
|
12
|
+
# onto an occupied square, capturing the piece there.
|
|
13
|
+
#
|
|
14
|
+
# Format: [<piece>].<destination>[=<piece>]
|
|
15
|
+
# Examples: "L.b4", ".c3", "P.e5=+P"
|
|
16
|
+
class DropCapture
|
|
17
|
+
# Action type
|
|
18
|
+
TYPE = :drop_capture
|
|
19
|
+
|
|
20
|
+
# Operator constant
|
|
21
|
+
OPERATOR = "."
|
|
22
|
+
|
|
23
|
+
# Transformation separator
|
|
24
|
+
TRANSFORMATION_SEPARATOR = "="
|
|
25
|
+
|
|
26
|
+
# Error messages
|
|
27
|
+
ERROR_INVALID_DROP_CAPTURE = "Invalid drop capture notation: %s"
|
|
28
|
+
ERROR_INVALID_DESTINATION = "Invalid destination coordinate: %s"
|
|
29
|
+
ERROR_INVALID_PIECE = "Invalid piece identifier: %s"
|
|
30
|
+
ERROR_INVALID_TRANSFORMATION = "Invalid transformation piece: %s"
|
|
31
|
+
|
|
32
|
+
# @return [String] destination CELL coordinate
|
|
33
|
+
attr_reader :destination
|
|
34
|
+
|
|
35
|
+
# @return [String, nil] optional EPIN piece identifier
|
|
36
|
+
attr_reader :piece
|
|
37
|
+
|
|
38
|
+
# @return [String, nil] optional EPIN transformation
|
|
39
|
+
attr_reader :transformation
|
|
40
|
+
|
|
41
|
+
# Check if a string represents a valid drop capture action
|
|
42
|
+
#
|
|
43
|
+
# @param pan_string [String] the string to validate
|
|
44
|
+
# @return [Boolean] true if valid drop capture notation
|
|
45
|
+
#
|
|
46
|
+
# @example
|
|
47
|
+
# DropCapture.valid?("L.b4") # => true
|
|
48
|
+
# DropCapture.valid?(".c3") # => true
|
|
49
|
+
# DropCapture.valid?("P.e5=+P") # => true
|
|
50
|
+
def self.valid?(pan_string)
|
|
51
|
+
return false unless pan_string.is_a?(::String)
|
|
52
|
+
return false unless pan_string.include?(OPERATOR)
|
|
53
|
+
|
|
54
|
+
parts = pan_string.split(OPERATOR, 2)
|
|
55
|
+
return false if parts.size != 2
|
|
56
|
+
|
|
57
|
+
piece_part = parts[0]
|
|
58
|
+
dest_and_transform = parts[1]
|
|
59
|
+
|
|
60
|
+
# Piece part is optional, but if present must be valid EPIN
|
|
61
|
+
return false if !piece_part.empty? && !::Sashite::Epin.valid?(piece_part)
|
|
62
|
+
|
|
63
|
+
# Check if there's a transformation
|
|
64
|
+
if dest_and_transform.include?(TRANSFORMATION_SEPARATOR)
|
|
65
|
+
dest_parts = dest_and_transform.split(TRANSFORMATION_SEPARATOR, 2)
|
|
66
|
+
return false if dest_parts.size != 2
|
|
67
|
+
|
|
68
|
+
destination_part = dest_parts[0]
|
|
69
|
+
transformation_part = dest_parts[1]
|
|
70
|
+
|
|
71
|
+
return false unless ::Sashite::Cell.valid?(destination_part)
|
|
72
|
+
return false unless ::Sashite::Epin.valid?(transformation_part)
|
|
73
|
+
else
|
|
74
|
+
return false unless ::Sashite::Cell.valid?(dest_and_transform)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
true
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Parse a drop capture notation string into a DropCapture instance
|
|
81
|
+
#
|
|
82
|
+
# @param pan_string [String] drop capture notation string
|
|
83
|
+
# @return [DropCapture] drop capture action instance
|
|
84
|
+
# @raise [ArgumentError] if the string is not valid drop capture notation
|
|
85
|
+
#
|
|
86
|
+
# @example
|
|
87
|
+
# DropCapture.parse("L.b4") # => #<DropCapture piece="L" destination="b4">
|
|
88
|
+
# DropCapture.parse(".c3") # => #<DropCapture destination="c3">
|
|
89
|
+
# DropCapture.parse("P.e5=+P") # => #<DropCapture piece="P" destination="e5" transformation="+P">
|
|
90
|
+
def self.parse(pan_string)
|
|
91
|
+
raise ::ArgumentError, format(ERROR_INVALID_DROP_CAPTURE, pan_string) unless valid?(pan_string)
|
|
92
|
+
|
|
93
|
+
parts = pan_string.split(OPERATOR, 2)
|
|
94
|
+
piece = parts[0].empty? ? nil : parts[0]
|
|
95
|
+
dest_and_transform = parts[1]
|
|
96
|
+
|
|
97
|
+
if dest_and_transform.include?(TRANSFORMATION_SEPARATOR)
|
|
98
|
+
dest_parts = dest_and_transform.split(TRANSFORMATION_SEPARATOR, 2)
|
|
99
|
+
destination = dest_parts[0]
|
|
100
|
+
transformation = dest_parts[1]
|
|
101
|
+
else
|
|
102
|
+
destination = dest_and_transform
|
|
103
|
+
transformation = nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
new(destination, piece: piece, transformation: transformation)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Create a new drop capture action instance
|
|
110
|
+
#
|
|
111
|
+
# @param destination [String] destination CELL coordinate
|
|
112
|
+
# @param piece [String, nil] optional EPIN piece identifier
|
|
113
|
+
# @param transformation [String, nil] optional EPIN transformation
|
|
114
|
+
# @raise [ArgumentError] if coordinates, piece, or transformation are invalid
|
|
115
|
+
#
|
|
116
|
+
# @example
|
|
117
|
+
# DropCapture.new("b4", piece: "L") # => #<DropCapture ...>
|
|
118
|
+
# DropCapture.new("c3") # => #<DropCapture ...>
|
|
119
|
+
# DropCapture.new("e5", piece: "P", transformation: "+P") # => #<DropCapture ...>
|
|
120
|
+
def initialize(destination, piece: nil, transformation: nil)
|
|
121
|
+
unless ::Sashite::Cell.valid?(destination)
|
|
122
|
+
raise ::ArgumentError, format(ERROR_INVALID_DESTINATION, destination)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
raise ::ArgumentError, format(ERROR_INVALID_PIECE, piece) if piece && !::Sashite::Epin.valid?(piece)
|
|
126
|
+
|
|
127
|
+
if transformation && !::Sashite::Epin.valid?(transformation)
|
|
128
|
+
raise ::ArgumentError, format(ERROR_INVALID_TRANSFORMATION, transformation)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
@destination = destination
|
|
132
|
+
@piece = piece
|
|
133
|
+
@transformation = transformation
|
|
134
|
+
|
|
135
|
+
freeze
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Get the action type
|
|
139
|
+
#
|
|
140
|
+
# @return [Symbol] :drop_capture
|
|
141
|
+
def type
|
|
142
|
+
TYPE
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Get the source coordinate
|
|
146
|
+
#
|
|
147
|
+
# @return [nil] drop capture actions have no source
|
|
148
|
+
def source
|
|
149
|
+
nil
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Convert the action to its PAN string representation
|
|
153
|
+
#
|
|
154
|
+
# @return [String] drop capture notation
|
|
155
|
+
#
|
|
156
|
+
# @example
|
|
157
|
+
# action.to_s # => "L.b4" or ".c3" or "P.e5=+P"
|
|
158
|
+
def to_s
|
|
159
|
+
result = +""
|
|
160
|
+
result << piece if piece
|
|
161
|
+
result << OPERATOR
|
|
162
|
+
result << destination
|
|
163
|
+
result << TRANSFORMATION_SEPARATOR << transformation if transformation
|
|
164
|
+
result
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Check if this is a pass action
|
|
168
|
+
#
|
|
169
|
+
# @return [Boolean] false
|
|
170
|
+
def pass?
|
|
171
|
+
false
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Check if this is a move action
|
|
175
|
+
#
|
|
176
|
+
# @return [Boolean] false
|
|
177
|
+
def move?
|
|
178
|
+
false
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Check if this is a capture action
|
|
182
|
+
#
|
|
183
|
+
# @return [Boolean] false
|
|
184
|
+
def capture?
|
|
185
|
+
false
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Check if this is a special action
|
|
189
|
+
#
|
|
190
|
+
# @return [Boolean] false
|
|
191
|
+
def special?
|
|
192
|
+
false
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Check if this is a static capture action
|
|
196
|
+
#
|
|
197
|
+
# @return [Boolean] false
|
|
198
|
+
def static_capture?
|
|
199
|
+
false
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Check if this is a drop action
|
|
203
|
+
#
|
|
204
|
+
# @return [Boolean] false
|
|
205
|
+
def drop?
|
|
206
|
+
false
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Check if this is a drop capture action
|
|
210
|
+
#
|
|
211
|
+
# @return [Boolean] true
|
|
212
|
+
def drop_capture?
|
|
213
|
+
true
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Check if this is a modify action
|
|
217
|
+
#
|
|
218
|
+
# @return [Boolean] false
|
|
219
|
+
def modify?
|
|
220
|
+
false
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Check if this is a movement action
|
|
224
|
+
#
|
|
225
|
+
# @return [Boolean] false
|
|
226
|
+
def movement?
|
|
227
|
+
false
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Check if this is a drop action (drop or drop_capture)
|
|
231
|
+
#
|
|
232
|
+
# @return [Boolean] true
|
|
233
|
+
def drop_action?
|
|
234
|
+
true
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Custom equality comparison
|
|
238
|
+
#
|
|
239
|
+
# @param other [Object] object to compare with
|
|
240
|
+
# @return [Boolean] true if actions are equal
|
|
241
|
+
def ==(other)
|
|
242
|
+
return false unless other.is_a?(self.class)
|
|
243
|
+
|
|
244
|
+
destination == other.destination &&
|
|
245
|
+
piece == other.piece &&
|
|
246
|
+
transformation == other.transformation
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Alias for == to ensure Set functionality works correctly
|
|
250
|
+
alias eql? ==
|
|
251
|
+
|
|
252
|
+
# Custom hash implementation for use in collections
|
|
253
|
+
#
|
|
254
|
+
# @return [Integer] hash value
|
|
255
|
+
def hash
|
|
256
|
+
[self.class, destination, piece, transformation].hash
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sashite/cell"
|
|
4
|
+
require "sashite/epin"
|
|
5
|
+
|
|
6
|
+
module Sashite
|
|
7
|
+
module Pan
|
|
8
|
+
module Action
|
|
9
|
+
# Modify action class
|
|
10
|
+
#
|
|
11
|
+
# Handles in-place transformation actions where a piece changes
|
|
12
|
+
# its attributes without moving.
|
|
13
|
+
#
|
|
14
|
+
# Format: <square>=<piece>
|
|
15
|
+
# Examples: "e4=+P", "c3=k'"
|
|
16
|
+
class Modify
|
|
17
|
+
# Action type
|
|
18
|
+
TYPE = :modify
|
|
19
|
+
|
|
20
|
+
# Operator constant
|
|
21
|
+
OPERATOR = "="
|
|
22
|
+
|
|
23
|
+
# Error messages
|
|
24
|
+
ERROR_INVALID_MODIFY = "Invalid modify notation: %s"
|
|
25
|
+
ERROR_INVALID_SQUARE = "Invalid square coordinate: %s"
|
|
26
|
+
ERROR_INVALID_PIECE = "Invalid piece identifier: %s"
|
|
27
|
+
|
|
28
|
+
# @return [String] destination CELL coordinate (square being modified)
|
|
29
|
+
attr_reader :destination
|
|
30
|
+
|
|
31
|
+
# @return [String] EPIN piece identifier (final state)
|
|
32
|
+
attr_reader :piece
|
|
33
|
+
|
|
34
|
+
# Check if a string represents a valid modify action
|
|
35
|
+
#
|
|
36
|
+
# @param pan_string [String] the string to validate
|
|
37
|
+
# @return [Boolean] true if valid modify notation
|
|
38
|
+
#
|
|
39
|
+
# @example
|
|
40
|
+
# Modify.valid?("e4=+P") # => true
|
|
41
|
+
# Modify.valid?("c3=k'") # => true
|
|
42
|
+
# Modify.valid?("e4") # => false
|
|
43
|
+
def self.valid?(pan_string)
|
|
44
|
+
return false unless pan_string.is_a?(::String)
|
|
45
|
+
return false unless pan_string.include?(OPERATOR)
|
|
46
|
+
|
|
47
|
+
parts = pan_string.split(OPERATOR, 2)
|
|
48
|
+
return false if parts.size != 2
|
|
49
|
+
|
|
50
|
+
square_part = parts[0]
|
|
51
|
+
piece_part = parts[1]
|
|
52
|
+
|
|
53
|
+
return false unless ::Sashite::Cell.valid?(square_part)
|
|
54
|
+
return false unless ::Sashite::Epin.valid?(piece_part)
|
|
55
|
+
|
|
56
|
+
true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Parse a modify notation string into a Modify instance
|
|
60
|
+
#
|
|
61
|
+
# @param pan_string [String] modify notation string
|
|
62
|
+
# @return [Modify] modify action instance
|
|
63
|
+
# @raise [ArgumentError] if the string is not valid modify notation
|
|
64
|
+
#
|
|
65
|
+
# @example
|
|
66
|
+
# Modify.parse("e4=+P") # => #<Modify destination="e4" piece="+P">
|
|
67
|
+
# Modify.parse("c3=k'") # => #<Modify destination="c3" piece="k'">
|
|
68
|
+
def self.parse(pan_string)
|
|
69
|
+
raise ::ArgumentError, format(ERROR_INVALID_MODIFY, pan_string) unless valid?(pan_string)
|
|
70
|
+
|
|
71
|
+
parts = pan_string.split(OPERATOR, 2)
|
|
72
|
+
square = parts[0]
|
|
73
|
+
piece = parts[1]
|
|
74
|
+
|
|
75
|
+
new(square, piece)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Create a new modify action instance
|
|
79
|
+
#
|
|
80
|
+
# @param square [String] CELL coordinate
|
|
81
|
+
# @param piece [String] EPIN piece identifier (final state)
|
|
82
|
+
# @raise [ArgumentError] if coordinate or piece is invalid
|
|
83
|
+
#
|
|
84
|
+
# @example
|
|
85
|
+
# Modify.new("e4", "+P") # => #<Modify ...>
|
|
86
|
+
# Modify.new("c3", "k'") # => #<Modify ...>
|
|
87
|
+
def initialize(square, piece)
|
|
88
|
+
raise ::ArgumentError, format(ERROR_INVALID_SQUARE, square) unless ::Sashite::Cell.valid?(square)
|
|
89
|
+
raise ::ArgumentError, format(ERROR_INVALID_PIECE, piece) unless ::Sashite::Epin.valid?(piece)
|
|
90
|
+
|
|
91
|
+
@destination = square
|
|
92
|
+
@piece = piece
|
|
93
|
+
|
|
94
|
+
freeze
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Get the action type
|
|
98
|
+
#
|
|
99
|
+
# @return [Symbol] :modify
|
|
100
|
+
def type
|
|
101
|
+
TYPE
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Get the source coordinate
|
|
105
|
+
#
|
|
106
|
+
# @return [nil] modify actions have no source
|
|
107
|
+
def source
|
|
108
|
+
nil
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Get the transformation piece
|
|
112
|
+
#
|
|
113
|
+
# @return [nil] modify actions have no separate transformation (piece represents final state)
|
|
114
|
+
def transformation
|
|
115
|
+
nil
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Convert the action to its PAN string representation
|
|
119
|
+
#
|
|
120
|
+
# @return [String] modify notation
|
|
121
|
+
#
|
|
122
|
+
# @example
|
|
123
|
+
# action.to_s # => "e4=+P" or "c3=k'"
|
|
124
|
+
def to_s
|
|
125
|
+
"#{destination}#{OPERATOR}#{piece}"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Check if this is a pass action
|
|
129
|
+
#
|
|
130
|
+
# @return [Boolean] false
|
|
131
|
+
def pass?
|
|
132
|
+
false
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Check if this is a move action
|
|
136
|
+
#
|
|
137
|
+
# @return [Boolean] false
|
|
138
|
+
def move?
|
|
139
|
+
false
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Check if this is a capture action
|
|
143
|
+
#
|
|
144
|
+
# @return [Boolean] false
|
|
145
|
+
def capture?
|
|
146
|
+
false
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Check if this is a special action
|
|
150
|
+
#
|
|
151
|
+
# @return [Boolean] false
|
|
152
|
+
def special?
|
|
153
|
+
false
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Check if this is a static capture action
|
|
157
|
+
#
|
|
158
|
+
# @return [Boolean] false
|
|
159
|
+
def static_capture?
|
|
160
|
+
false
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Check if this is a drop action
|
|
164
|
+
#
|
|
165
|
+
# @return [Boolean] false
|
|
166
|
+
def drop?
|
|
167
|
+
false
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Check if this is a drop capture action
|
|
171
|
+
#
|
|
172
|
+
# @return [Boolean] false
|
|
173
|
+
def drop_capture?
|
|
174
|
+
false
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Check if this is a modify action
|
|
178
|
+
#
|
|
179
|
+
# @return [Boolean] true
|
|
180
|
+
def modify?
|
|
181
|
+
true
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Check if this is a movement action
|
|
185
|
+
#
|
|
186
|
+
# @return [Boolean] false
|
|
187
|
+
def movement?
|
|
188
|
+
false
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Check if this is a drop action (drop or drop_capture)
|
|
192
|
+
#
|
|
193
|
+
# @return [Boolean] false
|
|
194
|
+
def drop_action?
|
|
195
|
+
false
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Custom equality comparison
|
|
199
|
+
#
|
|
200
|
+
# @param other [Object] object to compare with
|
|
201
|
+
# @return [Boolean] true if actions are equal
|
|
202
|
+
def ==(other)
|
|
203
|
+
return false unless other.is_a?(self.class)
|
|
204
|
+
|
|
205
|
+
destination == other.destination &&
|
|
206
|
+
piece == other.piece
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Alias for == to ensure Set functionality works correctly
|
|
210
|
+
alias eql? ==
|
|
211
|
+
|
|
212
|
+
# Custom hash implementation for use in collections
|
|
213
|
+
#
|
|
214
|
+
# @return [Integer] hash value
|
|
215
|
+
def hash
|
|
216
|
+
[self.class, destination, piece].hash
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|