twisty_puzzles 0.0.1
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 +7 -0
- data/CHANGELOG.md +9 -0
- data/CODE_OF_CONDUCT.md +76 -0
- data/LICENSE +21 -0
- data/README.md +32 -0
- data/ext/twisty_puzzles/native/extconf.rb +5 -0
- data/lib/twisty_puzzles/abstract_direction.rb +54 -0
- data/lib/twisty_puzzles/abstract_move.rb +170 -0
- data/lib/twisty_puzzles/abstract_move_parser.rb +45 -0
- data/lib/twisty_puzzles/algorithm.rb +155 -0
- data/lib/twisty_puzzles/algorithm_transformation.rb +33 -0
- data/lib/twisty_puzzles/axis_face_and_direction_move.rb +78 -0
- data/lib/twisty_puzzles/cancellation_helper.rb +165 -0
- data/lib/twisty_puzzles/color_scheme.rb +174 -0
- data/lib/twisty_puzzles/commutator.rb +118 -0
- data/lib/twisty_puzzles/compiled_algorithm.rb +48 -0
- data/lib/twisty_puzzles/compiled_cube_algorithm.rb +67 -0
- data/lib/twisty_puzzles/compiled_skewb_algorithm.rb +28 -0
- data/lib/twisty_puzzles/coordinate.rb +318 -0
- data/lib/twisty_puzzles/cube.rb +660 -0
- data/lib/twisty_puzzles/cube_constants.rb +53 -0
- data/lib/twisty_puzzles/cube_direction.rb +27 -0
- data/lib/twisty_puzzles/cube_move.rb +384 -0
- data/lib/twisty_puzzles/cube_move_parser.rb +100 -0
- data/lib/twisty_puzzles/cube_print_helper.rb +160 -0
- data/lib/twisty_puzzles/cube_state.rb +113 -0
- data/lib/twisty_puzzles/letter_scheme.rb +72 -0
- data/lib/twisty_puzzles/move_type_creator.rb +27 -0
- data/lib/twisty_puzzles/parser.rb +222 -0
- data/lib/twisty_puzzles/part_cycle_factory.rb +59 -0
- data/lib/twisty_puzzles/puzzle.rb +26 -0
- data/lib/twisty_puzzles/reversible_applyable.rb +37 -0
- data/lib/twisty_puzzles/rotation.rb +105 -0
- data/lib/twisty_puzzles/skewb_direction.rb +24 -0
- data/lib/twisty_puzzles/skewb_move.rb +59 -0
- data/lib/twisty_puzzles/skewb_move_parser.rb +73 -0
- data/lib/twisty_puzzles/skewb_notation.rb +147 -0
- data/lib/twisty_puzzles/skewb_state.rb +163 -0
- data/lib/twisty_puzzles/state_helper.rb +32 -0
- data/lib/twisty_puzzles/sticker_cycle.rb +70 -0
- data/lib/twisty_puzzles/twisty_puzzles_error.rb +6 -0
- data/lib/twisty_puzzles/utils/array_helper.rb +109 -0
- data/lib/twisty_puzzles/utils/string_helper.rb +26 -0
- data/lib/twisty_puzzles/utils.rb +7 -0
- data/lib/twisty_puzzles/version.rb +3 -0
- data/lib/twisty_puzzles.rb +5 -0
- metadata +249 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'twisty_puzzles/cube_direction'
|
4
|
+
require 'twisty_puzzles/rotation'
|
5
|
+
|
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
|
14
|
+
|
15
|
+
def identity?
|
16
|
+
rotation.identity? && !mirror
|
17
|
+
end
|
18
|
+
|
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
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.around_face_without_identity(face)
|
29
|
+
around_face(face).reject(&:identity?)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'twisty_puzzles/abstract_move'
|
4
|
+
require 'twisty_puzzles/cube_direction'
|
5
|
+
|
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)
|
14
|
+
|
15
|
+
@axis_face = axis_face
|
16
|
+
@direction = direction
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :direction, :axis_face
|
20
|
+
|
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
|
27
|
+
end
|
28
|
+
|
29
|
+
def same_axis?(other)
|
30
|
+
@axis_face.same_axis?(other.axis_face)
|
31
|
+
end
|
32
|
+
|
33
|
+
def identifying_fields
|
34
|
+
[@axis_face, @direction]
|
35
|
+
end
|
36
|
+
|
37
|
+
def canonical_direction
|
38
|
+
@axis_face.canonical_axis_face? ? @direction : @direction.inverse
|
39
|
+
end
|
40
|
+
|
41
|
+
def can_swap?(other)
|
42
|
+
super || same_axis?(other)
|
43
|
+
end
|
44
|
+
|
45
|
+
def swap_internal(other)
|
46
|
+
if same_axis?(other)
|
47
|
+
[other, self]
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
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
|
64
|
+
end
|
65
|
+
|
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
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'twisty_puzzles/abstract_move'
|
4
|
+
require 'twisty_puzzles/algorithm'
|
5
|
+
require 'twisty_puzzles/cube_constants'
|
6
|
+
require 'twisty_puzzles/cube_state'
|
7
|
+
|
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)
|
25
|
+
end
|
26
|
+
|
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?
|
38
|
+
|
39
|
+
variants
|
40
|
+
end
|
41
|
+
|
42
|
+
# Cancel this algorithm as much as possilbe
|
43
|
+
def self.cancel(algorithm, cube_size)
|
44
|
+
raise TypeError unless algorithm.is_a?(Algorithm)
|
45
|
+
|
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
|
53
|
+
|
54
|
+
def self.combine_transformations(left, right)
|
55
|
+
left.dup.transform_values { |e| right[e] }.freeze
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.apply_transformation_to!(transformation, face_state)
|
59
|
+
face_state.map! { |f| transformation[f] }
|
60
|
+
end
|
61
|
+
|
62
|
+
TRIVIAL_CENTER_TRANSFORMATION = { U: :U, F: :F, R: :R, L: :L, B: :B, D: :D }.freeze
|
63
|
+
|
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
|
72
|
+
|
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
|
87
|
+
|
88
|
+
def self.center_transformation(rotation)
|
89
|
+
CENTER_TRANSFORMATIONS[rotation.axis_face.face_symbol][rotation.direction.value]
|
90
|
+
end
|
91
|
+
|
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
|
97
|
+
|
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
|
106
|
+
end
|
107
|
+
|
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
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.cancelled_rotations(rotations)
|
122
|
+
center_state = rotated_center_state(rotations)
|
123
|
+
rotation_sequences[center_state]
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.num_tail_rotations(algorithm)
|
127
|
+
num = 0
|
128
|
+
algorithm.moves.reverse_each do |e|
|
129
|
+
break unless e.is_a?(Rotation)
|
130
|
+
|
131
|
+
num += 1
|
132
|
+
end
|
133
|
+
num
|
134
|
+
end
|
135
|
+
|
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
|
144
|
+
end
|
145
|
+
|
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?
|
149
|
+
|
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]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'twisty_puzzles/cube'
|
4
|
+
require 'twisty_puzzles/cube_constants'
|
5
|
+
require 'twisty_puzzles/cube_state'
|
6
|
+
require 'twisty_puzzles/skewb_state'
|
7
|
+
require 'twisty_puzzles/utils/array_helper'
|
8
|
+
|
9
|
+
module TwistyPuzzles
|
10
|
+
# A color scheme that assigns a color to each face.
|
11
|
+
class ColorScheme
|
12
|
+
include CubeConstants
|
13
|
+
include Utils::ArrayHelper
|
14
|
+
|
15
|
+
RESERVED_COLORS = %i[transparent unknown oriented].freeze
|
16
|
+
|
17
|
+
def initialize(face_symbols_to_colors)
|
18
|
+
check_face_symbols_to_colors(face_symbols_to_colors)
|
19
|
+
|
20
|
+
num_uniq_colors = face_symbols_to_colors.values.uniq.length
|
21
|
+
unless num_uniq_colors == FACE_SYMBOLS.length
|
22
|
+
raise ArgumentError, "Got #{num_uniq_colors} unique colors " \
|
23
|
+
"#{face_symbols_to_colors.values.uniq}, " \
|
24
|
+
"but needed #{FACE_SYMBOLS.length}."
|
25
|
+
end
|
26
|
+
|
27
|
+
@face_symbols_to_colors = face_symbols_to_colors
|
28
|
+
@colors_to_face_symbols = face_symbols_to_colors.invert
|
29
|
+
end
|
30
|
+
|
31
|
+
def color(face_symbol)
|
32
|
+
@face_symbols_to_colors[face_symbol]
|
33
|
+
end
|
34
|
+
|
35
|
+
def opposite_color(color)
|
36
|
+
color(opposite_face_symbol(face_symbol(color)))
|
37
|
+
end
|
38
|
+
|
39
|
+
def part_for_colors(part_type, colors)
|
40
|
+
raise ArgumentError unless part_type.is_a?(Class)
|
41
|
+
|
42
|
+
part_type.for_face_symbols(colors.map { |c| face_symbol(c) })
|
43
|
+
end
|
44
|
+
|
45
|
+
def face_symbol(color)
|
46
|
+
@colors_to_face_symbols[color]
|
47
|
+
end
|
48
|
+
|
49
|
+
def colors
|
50
|
+
@face_symbols_to_colors.values
|
51
|
+
end
|
52
|
+
|
53
|
+
def turned(top_color, front_color)
|
54
|
+
raise ArgumentError if top_color == front_color
|
55
|
+
raise ArgumentError if opposite_color(top_color) == front_color
|
56
|
+
raise ArgumentError unless colors.include?(top_color)
|
57
|
+
raise ArgumentError unless colors.include?(front_color)
|
58
|
+
|
59
|
+
# Note: The reason that this is so complicated is that we want it to still work if the
|
60
|
+
# chirality corner gets exchanged.
|
61
|
+
|
62
|
+
# Do the obvious and handle opposites of the top and front color so we have no
|
63
|
+
# assumptions that the chirality corner contains U and F.
|
64
|
+
turned_face_symbols_to_colors =
|
65
|
+
obvious_turned_face_symbols_to_colors(top_color, front_color)
|
66
|
+
|
67
|
+
# Now find the corner that gets mapped to the chirality corner. We know
|
68
|
+
# two of its colors and the position of the missing color.
|
69
|
+
chirality_corner_source, unknown_index =
|
70
|
+
chirality_corner_source_and_unknown_index(turned_face_symbols_to_colors)
|
71
|
+
|
72
|
+
add_missing_mappings(turned_face_symbols_to_colors, chirality_corner_source, unknown_index)
|
73
|
+
|
74
|
+
ColorScheme.new(turned_face_symbols_to_colors)
|
75
|
+
end
|
76
|
+
|
77
|
+
def solved_cube_state(cube_size)
|
78
|
+
stickers =
|
79
|
+
ordered_colors.map do |c|
|
80
|
+
Array.new(cube_size) { Array.new(cube_size) { c } }
|
81
|
+
end
|
82
|
+
CubeState.from_stickers(cube_size, stickers)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Colors in the order of the face symbols.
|
86
|
+
def ordered_colors
|
87
|
+
FACE_SYMBOLS.map { |s| color(s) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def solved_skewb_state
|
91
|
+
SkewbState.for_solved_colors(@face_symbols_to_colors.dup)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def chirality_corner_source_and_unknown_index(obvious_turned_face_symbols_to_colors)
|
97
|
+
corner_matcher =
|
98
|
+
CornerMatcher.new(CHIRALITY_FACE_SYMBOLS.map do |s|
|
99
|
+
# This will return nil for exactly one face that we don't know yet.
|
100
|
+
@colors_to_face_symbols[obvious_turned_face_symbols_to_colors[s]]
|
101
|
+
end)
|
102
|
+
|
103
|
+
# There should be exactly one corner that gets mapped to the chirality corner.
|
104
|
+
chirality_corner_source =
|
105
|
+
find_only(Corner::ELEMENTS) do |corner|
|
106
|
+
corner_matcher.matches?(corner)
|
107
|
+
end
|
108
|
+
[chirality_corner_source, corner_matcher.wildcard_index]
|
109
|
+
end
|
110
|
+
|
111
|
+
# Corner matcher that finds a corner that has one arbitrary
|
112
|
+
# face symbol and two given face symbol.
|
113
|
+
class CornerMatcher
|
114
|
+
def initialize(face_symbol_matchers)
|
115
|
+
unless face_symbol_matchers.count(&:nil?) == 1
|
116
|
+
raise ArgumentError, 'Exactly one nil allowed in face symbol matchers.'
|
117
|
+
end
|
118
|
+
|
119
|
+
@face_symbol_matchers = face_symbol_matchers
|
120
|
+
end
|
121
|
+
|
122
|
+
def matches?(corner)
|
123
|
+
corner.face_symbols.zip(@face_symbol_matchers).all? do |face_symbol, face_symbol_matcher|
|
124
|
+
face_symbol_matcher.nil? || face_symbol == face_symbol_matcher
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def wildcard_index
|
129
|
+
@face_symbol_matchers.index(nil)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def check_face_symbols_to_colors(face_symbols_to_colors)
|
134
|
+
raise ArgumentError unless face_symbols_to_colors.keys.sort == FACE_SYMBOLS.sort
|
135
|
+
|
136
|
+
face_symbols_to_colors.each_value do |c|
|
137
|
+
raise TypeError unless c.is_a?(Symbol)
|
138
|
+
|
139
|
+
if RESERVED_COLORS.include?(c)
|
140
|
+
raise ArgumentError,
|
141
|
+
"Color #{c} cannot be part of the color scheme because it is a reserved color."
|
142
|
+
end
|
143
|
+
end
|
144
|
+
raise ArgumentError unless face_symbols_to_colors.values.all? { |c| c.is_a?(Symbol) }
|
145
|
+
end
|
146
|
+
|
147
|
+
def add_missing_mappings(turned_face_symbols_to_colors, chirality_corner_source, unknown_index)
|
148
|
+
missing_face_symbol = CHIRALITY_FACE_SYMBOLS[unknown_index]
|
149
|
+
missing_face_symbol_source =
|
150
|
+
chirality_corner_source.face_symbols[unknown_index]
|
151
|
+
turned_face_symbols_to_colors[missing_face_symbol] = color(missing_face_symbol_source)
|
152
|
+
turned_face_symbols_to_colors[opposite_face_symbol(missing_face_symbol)] =
|
153
|
+
color(opposite_face_symbol(missing_face_symbol_source))
|
154
|
+
end
|
155
|
+
|
156
|
+
def obvious_turned_face_symbols_to_colors(top_color, front_color)
|
157
|
+
result = { U: top_color, F: front_color }
|
158
|
+
opposites = result.map do |face_symbol, color|
|
159
|
+
[opposite_face_symbol(face_symbol), opposite_color(color)]
|
160
|
+
end.to_h
|
161
|
+
result.merge!(opposites)
|
162
|
+
end
|
163
|
+
|
164
|
+
WCA = new(
|
165
|
+
U: :white,
|
166
|
+
F: :green,
|
167
|
+
R: :red,
|
168
|
+
L: :orange,
|
169
|
+
B: :blue,
|
170
|
+
D: :yellow
|
171
|
+
).freeze
|
172
|
+
BERNHARD = WCA.turned(:yellow, :red).freeze
|
173
|
+
end
|
174
|
+
end
|