Class: TwistyPuzzles::Coordinate

Inherits:
Object
  • Object
show all
Defined in:
lib/twisty_puzzles/coordinate.rb

Overview

Coordinate of a sticker on the cube. rubocop:disable Metrics/ClassLength

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(native) ⇒ Coordinate

Returns a new instance of Coordinate.

Raises:

  • (TypeError)


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

#nativeObject (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

Raises:

  • (ArgumentError)


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

Raises:

  • (ArgumentError)


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

Raises:

  • (TypeError)


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

Raises:

  • (ArgumentError)


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.

Raises:

  • (TypeError)


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

Returns:

  • (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

Returns:

  • (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

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)


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_facesObject

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

#coordinatesObject



207
208
209
# File 'lib/twisty_puzzles/coordinate.rb', line 207

def coordinates
  @coordinates ||= [x, y].freeze
end

#cube_sizeObject



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: ==

Returns:

  • (Boolean)


219
220
221
# File 'lib/twisty_puzzles/coordinate.rb', line 219

def eql?(other)
  self.class.equal?(other.class) && @native == other.native
end

#faceObject



195
196
197
# File 'lib/twisty_puzzles/coordinate.rb', line 195

def face
  @face ||= Face.for_face_symbol(@native.face)
end

#hashObject



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

Raises:

  • (ArgumentError)


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

#rotateObject

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

#rotationsObject

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_sObject



176
177
178
# File 'lib/twisty_puzzles/coordinate.rb', line 176

def to_s
  "#{self.class}(#{face}, #{cube_size}, #{x}, #{y})"
end

#xObject



211
212
213
# File 'lib/twisty_puzzles/coordinate.rb', line 211

def x
  @x ||= coordinate(0)
end

#yObject



215
216
217
# File 'lib/twisty_puzzles/coordinate.rb', line 215

def y
  @y ||= coordinate(1)
end