twisty_puzzles 0.0.5 → 0.0.10

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -1
  3. data/ext/twisty_puzzles/native/cube_algorithm.c +10 -18
  4. data/ext/twisty_puzzles/native/cube_average.c +2 -2
  5. data/ext/twisty_puzzles/native/cube_coordinate.c +16 -15
  6. data/ext/twisty_puzzles/native/cube_coordinate.h +7 -7
  7. data/ext/twisty_puzzles/native/cube_state.c +23 -33
  8. data/ext/twisty_puzzles/native/cube_state.h +2 -2
  9. data/ext/twisty_puzzles/native/face_symbols.h +2 -2
  10. data/ext/twisty_puzzles/native/skewb_algorithm.c +3 -12
  11. data/ext/twisty_puzzles/native/skewb_coordinate.c +0 -11
  12. data/ext/twisty_puzzles/native/skewb_layer_fingerprint.c +3 -2
  13. data/ext/twisty_puzzles/native/skewb_state.c +0 -2
  14. data/ext/twisty_puzzles/native/utils.c +7 -1
  15. data/ext/twisty_puzzles/native/utils.h +2 -3
  16. data/lib/twisty_puzzles/abstract_move.rb +5 -2
  17. data/lib/twisty_puzzles/abstract_move_parser.rb +3 -3
  18. data/lib/twisty_puzzles/algorithm.rb +1 -1
  19. data/lib/twisty_puzzles/algorithm_transformation.rb +27 -19
  20. data/lib/twisty_puzzles/axis_face_and_direction_move.rb +4 -1
  21. data/lib/twisty_puzzles/cancellation_helper.rb +1 -1
  22. data/lib/twisty_puzzles/color_scheme.rb +8 -6
  23. data/lib/twisty_puzzles/commutator.rb +7 -0
  24. data/lib/twisty_puzzles/compiled_algorithm.rb +4 -4
  25. data/lib/twisty_puzzles/coordinate.rb +4 -6
  26. data/lib/twisty_puzzles/cube.rb +19 -23
  27. data/lib/twisty_puzzles/cube_move.rb +0 -4
  28. data/lib/twisty_puzzles/cube_move_parser.rb +23 -23
  29. data/lib/twisty_puzzles/cube_print_helper.rb +4 -3
  30. data/lib/twisty_puzzles/cube_state.rb +10 -0
  31. data/lib/twisty_puzzles/parser.rb +3 -3
  32. data/lib/twisty_puzzles/rotation.rb +8 -4
  33. data/lib/twisty_puzzles/skewb_move.rb +1 -0
  34. data/lib/twisty_puzzles/skewb_move_parser.rb +1 -0
  35. data/lib/twisty_puzzles/skewb_notation.rb +7 -1
  36. data/lib/twisty_puzzles/skewb_state.rb +6 -4
  37. data/lib/twisty_puzzles/version.rb +1 -1
  38. metadata +11 -12
@@ -56,17 +56,6 @@ size_t center_sticker_index(const face_index_t on_face_index) {
56
56
  return on_face_index * skewb_stickers_per_face;
57
57
  }
58
58
 
59
- static VALUE part_type_from_symbol(const VALUE part_type_symbol) {
60
- Check_Type(face_symbol, T_SYMBOL);
61
- if (SYM2ID(part_type_symbol) == center_part_type_id) {
62
- return CENTER;
63
- } else if (SYM2ID(part_type_symbol) == corner_part_type_id) {
64
- return CORNER;
65
- } else {
66
- rb_raise(rb_eArgError, "Invalid part type symbol %+"PRIsVALUE"", part_type_symbol);
67
- }
68
- }
69
-
70
59
  static VALUE part_type_to_symbol(const SkewbPartType part_type) {
71
60
  // Caching these keys isn't easy because the garbage collector will get them.
72
61
  switch (part_type) {
@@ -32,7 +32,7 @@ static ID times = 848;
32
32
 
33
33
  typedef struct {
34
34
  Corner corner_pairs[max_corner_pair_group_size][2];
35
- int num_corner_pairs;
35
+ size_t num_corner_pairs;
36
36
  // Number of different group fingerprints. This can be used to merge several group fingerprints.
37
37
  int num_group_fingerprints;
38
38
  } CornerPairGroup;
@@ -55,7 +55,7 @@ static bool has_color_at(const ActualCornerStickers actual, const size_t index,
55
55
  }
56
56
 
57
57
  typedef struct {
58
- int layer_index;
58
+ size_t layer_index;
59
59
  bool is_oriented;
60
60
  bool is_present;
61
61
  } ActualCornerStickersInfo;
@@ -268,4 +268,5 @@ void init_skewb_layer_fingerprint_method_under(const VALUE module) {
268
268
  plus = rb_intern("+");
269
269
  times = rb_intern("*");
270
270
  rb_define_singleton_method(module, "skewb_layer_fingerprint", skewb_layer_fingerprint, 2);
271
+ init_corner_pair_groups();
271
272
  }
@@ -43,8 +43,6 @@ static int SkewbState_replace_face(const VALUE key, const VALUE value, const VAL
43
43
 
44
44
  static VALUE SkewbState_initialize(const VALUE self, const VALUE stickers) {
45
45
  Check_Type(stickers, T_HASH);
46
- SkewbStateData* data;
47
- GetSkewbStateData(self, data);
48
46
  if (RHASH_SIZE(stickers) != skewb_faces) {
49
47
  rb_raise(rb_eTypeError, "Skewbs must have %d faces. Got %ld.", skewb_faces, RHASH_SIZE(stickers));
50
48
  }
@@ -64,7 +64,7 @@ int log2_64_floor(uint64_t value) {
64
64
  return tab64[((uint64_t)((value - (value >> 1))*0x07EDD5E59A4E28C2)) >> 58];
65
65
  }
66
66
 
67
- uint64_t iexp(const uint64_t base, uint32_t exp) {
67
+ uint64_t iexp(const uint64_t base, const uint32_t exp) {
68
68
  uint64_t result = 1;
69
69
  for (uint32_t m = 1 << 31; m; m >>= 1) {
70
70
  result = result * result;
@@ -74,3 +74,9 @@ uint64_t iexp(const uint64_t base, uint32_t exp) {
74
74
  }
75
75
  return result;
76
76
  }
77
+
78
+ void check_cube_size(const long cube_size) {
79
+ if (cube_size <= 0) {
80
+ rb_raise(rb_eArgError, "Cube size must be positive. Got %ld.", cube_size);
81
+ }
82
+ }
@@ -5,11 +5,8 @@
5
5
 
6
6
  #define MIN(a, b) ((a) > (b) ? (b) : (a))
7
7
  #define MAX(a, b) ((a) > (b) ? (a) : (b))
8
- #define FALSE 0
9
- #define TRUE 1
10
8
  #define CROP_MOD(a, b) (((a) % (b) + (b)) % (b))
11
9
 
12
- typedef char bool;
13
10
  typedef char direction_t;
14
11
 
15
12
  typedef struct {
@@ -29,3 +26,5 @@ int color_eq(VALUE left, VALUE right);
29
26
  int log2_64_floor(uint64_t value);
30
27
 
31
28
  uint64_t iexp(uint64_t base, uint32_t exp);
29
+
30
+ void check_cube_size(long cube_size);
@@ -143,6 +143,10 @@ module TwistyPuzzles
143
143
  prepend_slice_move(other, cube_size)
144
144
  end
145
145
 
146
+ def prepend_slice_move(_other, _cube_size)
147
+ raise NotImplementedError, "#{self.class}#prepend_slice_move is not implemented"
148
+ end
149
+
146
150
  private
147
151
 
148
152
  def move_count_internal(metric, slice_factor, direction_factor)
@@ -150,8 +154,7 @@ module TwistyPuzzles
150
154
  when :qtm then slice_factor * direction_factor
151
155
  when :htm then slice_factor
152
156
  when :stm then 1
153
- when :qstm then direction_factor
154
- when :sqtm then direction_factor
157
+ when :qstm, :sqtm then direction_factor
155
158
  else raise ArgumentError, "Invalid move metric #{metric.inspect}."
156
159
  end
157
160
  end
@@ -20,7 +20,7 @@ module TwistyPuzzles
20
20
  end
21
21
 
22
22
  def parse_named_captures(match)
23
- present_named_captures = match.named_captures.reject { |_n, v| v.nil? }
23
+ present_named_captures = match.named_captures.compact
24
24
  present_named_captures.map do |name, string|
25
25
  key = parse_part_key(name).to_sym
26
26
  value = parse_move_part(name, string)
@@ -31,14 +31,14 @@ module TwistyPuzzles
31
31
  def parse_move(move_string)
32
32
  match = move_string.match(regexp)
33
33
  if !match || !match.pre_match.empty? || !match.post_match.empty?
34
- raise ArgumentError("Invalid move #{move_string}.")
34
+ raise ArgumentError, "Invalid move #{move_string}."
35
35
  end
36
36
 
37
37
  parsed_parts = parse_named_captures(match)
38
38
  move_type_creators.each do |parser|
39
39
  return parser.create(parsed_parts) if parser.applies_to?(parsed_parts)
40
40
  end
41
- raise "No move type creator applies to #{parsed_parts}"
41
+ raise ArgumentError, "No move type creator applies to #{parsed_parts}"
42
42
  end
43
43
  end
44
44
  end
@@ -128,7 +128,7 @@ module TwistyPuzzles
128
128
  AbstractMove.check_move_metric(metric)
129
129
  return 0 if empty?
130
130
 
131
- @moves.map { |m| m.move_count(cube_size, metric) }.reduce(:+)
131
+ @moves.sum { |m| m.move_count(cube_size, metric) }
132
132
  end
133
133
 
134
134
  def *(other)
@@ -4,28 +4,36 @@ require 'twisty_puzzles/cube_direction'
4
4
  require 'twisty_puzzles/rotation'
5
5
 
6
6
  module TwistyPuzzles
7
- AlgorithmTransformation =
8
- Struct.new(:rotation, :mirror, :mirror_normal_face) do
9
- def transformed(algorithm)
10
- algorithm = algorithm.mirror(mirror_normal_face) if mirror
11
- algorithm.rotate_by(rotation)
12
- end
7
+ # A transformation consisting of mirroring and rotating that can be applied to an algorithm.
8
+ class AlgorithmTransformation
9
+ def initialize(rotation, mirror, mirror_normal_face)
10
+ @rotation = rotation
11
+ @mirror = mirror
12
+ @mirror_normal_face = mirror_normal_face
13
+ end
13
14
 
14
- def identity?
15
- rotation.identity? && !mirror
16
- end
15
+ attr_reader :rotation, :mirror, :mirror_normal_face
17
16
 
18
- # Returns algorithm transformations that mirror an algorithm and rotate it around a face.
19
- def self.around_face(face)
20
- around_face_rotations = CubeDirection::ALL_DIRECTIONS.map { |d| Rotation.new(face, d) }
21
- mirror_normal_face = face.neighbors.first
22
- around_face_rotations.product([true, false]).map do |r, m|
23
- AlgorithmTransformation.new(r, m, mirror_normal_face)
24
- end
25
- end
17
+ def transformed(algorithm)
18
+ algorithm = algorithm.mirror(mirror_normal_face) if mirror
19
+ algorithm.rotate_by(rotation)
20
+ end
26
21
 
27
- def self.around_face_without_identity(face)
28
- around_face(face).reject(&:identity?)
22
+ def identity?
23
+ rotation.identity? && !mirror
24
+ end
25
+
26
+ # Returns algorithm transformations that mirror an algorithm and rotate it around a face.
27
+ def self.around_face(face)
28
+ around_face_rotations = CubeDirection::ALL_DIRECTIONS.map { |d| Rotation.new(face, d) }
29
+ mirror_normal_face = face.neighbors.first
30
+ around_face_rotations.product([true, false]).map do |r, m|
31
+ AlgorithmTransformation.new(r, m, mirror_normal_face)
29
32
  end
30
33
  end
34
+
35
+ def self.around_face_without_identity(face)
36
+ around_face(face).reject(&:identity?)
37
+ end
38
+ end
31
39
  end
@@ -11,6 +11,7 @@ module TwistyPuzzles
11
11
  raise TypeError, "Unsuitable axis face #{axis_face}." unless axis_face.is_a?(Face)
12
12
  raise TypeError unless direction.is_a?(CubeDirection)
13
13
 
14
+ super()
14
15
  @axis_face = axis_face
15
16
  @direction = direction
16
17
  end
@@ -54,7 +55,9 @@ module TwistyPuzzles
54
55
  self
55
56
  else
56
57
  rotation_neighbors = rotation.axis_face.neighbors
57
- face_index = rotation_neighbors.index(@axis_face) || raise
58
+ face_index = rotation_neighbors.index(@axis_face)
59
+ raise unless face_index
60
+
58
61
  new_axis_face =
59
62
  rotation_neighbors[(face_index + rotation.direction.value) % rotation_neighbors.length]
60
63
  fields = replace_once(identifying_fields, @axis_face, new_axis_face)
@@ -135,7 +135,7 @@ module TwistyPuzzles
135
135
  def self.alg_plus_cancelled_move(algorithm, move, cube_size)
136
136
  if move.is_a?(Rotation) && (tail_rotations = num_tail_rotations(algorithm)) >= 2
137
137
  Algorithm.new(algorithm.moves[0...-tail_rotations]) +
138
- cancelled_rotations(algorithm.moves[-tail_rotations..-1] + [move])
138
+ cancelled_rotations(algorithm.moves[-tail_rotations..] + [move])
139
139
  else
140
140
  Algorithm.new(algorithm.moves[0...-1]) +
141
141
  algorithm.moves[-1].join_with_cancellation(move, cube_size)
@@ -37,7 +37,7 @@ module TwistyPuzzles
37
37
  end
38
38
 
39
39
  def part_for_colors(part_type, colors)
40
- raise ArgumentError unless part_type.is_a?(Class)
40
+ raise ArgumentError unless part_type.is_a?(Class) && (part_type < Part)
41
41
 
42
42
  part_type.for_face_symbols(colors.map { |c| face_symbol(c) })
43
43
  end
@@ -56,7 +56,7 @@ module TwistyPuzzles
56
56
  raise ArgumentError unless colors.include?(top_color)
57
57
  raise ArgumentError unless colors.include?(front_color)
58
58
 
59
- # Note: The reason that this is so complicated is that we want it to still work if the
59
+ # NOTE: The reason that this is so complicated is that we want it to still work if the
60
60
  # chirality corner gets exchanged.
61
61
 
62
62
  # Do the obvious and handle opposites of the top and front color so we have no
@@ -95,10 +95,12 @@ module TwistyPuzzles
95
95
 
96
96
  def chirality_corner_source_and_unknown_index(obvious_turned_face_symbols_to_colors)
97
97
  corner_matcher =
98
- CornerMatcher.new(CHIRALITY_FACE_SYMBOLS.map do |s|
99
- # This will return nil for exactly one face that we don't know yet.
100
- @colors_to_face_symbols[obvious_turned_face_symbols_to_colors[s]]
101
- end)
98
+ CornerMatcher.new(
99
+ CHIRALITY_FACE_SYMBOLS.map do |s|
100
+ # This will return nil for exactly one face that we don't know yet.
101
+ @colors_to_face_symbols[obvious_turned_face_symbols_to_colors[s]]
102
+ end
103
+ )
102
104
 
103
105
  # There should be exactly one corner that gets mapped to the chirality corner.
104
106
  chirality_corner_source =
@@ -10,6 +10,10 @@ module TwistyPuzzles
10
10
  def cancellations(other, cube_size, metric = :htm)
11
11
  algorithm.cancellations(other.algorithm, cube_size, metric)
12
12
  end
13
+
14
+ def algorithm
15
+ raise NotImplementedError
16
+ end
13
17
  end
14
18
 
15
19
  # Algorithm that is used like a commutator but actually isn't one.
@@ -17,6 +21,7 @@ module TwistyPuzzles
17
21
  def initialize(algorithm)
18
22
  raise ArgumentError unless algorithm.is_a?(Algorithm)
19
23
 
24
+ super()
20
25
  @algorithm = algorithm
21
26
  end
22
27
 
@@ -47,6 +52,7 @@ module TwistyPuzzles
47
52
  raise ArgumentError unless first_part.is_a?(Algorithm)
48
53
  raise ArgumentError unless second_part.is_a?(Algorithm)
49
54
 
55
+ super()
50
56
  @first_part = first_part
51
57
  @second_part = second_part
52
58
  end
@@ -85,6 +91,7 @@ module TwistyPuzzles
85
91
  raise ArgumentError, 'Inner commutator has to be a commutator.'
86
92
  end
87
93
 
94
+ super()
88
95
  @setup = setup
89
96
  @inner_commutator = inner_commutator
90
97
  end
@@ -26,10 +26,10 @@ module TwistyPuzzles
26
26
  def inverse
27
27
  @inverse ||=
28
28
  begin
29
- alg = self.class.new(@native.inverse)
30
- alg.inverse = self
31
- alg
32
- end
29
+ alg = self.class.new(@native.inverse)
30
+ alg.inverse = self
31
+ alg
32
+ end
33
33
  end
34
34
 
35
35
  def +(other)
@@ -90,7 +90,7 @@ module TwistyPuzzles
90
90
  base_coordinate = Coordinate.from_indices(
91
91
  part.solved_face, cube_size, *part.base_index_on_face(cube_size, incarnation_index)
92
92
  )
93
- other_face_symbols = part.corresponding_part.face_symbols[1..-1]
93
+ other_face_symbols = part.corresponding_part.face_symbols[1..]
94
94
  match_coordinate_internal(base_coordinate, other_face_symbols)
95
95
  end
96
96
 
@@ -99,7 +99,7 @@ module TwistyPuzzles
99
99
  def self.solved_positions(part, cube_size, incarnation_index)
100
100
  solved_coordinate = solved_position(part, cube_size, incarnation_index)
101
101
  other_coordinates =
102
- part.face_symbols[1..-1].map.with_index do |f, i|
102
+ part.face_symbols[1..].map.with_index do |f, i|
103
103
  face = Face.for_face_symbol(f)
104
104
  # The reverse is important for edge like parts. We are not in the same position as usual
105
105
  # solved pieces would be.
@@ -109,7 +109,7 @@ module TwistyPuzzles
109
109
  base_coordinate = Coordinate.from_indices(face, cube_size, *base_indices)
110
110
  other_face_symbols = [part.face_symbols[0]] +
111
111
  part.corresponding_part.face_symbols[1...i + 1] +
112
- part.corresponding_part.face_symbols[i + 2..-1]
112
+ part.corresponding_part.face_symbols[i + 2..]
113
113
  match_coordinate_internal(base_coordinate, other_face_symbols)
114
114
  end
115
115
  [solved_coordinate] + other_coordinates
@@ -276,7 +276,7 @@ module TwistyPuzzles
276
276
  @native = native
277
277
  end
278
278
 
279
- attr_reader :native
279
+ attr_reader :native, :coordinate
280
280
 
281
281
  private_class_method :new
282
282
 
@@ -311,7 +311,5 @@ module TwistyPuzzles
311
311
  def face
312
312
  @face ||= Face.for_face_symbol(@native.face)
313
313
  end
314
-
315
- attr_reader :coordinate
316
314
  end
317
315
  end
@@ -64,10 +64,18 @@ module TwistyPuzzles
64
64
  true
65
65
  end
66
66
 
67
+ def num_incarnations(_cube_size)
68
+ 1
69
+ end
70
+
67
71
  def base_index_on_face(cube_size, incarnation_index)
68
72
  base_index_on_other_face(solved_face, cube_size, incarnation_index)
69
73
  end
70
74
 
75
+ def base_index_on_other_face(face, cube_size, incarnation_index)
76
+ raise NotImplementedError
77
+ end
78
+
71
79
  def self.for_face_symbols_internal(face_symbols)
72
80
  raise unless face_symbols.length == self::FACES
73
81
 
@@ -82,6 +90,10 @@ module TwistyPuzzles
82
90
  self::ELEMENTS[index]
83
91
  end
84
92
 
93
+ def self.valid?(_face_symbols)
94
+ false
95
+ end
96
+
85
97
  def <=>(other)
86
98
  @piece_index <=> other.piece_index
87
99
  end
@@ -97,7 +109,7 @@ module TwistyPuzzles
97
109
  end
98
110
 
99
111
  def inspect
100
- self.class.to_s + '(' + @face_symbols.map(&:to_s).join(', ') + ')'
112
+ "#{self.class}(#{@face_symbols.map(&:to_s).join(', ')})"
101
113
  end
102
114
 
103
115
  def to_s
@@ -110,7 +122,7 @@ module TwistyPuzzles
110
122
  # Rotate a piece such that the given face symbol is the first face symbol.
111
123
  def rotate_face_symbol_up(face_symbol)
112
124
  index = @face_symbols.index(face_symbol)
113
- raise "Part #{self} doesn't have face symbol #{c}." unless index
125
+ raise "Part #{self} doesn't have face symbol #{face_symbol}." unless index
114
126
 
115
127
  rotate_by(index)
116
128
  end
@@ -133,10 +145,6 @@ module TwistyPuzzles
133
145
  (0...@face_symbols.length).map { |i| rotate_by(i) }
134
146
  end
135
147
 
136
- def self.create_for_face_symbols(face_symbols)
137
- new(face_symbols)
138
- end
139
-
140
148
  def self.parse(piece_description)
141
149
  face_symbols =
142
150
  piece_description.upcase.strip.split('').map do |e|
@@ -338,10 +346,6 @@ module TwistyPuzzles
338
346
  find_only(self::ELEMENTS) { |e| e.corresponding_part == corresponding_part }
339
347
  end
340
348
 
341
- def self.create_for_face_symbols(face_symbols)
342
- new(self::CORRESPONDING_PART_CLASS.create_for_face_symbols(face_symbols))
343
- end
344
-
345
349
  def face_symbol
346
350
  @face_symbols[0]
347
351
  end
@@ -365,7 +369,7 @@ module TwistyPuzzles
365
369
  attr_reader :corresponding_part
366
370
 
367
371
  def inspect
368
- self.class.to_s + '(' + face_symbol.to_s + ', ' + @corresponding_part.inspect + ')'
372
+ "#{self.class}(#{face_symbol}, #{@corresponding_part.inspect})"
369
373
  end
370
374
 
371
375
  def rotate_by(_number)
@@ -514,7 +518,7 @@ module TwistyPuzzles
514
518
  end
515
519
 
516
520
  def num_incarnations(cube_size)
517
- [cube_size / 2 - 1, 0].max
521
+ cube_size > 3 ? cube_size / 2 - 1 : 0
518
522
  end
519
523
 
520
524
  # One index of such a piece on a on a NxN face.
@@ -530,14 +534,6 @@ module TwistyPuzzles
530
534
  class Corner < Part
531
535
  FACES = 3
532
536
 
533
- def self.create_for_face_symbols(face_symbols)
534
- piece_candidates =
535
- face_symbols[1..-1].permutation.map do |cs|
536
- new([face_symbols[0]] + cs)
537
- end
538
- find_only(piece_candidates, &:valid?)
539
- end
540
-
541
537
  def self.for_face_symbols(face_symbols)
542
538
  unless face_symbols.length == FACES
543
539
  raise "Invalid number of face_symbols to create a corner: #{face_symbols.inspect}"
@@ -621,7 +617,7 @@ module TwistyPuzzles
621
617
  ELEMENTS = generate_parts
622
618
 
623
619
  def num_incarnations(cube_size)
624
- [cube_size / 2 - 1, 0].max
620
+ cube_size > 3 ? cube_size / 2 - 1 : 0
625
621
  end
626
622
 
627
623
  # One index of such a piece on a on a NxN face.
@@ -644,10 +640,10 @@ module TwistyPuzzles
644
640
  end
645
641
 
646
642
  def num_incarnations(cube_size)
647
- if cube_size.even?
643
+ if cube_size.even? || cube_size <= 3
648
644
  0
649
645
  else
650
- [cube_size / 2 - 1, 0].max
646
+ cube_size / 2 - 1
651
647
  end
652
648
  end
653
649