twisty_puzzles 0.0.1 → 0.0.2

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -1
  3. data/lib/twisty_puzzles.rb +37 -0
  4. data/lib/twisty_puzzles/abstract_direction.rb +38 -39
  5. data/lib/twisty_puzzles/abstract_move_parser.rb +32 -33
  6. data/lib/twisty_puzzles/algorithm.rb +112 -113
  7. data/lib/twisty_puzzles/algorithm_transformation.rb +19 -21
  8. data/lib/twisty_puzzles/axis_face_and_direction_move.rb +55 -56
  9. data/lib/twisty_puzzles/cancellation_helper.rb +124 -125
  10. data/lib/twisty_puzzles/commutator.rb +79 -80
  11. data/lib/twisty_puzzles/compiled_algorithm.rb +31 -32
  12. data/lib/twisty_puzzles/compiled_cube_algorithm.rb +49 -50
  13. data/lib/twisty_puzzles/compiled_skewb_algorithm.rb +18 -19
  14. data/lib/twisty_puzzles/coordinate.rb +245 -246
  15. data/lib/twisty_puzzles/cube.rb +494 -495
  16. data/lib/twisty_puzzles/cube_constants.rb +40 -41
  17. data/lib/twisty_puzzles/cube_direction.rb +15 -18
  18. data/lib/twisty_puzzles/cube_move.rb +289 -290
  19. data/lib/twisty_puzzles/cube_move_parser.rb +75 -76
  20. data/lib/twisty_puzzles/cube_print_helper.rb +132 -133
  21. data/lib/twisty_puzzles/cube_state.rb +80 -81
  22. data/lib/twisty_puzzles/move_type_creator.rb +17 -18
  23. data/lib/twisty_puzzles/parser.rb +176 -179
  24. data/lib/twisty_puzzles/part_cycle_factory.rb +39 -42
  25. data/lib/twisty_puzzles/puzzle.rb +16 -17
  26. data/lib/twisty_puzzles/reversible_applyable.rb +24 -25
  27. data/lib/twisty_puzzles/rotation.rb +74 -75
  28. data/lib/twisty_puzzles/skewb_direction.rb +14 -15
  29. data/lib/twisty_puzzles/skewb_move.rb +48 -49
  30. data/lib/twisty_puzzles/skewb_move_parser.rb +50 -51
  31. data/lib/twisty_puzzles/skewb_notation.rb +115 -118
  32. data/lib/twisty_puzzles/skewb_state.rb +120 -121
  33. data/lib/twisty_puzzles/state_helper.rb +20 -21
  34. data/lib/twisty_puzzles/sticker_cycle.rb +43 -44
  35. data/lib/twisty_puzzles/utils.rb +3 -0
  36. data/lib/twisty_puzzles/version.rb +3 -1
  37. metadata +3 -3
@@ -4,30 +4,28 @@ require 'twisty_puzzles/cube_direction'
4
4
  require 'twisty_puzzles/rotation'
5
5
 
6
6
  module TwistyPuzzles
7
-
8
- AlgorithmTransformation =
9
- Struct.new(:rotation, :mirror, :mirror_normal_face) do
10
- def transformed(algorithm)
11
- algorithm = algorithm.mirror(mirror_normal_face) if mirror
12
- algorithm.rotate_by(rotation)
13
- end
7
+ AlgorithmTransformation =
8
+ Struct.new(:rotation, :mirror, :mirror_normal_face) do
9
+ def transformed(algorithm)
10
+ algorithm = algorithm.mirror(mirror_normal_face) if mirror
11
+ algorithm.rotate_by(rotation)
12
+ end
14
13
 
15
- def identity?
16
- rotation.identity? && !mirror
17
- end
14
+ def identity?
15
+ rotation.identity? && !mirror
16
+ end
18
17
 
19
- # Returns algorithm transformations that mirror an algorithm and rotate it around a face.
20
- def self.around_face(face)
21
- around_face_rotations = CubeDirection::ALL_DIRECTIONS.map { |d| Rotation.new(face, d) }
22
- mirror_normal_face = face.neighbors.first
23
- around_face_rotations.product([true, false]).map do |r, m|
24
- AlgorithmTransformation.new(r, m, mirror_normal_face)
25
- end
18
+ # Returns algorithm transformations that mirror an algorithm and rotate it around a face.
19
+ def self.around_face(face)
20
+ around_face_rotations = CubeDirection::ALL_DIRECTIONS.map { |d| Rotation.new(face, d) }
21
+ mirror_normal_face = face.neighbors.first
22
+ around_face_rotations.product([true, false]).map do |r, m|
23
+ AlgorithmTransformation.new(r, m, mirror_normal_face)
26
24
  end
25
+ end
27
26
 
28
- def self.around_face_without_identity(face)
29
- around_face(face).reject(&:identity?)
30
- end
27
+ def self.around_face_without_identity(face)
28
+ around_face(face).reject(&:identity?)
31
29
  end
32
- end
30
+ end
33
31
  end
@@ -4,75 +4,74 @@ require 'twisty_puzzles/abstract_move'
4
4
  require 'twisty_puzzles/cube_direction'
5
5
 
6
6
  module TwistyPuzzles
7
-
8
- # Intermediate base class for all types of moves that have an axis face and a direction,
9
- # i.e. cube moves and rotations.
10
- class AxisFaceAndDirectionMove < AbstractMove
11
- def initialize(axis_face, direction)
12
- raise TypeError, "Unsuitable axis face #{axis_face}." unless axis_face.is_a?(Face)
13
- raise TypeError unless direction.is_a?(CubeDirection)
7
+ # Intermediate base class for all types of moves that have an axis face and a direction,
8
+ # i.e. cube moves and rotations.
9
+ class AxisFaceAndDirectionMove < AbstractMove
10
+ def initialize(axis_face, direction)
11
+ raise TypeError, "Unsuitable axis face #{axis_face}." unless axis_face.is_a?(Face)
12
+ raise TypeError unless direction.is_a?(CubeDirection)
14
13
 
15
- @axis_face = axis_face
16
- @direction = direction
17
- end
14
+ @axis_face = axis_face
15
+ @direction = direction
16
+ end
18
17
 
19
- attr_reader :direction, :axis_face
18
+ attr_reader :direction, :axis_face
20
19
 
21
- def translated_direction(other_axis_face)
22
- case @axis_face
23
- when other_axis_face then @direction
24
- when other_axis_face.opposite then @direction.inverse
25
- else raise ArgumentError
26
- end
20
+ def translated_direction(other_axis_face)
21
+ case @axis_face
22
+ when other_axis_face then @direction
23
+ when other_axis_face.opposite then @direction.inverse
24
+ else raise ArgumentError
27
25
  end
26
+ end
28
27
 
29
- def same_axis?(other)
30
- @axis_face.same_axis?(other.axis_face)
31
- end
28
+ def same_axis?(other)
29
+ @axis_face.same_axis?(other.axis_face)
30
+ end
32
31
 
33
- def identifying_fields
34
- [@axis_face, @direction]
35
- end
32
+ def identifying_fields
33
+ [@axis_face, @direction]
34
+ end
36
35
 
37
- def canonical_direction
38
- @axis_face.canonical_axis_face? ? @direction : @direction.inverse
39
- end
36
+ def canonical_direction
37
+ @axis_face.canonical_axis_face? ? @direction : @direction.inverse
38
+ end
40
39
 
41
- def can_swap?(other)
42
- super || same_axis?(other)
43
- end
40
+ def can_swap?(other)
41
+ super || same_axis?(other)
42
+ end
44
43
 
45
- def swap_internal(other)
46
- if same_axis?(other)
47
- [other, self]
48
- else
49
- super
50
- end
44
+ def swap_internal(other)
45
+ if same_axis?(other)
46
+ [other, self]
47
+ else
48
+ super
51
49
  end
50
+ end
52
51
 
53
- def rotate_by(rotation)
54
- if same_axis?(rotation)
55
- self
56
- else
57
- rotation_neighbors = rotation.axis_face.neighbors
58
- face_index = rotation_neighbors.index(@axis_face) || raise
59
- new_axis_face =
60
- rotation_neighbors[(face_index + rotation.direction.value) % rotation_neighbors.length]
61
- fields = replace_once(identifying_fields, @axis_face, new_axis_face)
62
- self.class.new(*fields)
63
- end
52
+ def rotate_by(rotation)
53
+ if same_axis?(rotation)
54
+ self
55
+ else
56
+ rotation_neighbors = rotation.axis_face.neighbors
57
+ face_index = rotation_neighbors.index(@axis_face) || raise
58
+ new_axis_face =
59
+ rotation_neighbors[(face_index + rotation.direction.value) % rotation_neighbors.length]
60
+ fields = replace_once(identifying_fields, @axis_face, new_axis_face)
61
+ self.class.new(*fields)
64
62
  end
63
+ end
65
64
 
66
- def mirror(normal_face)
67
- if normal_face.same_axis?(@axis_face)
68
- fields = replace_once(
69
- replace_once(identifying_fields, @direction, @direction.inverse),
70
- @axis_face, @axis_face.opposite
71
- )
72
- self.class.new(*fields)
73
- else
74
- inverse
75
- end
65
+ def mirror(normal_face)
66
+ if normal_face.same_axis?(@axis_face)
67
+ fields = replace_once(
68
+ replace_once(identifying_fields, @direction, @direction.inverse),
69
+ @axis_face, @axis_face.opposite
70
+ )
71
+ self.class.new(*fields)
72
+ else
73
+ inverse
76
74
  end
77
75
  end
76
+ end
78
77
  end
@@ -6,160 +6,159 @@ require 'twisty_puzzles/cube_constants'
6
6
  require 'twisty_puzzles/cube_state'
7
7
 
8
8
  module TwistyPuzzles
9
-
10
- # Helper class to figure out information about the cancellation between two algs.
11
- module CancellationHelper
12
- include CubeConstants
13
-
14
- def self.swap_to_end(algorithm, index)
15
- new_moves = algorithm.moves.dup
16
- index.upto(algorithm.length - 2) do |current_index|
17
- obstacle_index = current_index + 1
18
- current = new_moves[current_index]
19
- obstacle = new_moves[obstacle_index]
20
- return nil unless current.can_swap?(obstacle)
21
-
22
- new_moves[current_index], new_moves[obstacle_index] = current.swap(obstacle)
23
- end
24
- Algorithm.new(new_moves)
9
+ # Helper class to figure out information about the cancellation between two algs.
10
+ module CancellationHelper
11
+ include CubeConstants
12
+
13
+ def self.swap_to_end(algorithm, index)
14
+ new_moves = algorithm.moves.dup
15
+ index.upto(algorithm.length - 2) do |current_index|
16
+ obstacle_index = current_index + 1
17
+ current = new_moves[current_index]
18
+ obstacle = new_moves[obstacle_index]
19
+ return nil unless current.can_swap?(obstacle)
20
+
21
+ new_moves[current_index], new_moves[obstacle_index] = current.swap(obstacle)
25
22
  end
23
+ Algorithm.new(new_moves)
24
+ end
26
25
 
27
- # Possible variations of the algorithm where the last move has been swapped as much as allowed
28
- # (e.g. D U can swap).
29
- def self.cancel_variants(algorithm)
30
- variants = []
31
- algorithm.moves.each_index.reverse_each do |i|
32
- variant = swap_to_end(algorithm, i)
33
- break unless variant
34
-
35
- variants.push(variant)
36
- end
37
- raise if variants.empty?
26
+ # Possible variations of the algorithm where the last move has been swapped as much as allowed
27
+ # (e.g. D U can swap).
28
+ def self.cancel_variants(algorithm)
29
+ variants = []
30
+ algorithm.moves.each_index.reverse_each do |i|
31
+ variant = swap_to_end(algorithm, i)
32
+ break unless variant
38
33
 
39
- variants
34
+ variants.push(variant)
40
35
  end
36
+ raise if variants.empty?
41
37
 
42
- # Cancel this algorithm as much as possilbe
43
- def self.cancel(algorithm, cube_size)
44
- raise TypeError unless algorithm.is_a?(Algorithm)
38
+ variants
39
+ end
45
40
 
46
- CubeState.check_cube_size(cube_size)
47
- alg = Algorithm::EMPTY
48
- algorithm.moves.each do |m|
49
- alg = push_with_cancellation(alg, m, cube_size)
50
- end
51
- alg
52
- end
41
+ # Cancel this algorithm as much as possilbe
42
+ def self.cancel(algorithm, cube_size)
43
+ raise TypeError unless algorithm.is_a?(Algorithm)
53
44
 
54
- def self.combine_transformations(left, right)
55
- left.dup.transform_values { |e| right[e] }.freeze
45
+ CubeState.check_cube_size(cube_size)
46
+ alg = Algorithm::EMPTY
47
+ algorithm.moves.each do |m|
48
+ alg = push_with_cancellation(alg, m, cube_size)
56
49
  end
50
+ alg
51
+ end
57
52
 
58
- def self.apply_transformation_to!(transformation, face_state)
59
- face_state.map! { |f| transformation[f] }
60
- end
53
+ def self.combine_transformations(left, right)
54
+ left.dup.transform_values { |e| right[e] }.freeze
55
+ end
61
56
 
62
- TRIVIAL_CENTER_TRANSFORMATION = { U: :U, F: :F, R: :R, L: :L, B: :B, D: :D }.freeze
57
+ def self.apply_transformation_to!(transformation, face_state)
58
+ face_state.map! { |f| transformation[f] }
59
+ end
63
60
 
64
- def self.create_directed_transformations(basic_transformation, invert)
65
- twice = combine_transformations(basic_transformation, basic_transformation)
66
- thrice = combine_transformations(twice, basic_transformation)
67
- non_zero_transformations = [basic_transformation, twice, thrice]
68
- adjusted_non_zero_transformations =
69
- invert ? non_zero_transformations.reverse : non_zero_transformations
70
- [TRIVIAL_CENTER_TRANSFORMATION] + adjusted_non_zero_transformations
71
- end
61
+ TRIVIAL_CENTER_TRANSFORMATION = { U: :U, F: :F, R: :R, L: :L, B: :B, D: :D }.freeze
72
62
 
73
- CENTER_TRANSFORMATIONS =
74
- begin
75
- x_transformation = { U: :B, F: :U, R: :R, L: :L, B: :D, D: :F }.freeze
76
- y_transformation = { U: :U, F: :L, R: :F, L: :B, B: :R, D: :D }.freeze
77
- z_transformation = { U: :R, F: :F, R: :D, L: :U, B: :B, D: :L }.freeze
78
- {
79
- U: create_directed_transformations(y_transformation, false),
80
- F: create_directed_transformations(z_transformation, false),
81
- R: create_directed_transformations(x_transformation, false),
82
- L: create_directed_transformations(x_transformation, true),
83
- B: create_directed_transformations(z_transformation, true),
84
- D: create_directed_transformations(y_transformation, true)
85
- }
86
- end
63
+ def self.create_directed_transformations(basic_transformation, invert)
64
+ twice = combine_transformations(basic_transformation, basic_transformation)
65
+ thrice = combine_transformations(twice, basic_transformation)
66
+ non_zero_transformations = [basic_transformation, twice, thrice]
67
+ adjusted_non_zero_transformations =
68
+ invert ? non_zero_transformations.reverse : non_zero_transformations
69
+ [TRIVIAL_CENTER_TRANSFORMATION] + adjusted_non_zero_transformations
70
+ end
87
71
 
88
- def self.center_transformation(rotation)
89
- CENTER_TRANSFORMATIONS[rotation.axis_face.face_symbol][rotation.direction.value]
72
+ CENTER_TRANSFORMATIONS =
73
+ begin
74
+ x_transformation = { U: :B, F: :U, R: :R, L: :L, B: :D, D: :F }.freeze
75
+ y_transformation = { U: :U, F: :L, R: :F, L: :B, B: :R, D: :D }.freeze
76
+ z_transformation = { U: :R, F: :F, R: :D, L: :U, B: :B, D: :L }.freeze
77
+ {
78
+ U: create_directed_transformations(y_transformation, false),
79
+ F: create_directed_transformations(z_transformation, false),
80
+ R: create_directed_transformations(x_transformation, false),
81
+ L: create_directed_transformations(x_transformation, true),
82
+ B: create_directed_transformations(z_transformation, true),
83
+ D: create_directed_transformations(y_transformation, true)
84
+ }
90
85
  end
91
86
 
92
- def self.rotated_center_state(rotations)
93
- rotations.reduce(FACE_SYMBOLS.dup) do |center_state, rotation|
94
- apply_transformation_to!(center_transformation(rotation), center_state)
95
- end
96
- end
87
+ def self.center_transformation(rotation)
88
+ CENTER_TRANSFORMATIONS[rotation.axis_face.face_symbol][rotation.direction.value]
89
+ end
97
90
 
98
- def self.combined_rotation_algs
99
- Rotation::NON_ZERO_ROTATIONS.collect_concat do |left|
100
- second_rotations =
101
- Rotation::NON_ZERO_ROTATIONS.reject do |e|
102
- e.direction.double_move? || e.same_axis?(left)
103
- end
104
- second_rotations.map { |right| Algorithm.new([left, right]) }
105
- end
91
+ def self.rotated_center_state(rotations)
92
+ rotations.reduce(FACE_SYMBOLS.dup) do |center_state, rotation|
93
+ apply_transformation_to!(center_transformation(rotation), center_state)
106
94
  end
95
+ end
107
96
 
108
- def self.rotation_sequences
109
- @rotation_sequences ||=
110
- begin
111
- trivial_rotation_algs = [Algorithm::EMPTY]
112
- single_rotation_algs = Rotation::NON_ZERO_ROTATIONS.map { |e| Algorithm.move(e) }
113
- combined_rotation_algs = self.combined_rotation_algs
114
- rotation_algs = trivial_rotation_algs + single_rotation_algs + combined_rotation_algs
115
- rotation_algs.map do |alg|
116
- [rotated_center_state(alg.moves), alg]
117
- end.to_h.freeze
97
+ def self.combined_rotation_algs
98
+ Rotation::NON_ZERO_ROTATIONS.collect_concat do |left|
99
+ second_rotations =
100
+ Rotation::NON_ZERO_ROTATIONS.reject do |e|
101
+ e.direction.double_move? || e.same_axis?(left)
118
102
  end
103
+ second_rotations.map { |right| Algorithm.new([left, right]) }
119
104
  end
105
+ end
120
106
 
121
- def self.cancelled_rotations(rotations)
122
- center_state = rotated_center_state(rotations)
123
- rotation_sequences[center_state]
124
- end
107
+ def self.rotation_sequences
108
+ @rotation_sequences ||=
109
+ begin
110
+ trivial_rotation_algs = [Algorithm::EMPTY]
111
+ single_rotation_algs = Rotation::NON_ZERO_ROTATIONS.map { |e| Algorithm.move(e) }
112
+ combined_rotation_algs = self.combined_rotation_algs
113
+ rotation_algs = trivial_rotation_algs + single_rotation_algs + combined_rotation_algs
114
+ rotation_algs.map do |alg|
115
+ [rotated_center_state(alg.moves), alg]
116
+ end.to_h.freeze
117
+ end
118
+ end
119
+
120
+ def self.cancelled_rotations(rotations)
121
+ center_state = rotated_center_state(rotations)
122
+ rotation_sequences[center_state]
123
+ end
125
124
 
126
- def self.num_tail_rotations(algorithm)
127
- num = 0
128
- algorithm.moves.reverse_each do |e|
129
- break unless e.is_a?(Rotation)
125
+ def self.num_tail_rotations(algorithm)
126
+ num = 0
127
+ algorithm.moves.reverse_each do |e|
128
+ break unless e.is_a?(Rotation)
130
129
 
131
- num += 1
132
- end
133
- num
130
+ num += 1
134
131
  end
132
+ num
133
+ end
135
134
 
136
- def self.alg_plus_cancelled_move(algorithm, move, cube_size)
137
- if move.is_a?(Rotation) && (tail_rotations = num_tail_rotations(algorithm)) >= 2
138
- Algorithm.new(algorithm.moves[0...-tail_rotations]) +
139
- cancelled_rotations(algorithm.moves[-tail_rotations..-1] + [move])
140
- else
141
- Algorithm.new(algorithm.moves[0...-1]) +
142
- algorithm.moves[-1].join_with_cancellation(move, cube_size)
143
- end
135
+ def self.alg_plus_cancelled_move(algorithm, move, cube_size)
136
+ if move.is_a?(Rotation) && (tail_rotations = num_tail_rotations(algorithm)) >= 2
137
+ Algorithm.new(algorithm.moves[0...-tail_rotations]) +
138
+ cancelled_rotations(algorithm.moves[-tail_rotations..-1] + [move])
139
+ else
140
+ Algorithm.new(algorithm.moves[0...-1]) +
141
+ algorithm.moves[-1].join_with_cancellation(move, cube_size)
144
142
  end
143
+ end
145
144
 
146
- def self.push_with_cancellation(algorithm, move, cube_size)
147
- raise TypeError unless move.is_a?(AbstractMove)
148
- return Algorithm.move(move) if algorithm.empty?
145
+ def self.push_with_cancellation(algorithm, move, cube_size)
146
+ raise TypeError unless move.is_a?(AbstractMove)
147
+ return Algorithm.move(move) if algorithm.empty?
149
148
 
150
- cancel_variants =
151
- cancel_variants(algorithm).map do |alg|
152
- alg_plus_cancelled_move(alg, move, cube_size)
153
- end
154
- cancel_variants.min_by do |alg|
155
- # QTM is the most sensitive metric, so we use that as the highest priority for
156
- # cancellations.
157
- # We use HTM as a second priority to make sure something like RR still gets merged into
158
- # R2.
159
- # We use the length as tertiary priority to make sure rotations get cancelled even if they
160
- # don't change the move count.
161
- [alg.move_count(cube_size, :qtm), alg.move_count(cube_size, :htm), alg.length]
149
+ cancel_variants =
150
+ cancel_variants(algorithm).map do |alg|
151
+ alg_plus_cancelled_move(alg, move, cube_size)
162
152
  end
153
+ cancel_variants.min_by do |alg|
154
+ # QTM is the most sensitive metric, so we use that as the highest priority for
155
+ # cancellations.
156
+ # We use HTM as a second priority to make sure something like RR still gets merged into
157
+ # R2.
158
+ # We use the length as tertiary priority to make sure rotations get cancelled even if they
159
+ # don't change the move count.
160
+ [alg.move_count(cube_size, :qtm), alg.move_count(cube_size, :htm), alg.length]
163
161
  end
164
162
  end
163
+ end
165
164
  end