twisty_puzzles 0.0.1 → 0.0.6

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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -1
  3. data/README.md +6 -1
  4. data/ext/twisty_puzzles/native/cube_algorithm.c +267 -0
  5. data/ext/twisty_puzzles/native/cube_algorithm.h +5 -0
  6. data/ext/twisty_puzzles/native/cube_average.c +184 -0
  7. data/ext/twisty_puzzles/native/cube_average.h +5 -0
  8. data/ext/twisty_puzzles/native/cube_coordinate.c +207 -0
  9. data/ext/twisty_puzzles/native/cube_coordinate.h +34 -0
  10. data/ext/twisty_puzzles/native/cube_state.c +264 -0
  11. data/ext/twisty_puzzles/native/cube_state.h +31 -0
  12. data/ext/twisty_puzzles/native/extconf.rb +1 -1
  13. data/ext/twisty_puzzles/native/face_symbols.c +67 -0
  14. data/ext/twisty_puzzles/native/face_symbols.h +34 -0
  15. data/ext/twisty_puzzles/native/native.c +28 -0
  16. data/ext/twisty_puzzles/native/skewb_algorithm.c +331 -0
  17. data/ext/twisty_puzzles/native/skewb_algorithm.h +5 -0
  18. data/ext/twisty_puzzles/native/skewb_coordinate.c +237 -0
  19. data/ext/twisty_puzzles/native/skewb_coordinate.h +36 -0
  20. data/ext/twisty_puzzles/native/skewb_layer_fingerprint.c +271 -0
  21. data/ext/twisty_puzzles/native/skewb_layer_fingerprint.h +5 -0
  22. data/ext/twisty_puzzles/native/skewb_state.c +214 -0
  23. data/ext/twisty_puzzles/native/skewb_state.h +23 -0
  24. data/ext/twisty_puzzles/native/utils.c +76 -0
  25. data/ext/twisty_puzzles/native/utils.h +31 -0
  26. data/lib/twisty_puzzles.rb +38 -0
  27. data/lib/twisty_puzzles/abstract_direction.rb +38 -39
  28. data/lib/twisty_puzzles/abstract_move.rb +1 -2
  29. data/lib/twisty_puzzles/abstract_move_parser.rb +32 -33
  30. data/lib/twisty_puzzles/algorithm.rb +112 -113
  31. data/lib/twisty_puzzles/algorithm_transformation.rb +19 -21
  32. data/lib/twisty_puzzles/axis_face_and_direction_move.rb +56 -56
  33. data/lib/twisty_puzzles/cancellation_helper.rb +124 -125
  34. data/lib/twisty_puzzles/color_scheme.rb +1 -1
  35. data/lib/twisty_puzzles/commutator.rb +82 -80
  36. data/lib/twisty_puzzles/compiled_algorithm.rb +31 -32
  37. data/lib/twisty_puzzles/compiled_cube_algorithm.rb +49 -50
  38. data/lib/twisty_puzzles/compiled_skewb_algorithm.rb +18 -19
  39. data/lib/twisty_puzzles/coordinate.rb +243 -246
  40. data/lib/twisty_puzzles/cube.rb +494 -495
  41. data/lib/twisty_puzzles/cube_constants.rb +40 -41
  42. data/lib/twisty_puzzles/cube_direction.rb +15 -18
  43. data/lib/twisty_puzzles/cube_move.rb +285 -290
  44. data/lib/twisty_puzzles/cube_move_parser.rb +75 -76
  45. data/lib/twisty_puzzles/cube_print_helper.rb +133 -133
  46. data/lib/twisty_puzzles/cube_state.rb +80 -81
  47. data/lib/twisty_puzzles/move_type_creator.rb +17 -18
  48. data/lib/twisty_puzzles/parser.rb +176 -179
  49. data/lib/twisty_puzzles/part_cycle_factory.rb +39 -42
  50. data/lib/twisty_puzzles/puzzle.rb +16 -17
  51. data/lib/twisty_puzzles/reversible_applyable.rb +24 -25
  52. data/lib/twisty_puzzles/rotation.rb +76 -75
  53. data/lib/twisty_puzzles/skewb_direction.rb +14 -15
  54. data/lib/twisty_puzzles/skewb_move.rb +49 -49
  55. data/lib/twisty_puzzles/skewb_move_parser.rb +51 -51
  56. data/lib/twisty_puzzles/skewb_notation.rb +121 -118
  57. data/lib/twisty_puzzles/skewb_state.rb +120 -121
  58. data/lib/twisty_puzzles/state_helper.rb +20 -21
  59. data/lib/twisty_puzzles/sticker_cycle.rb +43 -44
  60. data/lib/twisty_puzzles/utils.rb +3 -0
  61. data/lib/twisty_puzzles/version.rb +3 -1
  62. metadata +30 -10
@@ -3,46 +3,45 @@
3
3
  require 'twisty_puzzles/reversible_applyable'
4
4
 
5
5
  module TwistyPuzzles
6
-
7
- # Base class for a compiled algorithm for a particular puzzle.
8
- class CompiledAlgorithm
9
- include ReversibleApplyable
6
+ # Base class for a compiled algorithm for a particular puzzle.
7
+ class CompiledAlgorithm
8
+ include ReversibleApplyable
10
9
 
11
- def initialize(native)
12
- raise TypeError unless native.is_a?(self.class::NATIVE_CLASS)
10
+ def initialize(native)
11
+ raise TypeError unless native.is_a?(self.class::NATIVE_CLASS)
13
12
 
14
- @native = native
15
- end
13
+ @native = native
14
+ end
16
15
 
17
- attr_reader :native
16
+ attr_reader :native
18
17
 
19
- def rotate_by(rotation)
20
- self.class.new(@native.rotate_by(rotation.axis_face.face_symbol, rotation.direction.value))
21
- end
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
- def mirror(normal_face)
24
- self.class.new(@native.mirror(normal_face.face_symbol))
25
- end
22
+ def mirror(normal_face)
23
+ self.class.new(@native.mirror(normal_face.face_symbol))
24
+ end
26
25
 
27
- def inverse
28
- @inverse ||=
29
- begin
30
- alg = self.class.new(@native.inverse)
31
- alg.inverse = self
32
- alg
33
- end
34
- end
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
- def +(other)
37
- self.class.new(@native + other.native)
38
- end
35
+ def +(other)
36
+ self.class.new(@native + other.native)
37
+ end
39
38
 
40
- def apply_to(state)
41
- @native.apply_to(state.native)
42
- end
39
+ def apply_to(state)
40
+ @native.apply_to(state.native)
41
+ end
43
42
 
44
- protected
43
+ protected
45
44
 
46
- attr_writer :inverse
47
- end
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
- # Wrapper of the native C implementation of a compiled algorithm for a particular cube size.
8
- class CompiledCubeAlgorithm < CompiledAlgorithm
9
- def self.transform_rotation(move, cube_size)
10
- slice_moves =
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
- end
25
-
26
- def self.transform_slice_move(move)
27
- [
28
- [:slice, move.axis_face.face_symbol, move.direction.value, move.slice_index]
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
- def self.transform_fat_move(move)
33
- slice_moves =
34
- 0.upto(move.width - 1).map do |i|
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
- private_class_method :transform_rotation, :transform_fat_mslice_move, :transform_slice_move,
43
- :transform_fat_move
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
- def self.transform_move(move, cube_size)
46
- decided_move = move.decide_meaning(cube_size)
47
- case decided_move
48
- when Rotation then transform_rotation(decided_move, cube_size)
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
- end
36
+ [
37
+ [:face, move.axis_face.face_symbol, move.direction.value]
38
+ ] + slice_moves
39
+ end
58
40
 
59
- def self.for_moves(cube_size, moves)
60
- transformed_moves = moves.collect_concat { |m| transform_move(m, cube_size) }
61
- native = Native::CubeAlgorithm.new(cube_size, transformed_moves)
62
- new(native)
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
- NATIVE_CLASS = Native::CubeAlgorithm
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
- # Wrapper of the native C implementation of a compiled algorithm for a particular cube size.
8
- class CompiledSkewbAlgorithm < CompiledAlgorithm
9
- def self.transform_move(move)
10
- case move
11
- when Rotation
12
- [:rotation, move.axis_face.face_symbol, move.direction.value]
13
- when SkewbMove
14
- [:move, move.axis_corner.face_symbols, move.direction.value]
15
- else
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
- NATIVE_CLASS = Native::SkewbAlgorithm
26
- EMPTY = for_moves([])
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
- # Coordinate of a sticker on the cube.
10
- class Coordinate
11
- def self.highest_coordinate(cube_size)
12
- cube_size - 1
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
- def self.coordinate_range(cube_size)
20
- 0.upto(highest_coordinate(cube_size))
21
- end
14
+ def self.invert_coordinate(index, cube_size)
15
+ highest_coordinate(cube_size) - index
16
+ end
22
17
 
23
- def self.middle(cube_size)
24
- raise ArgumentError if cube_size.even?
18
+ def self.coordinate_range(cube_size)
19
+ 0.upto(highest_coordinate(cube_size))
20
+ end
25
21
 
26
- cube_size / 2
27
- end
22
+ def self.middle(cube_size)
23
+ raise ArgumentError if cube_size.even?
28
24
 
29
- # Middle coordinate for uneven numbers, the one before for even numbers
30
- def self.middle_or_before(cube_size)
31
- cube_size - cube_size / 2 - 1
32
- end
25
+ cube_size / 2
26
+ end
33
27
 
34
- # Middle coordinate for uneven numbers, the one after for even numbers
35
- def self.middle_or_after(cube_size)
36
- cube_size / 2
37
- end
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
- # The last coordinate that is strictly before the middle
40
- def self.last_before_middle(cube_size)
41
- cube_size / 2 - 1
42
- end
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
- def self.canonicalize(index, cube_size)
45
- raise ArgumentError unless index.is_a?(Integer) && -cube_size <= index && index < cube_size
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
- index >= 0 ? index : cube_size + index
48
- end
43
+ def self.canonicalize(index, cube_size)
44
+ raise ArgumentError unless index.is_a?(Integer) && -cube_size <= index && index < cube_size
49
45
 
50
- def self.from_face_distances(face, cube_size, face_distances)
51
- raise ArgumentError if face_distances.length != 2
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
- from_indices(face, cube_size, *coordinates)
69
- end
49
+ def self.from_face_distances(face, cube_size, face_distances)
50
+ raise ArgumentError if face_distances.length != 2
70
51
 
71
- def self.match_coordinate_internal(base_coordinate, other_face_symbols)
72
- other_face_symbols.sort!
52
+ coordinates = [nil, nil]
53
+ face_distances.each do |neighbor, distance|
54
+ index = face.coordinate_index_close_to(neighbor)
73
55
  coordinate =
74
- base_coordinate.rotations.find do |coord|
75
- face_symbols_closeby = coord.close_neighbor_faces.map(&:face_symbol)
76
- face_symbols_closeby.sort == other_face_symbols
56
+ if neighbor.close_to_smaller_indices?
57
+ distance
58
+ else
59
+ invert_coordinate(distance, cube_size)
77
60
  end
78
- raise "Couldn't find a fitting coordinate on the solved face." if coordinate.nil?
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
- # The coordinate of the solved position of the main sticker of this part.
84
- def self.solved_position(part, cube_size, incarnation_index)
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
- # The coordinate of the solved position of all stickers of this part.
99
- # rubocop:disable Metrics/AbcSize
100
- def self.solved_positions(part, cube_size, incarnation_index)
101
- solved_coordinate = solved_position(part, cube_size, incarnation_index)
102
- other_coordinates =
103
- part.face_symbols[1..-1].map.with_index do |f, i|
104
- face = Face.for_face_symbol(f)
105
- # The reverse is important for edge like parts. We are not in the same position as usual
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
- def self.center(face, cube_size)
121
- m = middle(cube_size)
122
- from_indices(face, cube_size, m, m)
123
- end
79
+ coordinate
80
+ end
124
81
 
125
- def self.edges_outside(face, cube_size)
126
- face.neighbors.zip(face.neighbors.rotate(1)).collect_concat do |neighbor, next_neighbor|
127
- 1.upto(cube_size - 2).map do |i|
128
- from_face_distances(neighbor, cube_size, face => 0, next_neighbor => i)
129
- end
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
- end
115
+ [solved_coordinate] + other_coordinates
116
+ end
117
+ # rubocop:enable Metrics/AbcSize
132
118
 
133
- def self.from_indices(face, cube_size, x_index, y_index)
134
- raise TypeError, "Unsuitable face #{face.inspect}." unless face.is_a?(Face)
135
- raise TypeError unless cube_size.is_a?(Integer)
136
- raise ArgumentError unless cube_size.positive?
137
-
138
- x = Coordinate.canonicalize(x_index, cube_size)
139
- y = Coordinate.canonicalize(y_index, cube_size)
140
- native = Native::CubeCoordinate.new(
141
- cube_size,
142
- face.face_symbol,
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
- private_class_method :new
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
- def initialize(native)
154
- raise TypeError unless native.is_a?(Native::CubeCoordinate)
150
+ private_class_method :new
155
151
 
156
- @native = native
157
- end
152
+ def initialize(native)
153
+ raise TypeError unless native.is_a?(Native::CubeCoordinate)
158
154
 
159
- attr_reader :native
155
+ @native = native
156
+ end
160
157
 
161
- def face
162
- @face ||= Face.for_face_symbol(@native.face)
163
- end
158
+ attr_reader :native
164
159
 
165
- def cube_size
166
- @cube_size ||= @native.cube_size
167
- end
160
+ def face
161
+ @face ||= Face.for_face_symbol(@native.face)
162
+ end
168
163
 
169
- def coordinate(coordinate_index)
170
- native.coordinate(face.coordinate_index_base_face(coordinate_index).face_symbol)
171
- end
164
+ def cube_size
165
+ @cube_size ||= @native.cube_size
166
+ end
172
167
 
173
- def coordinates
174
- @coordinates ||= [x, y].freeze
175
- end
168
+ def coordinate(coordinate_index)
169
+ native.coordinate(face.coordinate_index_base_face(coordinate_index).face_symbol)
170
+ end
176
171
 
177
- def x
178
- @x ||= coordinate(0)
179
- end
172
+ def coordinates
173
+ @coordinates ||= [x, y].freeze
174
+ end
180
175
 
181
- def y
182
- @y ||= coordinate(1)
183
- end
176
+ def x
177
+ @x ||= coordinate(0)
178
+ end
184
179
 
185
- def eql?(other)
186
- self.class.equal?(other.class) && @native == other.native
187
- end
180
+ def y
181
+ @y ||= coordinate(1)
182
+ end
188
183
 
189
- alias == eql?
184
+ def eql?(other)
185
+ self.class.equal?(other.class) && @native == other.native
186
+ end
190
187
 
191
- def hash
192
- [self.class, @native].hash
193
- end
188
+ alias == eql?
194
189
 
195
- def can_jump_to?(to_face)
196
- raise ArgumentError unless to_face.is_a?(Face)
190
+ def hash
191
+ [self.class, @native].hash
192
+ end
197
193
 
198
- jump_coordinate_index = face.coordinate_index_close_to(to_face)
199
- jump_coordinate = coordinates[jump_coordinate_index]
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
- def jump_to_neighbor(to_face)
206
- raise ArgumentError unless to_face.is_a?(Face)
207
- raise ArgumentError unless face.neighbors.include?(to_face)
208
- raise ArgumentError unless can_jump_to?(to_face)
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
- new_coordinates = coordinates.dup
211
- new_coordinate_index = to_face.coordinate_index_close_to(face)
212
- new_coordinate = make_coordinate_at_edge_to(face)
213
- new_coordinates.insert(new_coordinate_index, new_coordinate)
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
- def jump_to_coordinates(new_coordinates)
218
- Coordinate.from_indices(@face, @cube_size, *new_coordinates)
219
- end
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
- def make_coordinate_at_edge_to(face)
222
- face.close_to_smaller_indices? ? 0 : Coordinate.highest_coordinate(cube_size)
223
- end
216
+ def jump_to_coordinates(new_coordinates)
217
+ Coordinate.from_indices(@face, @cube_size, *new_coordinates)
218
+ end
224
219
 
225
- # Returns neighbor faces that are closer to this coordinate than their opposite face.
226
- def close_neighbor_faces
227
- face.neighbors.select do |neighbor|
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
- def after_middle?(index)
238
- Coordinate.canonicalize(index, cube_size) > Coordinate.middle_or_before(cube_size)
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
- def before_middle?(index)
242
- Coordinate.canonicalize(index, cube_size) <= Coordinate.last_before_middle(cube_size)
243
- end
236
+ def after_middle?(index)
237
+ Coordinate.canonicalize(index, cube_size) > Coordinate.middle_or_before(cube_size)
238
+ end
244
239
 
245
- # On a nxn grid with integer coordinates between 0 and n - 1, iterates between the 4 points
246
- # that point (x, y) hits if you rotate by 90 degrees.
247
- def rotate
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
- # On a nxn grid with integer coordinates between 0 and n - 1, give the 4 points that point
252
- # (x, y) hits if you do a full rotation of the face in clockwise order.
253
- def rotations
254
- rots = []
255
- current = self
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
- rots
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
- end
259
+ raise unless current == self
265
260
 
266
- # Coordinate of a sticker on the Skewb.
267
- class SkewbCoordinate
268
- include Comparable
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
- @coordinate = coordinate
277
- @native = native
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
- attr_reader :native
275
+ @coordinate = coordinate
276
+ @native = native
277
+ end
281
278
 
282
- private_class_method :new
279
+ attr_reader :native, :coordinate
283
280
 
284
- def self.for_center(face)
285
- native = Native::SkewbCoordinate.for_center(face.face_symbol)
286
- new(face, 0, native)
287
- end
281
+ private_class_method :new
288
282
 
289
- def self.corners_on_face(face)
290
- face.clockwise_corners.map { |c| for_corner(c) }
291
- end
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
- def self.for_corner(corner)
294
- native = Native::SkewbCoordinate.for_corner(corner.face_symbols)
295
- new(Face.for_face_symbol(corner.face_symbols.first), 1 + corner.piece_index % 4, native)
296
- end
288
+ def self.corners_on_face(face)
289
+ face.clockwise_corners.map { |c| for_corner(c) }
290
+ end
297
291
 
298
- def hash
299
- @hash ||= [self.class, @native].hash
300
- end
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
- def eql?(other)
303
- @native.eql?(other.native)
304
- end
297
+ def hash
298
+ @hash ||= [self.class, @native].hash
299
+ end
305
300
 
306
- alias == eql?
301
+ def eql?(other)
302
+ @native.eql?(other.native)
303
+ end
307
304
 
308
- def <=>(other)
309
- @native <=> other.native
310
- end
305
+ alias == eql?
311
306
 
312
- def face
313
- @face ||= Face.for_face_symbol(@native.face)
314
- end
307
+ def <=>(other)
308
+ @native <=> other.native
309
+ end
315
310
 
316
- attr_reader :coordinate
311
+ def face
312
+ @face ||= Face.for_face_symbol(@native.face)
317
313
  end
314
+ end
318
315
  end