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.
- 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
|