twisty_puzzles 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -1
- data/lib/twisty_puzzles.rb +37 -0
- data/lib/twisty_puzzles/abstract_direction.rb +38 -39
- 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 +55 -56
- data/lib/twisty_puzzles/cancellation_helper.rb +124 -125
- data/lib/twisty_puzzles/commutator.rb +79 -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 +245 -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 +289 -290
- data/lib/twisty_puzzles/cube_move_parser.rb +75 -76
- data/lib/twisty_puzzles/cube_print_helper.rb +132 -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 +74 -75
- data/lib/twisty_puzzles/skewb_direction.rb +14 -15
- data/lib/twisty_puzzles/skewb_move.rb +48 -49
- data/lib/twisty_puzzles/skewb_move_parser.rb +50 -51
- data/lib/twisty_puzzles/skewb_notation.rb +115 -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 +3 -3
@@ -8,66 +8,65 @@ require 'twisty_puzzles/skewb_move'
|
|
8
8
|
require 'twisty_puzzles/skewb_notation'
|
9
9
|
|
10
10
|
module TwistyPuzzles
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
].freeze
|
11
|
+
# Parser for Skewb moves.
|
12
|
+
class SkewbMoveParser < AbstractMoveParser
|
13
|
+
MOVE_TYPE_CREATORS = [
|
14
|
+
MoveTypeCreator.new(%i[axis_face cube_direction], Rotation),
|
15
|
+
MoveTypeCreator.new(%i[axis_corner skewb_direction], SkewbMove)
|
16
|
+
].freeze
|
18
17
|
|
19
|
-
|
20
|
-
|
18
|
+
def initialize(notation)
|
19
|
+
raise TypeError unless notation.is_a?(SkewbNotation)
|
21
20
|
|
22
|
-
|
23
|
-
|
21
|
+
@notation = notation
|
22
|
+
end
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
FIXED_CORNER_INSTANCE = SkewbMoveParser.new(SkewbNotation.fixed_corner)
|
25
|
+
SARAH_INSTANCE = SkewbMoveParser.new(SkewbNotation.sarah)
|
26
|
+
RUBIKS_INSTANCE = SkewbMoveParser.new(SkewbNotation.rubiks)
|
28
27
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
28
|
+
def regexp
|
29
|
+
@regexp ||=
|
30
|
+
begin
|
31
|
+
skewb_direction_names =
|
32
|
+
AbstractDirection::POSSIBLE_SKEWB_DIRECTION_NAMES.flatten
|
33
|
+
move_part = "(?:(?<skewb_move>[#{@notation.move_strings.join}])" \
|
34
|
+
"(?<skewb_direction>[#{skewb_direction_names.join}]?))"
|
35
|
+
rotation_direction_names =
|
36
|
+
AbstractDirection::POSSIBLE_DIRECTION_NAMES.flatten
|
37
|
+
rotation_direction_names.sort_by! { |e| -e.length }
|
38
|
+
rotation_part = "(?:(?<axis_name>[#{AbstractMove::AXES.join}])" \
|
39
|
+
"(?<cube_direction>#{rotation_direction_names.join('|')}))"
|
40
|
+
Regexp.new("#{move_part}|#{rotation_part}")
|
41
|
+
end
|
42
|
+
end
|
44
43
|
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
def move_type_creators
|
45
|
+
MOVE_TYPE_CREATORS
|
46
|
+
end
|
48
47
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
48
|
+
def parse_skewb_direction(direction_string)
|
49
|
+
if AbstractDirection::POSSIBLE_DIRECTION_NAMES[0].include?(direction_string)
|
50
|
+
SkewbDirection::FORWARD
|
51
|
+
elsif AbstractDirection::POSSIBLE_DIRECTION_NAMES[-1].include?(direction_string)
|
52
|
+
SkewbDirection::BACKWARD
|
53
|
+
else
|
54
|
+
raise ArgumentError
|
57
55
|
end
|
56
|
+
end
|
58
57
|
|
59
|
-
|
60
|
-
|
61
|
-
|
58
|
+
def parse_part_key(name)
|
59
|
+
name.sub('name', 'face').sub('skewb_move', 'axis_corner')
|
60
|
+
end
|
62
61
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
62
|
+
def parse_move_part(name, value)
|
63
|
+
case name
|
64
|
+
when 'axis_name' then CubeMoveParser::INSTANCE.parse_axis_face(value)
|
65
|
+
when 'cube_direction' then CubeMoveParser::INSTANCE.parse_direction(value)
|
66
|
+
when 'skewb_move' then @notation.corner(value)
|
67
|
+
when 'skewb_direction' then parse_skewb_direction(value)
|
68
|
+
else raise
|
71
69
|
end
|
72
70
|
end
|
71
|
+
end
|
73
72
|
end
|
@@ -7,141 +7,138 @@ require 'twisty_puzzles/skewb_direction'
|
|
7
7
|
require 'twisty_puzzles/skewb_move'
|
8
8
|
|
9
9
|
module TwistyPuzzles
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
30
|
-
|
31
|
-
def check_move_corner_pairs(move_corner_pairs)
|
32
|
-
move_corner_pairs.each do |m|
|
33
|
-
raise ArgumentError unless m.length == 2
|
34
|
-
raise TypeError unless m[0].is_a?(String)
|
35
|
-
raise TypeError unless m[1].is_a?(Corner)
|
36
|
-
end
|
37
|
-
if move_corner_pairs.map(&:first).uniq.length != move_corner_pairs.length
|
38
|
-
raise ArgumentError
|
39
|
-
end
|
10
|
+
# Class that represents one notation for Skewb moves, e.g. Sarahs notation or fixed
|
11
|
+
# corner notation.
|
12
|
+
class SkewbNotation
|
13
|
+
def initialize(name, move_corner_pairs)
|
14
|
+
raise TypeError unless name.is_a?(String)
|
15
|
+
|
16
|
+
check_move_corner_pairs(move_corner_pairs)
|
17
|
+
@name = name
|
18
|
+
@move_to_corner = move_corner_pairs.to_h.freeze
|
19
|
+
@corner_to_move = move_corner_pairs.collect_concat do |m, c|
|
20
|
+
c.rotations.map { |e| [e, m] }
|
21
|
+
end.to_h.freeze
|
22
|
+
@move_strings = move_corner_pairs.map(&:first).freeze
|
23
|
+
@non_zero_moves =
|
24
|
+
move_corner_pairs.map(&:last).product(SkewbDirection::NON_ZERO_DIRECTIONS).map do |c, d|
|
25
|
+
SkewbMove.new(c, d)
|
26
|
+
end.freeze
|
27
|
+
freeze
|
28
|
+
end
|
40
29
|
|
41
|
-
|
30
|
+
def check_move_corner_pairs(move_corner_pairs)
|
31
|
+
move_corner_pairs.each do |m|
|
32
|
+
raise ArgumentError unless m.length == 2
|
33
|
+
raise TypeError unless m[0].is_a?(String)
|
34
|
+
raise TypeError unless m[1].is_a?(Corner)
|
42
35
|
end
|
36
|
+
raise ArgumentError if move_corner_pairs.map(&:first).uniq.length != move_corner_pairs.length
|
37
|
+
|
38
|
+
check_corner_coverage(move_corner_pairs.map(&:last))
|
39
|
+
end
|
43
40
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
41
|
+
def check_corner_coverage(corners)
|
42
|
+
corner_closure = corners + corners.map(&:diagonal_opposite)
|
43
|
+
Corner::ELEMENTS.each do |corner|
|
44
|
+
unless corner_closure.any? { |c| c.turned_equals?(corner) }
|
45
|
+
raise ArgumentError,
|
46
|
+
"Turns around corner #{corner} cannot be represented in notation #{name}."
|
51
47
|
end
|
52
48
|
end
|
49
|
+
end
|
53
50
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
51
|
+
attr_reader :name, :move_strings, :non_zero_moves
|
52
|
+
private_class_method :new
|
53
|
+
|
54
|
+
def self.fixed_corner
|
55
|
+
@fixed_corner ||= new(
|
56
|
+
'fixed corner', [
|
57
|
+
['U', Corner.for_face_symbols(%i[U L B])],
|
58
|
+
['R', Corner.for_face_symbols(%i[D R B])],
|
59
|
+
['L', Corner.for_face_symbols(%i[D F L])],
|
60
|
+
['B', Corner.for_face_symbols(%i[D B L])]
|
61
|
+
]
|
62
|
+
)
|
63
|
+
end
|
67
64
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
65
|
+
def self.sarah
|
66
|
+
@sarah ||= new(
|
67
|
+
'sarah', [
|
68
|
+
['F', Corner.for_face_symbols(%i[U R F])],
|
69
|
+
['R', Corner.for_face_symbols(%i[U B R])],
|
70
|
+
['B', Corner.for_face_symbols(%i[U L B])],
|
71
|
+
['L', Corner.for_face_symbols(%i[U F L])]
|
72
|
+
]
|
73
|
+
)
|
74
|
+
end
|
78
75
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
76
|
+
def self.rubiks
|
77
|
+
@rubiks ||= new(
|
78
|
+
'rubiks', [
|
79
|
+
['F', Corner.for_face_symbols(%i[U R F])],
|
80
|
+
['R', Corner.for_face_symbols(%i[U B R])],
|
81
|
+
['B', Corner.for_face_symbols(%i[U L B])],
|
82
|
+
['L', Corner.for_face_symbols(%i[U F L])],
|
83
|
+
['f', Corner.for_face_symbols(%i[D F R])],
|
84
|
+
['r', Corner.for_face_symbols(%i[D R B])],
|
85
|
+
['b', Corner.for_face_symbols(%i[D B L])],
|
86
|
+
['l', Corner.for_face_symbols(%i[D L F])]
|
87
|
+
]
|
88
|
+
)
|
89
|
+
end
|
93
90
|
|
94
|
-
|
95
|
-
|
96
|
-
|
91
|
+
def to_s
|
92
|
+
@name
|
93
|
+
end
|
97
94
|
|
98
|
-
|
99
|
-
|
100
|
-
|
95
|
+
def corner(move)
|
96
|
+
@move_to_corner[move] || (raise ArgumentError)
|
97
|
+
end
|
101
98
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
99
|
+
def algorithm_to_string(algorithm)
|
100
|
+
reversed_rotations = []
|
101
|
+
num_tail_rotations = CancellationHelper.num_tail_rotations(algorithm)
|
102
|
+
alg_string = algorithm.moves[0...algorithm.length - num_tail_rotations].map do |m|
|
103
|
+
move_to_string(m, reversed_rotations)
|
104
|
+
end.join(' ')
|
105
|
+
new_tail_rotations = reversed_rotations.reverse! +
|
106
|
+
algorithm.moves[algorithm.length - num_tail_rotations..-1]
|
107
|
+
cancelled_rotations = Algorithm.new(new_tail_rotations).cancelled(3)
|
108
|
+
cancelled_rotations.empty? ? alg_string : "#{alg_string} #{cancelled_rotations}"
|
109
|
+
end
|
113
110
|
|
114
|
-
|
111
|
+
private
|
115
112
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
end
|
113
|
+
def move_to_string(move, reversed_rotations)
|
114
|
+
reversed_rotations.each { |r| move = move.rotate_by(r.inverse) }
|
115
|
+
case move
|
116
|
+
when SkewbMove then skewb_move_to_string(move, reversed_rotations)
|
117
|
+
when Rotation then move.to_s
|
118
|
+
else raise ArgumentError, "Couldn't transform #{move} to #{@name} Skewb notation."
|
123
119
|
end
|
120
|
+
end
|
124
121
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
end
|
132
|
-
"#{move_string}#{move.direction.name}"
|
122
|
+
def skewb_move_to_string(move, reversed_rotations)
|
123
|
+
move_string, rotate = move_to_string_internal(move)
|
124
|
+
if rotate
|
125
|
+
reversed_additional_rotations =
|
126
|
+
Rotation.around_corner(move.axis_corner, move.direction).moves.reverse
|
127
|
+
reversed_rotations.concat(reversed_additional_rotations)
|
133
128
|
end
|
129
|
+
"#{move_string}#{move.direction.name}"
|
130
|
+
end
|
134
131
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
end
|
132
|
+
# Returns the move string of the given move and true if a rotation has to be done to correct
|
133
|
+
# for the fact that we actually used the opposite corner.
|
134
|
+
def move_to_string_internal(move)
|
135
|
+
if (move_string = @corner_to_move[move.axis_corner])
|
136
|
+
[move_string, false]
|
137
|
+
elsif (move_string = @corner_to_move[move.axis_corner.diagonal_opposite])
|
138
|
+
[move_string, !move.direction.zero?]
|
139
|
+
else
|
140
|
+
raise
|
145
141
|
end
|
146
142
|
end
|
143
|
+
end
|
147
144
|
end
|
@@ -7,157 +7,156 @@ require 'twisty_puzzles/state_helper'
|
|
7
7
|
require 'twisty_puzzles/cube_constants'
|
8
8
|
|
9
9
|
module TwistyPuzzles
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
Corner::ELEMENTS.each do |
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
])
|
34
|
-
end
|
35
|
-
matching_corners.push(check_parts)
|
10
|
+
# Represents the state (i.e. the sticker positions) of a Skewb.
|
11
|
+
class SkewbState
|
12
|
+
include CubePrintHelper
|
13
|
+
include StateHelper
|
14
|
+
include CubeConstants
|
15
|
+
# Pairs of coordinate pairs that should match in case of solved layers.
|
16
|
+
MATCHING_CORNERS =
|
17
|
+
begin
|
18
|
+
matching_corners = []
|
19
|
+
Corner::ELEMENTS.each do |c1|
|
20
|
+
Corner::ELEMENTS.each do |c2|
|
21
|
+
# Take corner pairs that have a common edge.
|
22
|
+
next unless c1.common_edge_with?(c2)
|
23
|
+
|
24
|
+
check_parts = []
|
25
|
+
c1.rotations.each do |c1_rot|
|
26
|
+
next unless c2.face_symbols.include?(c1_rot.face_symbols.first)
|
27
|
+
|
28
|
+
c2_rot = c2.rotate_face_symbol_up(c1_rot.face_symbols.first)
|
29
|
+
check_parts.push([
|
30
|
+
SkewbCoordinate.for_corner(c1_rot),
|
31
|
+
SkewbCoordinate.for_corner(c2_rot)
|
32
|
+
])
|
36
33
|
end
|
34
|
+
matching_corners.push(check_parts)
|
37
35
|
end
|
38
|
-
matching_corners.uniq
|
39
36
|
end
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
37
|
+
matching_corners.uniq
|
38
|
+
end
|
39
|
+
# Pairs of stickers that can be used to check whether the "outside" of a layer on the given
|
40
|
+
# face is a proper layer.
|
41
|
+
LAYER_CHECK_NEIGHBORS =
|
42
|
+
begin
|
43
|
+
layer_check_neighbors = {}
|
44
|
+
MATCHING_CORNERS.each do |a, b|
|
45
|
+
[[a.first.face, b], [b.first.face, a]].each do |face, coordinates|
|
46
|
+
# We take the first one we encounter, but it doesn't matter, we could take any.
|
47
|
+
layer_check_neighbors[face] ||= coordinates
|
50
48
|
end
|
51
|
-
layer_check_neighbors
|
52
49
|
end
|
50
|
+
layer_check_neighbors
|
51
|
+
end
|
53
52
|
|
54
|
-
|
55
|
-
|
53
|
+
def initialize(native)
|
54
|
+
raise TypeError unless native.is_a?(Native::SkewbState)
|
56
55
|
|
57
|
-
|
58
|
-
|
56
|
+
@native = native
|
57
|
+
end
|
59
58
|
|
60
|
-
|
59
|
+
attr_reader :native
|
61
60
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
61
|
+
def self.for_solved_colors(solved_colors)
|
62
|
+
native = Native::SkewbState.new(solved_colors)
|
63
|
+
new(native)
|
64
|
+
end
|
66
65
|
|
67
|
-
|
68
|
-
|
69
|
-
|
66
|
+
def eql?(other)
|
67
|
+
self.class.equal?(other.class) && @native == other.native
|
68
|
+
end
|
70
69
|
|
71
|
-
|
70
|
+
alias == eql?
|
72
71
|
|
73
|
-
|
74
|
-
|
75
|
-
|
72
|
+
def hash
|
73
|
+
@hash ||= [self.class, @native].hash
|
74
|
+
end
|
76
75
|
|
77
|
-
|
78
|
-
|
79
|
-
|
76
|
+
# TODO: Get rid of this backwards compatibility artifact
|
77
|
+
def sticker_array(face)
|
78
|
+
raise TypeError unless face.is_a?(Face)
|
80
79
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
80
|
+
center_sticker = self[SkewbCoordinate.for_center(face)]
|
81
|
+
corner_stickers =
|
82
|
+
face.clockwise_corners.sort.map do |c|
|
83
|
+
self[SkewbCoordinate.for_corner(c)]
|
84
|
+
end
|
85
|
+
[center_sticker] + corner_stickers
|
86
|
+
end
|
88
87
|
|
89
|
-
|
90
|
-
|
91
|
-
|
88
|
+
def dup
|
89
|
+
SkewbState.new(@native.dup)
|
90
|
+
end
|
92
91
|
|
93
|
-
|
94
|
-
|
95
|
-
|
92
|
+
def to_s
|
93
|
+
skewb_string(self, :nocolor)
|
94
|
+
end
|
96
95
|
|
97
|
-
|
98
|
-
|
99
|
-
|
96
|
+
def colored_to_s
|
97
|
+
skewb_string(self, :color)
|
98
|
+
end
|
100
99
|
|
101
|
-
|
102
|
-
|
103
|
-
|
100
|
+
def apply_move(move)
|
101
|
+
move.apply_to(self)
|
102
|
+
end
|
104
103
|
|
105
|
-
|
106
|
-
|
107
|
-
|
104
|
+
def apply_algorithm(alg)
|
105
|
+
alg.apply_to(self)
|
106
|
+
end
|
108
107
|
|
109
|
-
|
110
|
-
|
111
|
-
|
108
|
+
def apply_rotation(rot)
|
109
|
+
rot.apply_to_skewb(self)
|
110
|
+
end
|
112
111
|
|
113
|
-
|
114
|
-
|
115
|
-
|
112
|
+
def [](coordinate)
|
113
|
+
@native[coordinate.native]
|
114
|
+
end
|
116
115
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
116
|
+
def []=(coordinate, color)
|
117
|
+
@native[coordinate.native] = color
|
118
|
+
sticker_array(coordinate.face)[coordinate.coordinate] = color
|
119
|
+
end
|
121
120
|
|
122
|
-
|
123
|
-
|
124
|
-
|
121
|
+
def any_layer_solved?
|
122
|
+
!solved_layers.empty?
|
123
|
+
end
|
125
124
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
125
|
+
# Returns the color of all solved layers. Empty if there is none.
|
126
|
+
def solved_layers
|
127
|
+
solved_faces = Face::ELEMENTS.select { |f| layer_at_face_solved?(f) }
|
128
|
+
solved_faces.map { |f| self[SkewbCoordinate.for_center(f)] }
|
129
|
+
end
|
131
130
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
end
|
131
|
+
def layer_solved?(color)
|
132
|
+
Face::ELEMENTS.any? do |f|
|
133
|
+
self[SkewbCoordinate.for_center(f)] == color && layer_at_face_solved?(f)
|
136
134
|
end
|
135
|
+
end
|
137
136
|
|
138
|
-
|
139
|
-
|
140
|
-
|
137
|
+
def center_face(color)
|
138
|
+
Face::ELEMENTS.find { |f| self[SkewbCoordinate.for_center(f)] == color }
|
139
|
+
end
|
141
140
|
|
142
|
-
|
143
|
-
|
144
|
-
|
141
|
+
def layer_check_neighbors(face)
|
142
|
+
LAYER_CHECK_NEIGHBORS[face]
|
143
|
+
end
|
145
144
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
145
|
+
# Note that this does NOT say that the layer corresponding to the given face is solved.
|
146
|
+
# The face argument is used as the position where a solved face is present.
|
147
|
+
def layer_at_face_solved?(face)
|
148
|
+
return false unless native.face_solved?(face.face_symbol)
|
150
149
|
|
151
|
-
|
152
|
-
|
150
|
+
layer_check_neighbors(face).map { |c| self[c] }.uniq.length == 1
|
151
|
+
end
|
153
152
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
end
|
153
|
+
def rotate_face(face, direction)
|
154
|
+
neighbors = face.neighbors
|
155
|
+
inverse_order_face = face.coordinate_index_close_to(neighbors[0]) <
|
156
|
+
face.coordinate_index_close_to(neighbors[1])
|
157
|
+
direction = direction.inverse if inverse_order_face
|
158
|
+
cycle = SkewbCoordinate.corners_on_face(face)
|
159
|
+
apply_4sticker_cycle(cycle, direction)
|
162
160
|
end
|
161
|
+
end
|
163
162
|
end
|