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