Class: TwistyPuzzles::Coordinate
- Inherits:
-
Object
- Object
- TwistyPuzzles::Coordinate
- Defined in:
- lib/twisty_puzzles/coordinate.rb
Overview
Coordinate of a sticker on the cube. rubocop:disable Metrics/ClassLength
Instance Attribute Summary collapse
-
#native ⇒ Object
readonly
Returns the value of attribute native.
Class Method Summary collapse
- .canonicalize(index, cube_size) ⇒ Object
-
.center(face, cube_size) ⇒ Object
rubocop:enable Metrics/AbcSize.
- .coordinate_range(cube_size) ⇒ Object
- .edges_outside(face, cube_size) ⇒ Object
- .face(face, cube_size) ⇒ Object
- .from_face_distances(face, cube_size, face_distances) ⇒ Object
- .from_indices(face, cube_size, x_index, y_index) ⇒ Object
- .highest_coordinate(cube_size) ⇒ Object
- .invert_coordinate(index, cube_size) ⇒ Object
-
.last_before_middle(cube_size) ⇒ Object
The last coordinate that is strictly before the middle.
- .layer(face, cube_size) ⇒ Object
- .match_coordinate_internal(base_coordinate, other_face_symbols) ⇒ Object
- .middle(cube_size) ⇒ Object
-
.middle_or_after(cube_size) ⇒ Object
Middle coordinate for uneven numbers, the one after for even numbers.
-
.middle_or_before(cube_size) ⇒ Object
Middle coordinate for uneven numbers, the one before for even numbers.
-
.solved_position(part, cube_size, incarnation_index) ⇒ Object
The coordinate of the solved position of the main sticker of this part.
-
.solved_positions(part, cube_size, incarnation_index) ⇒ Object
The coordinate of the solved position of all stickers of this part.
Instance Method Summary collapse
- #after_middle?(index) ⇒ Boolean
- #before_middle?(index) ⇒ Boolean
- #can_jump_to?(to_face) ⇒ Boolean
-
#close_neighbor_faces ⇒ Object
Returns neighbor faces that are closer to this coordinate than their opposite face.
- #coordinate(coordinate_index) ⇒ Object
- #coordinates ⇒ Object
- #cube_size ⇒ Object
- #distance_to(to_face) ⇒ Object
- #eql?(other) ⇒ Boolean (also: #==)
- #face ⇒ Object
- #hash ⇒ Object
-
#initialize(native) ⇒ Coordinate
constructor
A new instance of Coordinate.
- #jump_to_coordinates(new_coordinates) ⇒ Object
- #jump_to_neighbor(to_face) ⇒ Object
- #make_coordinate_at_edge_to(face) ⇒ Object
-
#rotate ⇒ Object
On a nxn grid with integer coordinates between 0 and n - 1, iterates between the 4 points that point (x, y) hits if you rotate by 90 degrees.
-
#rotations ⇒ Object
On a nxn grid with integer coordinates between 0 and n - 1, give the 4 points that point (x, y) hits if you do a full rotation of the face in clockwise order.
- #to_s ⇒ Object
- #x ⇒ Object
- #y ⇒ Object
Constructor Details
#initialize(native) ⇒ Coordinate
Returns a new instance of Coordinate.
170 171 172 173 174 |
# File 'lib/twisty_puzzles/coordinate.rb', line 170 def initialize(native) raise TypeError unless native.is_a?(Native::CubeCoordinate) @native = native end |
Instance Attribute Details
#native ⇒ Object (readonly)
Returns the value of attribute native.
180 181 182 |
# File 'lib/twisty_puzzles/coordinate.rb', line 180 def native @native end |
Class Method Details
.canonicalize(index, cube_size) ⇒ Object
44 45 46 47 48 |
# File 'lib/twisty_puzzles/coordinate.rb', line 44 def self.canonicalize(index, cube_size) raise ArgumentError unless index.is_a?(Integer) && -cube_size <= index && index < cube_size index >= 0 ? index : cube_size + index end |
.center(face, cube_size) ⇒ Object
rubocop:enable Metrics/AbcSize
120 121 122 123 |
# File 'lib/twisty_puzzles/coordinate.rb', line 120 def self.center(face, cube_size) m = middle(cube_size) from_indices(face, cube_size, m, m) end |
.coordinate_range(cube_size) ⇒ Object
19 20 21 |
# File 'lib/twisty_puzzles/coordinate.rb', line 19 def self.coordinate_range(cube_size) 0.upto(highest_coordinate(cube_size)) end |
.edges_outside(face, cube_size) ⇒ Object
142 143 144 145 146 147 148 |
# File 'lib/twisty_puzzles/coordinate.rb', line 142 def self.edges_outside(face, cube_size) face.neighbors.zip(face.neighbors.rotate(1)).flat_map do |neighbor, next_neighbor| 1.upto(cube_size - 2).map do |i| from_face_distances(neighbor, cube_size, face => 0, next_neighbor => i) end end end |
.face(face, cube_size) ⇒ Object
125 126 127 128 129 130 131 132 |
# File 'lib/twisty_puzzles/coordinate.rb', line 125 def self.face(face, cube_size) neighbor_a, neighbor_b = face.neighbors[0..1] coordinate_range(cube_size).flat_map do |x| coordinate_range(cube_size).map do |y| from_face_distances(face, cube_size, neighbor_a => x, neighbor_b => y) end end end |
.from_face_distances(face, cube_size, face_distances) ⇒ Object
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/twisty_puzzles/coordinate.rb', line 50 def self.from_face_distances(face, cube_size, face_distances) raise ArgumentError if face_distances.length != 2 coordinates = [nil, nil] face_distances.each do |neighbor, distance| index = face.coordinate_index_close_to(neighbor) coordinate = if neighbor.close_to_smaller_indices? distance else invert_coordinate(distance, cube_size) end raise ArgumentError if coordinates[index] coordinates[index] = coordinate end raise ArgumentError if coordinates.any?(&:nil?) from_indices(face, cube_size, *coordinates) end |
.from_indices(face, cube_size, x_index, y_index) ⇒ Object
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/twisty_puzzles/coordinate.rb', line 150 def self.from_indices(face, cube_size, x_index, y_index) raise TypeError, "Unsuitable face #{face.inspect}." unless face.is_a?(Face) raise TypeError unless cube_size.is_a?(Integer) raise ArgumentError unless cube_size.positive? x = Coordinate.canonicalize(x_index, cube_size) y = Coordinate.canonicalize(y_index, cube_size) native = Native::CubeCoordinate.new( cube_size, face.face_symbol, face.coordinate_index_base_face(0).face_symbol, face.coordinate_index_base_face(1).face_symbol, x, y ) new(native) end |
.highest_coordinate(cube_size) ⇒ Object
11 12 13 |
# File 'lib/twisty_puzzles/coordinate.rb', line 11 def self.highest_coordinate(cube_size) cube_size - 1 end |
.invert_coordinate(index, cube_size) ⇒ Object
15 16 17 |
# File 'lib/twisty_puzzles/coordinate.rb', line 15 def self.invert_coordinate(index, cube_size) highest_coordinate(cube_size) - index end |
.last_before_middle(cube_size) ⇒ Object
The last coordinate that is strictly before the middle
40 41 42 |
# File 'lib/twisty_puzzles/coordinate.rb', line 40 def self.last_before_middle(cube_size) (cube_size / 2) - 1 end |
.layer(face, cube_size) ⇒ Object
134 135 136 137 138 139 140 |
# File 'lib/twisty_puzzles/coordinate.rb', line 134 def self.layer(face, cube_size) face.neighbors.zip(face.neighbors.rotate(1)).flat_map do |neighbor, next_neighbor| coordinate_range(cube_size).map do |i| from_face_distances(neighbor, cube_size, face => 0, next_neighbor => i) end end + self.face(face, cube_size) end |
.match_coordinate_internal(base_coordinate, other_face_symbols) ⇒ Object
71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/twisty_puzzles/coordinate.rb', line 71 def self.match_coordinate_internal(base_coordinate, other_face_symbols) other_face_symbols.sort! coordinate = base_coordinate.rotations.find do |coord| face_symbols_closeby = coord.close_neighbor_faces.map(&:face_symbol) face_symbols_closeby.sort == other_face_symbols end raise "Couldn't find a fitting coordinate on the solved face." if coordinate.nil? coordinate end |
.middle(cube_size) ⇒ Object
23 24 25 26 27 |
# File 'lib/twisty_puzzles/coordinate.rb', line 23 def self.middle(cube_size) raise ArgumentError if cube_size.even? cube_size / 2 end |
.middle_or_after(cube_size) ⇒ Object
Middle coordinate for uneven numbers, the one after for even numbers
35 36 37 |
# File 'lib/twisty_puzzles/coordinate.rb', line 35 def self.middle_or_after(cube_size) cube_size / 2 end |
.middle_or_before(cube_size) ⇒ Object
Middle coordinate for uneven numbers, the one before for even numbers
30 31 32 |
# File 'lib/twisty_puzzles/coordinate.rb', line 30 def self.middle_or_before(cube_size) cube_size - (cube_size / 2) - 1 end |
.solved_position(part, cube_size, incarnation_index) ⇒ Object
The coordinate of the solved position of the main sticker of this part.
84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/twisty_puzzles/coordinate.rb', line 84 def self.solved_position(part, cube_size, incarnation_index) raise TypeError unless part.is_a?(Part) raise unless part.class::ELEMENTS.length == 24 raise unless incarnation_index >= 0 && incarnation_index < part.num_incarnations(cube_size) # This is a coordinate on the same face and belonging to an equivalent part. # But it might not be the right one. base_coordinate = Coordinate.from_indices( part.solved_face, cube_size, *part.base_index_on_face(cube_size, incarnation_index) ) other_face_symbols = part.corresponding_part.face_symbols[1..] match_coordinate_internal(base_coordinate, other_face_symbols) end |
.solved_positions(part, cube_size, incarnation_index) ⇒ Object
The coordinate of the solved position of all stickers of this part. rubocop:disable Metrics/AbcSize
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/twisty_puzzles/coordinate.rb', line 100 def self.solved_positions(part, cube_size, incarnation_index) solved_coordinate = solved_position(part, cube_size, incarnation_index) other_coordinates = part.face_symbols[1..].map.with_index do |f, i| face = Face.for_face_symbol(f) # The reverse is important for edge like parts. We are not in the same position as usual # solved pieces would be. # For other types of pieces, it doesn't make a difference as the base index will just be # a rotation of the original one, but we will anyway look at all rotations later. base_indices = part.base_index_on_other_face(face, cube_size, incarnation_index).reverse base_coordinate = Coordinate.from_indices(face, cube_size, *base_indices) other_face_symbols = [part.face_symbols[0]] + part.corresponding_part.face_symbols[1...i + 1] + part.corresponding_part.face_symbols[i + 2..] match_coordinate_internal(base_coordinate, other_face_symbols) end [solved_coordinate] + other_coordinates end |
Instance Method Details
#after_middle?(index) ⇒ Boolean
271 272 273 |
# File 'lib/twisty_puzzles/coordinate.rb', line 271 def after_middle?(index) Coordinate.canonicalize(index, cube_size) > Coordinate.middle_or_before(cube_size) end |
#before_middle?(index) ⇒ Boolean
275 276 277 |
# File 'lib/twisty_puzzles/coordinate.rb', line 275 def before_middle?(index) Coordinate.canonicalize(index, cube_size) <= Coordinate.last_before_middle(cube_size) end |
#can_jump_to?(to_face) ⇒ Boolean
229 230 231 232 233 234 235 236 237 |
# File 'lib/twisty_puzzles/coordinate.rb', line 229 def can_jump_to?(to_face) raise ArgumentError unless to_face.is_a?(Face) jump_coordinate_index = face.coordinate_index_close_to(to_face) jump_coordinate = coordinates[jump_coordinate_index] (jump_coordinate.zero? && to_face.close_to_smaller_indices?) || (jump_coordinate == Coordinate.highest_coordinate(cube_size) && !to_face.close_to_smaller_indices?) end |
#close_neighbor_faces ⇒ Object
Returns neighbor faces that are closer to this coordinate than their opposite face.
260 261 262 263 264 265 266 267 268 269 |
# File 'lib/twisty_puzzles/coordinate.rb', line 260 def close_neighbor_faces face.neighbors.select do |neighbor| coordinate = coordinates[face.coordinate_index_close_to(neighbor)] if neighbor.close_to_smaller_indices? before_middle?(coordinate) else after_middle?(coordinate) end end end |
#coordinate(coordinate_index) ⇒ Object
203 204 205 |
# File 'lib/twisty_puzzles/coordinate.rb', line 203 def coordinate(coordinate_index) native.coordinate(face.coordinate_index_base_face(coordinate_index).face_symbol) end |
#coordinates ⇒ Object
207 208 209 |
# File 'lib/twisty_puzzles/coordinate.rb', line 207 def coordinates @coordinates ||= [x, y].freeze end |
#cube_size ⇒ Object
199 200 201 |
# File 'lib/twisty_puzzles/coordinate.rb', line 199 def cube_size @cube_size ||= @native.cube_size end |
#distance_to(to_face) ⇒ Object
182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/twisty_puzzles/coordinate.rb', line 182 def distance_to(to_face) return 0 if face == to_face return cube_size if face == to_face.opposite index = face.coordinate_index_close_to(to_face) coordinate = coordinate(index) if to_face.close_to_smaller_indices? coordinate else Coordinate.invert_coordinate(coordinate, cube_size) end end |
#eql?(other) ⇒ Boolean Also known as: ==
219 220 221 |
# File 'lib/twisty_puzzles/coordinate.rb', line 219 def eql?(other) self.class.equal?(other.class) && @native == other.native end |
#face ⇒ Object
195 196 197 |
# File 'lib/twisty_puzzles/coordinate.rb', line 195 def face @face ||= Face.for_face_symbol(@native.face) end |
#hash ⇒ Object
225 226 227 |
# File 'lib/twisty_puzzles/coordinate.rb', line 225 def hash [self.class, @native].hash end |
#jump_to_coordinates(new_coordinates) ⇒ Object
251 252 253 |
# File 'lib/twisty_puzzles/coordinate.rb', line 251 def jump_to_coordinates(new_coordinates) Coordinate.from_indices(@face, @cube_size, *new_coordinates) end |
#jump_to_neighbor(to_face) ⇒ Object
239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/twisty_puzzles/coordinate.rb', line 239 def jump_to_neighbor(to_face) raise ArgumentError unless to_face.is_a?(Face) raise ArgumentError unless face.neighbors.include?(to_face) raise ArgumentError unless can_jump_to?(to_face) new_coordinates = coordinates.dup new_coordinate_index = to_face.coordinate_index_close_to(face) new_coordinate = make_coordinate_at_edge_to(face) new_coordinates.insert(new_coordinate_index, new_coordinate) Coordinate.from_indices(to_face, cube_size, *new_coordinates) end |
#make_coordinate_at_edge_to(face) ⇒ Object
255 256 257 |
# File 'lib/twisty_puzzles/coordinate.rb', line 255 def make_coordinate_at_edge_to(face) face.close_to_smaller_indices? ? 0 : Coordinate.highest_coordinate(cube_size) end |
#rotate ⇒ Object
On a nxn grid with integer coordinates between 0 and n - 1, iterates between the 4 points that point (x, y) hits if you rotate by 90 degrees.
281 282 283 |
# File 'lib/twisty_puzzles/coordinate.rb', line 281 def rotate jump_to_coordinates([y, Coordinate.invert_coordinate(x, cube_size)]) end |
#rotations ⇒ Object
On a nxn grid with integer coordinates between 0 and n - 1, give the 4 points that point (x, y) hits if you do a full rotation of the face in clockwise order.
287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/twisty_puzzles/coordinate.rb', line 287 def rotations rots = [] current = self 4.times do rots.push(current) current = current.rotate end raise unless current == self rots end |
#to_s ⇒ Object
176 177 178 |
# File 'lib/twisty_puzzles/coordinate.rb', line 176 def to_s "#{self.class}(#{face}, #{cube_size}, #{x}, #{y})" end |
#x ⇒ Object
211 212 213 |
# File 'lib/twisty_puzzles/coordinate.rb', line 211 def x @x ||= coordinate(0) end |
#y ⇒ Object
215 216 217 |
# File 'lib/twisty_puzzles/coordinate.rb', line 215 def y @y ||= coordinate(1) end |