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
@@ -3,46 +3,45 @@
|
|
3
3
|
require 'twisty_puzzles/reversible_applyable'
|
4
4
|
|
5
5
|
module TwistyPuzzles
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
include ReversibleApplyable
|
6
|
+
# Base class for a compiled algorithm for a particular puzzle.
|
7
|
+
class CompiledAlgorithm
|
8
|
+
include ReversibleApplyable
|
10
9
|
|
11
|
-
|
12
|
-
|
10
|
+
def initialize(native)
|
11
|
+
raise TypeError unless native.is_a?(self.class::NATIVE_CLASS)
|
13
12
|
|
14
|
-
|
15
|
-
|
13
|
+
@native = native
|
14
|
+
end
|
16
15
|
|
17
|
-
|
16
|
+
attr_reader :native
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
def rotate_by(rotation)
|
19
|
+
self.class.new(@native.rotate_by(rotation.axis_face.face_symbol, rotation.direction.value))
|
20
|
+
end
|
22
21
|
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
def mirror(normal_face)
|
23
|
+
self.class.new(@native.mirror(normal_face.face_symbol))
|
24
|
+
end
|
26
25
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
26
|
+
def inverse
|
27
|
+
@inverse ||=
|
28
|
+
begin
|
29
|
+
alg = self.class.new(@native.inverse)
|
30
|
+
alg.inverse = self
|
31
|
+
alg
|
32
|
+
end
|
33
|
+
end
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
def +(other)
|
36
|
+
self.class.new(@native + other.native)
|
37
|
+
end
|
39
38
|
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
def apply_to(state)
|
40
|
+
@native.apply_to(state.native)
|
41
|
+
end
|
43
42
|
|
44
|
-
|
43
|
+
protected
|
45
44
|
|
46
|
-
|
47
|
-
|
45
|
+
attr_writer :inverse
|
46
|
+
end
|
48
47
|
end
|
@@ -3,65 +3,64 @@
|
|
3
3
|
require 'twisty_puzzles/compiled_algorithm'
|
4
4
|
|
5
5
|
module TwistyPuzzles
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
0.upto(cube_size - 1).map do |i|
|
12
|
-
[:slice, move.axis_face.face_symbol, move.direction.value, i]
|
13
|
-
end
|
14
|
-
[
|
15
|
-
[:face, move.axis_face.face_symbol, move.direction.value],
|
16
|
-
[:face, move.axis_face.opposite.face_symbol, move.direction.inverse.value]
|
17
|
-
] + slice_moves
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.transform_fat_mslice_move(move, cube_size)
|
21
|
-
1.upto(cube_size - 2).map do |i|
|
6
|
+
# Wrapper of the native C implementation of a compiled algorithm for a particular cube size.
|
7
|
+
class CompiledCubeAlgorithm < CompiledAlgorithm
|
8
|
+
def self.transform_rotation(move, cube_size)
|
9
|
+
slice_moves =
|
10
|
+
0.upto(cube_size - 1).map do |i|
|
22
11
|
[:slice, move.axis_face.face_symbol, move.direction.value, i]
|
23
12
|
end
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
]
|
30
|
-
end
|
13
|
+
[
|
14
|
+
[:face, move.axis_face.face_symbol, move.direction.value],
|
15
|
+
[:face, move.axis_face.opposite.face_symbol, move.direction.inverse.value]
|
16
|
+
] + slice_moves
|
17
|
+
end
|
31
18
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
[:slice, move.axis_face.face_symbol, move.direction.value, i]
|
36
|
-
end
|
37
|
-
[
|
38
|
-
[:face, move.axis_face.face_symbol, move.direction.value]
|
39
|
-
] + slice_moves
|
19
|
+
def self.transform_fat_mslice_move(move, cube_size)
|
20
|
+
1.upto(cube_size - 2).map do |i|
|
21
|
+
[:slice, move.axis_face.face_symbol, move.direction.value, i]
|
40
22
|
end
|
23
|
+
end
|
41
24
|
|
42
|
-
|
43
|
-
|
25
|
+
def self.transform_slice_move(move)
|
26
|
+
[
|
27
|
+
[:slice, move.axis_face.face_symbol, move.direction.value, move.slice_index]
|
28
|
+
]
|
29
|
+
end
|
44
30
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
when FatMSliceMove then transform_fat_mslice_move(decided_move, cube_size)
|
50
|
-
# Note that this also covers InnerMSliceMove
|
51
|
-
when SliceMove then transform_slice_move(decided_move)
|
52
|
-
when FatMove then transform_fat_move(decided_move)
|
53
|
-
else
|
54
|
-
raise TypeError, "Invalid move type #{move.class} that becomes #{decided_move.class} "\
|
55
|
-
"for cube size #{cube_size}."
|
31
|
+
def self.transform_fat_move(move)
|
32
|
+
slice_moves =
|
33
|
+
0.upto(move.width - 1).map do |i|
|
34
|
+
[:slice, move.axis_face.face_symbol, move.direction.value, i]
|
56
35
|
end
|
57
|
-
|
36
|
+
[
|
37
|
+
[:face, move.axis_face.face_symbol, move.direction.value]
|
38
|
+
] + slice_moves
|
39
|
+
end
|
58
40
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
41
|
+
private_class_method :transform_rotation, :transform_fat_mslice_move, :transform_slice_move,
|
42
|
+
:transform_fat_move
|
43
|
+
|
44
|
+
def self.transform_move(move, cube_size)
|
45
|
+
decided_move = move.decide_meaning(cube_size)
|
46
|
+
case decided_move
|
47
|
+
when Rotation then transform_rotation(decided_move, cube_size)
|
48
|
+
when FatMSliceMove then transform_fat_mslice_move(decided_move, cube_size)
|
49
|
+
# Note that this also covers InnerMSliceMove
|
50
|
+
when SliceMove then transform_slice_move(decided_move)
|
51
|
+
when FatMove then transform_fat_move(decided_move)
|
52
|
+
else
|
53
|
+
raise TypeError, "Invalid move type #{move.class} that becomes #{decided_move.class} "\
|
54
|
+
"for cube size #{cube_size}."
|
63
55
|
end
|
56
|
+
end
|
64
57
|
|
65
|
-
|
58
|
+
def self.for_moves(cube_size, moves)
|
59
|
+
transformed_moves = moves.collect_concat { |m| transform_move(m, cube_size) }
|
60
|
+
native = Native::CubeAlgorithm.new(cube_size, transformed_moves)
|
61
|
+
new(native)
|
66
62
|
end
|
63
|
+
|
64
|
+
NATIVE_CLASS = Native::CubeAlgorithm
|
65
|
+
end
|
67
66
|
end
|
@@ -3,26 +3,25 @@
|
|
3
3
|
require 'twisty_puzzles/compiled_algorithm'
|
4
4
|
|
5
5
|
module TwistyPuzzles
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
raise TypeError
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.for_moves(moves)
|
21
|
-
native = Native::SkewbAlgorithm.new(moves.map { |m| transform_move(m) })
|
22
|
-
new(native)
|
6
|
+
# Wrapper of the native C implementation of a compiled algorithm for a particular cube size.
|
7
|
+
class CompiledSkewbAlgorithm < CompiledAlgorithm
|
8
|
+
def self.transform_move(move)
|
9
|
+
case move
|
10
|
+
when Rotation
|
11
|
+
[:rotation, move.axis_face.face_symbol, move.direction.value]
|
12
|
+
when SkewbMove
|
13
|
+
[:move, move.axis_corner.face_symbols, move.direction.value]
|
14
|
+
else
|
15
|
+
raise TypeError
|
23
16
|
end
|
17
|
+
end
|
24
18
|
|
25
|
-
|
26
|
-
|
19
|
+
def self.for_moves(moves)
|
20
|
+
native = Native::SkewbAlgorithm.new(moves.map { |m| transform_move(m) })
|
21
|
+
new(native)
|
27
22
|
end
|
23
|
+
|
24
|
+
NATIVE_CLASS = Native::SkewbAlgorithm
|
25
|
+
EMPTY = for_moves([])
|
26
|
+
end
|
28
27
|
end
|
@@ -5,314 +5,311 @@ require 'twisty_puzzles/cube_constants'
|
|
5
5
|
require 'twisty_puzzles/native'
|
6
6
|
|
7
7
|
module TwistyPuzzles
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.invert_coordinate(index, cube_size)
|
16
|
-
highest_coordinate(cube_size) - index
|
17
|
-
end
|
8
|
+
# Coordinate of a sticker on the cube.
|
9
|
+
class Coordinate
|
10
|
+
def self.highest_coordinate(cube_size)
|
11
|
+
cube_size - 1
|
12
|
+
end
|
18
13
|
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
def self.invert_coordinate(index, cube_size)
|
15
|
+
highest_coordinate(cube_size) - index
|
16
|
+
end
|
22
17
|
|
23
|
-
|
24
|
-
|
18
|
+
def self.coordinate_range(cube_size)
|
19
|
+
0.upto(highest_coordinate(cube_size))
|
20
|
+
end
|
25
21
|
|
26
|
-
|
27
|
-
|
22
|
+
def self.middle(cube_size)
|
23
|
+
raise ArgumentError if cube_size.even?
|
28
24
|
|
29
|
-
|
30
|
-
|
31
|
-
cube_size - cube_size / 2 - 1
|
32
|
-
end
|
25
|
+
cube_size / 2
|
26
|
+
end
|
33
27
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
28
|
+
# Middle coordinate for uneven numbers, the one before for even numbers
|
29
|
+
def self.middle_or_before(cube_size)
|
30
|
+
cube_size - cube_size / 2 - 1
|
31
|
+
end
|
38
32
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
33
|
+
# Middle coordinate for uneven numbers, the one after for even numbers
|
34
|
+
def self.middle_or_after(cube_size)
|
35
|
+
cube_size / 2
|
36
|
+
end
|
43
37
|
|
44
|
-
|
45
|
-
|
38
|
+
# The last coordinate that is strictly before the middle
|
39
|
+
def self.last_before_middle(cube_size)
|
40
|
+
cube_size / 2 - 1
|
41
|
+
end
|
46
42
|
|
47
|
-
|
48
|
-
|
43
|
+
def self.canonicalize(index, cube_size)
|
44
|
+
raise ArgumentError unless index.is_a?(Integer) && -cube_size <= index && index < cube_size
|
49
45
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
coordinates = [nil, nil]
|
54
|
-
face_distances.each do |neighbor, distance|
|
55
|
-
index = face.coordinate_index_close_to(neighbor)
|
56
|
-
coordinate =
|
57
|
-
if neighbor.close_to_smaller_indices?
|
58
|
-
distance
|
59
|
-
else
|
60
|
-
invert_coordinate(distance, cube_size)
|
61
|
-
end
|
62
|
-
raise ArgumentError if coordinates[index]
|
63
|
-
|
64
|
-
coordinates[index] = coordinate
|
65
|
-
end
|
66
|
-
raise ArgumentError if coordinates.any?(&:nil?)
|
46
|
+
index >= 0 ? index : cube_size + index
|
47
|
+
end
|
67
48
|
|
68
|
-
|
69
|
-
|
49
|
+
def self.from_face_distances(face, cube_size, face_distances)
|
50
|
+
raise ArgumentError if face_distances.length != 2
|
70
51
|
|
71
|
-
|
72
|
-
|
52
|
+
coordinates = [nil, nil]
|
53
|
+
face_distances.each do |neighbor, distance|
|
54
|
+
index = face.coordinate_index_close_to(neighbor)
|
73
55
|
coordinate =
|
74
|
-
|
75
|
-
|
76
|
-
|
56
|
+
if neighbor.close_to_smaller_indices?
|
57
|
+
distance
|
58
|
+
else
|
59
|
+
invert_coordinate(distance, cube_size)
|
77
60
|
end
|
78
|
-
raise
|
61
|
+
raise ArgumentError if coordinates[index]
|
79
62
|
|
80
|
-
coordinate
|
63
|
+
coordinates[index] = coordinate
|
81
64
|
end
|
65
|
+
raise ArgumentError if coordinates.any?(&:nil?)
|
82
66
|
|
83
|
-
|
84
|
-
|
85
|
-
raise TypeError unless part.is_a?(Part)
|
86
|
-
raise unless part.class::ELEMENTS.length == 24
|
87
|
-
raise unless incarnation_index >= 0 && incarnation_index < part.num_incarnations(cube_size)
|
88
|
-
|
89
|
-
# This is a coordinate on the same face and belonging to an equivalent part.
|
90
|
-
# But it might not be the right one.
|
91
|
-
base_coordinate = Coordinate.from_indices(
|
92
|
-
part.solved_face, cube_size, *part.base_index_on_face(cube_size, incarnation_index)
|
93
|
-
)
|
94
|
-
other_face_symbols = part.corresponding_part.face_symbols[1..-1]
|
95
|
-
match_coordinate_internal(base_coordinate, other_face_symbols)
|
96
|
-
end
|
67
|
+
from_indices(face, cube_size, *coordinates)
|
68
|
+
end
|
97
69
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
# solved pieces would be.
|
107
|
-
# For other types of pieces, it doesn't make a difference as the base index will just be
|
108
|
-
# a rotation of the original one, but we will anyway look at all rotations later.
|
109
|
-
base_indices = part.base_index_on_other_face(face, cube_size, incarnation_index).reverse
|
110
|
-
base_coordinate = Coordinate.from_indices(face, cube_size, *base_indices)
|
111
|
-
other_face_symbols = [part.face_symbols[0]] +
|
112
|
-
part.corresponding_part.face_symbols[1...i + 1] +
|
113
|
-
part.corresponding_part.face_symbols[i + 2..-1]
|
114
|
-
match_coordinate_internal(base_coordinate, other_face_symbols)
|
115
|
-
end
|
116
|
-
[solved_coordinate] + other_coordinates
|
117
|
-
end
|
118
|
-
# rubocop:enable Metrics/AbcSize
|
70
|
+
def self.match_coordinate_internal(base_coordinate, other_face_symbols)
|
71
|
+
other_face_symbols.sort!
|
72
|
+
coordinate =
|
73
|
+
base_coordinate.rotations.find do |coord|
|
74
|
+
face_symbols_closeby = coord.close_neighbor_faces.map(&:face_symbol)
|
75
|
+
face_symbols_closeby.sort == other_face_symbols
|
76
|
+
end
|
77
|
+
raise "Couldn't find a fitting coordinate on the solved face." if coordinate.nil?
|
119
78
|
|
120
|
-
|
121
|
-
|
122
|
-
from_indices(face, cube_size, m, m)
|
123
|
-
end
|
79
|
+
coordinate
|
80
|
+
end
|
124
81
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
82
|
+
# The coordinate of the solved position of the main sticker of this part.
|
83
|
+
def self.solved_position(part, cube_size, incarnation_index)
|
84
|
+
raise TypeError unless part.is_a?(Part)
|
85
|
+
raise unless part.class::ELEMENTS.length == 24
|
86
|
+
raise unless incarnation_index >= 0 && incarnation_index < part.num_incarnations(cube_size)
|
87
|
+
|
88
|
+
# This is a coordinate on the same face and belonging to an equivalent part.
|
89
|
+
# But it might not be the right one.
|
90
|
+
base_coordinate = Coordinate.from_indices(
|
91
|
+
part.solved_face, cube_size, *part.base_index_on_face(cube_size, incarnation_index)
|
92
|
+
)
|
93
|
+
other_face_symbols = part.corresponding_part.face_symbols[1..]
|
94
|
+
match_coordinate_internal(base_coordinate, other_face_symbols)
|
95
|
+
end
|
96
|
+
|
97
|
+
# The coordinate of the solved position of all stickers of this part.
|
98
|
+
# rubocop:disable Metrics/AbcSize
|
99
|
+
def self.solved_positions(part, cube_size, incarnation_index)
|
100
|
+
solved_coordinate = solved_position(part, cube_size, incarnation_index)
|
101
|
+
other_coordinates =
|
102
|
+
part.face_symbols[1..].map.with_index do |f, i|
|
103
|
+
face = Face.for_face_symbol(f)
|
104
|
+
# The reverse is important for edge like parts. We are not in the same position as usual
|
105
|
+
# solved pieces would be.
|
106
|
+
# For other types of pieces, it doesn't make a difference as the base index will just be
|
107
|
+
# a rotation of the original one, but we will anyway look at all rotations later.
|
108
|
+
base_indices = part.base_index_on_other_face(face, cube_size, incarnation_index).reverse
|
109
|
+
base_coordinate = Coordinate.from_indices(face, cube_size, *base_indices)
|
110
|
+
other_face_symbols = [part.face_symbols[0]] +
|
111
|
+
part.corresponding_part.face_symbols[1...i + 1] +
|
112
|
+
part.corresponding_part.face_symbols[i + 2..]
|
113
|
+
match_coordinate_internal(base_coordinate, other_face_symbols)
|
130
114
|
end
|
131
|
-
|
115
|
+
[solved_coordinate] + other_coordinates
|
116
|
+
end
|
117
|
+
# rubocop:enable Metrics/AbcSize
|
132
118
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
cube_size,
|
142
|
-
|
143
|
-
face.coordinate_index_base_face(0).face_symbol,
|
144
|
-
face.coordinate_index_base_face(1).face_symbol,
|
145
|
-
x,
|
146
|
-
y
|
147
|
-
)
|
148
|
-
new(native)
|
119
|
+
def self.center(face, cube_size)
|
120
|
+
m = middle(cube_size)
|
121
|
+
from_indices(face, cube_size, m, m)
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.edges_outside(face, cube_size)
|
125
|
+
face.neighbors.zip(face.neighbors.rotate(1)).collect_concat do |neighbor, next_neighbor|
|
126
|
+
1.upto(cube_size - 2).map do |i|
|
127
|
+
from_face_distances(neighbor, cube_size, face => 0, next_neighbor => i)
|
128
|
+
end
|
149
129
|
end
|
130
|
+
end
|
150
131
|
|
151
|
-
|
132
|
+
def self.from_indices(face, cube_size, x_index, y_index)
|
133
|
+
raise TypeError, "Unsuitable face #{face.inspect}." unless face.is_a?(Face)
|
134
|
+
raise TypeError unless cube_size.is_a?(Integer)
|
135
|
+
raise ArgumentError unless cube_size.positive?
|
136
|
+
|
137
|
+
x = Coordinate.canonicalize(x_index, cube_size)
|
138
|
+
y = Coordinate.canonicalize(y_index, cube_size)
|
139
|
+
native = Native::CubeCoordinate.new(
|
140
|
+
cube_size,
|
141
|
+
face.face_symbol,
|
142
|
+
face.coordinate_index_base_face(0).face_symbol,
|
143
|
+
face.coordinate_index_base_face(1).face_symbol,
|
144
|
+
x,
|
145
|
+
y
|
146
|
+
)
|
147
|
+
new(native)
|
148
|
+
end
|
152
149
|
|
153
|
-
|
154
|
-
raise TypeError unless native.is_a?(Native::CubeCoordinate)
|
150
|
+
private_class_method :new
|
155
151
|
|
156
|
-
|
157
|
-
|
152
|
+
def initialize(native)
|
153
|
+
raise TypeError unless native.is_a?(Native::CubeCoordinate)
|
158
154
|
|
159
|
-
|
155
|
+
@native = native
|
156
|
+
end
|
160
157
|
|
161
|
-
|
162
|
-
@face ||= Face.for_face_symbol(@native.face)
|
163
|
-
end
|
158
|
+
attr_reader :native
|
164
159
|
|
165
|
-
|
166
|
-
|
167
|
-
|
160
|
+
def face
|
161
|
+
@face ||= Face.for_face_symbol(@native.face)
|
162
|
+
end
|
168
163
|
|
169
|
-
|
170
|
-
|
171
|
-
|
164
|
+
def cube_size
|
165
|
+
@cube_size ||= @native.cube_size
|
166
|
+
end
|
172
167
|
|
173
|
-
|
174
|
-
|
175
|
-
|
168
|
+
def coordinate(coordinate_index)
|
169
|
+
native.coordinate(face.coordinate_index_base_face(coordinate_index).face_symbol)
|
170
|
+
end
|
176
171
|
|
177
|
-
|
178
|
-
|
179
|
-
|
172
|
+
def coordinates
|
173
|
+
@coordinates ||= [x, y].freeze
|
174
|
+
end
|
180
175
|
|
181
|
-
|
182
|
-
|
183
|
-
|
176
|
+
def x
|
177
|
+
@x ||= coordinate(0)
|
178
|
+
end
|
184
179
|
|
185
|
-
|
186
|
-
|
187
|
-
|
180
|
+
def y
|
181
|
+
@y ||= coordinate(1)
|
182
|
+
end
|
188
183
|
|
189
|
-
|
184
|
+
def eql?(other)
|
185
|
+
self.class.equal?(other.class) && @native == other.native
|
186
|
+
end
|
190
187
|
|
191
|
-
|
192
|
-
[self.class, @native].hash
|
193
|
-
end
|
188
|
+
alias == eql?
|
194
189
|
|
195
|
-
|
196
|
-
|
190
|
+
def hash
|
191
|
+
[self.class, @native].hash
|
192
|
+
end
|
197
193
|
|
198
|
-
|
199
|
-
|
200
|
-
(jump_coordinate.zero? && to_face.close_to_smaller_indices?) ||
|
201
|
-
(jump_coordinate == Coordinate.highest_coordinate(cube_size) &&
|
202
|
-
!to_face.close_to_smaller_indices?)
|
203
|
-
end
|
194
|
+
def can_jump_to?(to_face)
|
195
|
+
raise ArgumentError unless to_face.is_a?(Face)
|
204
196
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
197
|
+
jump_coordinate_index = face.coordinate_index_close_to(to_face)
|
198
|
+
jump_coordinate = coordinates[jump_coordinate_index]
|
199
|
+
(jump_coordinate.zero? && to_face.close_to_smaller_indices?) ||
|
200
|
+
(jump_coordinate == Coordinate.highest_coordinate(cube_size) &&
|
201
|
+
!to_face.close_to_smaller_indices?)
|
202
|
+
end
|
209
203
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
Coordinate.from_indices(to_face, cube_size, *new_coordinates)
|
215
|
-
end
|
204
|
+
def jump_to_neighbor(to_face)
|
205
|
+
raise ArgumentError unless to_face.is_a?(Face)
|
206
|
+
raise ArgumentError unless face.neighbors.include?(to_face)
|
207
|
+
raise ArgumentError unless can_jump_to?(to_face)
|
216
208
|
|
217
|
-
|
218
|
-
|
219
|
-
|
209
|
+
new_coordinates = coordinates.dup
|
210
|
+
new_coordinate_index = to_face.coordinate_index_close_to(face)
|
211
|
+
new_coordinate = make_coordinate_at_edge_to(face)
|
212
|
+
new_coordinates.insert(new_coordinate_index, new_coordinate)
|
213
|
+
Coordinate.from_indices(to_face, cube_size, *new_coordinates)
|
214
|
+
end
|
220
215
|
|
221
|
-
|
222
|
-
|
223
|
-
|
216
|
+
def jump_to_coordinates(new_coordinates)
|
217
|
+
Coordinate.from_indices(@face, @cube_size, *new_coordinates)
|
218
|
+
end
|
224
219
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
coordinate = coordinates[face.coordinate_index_close_to(neighbor)]
|
229
|
-
if neighbor.close_to_smaller_indices?
|
230
|
-
before_middle?(coordinate)
|
231
|
-
else
|
232
|
-
after_middle?(coordinate)
|
233
|
-
end
|
234
|
-
end
|
235
|
-
end
|
220
|
+
def make_coordinate_at_edge_to(face)
|
221
|
+
face.close_to_smaller_indices? ? 0 : Coordinate.highest_coordinate(cube_size)
|
222
|
+
end
|
236
223
|
|
237
|
-
|
238
|
-
|
224
|
+
# Returns neighbor faces that are closer to this coordinate than their opposite face.
|
225
|
+
def close_neighbor_faces
|
226
|
+
face.neighbors.select do |neighbor|
|
227
|
+
coordinate = coordinates[face.coordinate_index_close_to(neighbor)]
|
228
|
+
if neighbor.close_to_smaller_indices?
|
229
|
+
before_middle?(coordinate)
|
230
|
+
else
|
231
|
+
after_middle?(coordinate)
|
232
|
+
end
|
239
233
|
end
|
234
|
+
end
|
240
235
|
|
241
|
-
|
242
|
-
|
243
|
-
|
236
|
+
def after_middle?(index)
|
237
|
+
Coordinate.canonicalize(index, cube_size) > Coordinate.middle_or_before(cube_size)
|
238
|
+
end
|
244
239
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
jump_to_coordinates([y, Coordinate.invert_coordinate(x, cube_size)])
|
249
|
-
end
|
240
|
+
def before_middle?(index)
|
241
|
+
Coordinate.canonicalize(index, cube_size) <= Coordinate.last_before_middle(cube_size)
|
242
|
+
end
|
250
243
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
4.times do
|
257
|
-
rots.push(current)
|
258
|
-
current = current.rotate
|
259
|
-
end
|
260
|
-
raise unless current == self
|
244
|
+
# On a nxn grid with integer coordinates between 0 and n - 1, iterates between the 4 points
|
245
|
+
# that point (x, y) hits if you rotate by 90 degrees.
|
246
|
+
def rotate
|
247
|
+
jump_to_coordinates([y, Coordinate.invert_coordinate(x, cube_size)])
|
248
|
+
end
|
261
249
|
|
262
|
-
|
250
|
+
# On a nxn grid with integer coordinates between 0 and n - 1, give the 4 points that point
|
251
|
+
# (x, y) hits if you do a full rotation of the face in clockwise order.
|
252
|
+
def rotations
|
253
|
+
rots = []
|
254
|
+
current = self
|
255
|
+
4.times do
|
256
|
+
rots.push(current)
|
257
|
+
current = current.rotate
|
263
258
|
end
|
264
|
-
|
259
|
+
raise unless current == self
|
265
260
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
include CubeConstants
|
270
|
-
def initialize(face, coordinate, native)
|
271
|
-
raise ArgumentError, "Unsuitable face #{face.inspect}." unless face.is_a?(Face)
|
272
|
-
unless coordinate.is_a?(Integer) && coordinate >= 0 && coordinate < SKEWB_STICKERS
|
273
|
-
raise ArgumentError
|
274
|
-
end
|
261
|
+
rots
|
262
|
+
end
|
263
|
+
end
|
275
264
|
|
276
|
-
|
277
|
-
|
265
|
+
# Coordinate of a sticker on the Skewb.
|
266
|
+
class SkewbCoordinate
|
267
|
+
include Comparable
|
268
|
+
include CubeConstants
|
269
|
+
def initialize(face, coordinate, native)
|
270
|
+
raise ArgumentError, "Unsuitable face #{face.inspect}." unless face.is_a?(Face)
|
271
|
+
unless coordinate.is_a?(Integer) && coordinate >= 0 && coordinate < SKEWB_STICKERS
|
272
|
+
raise ArgumentError
|
278
273
|
end
|
279
274
|
|
280
|
-
|
275
|
+
@coordinate = coordinate
|
276
|
+
@native = native
|
277
|
+
end
|
281
278
|
|
282
|
-
|
279
|
+
attr_reader :native, :coordinate
|
283
280
|
|
284
|
-
|
285
|
-
native = Native::SkewbCoordinate.for_center(face.face_symbol)
|
286
|
-
new(face, 0, native)
|
287
|
-
end
|
281
|
+
private_class_method :new
|
288
282
|
|
289
|
-
|
290
|
-
|
291
|
-
|
283
|
+
def self.for_center(face)
|
284
|
+
native = Native::SkewbCoordinate.for_center(face.face_symbol)
|
285
|
+
new(face, 0, native)
|
286
|
+
end
|
292
287
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
end
|
288
|
+
def self.corners_on_face(face)
|
289
|
+
face.clockwise_corners.map { |c| for_corner(c) }
|
290
|
+
end
|
297
291
|
|
298
|
-
|
299
|
-
|
300
|
-
|
292
|
+
def self.for_corner(corner)
|
293
|
+
native = Native::SkewbCoordinate.for_corner(corner.face_symbols)
|
294
|
+
new(Face.for_face_symbol(corner.face_symbols.first), 1 + corner.piece_index % 4, native)
|
295
|
+
end
|
301
296
|
|
302
|
-
|
303
|
-
|
304
|
-
|
297
|
+
def hash
|
298
|
+
@hash ||= [self.class, @native].hash
|
299
|
+
end
|
305
300
|
|
306
|
-
|
301
|
+
def eql?(other)
|
302
|
+
@native.eql?(other.native)
|
303
|
+
end
|
307
304
|
|
308
|
-
|
309
|
-
@native <=> other.native
|
310
|
-
end
|
305
|
+
alias == eql?
|
311
306
|
|
312
|
-
|
313
|
-
|
314
|
-
|
307
|
+
def <=>(other)
|
308
|
+
@native <=> other.native
|
309
|
+
end
|
315
310
|
|
316
|
-
|
311
|
+
def face
|
312
|
+
@face ||= Face.for_face_symbol(@native.face)
|
317
313
|
end
|
314
|
+
end
|
318
315
|
end
|