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