twisty_puzzles 0.0.1 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -1
- data/README.md +6 -1
- data/ext/twisty_puzzles/native/cube_algorithm.c +267 -0
- data/ext/twisty_puzzles/native/cube_algorithm.h +5 -0
- data/ext/twisty_puzzles/native/cube_average.c +184 -0
- data/ext/twisty_puzzles/native/cube_average.h +5 -0
- data/ext/twisty_puzzles/native/cube_coordinate.c +207 -0
- data/ext/twisty_puzzles/native/cube_coordinate.h +34 -0
- data/ext/twisty_puzzles/native/cube_state.c +264 -0
- data/ext/twisty_puzzles/native/cube_state.h +31 -0
- data/ext/twisty_puzzles/native/extconf.rb +1 -1
- data/ext/twisty_puzzles/native/face_symbols.c +67 -0
- data/ext/twisty_puzzles/native/face_symbols.h +34 -0
- data/ext/twisty_puzzles/native/native.c +28 -0
- data/ext/twisty_puzzles/native/skewb_algorithm.c +331 -0
- data/ext/twisty_puzzles/native/skewb_algorithm.h +5 -0
- data/ext/twisty_puzzles/native/skewb_coordinate.c +237 -0
- data/ext/twisty_puzzles/native/skewb_coordinate.h +36 -0
- data/ext/twisty_puzzles/native/skewb_layer_fingerprint.c +271 -0
- data/ext/twisty_puzzles/native/skewb_layer_fingerprint.h +5 -0
- data/ext/twisty_puzzles/native/skewb_state.c +214 -0
- data/ext/twisty_puzzles/native/skewb_state.h +23 -0
- data/ext/twisty_puzzles/native/utils.c +76 -0
- data/ext/twisty_puzzles/native/utils.h +31 -0
- data/lib/twisty_puzzles.rb +38 -0
- data/lib/twisty_puzzles/abstract_direction.rb +38 -39
- data/lib/twisty_puzzles/abstract_move.rb +1 -2
- 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 +56 -56
- data/lib/twisty_puzzles/cancellation_helper.rb +124 -125
- data/lib/twisty_puzzles/color_scheme.rb +1 -1
- data/lib/twisty_puzzles/commutator.rb +82 -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 +243 -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 +285 -290
- data/lib/twisty_puzzles/cube_move_parser.rb +75 -76
- data/lib/twisty_puzzles/cube_print_helper.rb +133 -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 +76 -75
- data/lib/twisty_puzzles/skewb_direction.rb +14 -15
- data/lib/twisty_puzzles/skewb_move.rb +49 -49
- data/lib/twisty_puzzles/skewb_move_parser.rb +51 -51
- data/lib/twisty_puzzles/skewb_notation.rb +121 -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 +30 -10
@@ -4,56 +4,53 @@ require 'twisty_puzzles/sticker_cycle'
|
|
4
4
|
require 'twisty_puzzles/utils/array_helper'
|
5
5
|
|
6
6
|
module TwistyPuzzles
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
raise ArgumentError, "Invalid incarnation index #{incarnation_index}."
|
16
|
-
end
|
17
|
-
|
18
|
-
@cube_size = cube_size
|
19
|
-
@incarnation_index = incarnation_index
|
20
|
-
@cache = {}
|
7
|
+
# Factory for sticker cycles given part cycles.
|
8
|
+
class PartCycleFactory
|
9
|
+
include Utils::ArrayHelper
|
10
|
+
|
11
|
+
def initialize(cube_size, incarnation_index)
|
12
|
+
CubeState.check_cube_size(cube_size)
|
13
|
+
unless incarnation_index.is_a?(Integer) && incarnation_index >= 0
|
14
|
+
raise ArgumentError, "Invalid incarnation index #{incarnation_index}."
|
21
15
|
end
|
22
16
|
|
23
|
-
|
24
|
-
|
25
|
-
|
17
|
+
@cube_size = cube_size
|
18
|
+
@incarnation_index = incarnation_index
|
19
|
+
@cache = {}
|
20
|
+
end
|
26
21
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
22
|
+
def coordinates(part)
|
23
|
+
@cache[part] ||= Coordinate.solved_positions(part, @cube_size, @incarnation_index)
|
24
|
+
end
|
31
25
|
|
32
|
-
|
33
|
-
|
26
|
+
def multi_twist(parts)
|
27
|
+
unless parts.all? { |p| p.is_a?(Corner) || p.is_a?(Edge) }
|
28
|
+
raise TypeError, 'Twists are only supported for edges and corners.'
|
34
29
|
end
|
35
30
|
|
36
|
-
|
37
|
-
|
31
|
+
cycles = parts.map { |p| StickerCycle.new(@cube_size, coordinates(p)) }
|
32
|
+
StickerCycles.new(@cube_size, cycles)
|
33
|
+
end
|
38
34
|
|
39
|
-
|
40
|
-
|
35
|
+
def check_type_consistency(parts)
|
36
|
+
return unless parts.any? { |p| p.class != parts.first.class }
|
41
37
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
check_types(parts, Part)
|
53
|
-
check_type_consistency(parts)
|
54
|
-
part_coordinates = parts.map { |p| coordinates(p) }
|
55
|
-
cycles = part_coordinates.transpose.map { |c| StickerCycle.new(@cube_size, c) }
|
56
|
-
StickerCycles.new(@cube_size, cycles)
|
38
|
+
raise TypeError, "Cycles of heterogenous piece types #{parts.inspect} are not supported."
|
39
|
+
end
|
40
|
+
|
41
|
+
def construct(parts)
|
42
|
+
raise ArgumentError, 'Cycles of length smaller than 2 are not supported.' if parts.length < 2
|
43
|
+
|
44
|
+
unless @incarnation_index < parts.first.num_incarnations(@cube_size)
|
45
|
+
raise ArgumentError, "Incarnation index #{@incarnation_index} for cube size " \
|
46
|
+
"#{@cube_size} is not supported for #{parts.first.inspect}."
|
57
47
|
end
|
48
|
+
|
49
|
+
check_types(parts, Part)
|
50
|
+
check_type_consistency(parts)
|
51
|
+
part_coordinates = parts.map { |p| coordinates(p) }
|
52
|
+
cycles = part_coordinates.transpose.map { |c| StickerCycle.new(@cube_size, c) }
|
53
|
+
StickerCycles.new(@cube_size, cycles)
|
58
54
|
end
|
55
|
+
end
|
59
56
|
end
|
@@ -1,26 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module TwistyPuzzles
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
NXN_CUBE = Puzzle.new('nxn cube')
|
12
|
-
SKEWB = Puzzle.new('skewb')
|
4
|
+
# Represents one type of puzzle.
|
5
|
+
class Puzzle
|
6
|
+
def initialize(name)
|
7
|
+
@name = name
|
8
|
+
end
|
13
9
|
|
14
|
-
|
10
|
+
NXN_CUBE = Puzzle.new('nxn cube')
|
11
|
+
SKEWB = Puzzle.new('skewb')
|
15
12
|
|
16
|
-
|
17
|
-
self.class == other.class && name == other.name
|
18
|
-
end
|
13
|
+
attr_reader :name
|
19
14
|
|
20
|
-
|
21
|
-
|
22
|
-
|
15
|
+
def eql?(other)
|
16
|
+
self.class == other.class && name == other.name
|
17
|
+
end
|
23
18
|
|
24
|
-
|
19
|
+
def hash
|
20
|
+
@hash ||= [self.class, @name].hash
|
25
21
|
end
|
22
|
+
|
23
|
+
alias == eql?
|
24
|
+
end
|
26
25
|
end
|
@@ -3,35 +3,34 @@
|
|
3
3
|
require 'twisty_puzzles/skewb_state'
|
4
4
|
|
5
5
|
module TwistyPuzzles
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
6
|
+
# Module that makes a class that has an `apply_to` and `reverse` be able to apply temporarily.
|
7
|
+
module ReversibleApplyable
|
8
|
+
def apply_to_dupped(puzzle_state)
|
9
|
+
dupped = puzzle_state.dup
|
10
|
+
apply_to(dupped)
|
11
|
+
dupped
|
12
|
+
end
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
# Applies the current algorithm/cycle/whatever to the given puzzle state and yields the
|
15
|
+
# modified version. The puzzle state will be the same as the original after this function
|
16
|
+
# returns.
|
17
|
+
# Whether the yielded puzzle state is actually the same as the passed one or a copy is an
|
18
|
+
# implementation detail.
|
19
|
+
def apply_temporarily_to(puzzle_state)
|
20
|
+
return yield(apply_to_dupped(puzzle_state)) if with_dup_is_faster?(puzzle_state)
|
22
21
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
22
|
+
apply_to(puzzle_state)
|
23
|
+
begin
|
24
|
+
yield(puzzle_state)
|
25
|
+
ensure
|
26
|
+
inverse.apply_to(puzzle_state)
|
29
27
|
end
|
28
|
+
end
|
30
29
|
|
31
|
-
|
30
|
+
private
|
32
31
|
|
33
|
-
|
34
|
-
|
35
|
-
end
|
32
|
+
def with_dup_is_faster?(state)
|
33
|
+
!state.is_a?(CubeState) || state.n <= 4
|
36
34
|
end
|
35
|
+
end
|
37
36
|
end
|
@@ -8,98 +8,99 @@ require 'twisty_puzzles/cube_move'
|
|
8
8
|
require 'twisty_puzzles/puzzle'
|
9
9
|
|
10
10
|
module TwistyPuzzles
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
when SkewbDirection::BACKWARD then CubeDirection::BACKWARD
|
26
|
-
end
|
11
|
+
# A rotation of a Skewb or cube.
|
12
|
+
class Rotation < AxisFaceAndDirectionMove
|
13
|
+
ALL_ROTATIONS = Face::ELEMENTS.product(CubeDirection::ALL_DIRECTIONS).map { |f, d| new(f, d) }
|
14
|
+
NON_ZERO_ROTATIONS =
|
15
|
+
Face::ELEMENTS.product(CubeDirection::NON_ZERO_DIRECTIONS).map { |f, d| new(f, d) }
|
16
|
+
LEFT = new(Face::U, CubeDirection::BACKWARD)
|
17
|
+
RIGHT = new(Face::U, CubeDirection::FORWARD)
|
18
|
+
|
19
|
+
# Translates a Skewb direction into a cube direction.
|
20
|
+
def self.translated_direction(direction)
|
21
|
+
case direction
|
22
|
+
when SkewbDirection::ZERO then CubeDirection::ZERO
|
23
|
+
when SkewbDirection::FORWARD then CubeDirection::FORWARD
|
24
|
+
when SkewbDirection::BACKWARD then CubeDirection::BACKWARD
|
27
25
|
end
|
26
|
+
end
|
28
27
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
28
|
+
# Returns an algorithm consisting of two rotations that are equivalent to rotating
|
29
|
+
# the puzzle around a corner.
|
30
|
+
# Takes a Skewb direction as an argument (even for cubes) because rotating around
|
31
|
+
# is like a Skewb move given that it's modulo 3.
|
32
|
+
def self.around_corner(corner, skewb_direction)
|
33
|
+
raise TypeError unless corner.is_a?(Corner)
|
34
|
+
raise TypeError unless skewb_direction.is_a?(SkewbDirection)
|
36
35
|
|
37
|
-
|
36
|
+
direction = translated_direction(skewb_direction)
|
38
37
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
38
|
+
Algorithm.new([
|
39
|
+
Rotation.new(corner.faces[skewb_direction.value], direction),
|
40
|
+
Rotation.new(corner.faces[0], direction)
|
41
|
+
])
|
42
|
+
end
|
44
43
|
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
def to_s
|
45
|
+
"#{AXES[@axis_face.axis_priority]}#{canonical_direction.name}"
|
46
|
+
end
|
48
47
|
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
def puzzles
|
49
|
+
[Puzzle::SKEWB, Puzzle::NXN_CUBE]
|
50
|
+
end
|
52
51
|
|
53
|
-
|
54
|
-
|
55
|
-
|
52
|
+
def slice_move?
|
53
|
+
false
|
54
|
+
end
|
56
55
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
56
|
+
# Returns an alternative representation of the same rotation
|
57
|
+
def alternative
|
58
|
+
Rotation.new(@axis_face.opposite, @direction.inverse)
|
59
|
+
end
|
61
60
|
|
62
|
-
|
63
|
-
|
64
|
-
|
61
|
+
def equivalent_internal?(other, _cube_size)
|
62
|
+
[self, alternative].include?(other)
|
63
|
+
end
|
65
64
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
65
|
+
# rubocop:disable Metrics/AbcSize
|
66
|
+
def prepend_rotation(other, _cube_size)
|
67
|
+
if same_axis?(other)
|
68
|
+
direction = translated_direction(other.axis_face)
|
69
|
+
Algorithm.move(Rotation.new(other.axis_face, direction + other.direction))
|
70
|
+
elsif @direction.double_move? && other.direction.double_move?
|
71
|
+
used_axis_priorities = [@axis_face, other.axis_face].map(&:axis_priority)
|
72
|
+
# Note that there are two solutions, but any works.
|
73
|
+
remaining_face =
|
74
|
+
Face::ELEMENTS.find { |f| !used_axis_priorities.include?(f.axis_priority) }
|
75
|
+
Algorithm.move(Rotation.new(remaining_face, CubeDirection::DOUBLE))
|
77
76
|
end
|
77
|
+
end
|
78
|
+
# rubocop:enable Metrics/AbcSize
|
78
79
|
|
79
|
-
|
80
|
-
|
81
|
-
|
80
|
+
def prepend_fat_m_slice_move(_other, _cube_size)
|
81
|
+
nil
|
82
|
+
end
|
82
83
|
|
83
|
-
|
84
|
-
|
84
|
+
def prepend_fat_move(other, cube_size)
|
85
|
+
return unless compatible_fat_move?(other)
|
85
86
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
87
|
+
Algorithm.move(
|
88
|
+
FatMove.new(other.axis_face.opposite, other.direction, other.inverted_width(cube_size))
|
89
|
+
)
|
90
|
+
end
|
90
91
|
|
91
|
-
|
92
|
-
|
93
|
-
|
92
|
+
def prepend_slice_move(_other, _cube_size)
|
93
|
+
nil
|
94
|
+
end
|
94
95
|
|
95
|
-
|
96
|
-
|
97
|
-
|
96
|
+
def move_count(_cube_size, _metric = :htm)
|
97
|
+
0
|
98
|
+
end
|
98
99
|
|
99
|
-
|
100
|
+
private
|
100
101
|
|
101
|
-
|
102
|
-
|
103
|
-
end
|
102
|
+
def compatible_fat_move?(other)
|
103
|
+
same_axis?(other) && translated_direction(other.axis_face) == other.direction.inverse
|
104
104
|
end
|
105
|
+
end
|
105
106
|
end
|
@@ -3,22 +3,21 @@
|
|
3
3
|
require 'twisty_puzzles/abstract_direction'
|
4
4
|
|
5
5
|
module TwistyPuzzles
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
BACKWARD = new(2)
|
6
|
+
# Represents the direction of a Skewb move except a rotation.
|
7
|
+
class SkewbDirection < AbstractDirection
|
8
|
+
NUM_DIRECTIONS = 3
|
9
|
+
NON_ZERO_DIRECTIONS = (1...NUM_DIRECTIONS).map { |d| new(d) }.freeze
|
10
|
+
ALL_DIRECTIONS = Array.new(NUM_DIRECTIONS) { |d| new(d) }.freeze
|
11
|
+
ZERO = new(0)
|
12
|
+
FORWARD = new(1)
|
13
|
+
BACKWARD = new(2)
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
def name
|
16
|
+
SIMPLE_SKEWB_DIRECTION_NAMES[@value]
|
17
|
+
end
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
end
|
19
|
+
def double_move?
|
20
|
+
false
|
23
21
|
end
|
22
|
+
end
|
24
23
|
end
|
@@ -6,54 +6,54 @@ require 'twisty_puzzles/skewb_direction'
|
|
6
6
|
require 'twisty_puzzles/puzzle'
|
7
7
|
|
8
8
|
module TwistyPuzzles
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
9
|
+
# Base class for skewb moves.
|
10
|
+
class SkewbMove < AbstractMove
|
11
|
+
def initialize(axis_corner, direction)
|
12
|
+
raise TypeError unless axis_corner.is_a?(Corner)
|
13
|
+
raise TypeError unless direction.is_a?(SkewbDirection)
|
14
|
+
|
15
|
+
super()
|
16
|
+
@axis_corner = axis_corner.rotate_face_up(axis_corner.faces.min_by(&:piece_index))
|
17
|
+
@direction = direction
|
58
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
59
|
end
|