Module: TwistyPuzzles::CancellationHelper

Includes:
CubeConstants
Defined in:
lib/twisty_puzzles/cancellation_helper.rb

Overview

Helper class to figure out information about the cancellation between two algs.

Constant Summary collapse

TRIVIAL_CENTER_TRANSFORMATION =
{ U: :U, F: :F, R: :R, L: :L, B: :B, D: :D }.freeze
CENTER_TRANSFORMATIONS =
begin
  x_transformation = { U: :B, F: :U, R: :R, L: :L, B: :D, D: :F }.freeze
  y_transformation = { U: :U, F: :L, R: :F, L: :B, B: :R, D: :D }.freeze
  z_transformation = { U: :R, F: :F, R: :D, L: :U, B: :B, D: :L }.freeze
  {
    U: create_directed_transformations(y_transformation, false),
    F: create_directed_transformations(z_transformation, false),
    R: create_directed_transformations(x_transformation, false),
    L: create_directed_transformations(x_transformation, true),
    B: create_directed_transformations(z_transformation, true),
    D: create_directed_transformations(y_transformation, true)
  }
end

Constants included from CubeConstants

TwistyPuzzles::CubeConstants::ALPHABET_SIZE, TwistyPuzzles::CubeConstants::CHIRALITY_FACE_SYMBOLS, TwistyPuzzles::CubeConstants::FACE_NAMES, TwistyPuzzles::CubeConstants::FACE_SYMBOLS, TwistyPuzzles::CubeConstants::OPPOSITE_FACE_SYMBOLS, TwistyPuzzles::CubeConstants::SKEWB_STICKERS

Class Method Summary collapse

Methods included from CubeConstants

#chirality_canonical_face_symbol, #opposite_face_symbol, #valid_chirality?

Methods included from Utils::ArrayHelper

#apply_permutation, #check_types, #find_only, #only, #replace_once, #rotate_out_nils, #turned_equals?

Class Method Details

.alg_plus_cancelled_move(algorithm, move, cube_size) ⇒ Object



135
136
137
138
139
140
141
142
143
# File 'lib/twisty_puzzles/cancellation_helper.rb', line 135

def self.alg_plus_cancelled_move(algorithm, move, cube_size)
  if move.is_a?(Rotation) && (tail_rotations = num_tail_rotations(algorithm)) >= 2
    Algorithm.new(algorithm.moves[0...-tail_rotations]) +
      cancelled_rotations(algorithm.moves[-tail_rotations..] + [move])
  else
    Algorithm.new(algorithm.moves[0...-1]) +
      algorithm.moves[-1].join_with_cancellation(move, cube_size)
  end
end

.apply_transformation_to!(transformation, face_state) ⇒ Object



57
58
59
# File 'lib/twisty_puzzles/cancellation_helper.rb', line 57

def self.apply_transformation_to!(transformation, face_state)
  face_state.map! { |f| transformation[f] }
end

.cancel(algorithm, cube_size) ⇒ Object

Cancel this algorithm as much as possilbe

Raises:

  • (TypeError)


42
43
44
45
46
47
48
49
50
51
# File 'lib/twisty_puzzles/cancellation_helper.rb', line 42

def self.cancel(algorithm, cube_size)
  raise TypeError unless algorithm.is_a?(Algorithm)

  CubeState.check_cube_size(cube_size)
  alg = Algorithm.empty
  algorithm.moves.each do |m|
    alg = push_with_cancellation(alg, m, cube_size)
  end
  alg
end

.cancel_variants(algorithm) ⇒ Object

Possible variations of the algorithm where the last move has been swapped as much as allowed (e.g. D U can swap).



28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/twisty_puzzles/cancellation_helper.rb', line 28

def self.cancel_variants(algorithm)
  variants = []
  algorithm.moves.each_index.reverse_each do |i|
    variant = swap_to_end(algorithm, i)
    break unless variant

    variants.push(variant)
  end
  raise if variants.empty?

  variants
end

.cancelled_rotations(rotations) ⇒ Object



120
121
122
123
# File 'lib/twisty_puzzles/cancellation_helper.rb', line 120

def self.cancelled_rotations(rotations)
  center_state = rotated_center_state(rotations)
  rotation_sequences[center_state]
end

.center_transformation(rotation) ⇒ Object



87
88
89
# File 'lib/twisty_puzzles/cancellation_helper.rb', line 87

def self.center_transformation(rotation)
  CENTER_TRANSFORMATIONS[rotation.axis_face.face_symbol][rotation.direction.value]
end

.combine_transformations(left, right) ⇒ Object



53
54
55
# File 'lib/twisty_puzzles/cancellation_helper.rb', line 53

def self.combine_transformations(left, right)
  left.dup.transform_values { |e| right[e] }.freeze
end

.combined_rotation_algsObject



97
98
99
100
101
102
103
104
105
# File 'lib/twisty_puzzles/cancellation_helper.rb', line 97

def self.combined_rotation_algs
  Rotation::NON_ZERO_ROTATIONS.flat_map do |left|
    second_rotations =
      Rotation::NON_ZERO_ROTATIONS.reject do |e|
        e.direction.double_move? || e.same_axis?(left)
      end
    second_rotations.map { |right| Algorithm.new([left, right]) }
  end
end

.create_directed_transformations(basic_transformation, invert) ⇒ Object



63
64
65
66
67
68
69
70
# File 'lib/twisty_puzzles/cancellation_helper.rb', line 63

def self.create_directed_transformations(basic_transformation, invert)
  twice = combine_transformations(basic_transformation, basic_transformation)
  thrice = combine_transformations(twice, basic_transformation)
  non_zero_transformations = [basic_transformation, twice, thrice]
  adjusted_non_zero_transformations =
    invert ? non_zero_transformations.reverse : non_zero_transformations
  [TRIVIAL_CENTER_TRANSFORMATION] + adjusted_non_zero_transformations
end

.num_tail_rotations(algorithm) ⇒ Object



125
126
127
128
129
130
131
132
133
# File 'lib/twisty_puzzles/cancellation_helper.rb', line 125

def self.num_tail_rotations(algorithm)
  num = 0
  algorithm.moves.reverse_each do |e|
    break unless e.is_a?(Rotation)

    num += 1
  end
  num
end

.push_with_cancellation(algorithm, move, cube_size) ⇒ Object

Raises:

  • (TypeError)


145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/twisty_puzzles/cancellation_helper.rb', line 145

def self.push_with_cancellation(algorithm, move, cube_size)
  raise TypeError unless move.is_a?(AbstractMove)
  return Algorithm.move(move) if algorithm.empty?

  cancel_variants =
    cancel_variants(algorithm).map do |alg|
      alg_plus_cancelled_move(alg, move, cube_size)
    end
  cancel_variants.min_by do |alg|
    # QTM is the most sensitive metric, so we use that as the highest priority for
    # cancellations.
    # We use HTM as a second priority to make sure something like RR still gets merged into
    # R2.
    # We use the length as tertiary priority to make sure rotations get cancelled even if they
    # don't change the move count.
    [alg.move_count(cube_size, :qtm), alg.move_count(cube_size, :htm), alg.length]
  end
end

.rotated_center_state(rotations) ⇒ Object



91
92
93
94
95
# File 'lib/twisty_puzzles/cancellation_helper.rb', line 91

def self.rotated_center_state(rotations)
  rotations.reduce(FACE_SYMBOLS.dup) do |center_state, rotation|
    apply_transformation_to!(center_transformation(rotation), center_state)
  end
end

.rotation_sequencesObject



107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/twisty_puzzles/cancellation_helper.rb', line 107

def self.rotation_sequences
  @rotation_sequences ||=
    begin
      trivial_rotation_algs = [Algorithm.empty]
      single_rotation_algs = Rotation::NON_ZERO_ROTATIONS.map { |e| Algorithm.move(e) }
      combined_rotation_algs = self.combined_rotation_algs
      rotation_algs = trivial_rotation_algs + single_rotation_algs + combined_rotation_algs
      rotation_algs.to_h do |alg|
        [rotated_center_state(alg.moves), alg]
      end.freeze
    end
end

.swap_to_end(algorithm, index) ⇒ Object



13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/twisty_puzzles/cancellation_helper.rb', line 13

def self.swap_to_end(algorithm, index)
  new_moves = algorithm.moves.dup
  index.upto(algorithm.length - 2) do |current_index|
    obstacle_index = current_index + 1
    current = new_moves[current_index]
    obstacle = new_moves[obstacle_index]
    return nil unless current.can_swap?(obstacle)

    new_moves[current_index], new_moves[obstacle_index] = current.swap(obstacle)
  end
  Algorithm.new(new_moves)
end