twisty_puzzles 0.0.1 → 0.0.6
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/CHANGELOG.md +21 -1
- data/README.md +6 -1
- data/ext/twisty_puzzles/native/cube_algorithm.c +267 -0
- data/ext/twisty_puzzles/native/cube_algorithm.h +5 -0
- data/ext/twisty_puzzles/native/cube_average.c +184 -0
- data/ext/twisty_puzzles/native/cube_average.h +5 -0
- data/ext/twisty_puzzles/native/cube_coordinate.c +207 -0
- data/ext/twisty_puzzles/native/cube_coordinate.h +34 -0
- data/ext/twisty_puzzles/native/cube_state.c +264 -0
- data/ext/twisty_puzzles/native/cube_state.h +31 -0
- data/ext/twisty_puzzles/native/extconf.rb +1 -1
- data/ext/twisty_puzzles/native/face_symbols.c +67 -0
- data/ext/twisty_puzzles/native/face_symbols.h +34 -0
- data/ext/twisty_puzzles/native/native.c +28 -0
- data/ext/twisty_puzzles/native/skewb_algorithm.c +331 -0
- data/ext/twisty_puzzles/native/skewb_algorithm.h +5 -0
- data/ext/twisty_puzzles/native/skewb_coordinate.c +237 -0
- data/ext/twisty_puzzles/native/skewb_coordinate.h +36 -0
- data/ext/twisty_puzzles/native/skewb_layer_fingerprint.c +271 -0
- data/ext/twisty_puzzles/native/skewb_layer_fingerprint.h +5 -0
- data/ext/twisty_puzzles/native/skewb_state.c +214 -0
- data/ext/twisty_puzzles/native/skewb_state.h +23 -0
- data/ext/twisty_puzzles/native/utils.c +76 -0
- data/ext/twisty_puzzles/native/utils.h +31 -0
- data/lib/twisty_puzzles.rb +38 -0
- data/lib/twisty_puzzles/abstract_direction.rb +38 -39
- data/lib/twisty_puzzles/abstract_move.rb +1 -2
- data/lib/twisty_puzzles/abstract_move_parser.rb +32 -33
- data/lib/twisty_puzzles/algorithm.rb +112 -113
- data/lib/twisty_puzzles/algorithm_transformation.rb +19 -21
- data/lib/twisty_puzzles/axis_face_and_direction_move.rb +56 -56
- data/lib/twisty_puzzles/cancellation_helper.rb +124 -125
- data/lib/twisty_puzzles/color_scheme.rb +1 -1
- data/lib/twisty_puzzles/commutator.rb +82 -80
- data/lib/twisty_puzzles/compiled_algorithm.rb +31 -32
- data/lib/twisty_puzzles/compiled_cube_algorithm.rb +49 -50
- data/lib/twisty_puzzles/compiled_skewb_algorithm.rb +18 -19
- data/lib/twisty_puzzles/coordinate.rb +243 -246
- data/lib/twisty_puzzles/cube.rb +494 -495
- data/lib/twisty_puzzles/cube_constants.rb +40 -41
- data/lib/twisty_puzzles/cube_direction.rb +15 -18
- data/lib/twisty_puzzles/cube_move.rb +285 -290
- data/lib/twisty_puzzles/cube_move_parser.rb +75 -76
- data/lib/twisty_puzzles/cube_print_helper.rb +133 -133
- data/lib/twisty_puzzles/cube_state.rb +80 -81
- data/lib/twisty_puzzles/move_type_creator.rb +17 -18
- data/lib/twisty_puzzles/parser.rb +176 -179
- data/lib/twisty_puzzles/part_cycle_factory.rb +39 -42
- data/lib/twisty_puzzles/puzzle.rb +16 -17
- data/lib/twisty_puzzles/reversible_applyable.rb +24 -25
- data/lib/twisty_puzzles/rotation.rb +76 -75
- data/lib/twisty_puzzles/skewb_direction.rb +14 -15
- data/lib/twisty_puzzles/skewb_move.rb +49 -49
- data/lib/twisty_puzzles/skewb_move_parser.rb +51 -51
- data/lib/twisty_puzzles/skewb_notation.rb +121 -118
- data/lib/twisty_puzzles/skewb_state.rb +120 -121
- data/lib/twisty_puzzles/state_helper.rb +20 -21
- data/lib/twisty_puzzles/sticker_cycle.rb +43 -44
- data/lib/twisty_puzzles/utils.rb +3 -0
- data/lib/twisty_puzzles/version.rb +3 -1
- metadata +30 -10
@@ -6,160 +6,159 @@ require 'twisty_puzzles/cube_constants'
|
|
6
6
|
require 'twisty_puzzles/cube_state'
|
7
7
|
|
8
8
|
module TwistyPuzzles
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
43
|
-
|
44
|
-
raise TypeError unless algorithm.is_a?(Algorithm)
|
38
|
+
variants
|
39
|
+
end
|
45
40
|
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
55
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
53
|
+
def self.combine_transformations(left, right)
|
54
|
+
left.dup.transform_values { |e| right[e] }.freeze
|
55
|
+
end
|
61
56
|
|
62
|
-
|
57
|
+
def self.apply_transformation_to!(transformation, face_state)
|
58
|
+
face_state.map! { |f| transformation[f] }
|
59
|
+
end
|
63
60
|
|
64
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
89
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
-
|
132
|
-
end
|
133
|
-
num
|
130
|
+
num += 1
|
134
131
|
end
|
132
|
+
num
|
133
|
+
end
|
135
134
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
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..] + [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
|
-
|
147
|
-
|
148
|
-
|
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
|
-
|
151
|
-
|
152
|
-
|
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
|
@@ -56,7 +56,7 @@ module TwistyPuzzles
|
|
56
56
|
raise ArgumentError unless colors.include?(top_color)
|
57
57
|
raise ArgumentError unless colors.include?(front_color)
|
58
58
|
|
59
|
-
#
|
59
|
+
# NOTE: The reason that this is so complicated is that we want it to still work if the
|
60
60
|
# chirality corner gets exchanged.
|
61
61
|
|
62
62
|
# Do the obvious and handle opposites of the top and front color so we have no
|
@@ -5,114 +5,116 @@ require 'twisty_puzzles/algorithm'
|
|
5
5
|
require 'twisty_puzzles/cube'
|
6
6
|
|
7
7
|
module TwistyPuzzles
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
algorithm.cancellations(other.algorithm, cube_size, metric)
|
13
|
-
end
|
8
|
+
# Base class for Commutators.
|
9
|
+
class Commutator
|
10
|
+
def cancellations(other, cube_size, metric = :htm)
|
11
|
+
algorithm.cancellations(other.algorithm, cube_size, metric)
|
14
12
|
end
|
13
|
+
end
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
@algorithm = algorithm
|
22
|
-
end
|
15
|
+
# Algorithm that is used like a commutator but actually isn't one.
|
16
|
+
class FakeCommutator < Commutator
|
17
|
+
def initialize(algorithm)
|
18
|
+
raise ArgumentError unless algorithm.is_a?(Algorithm)
|
23
19
|
|
24
|
-
|
20
|
+
super()
|
21
|
+
@algorithm = algorithm
|
22
|
+
end
|
25
23
|
|
26
|
-
|
27
|
-
self.class.equal?(other.class) && @algorithm == other.algorithm
|
28
|
-
end
|
24
|
+
attr_reader :algorithm
|
29
25
|
|
30
|
-
|
26
|
+
def eql?(other)
|
27
|
+
self.class.equal?(other.class) && @algorithm == other.algorithm
|
28
|
+
end
|
31
29
|
|
32
|
-
|
33
|
-
@hash ||= [self.class, @algorithm].hash
|
34
|
-
end
|
30
|
+
alias == eql?
|
35
31
|
|
36
|
-
|
37
|
-
|
38
|
-
|
32
|
+
def hash
|
33
|
+
@hash ||= [self.class, @algorithm].hash
|
34
|
+
end
|
39
35
|
|
40
|
-
|
41
|
-
|
42
|
-
end
|
36
|
+
def inverse
|
37
|
+
FakeCommutator.new(@algorithm.inverse)
|
43
38
|
end
|
44
39
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
raise ArgumentError unless second_part.is_a?(Algorithm)
|
40
|
+
def to_s
|
41
|
+
@algorithm.to_s
|
42
|
+
end
|
43
|
+
end
|
50
44
|
|
51
|
-
|
52
|
-
|
53
|
-
|
45
|
+
# Pure commutator of the form A B A' B'.
|
46
|
+
class PureCommutator < Commutator
|
47
|
+
def initialize(first_part, second_part)
|
48
|
+
raise ArgumentError unless first_part.is_a?(Algorithm)
|
49
|
+
raise ArgumentError unless second_part.is_a?(Algorithm)
|
54
50
|
|
55
|
-
|
51
|
+
super()
|
52
|
+
@first_part = first_part
|
53
|
+
@second_part = second_part
|
54
|
+
end
|
56
55
|
|
57
|
-
|
58
|
-
self.class.equal?(other.class) && @first_part == other.first_part &&
|
59
|
-
@second_part == other.second_part
|
60
|
-
end
|
56
|
+
attr_reader :first_part, :second_part
|
61
57
|
|
62
|
-
|
58
|
+
def eql?(other)
|
59
|
+
self.class.equal?(other.class) && @first_part == other.first_part &&
|
60
|
+
@second_part == other.second_part
|
61
|
+
end
|
63
62
|
|
64
|
-
|
65
|
-
@hash ||= [self.class, @first_part, @second_part].hash
|
66
|
-
end
|
63
|
+
alias == eql?
|
67
64
|
|
68
|
-
|
69
|
-
|
70
|
-
|
65
|
+
def hash
|
66
|
+
@hash ||= [self.class, @first_part, @second_part].hash
|
67
|
+
end
|
71
68
|
|
72
|
-
|
73
|
-
|
74
|
-
|
69
|
+
def inverse
|
70
|
+
PureCommutator.new(second_part, first_part)
|
71
|
+
end
|
75
72
|
|
76
|
-
|
77
|
-
|
78
|
-
end
|
73
|
+
def to_s
|
74
|
+
"[#{@first_part}, #{@second_part}]"
|
79
75
|
end
|
80
76
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
unless inner_commutator.is_a?(Commutator)
|
86
|
-
raise ArgumentError, 'Inner commutator has to be a commutator.'
|
87
|
-
end
|
77
|
+
def algorithm
|
78
|
+
first_part + second_part + first_part.inverse + second_part.inverse
|
79
|
+
end
|
80
|
+
end
|
88
81
|
|
89
|
-
|
90
|
-
|
82
|
+
# Setup commutator of the form A B A'.
|
83
|
+
class SetupCommutator < Commutator
|
84
|
+
def initialize(setup, inner_commutator)
|
85
|
+
raise ArgumentError, 'Setup move has to be an algorithm.' unless setup.is_a?(Algorithm)
|
86
|
+
unless inner_commutator.is_a?(Commutator)
|
87
|
+
raise ArgumentError, 'Inner commutator has to be a commutator.'
|
91
88
|
end
|
92
89
|
|
93
|
-
|
90
|
+
super()
|
91
|
+
@setup = setup
|
92
|
+
@inner_commutator = inner_commutator
|
93
|
+
end
|
94
|
+
|
95
|
+
attr_reader :setup, :inner_commutator
|
94
96
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
97
|
+
def eql?(other)
|
98
|
+
self.class.equal?(other.class) && @setup == other.setup &&
|
99
|
+
@inner_commutator == other.inner_commutator
|
100
|
+
end
|
99
101
|
|
100
|
-
|
102
|
+
alias == eql?
|
101
103
|
|
102
|
-
|
103
|
-
|
104
|
-
|
104
|
+
def hash
|
105
|
+
@hash ||= [self.class, @setup, @inner_commutator].hash
|
106
|
+
end
|
105
107
|
|
106
|
-
|
107
|
-
|
108
|
-
|
108
|
+
def inverse
|
109
|
+
SetupCommutator.new(setup, @inner_commutator.inverse)
|
110
|
+
end
|
109
111
|
|
110
|
-
|
111
|
-
|
112
|
-
|
112
|
+
def to_s
|
113
|
+
"[#{@setup} : #{@inner_commutator}]"
|
114
|
+
end
|
113
115
|
|
114
|
-
|
115
|
-
|
116
|
-
end
|
116
|
+
def algorithm
|
117
|
+
setup + inner_commutator.algorithm + setup.inverse
|
117
118
|
end
|
119
|
+
end
|
118
120
|
end
|