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