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