twisty_puzzles 0.0.1 → 0.0.2

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -1
  3. data/lib/twisty_puzzles.rb +37 -0
  4. data/lib/twisty_puzzles/abstract_direction.rb +38 -39
  5. data/lib/twisty_puzzles/abstract_move_parser.rb +32 -33
  6. data/lib/twisty_puzzles/algorithm.rb +112 -113
  7. data/lib/twisty_puzzles/algorithm_transformation.rb +19 -21
  8. data/lib/twisty_puzzles/axis_face_and_direction_move.rb +55 -56
  9. data/lib/twisty_puzzles/cancellation_helper.rb +124 -125
  10. data/lib/twisty_puzzles/commutator.rb +79 -80
  11. data/lib/twisty_puzzles/compiled_algorithm.rb +31 -32
  12. data/lib/twisty_puzzles/compiled_cube_algorithm.rb +49 -50
  13. data/lib/twisty_puzzles/compiled_skewb_algorithm.rb +18 -19
  14. data/lib/twisty_puzzles/coordinate.rb +245 -246
  15. data/lib/twisty_puzzles/cube.rb +494 -495
  16. data/lib/twisty_puzzles/cube_constants.rb +40 -41
  17. data/lib/twisty_puzzles/cube_direction.rb +15 -18
  18. data/lib/twisty_puzzles/cube_move.rb +289 -290
  19. data/lib/twisty_puzzles/cube_move_parser.rb +75 -76
  20. data/lib/twisty_puzzles/cube_print_helper.rb +132 -133
  21. data/lib/twisty_puzzles/cube_state.rb +80 -81
  22. data/lib/twisty_puzzles/move_type_creator.rb +17 -18
  23. data/lib/twisty_puzzles/parser.rb +176 -179
  24. data/lib/twisty_puzzles/part_cycle_factory.rb +39 -42
  25. data/lib/twisty_puzzles/puzzle.rb +16 -17
  26. data/lib/twisty_puzzles/reversible_applyable.rb +24 -25
  27. data/lib/twisty_puzzles/rotation.rb +74 -75
  28. data/lib/twisty_puzzles/skewb_direction.rb +14 -15
  29. data/lib/twisty_puzzles/skewb_move.rb +48 -49
  30. data/lib/twisty_puzzles/skewb_move_parser.rb +50 -51
  31. data/lib/twisty_puzzles/skewb_notation.rb +115 -118
  32. data/lib/twisty_puzzles/skewb_state.rb +120 -121
  33. data/lib/twisty_puzzles/state_helper.rb +20 -21
  34. data/lib/twisty_puzzles/sticker_cycle.rb +43 -44
  35. data/lib/twisty_puzzles/utils.rb +3 -0
  36. data/lib/twisty_puzzles/version.rb +3 -1
  37. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c23d462856d3827fe5386ca3d4d3816c2ce37cf6e06eb3876a9e4b18c4aef36
4
- data.tar.gz: 561a871d7287fde1d568a00b243dff15337c0ae785c3caa55e71a2e94a0d09a0
3
+ metadata.gz: 989d528a58cb32ca49f7502249b5e5eca71a1e46726f1a79ffa64e18381d5a86
4
+ data.tar.gz: 2c1bfff00d66c4b85f15592ee50fa6883e45149ae8456c7a24cc1cba60841285
5
5
  SHA512:
6
- metadata.gz: b944ba73e3cf1c4cad55156237fd4c9a87888796ea5e6fbff6606c36a9d97b84a59e1c85a42318e70c71848399205dd117c8385a01d592be6f2bdbcdaeef3570
7
- data.tar.gz: 41894ab27afc69dffef137acb506819be9d94b3b27b339ba3ff1bc75731736575f412609a7e127920d60c9c5954f808ef98deafee6ec27f23856f1e794f67fdd
6
+ metadata.gz: 870c957e8ba192090401e9e6092779e5f19f45fcd995d60ab1c2e52fed62b7cd25397ce06f18fb2eae6f4ac3df88e76975390f0b73ebc45a8fe2ecdc2cae41fd
7
+ data.tar.gz: 6140ebc4150d89e0577461b66b18158dec51c11d3527d265f028d24cd2b1e44f2299b05ae46a3264bbc040dcf0b34bda83be7ec15092884234539130a402d3ef
data/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.0.2]
8
+ ### Changed
9
+ - Now a simple `require 'twisty_puzzles'` is enough, users don't need to require files separately.
10
+
11
+ ### Fixed
12
+ - Syntax error in file that wasn't included in tests previously.
13
+ - Rubocop fixes across the codebase.
14
+
7
15
  ## [0.0.1]
8
16
  ### Added
9
- Split off core twisty puzzles functionality from cube_trainer repo into a Gem.
17
+ - Split off core twisty puzzles functionality from cube_trainer repo into a Gem.
@@ -1,5 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'twisty_puzzles/abstract_direction'
4
+ require 'twisty_puzzles/abstract_move'
5
+ require 'twisty_puzzles/abstract_move_parser'
6
+ require 'twisty_puzzles/algorithm'
7
+ require 'twisty_puzzles/algorithm_transformation'
8
+ require 'twisty_puzzles/axis_face_and_direction_move'
9
+ require 'twisty_puzzles/cancellation_helper'
10
+ require 'twisty_puzzles/color_scheme'
11
+ require 'twisty_puzzles/commutator'
12
+ require 'twisty_puzzles/compiled_algorithm'
13
+ require 'twisty_puzzles/compiled_cube_algorithm'
14
+ require 'twisty_puzzles/compiled_skewb_algorithm'
15
+ require 'twisty_puzzles/coordinate'
16
+ require 'twisty_puzzles/cube'
17
+ require 'twisty_puzzles/cube_constants'
18
+ require 'twisty_puzzles/cube_direction'
19
+ require 'twisty_puzzles/cube_move'
20
+ require 'twisty_puzzles/cube_move_parser'
21
+ require 'twisty_puzzles/cube_print_helper'
22
+ require 'twisty_puzzles/cube_state'
23
+ require 'twisty_puzzles/letter_scheme'
24
+ require 'twisty_puzzles/move_type_creator'
25
+ require 'twisty_puzzles/parser'
26
+ require 'twisty_puzzles/part_cycle_factory'
27
+ require 'twisty_puzzles/puzzle'
28
+ require 'twisty_puzzles/reversible_applyable'
29
+ require 'twisty_puzzles/rotation'
30
+ require 'twisty_puzzles/skewb_direction'
31
+ require 'twisty_puzzles/skewb_move'
32
+ require 'twisty_puzzles/skewb_move_parser'
33
+ require 'twisty_puzzles/skewb_notation'
34
+ require 'twisty_puzzles/skewb_state'
35
+ require 'twisty_puzzles/state_helper'
36
+ require 'twisty_puzzles/sticker_cycle'
37
+ require 'twisty_puzzles/twisty_puzzles_error'
38
+ require 'twisty_puzzles/version'
39
+
3
40
  # Libraries for the twisty puzzles.
4
41
  module TwistyPuzzles
5
42
  end
@@ -1,54 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TwistyPuzzles
4
-
5
- # Base class for directions.
6
- class AbstractDirection
7
- include Comparable
8
- POSSIBLE_DIRECTION_NAMES = [[''], ['2', '2\''], ['\'', '3']].freeze
9
- SIMPLE_DIRECTION_NAMES = (['0'] + POSSIBLE_DIRECTION_NAMES.map(&:first)).freeze
10
- POSSIBLE_SKEWB_DIRECTION_NAMES = [['', '2\''], ['\'', '2']].freeze
11
- SIMPLE_SKEWB_DIRECTION_NAMES = (['0'] + POSSIBLE_SKEWB_DIRECTION_NAMES.map(&:first)).freeze
12
-
13
- def initialize(value)
14
- raise TypeError, "Direction value #{value} isn't an integer." unless value.is_a?(Integer)
15
- unless value >= 0 && value < self.class::NUM_DIRECTIONS
16
- raise ArgumentError, "Invalid direction value #{value}."
17
- end
18
-
19
- @value = value
4
+ # Base class for directions.
5
+ class AbstractDirection
6
+ include Comparable
7
+ POSSIBLE_DIRECTION_NAMES = [[''], ['2', '2\''], ['\'', '3']].freeze
8
+ SIMPLE_DIRECTION_NAMES = (['0'] + POSSIBLE_DIRECTION_NAMES.map(&:first)).freeze
9
+ POSSIBLE_SKEWB_DIRECTION_NAMES = [['', '2\''], ['\'', '2']].freeze
10
+ SIMPLE_SKEWB_DIRECTION_NAMES = (['0'] + POSSIBLE_SKEWB_DIRECTION_NAMES.map(&:first)).freeze
11
+
12
+ def initialize(value)
13
+ raise TypeError, "Direction value #{value} isn't an integer." unless value.is_a?(Integer)
14
+ unless value >= 0 && value < self.class::NUM_DIRECTIONS
15
+ raise ArgumentError, "Invalid direction value #{value}."
20
16
  end
21
17
 
22
- attr_reader :value
18
+ @value = value
19
+ end
23
20
 
24
- def <=>(other)
25
- @value <=> other.value
26
- end
21
+ attr_reader :value
27
22
 
28
- def zero?
29
- @value.zero?
30
- end
23
+ def <=>(other)
24
+ @value <=> other.value
25
+ end
31
26
 
32
- def non_zero?
33
- @value.positive?
34
- end
27
+ def zero?
28
+ @value.zero?
29
+ end
35
30
 
36
- def inverse
37
- self.class.new((self.class::NUM_DIRECTIONS - @value) % self.class::NUM_DIRECTIONS)
38
- end
31
+ def non_zero?
32
+ @value.positive?
33
+ end
39
34
 
40
- def +(other)
41
- self.class.new((@value + other.value) % self.class::NUM_DIRECTIONS)
42
- end
35
+ def inverse
36
+ self.class.new((self.class::NUM_DIRECTIONS - @value) % self.class::NUM_DIRECTIONS)
37
+ end
43
38
 
44
- def eql?(other)
45
- self.class.equal?(other.class) && @value == other.value
46
- end
39
+ def +(other)
40
+ self.class.new((@value + other.value) % self.class::NUM_DIRECTIONS)
41
+ end
42
+
43
+ def eql?(other)
44
+ self.class.equal?(other.class) && @value == other.value
45
+ end
47
46
 
48
- alias == eql?
47
+ alias == eql?
49
48
 
50
- def hash
51
- @value.hash
52
- end
49
+ def hash
50
+ @value.hash
53
51
  end
52
+ end
54
53
  end
@@ -1,45 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TwistyPuzzles
4
-
5
- # Base class for move parsers.
6
- class AbstractMoveParser
7
- def regexp
8
- raise NotImplementedError
9
- end
4
+ # Base class for move parsers.
5
+ class AbstractMoveParser
6
+ def regexp
7
+ raise NotImplementedError
8
+ end
10
9
 
11
- def parse_part_key(_name)
12
- raise NotImplementedError
13
- end
10
+ def parse_part_key(_name)
11
+ raise NotImplementedError
12
+ end
14
13
 
15
- def parse_move_part(_name, _string)
16
- raise NotImplementedError
17
- end
14
+ def parse_move_part(_name, _string)
15
+ raise NotImplementedError
16
+ end
18
17
 
19
- def move_type_creators
20
- raise NotImplementedError
21
- end
18
+ def move_type_creators
19
+ raise NotImplementedError
20
+ end
22
21
 
23
- def parse_named_captures(match)
24
- present_named_captures = match.named_captures.reject { |_n, v| v.nil? }
25
- present_named_captures.map do |name, string|
26
- key = parse_part_key(name).to_sym
27
- value = parse_move_part(name, string)
28
- [key, value]
29
- end.to_h
30
- end
22
+ def parse_named_captures(match)
23
+ present_named_captures = match.named_captures.reject { |_n, v| v.nil? }
24
+ present_named_captures.map do |name, string|
25
+ key = parse_part_key(name).to_sym
26
+ value = parse_move_part(name, string)
27
+ [key, value]
28
+ end.to_h
29
+ end
31
30
 
32
- def parse_move(move_string)
33
- match = move_string.match(regexp)
34
- if !match || !match.pre_match.empty? || !match.post_match.empty?
35
- raise ArgumentError("Invalid move #{move_string}.")
36
- end
31
+ def parse_move(move_string)
32
+ match = move_string.match(regexp)
33
+ if !match || !match.pre_match.empty? || !match.post_match.empty?
34
+ raise ArgumentError("Invalid move #{move_string}.")
35
+ end
37
36
 
38
- parsed_parts = parse_named_captures(match)
39
- move_type_creators.each do |parser|
40
- return parser.create(parsed_parts) if parser.applies_to?(parsed_parts)
41
- end
42
- raise "No move type creator applies to #{parsed_parts}"
37
+ parsed_parts = parse_named_captures(match)
38
+ move_type_creators.each do |parser|
39
+ return parser.create(parsed_parts) if parser.applies_to?(parsed_parts)
43
40
  end
41
+ raise "No move type creator applies to #{parsed_parts}"
44
42
  end
43
+ end
45
44
  end
@@ -8,148 +8,147 @@ require 'twisty_puzzles/compiled_cube_algorithm'
8
8
  require 'twisty_puzzles/compiled_skewb_algorithm'
9
9
 
10
10
  module TwistyPuzzles
11
-
12
- # Represents a sequence of moves that can be applied to puzzle states.
13
- class Algorithm
14
- include ReversibleApplyable
15
- include Comparable
16
-
17
- def initialize(moves)
18
- moves.each do |m|
19
- raise TypeError, "#{m.inspect} is not a suitable move." unless m.is_a?(AbstractMove)
20
- end
21
- @moves = moves
22
- end
23
-
24
- EMPTY = Algorithm.new([])
11
+ # Represents a sequence of moves that can be applied to puzzle states.
12
+ class Algorithm
13
+ include ReversibleApplyable
14
+ include Comparable
25
15
 
26
- # Creates a one move algorithm.
27
- def self.move(move)
28
- Algorithm.new([move])
16
+ def initialize(moves)
17
+ moves.each do |m|
18
+ raise TypeError, "#{m.inspect} is not a suitable move." unless m.is_a?(AbstractMove)
29
19
  end
20
+ @moves = moves
21
+ end
30
22
 
31
- attr_reader :moves
23
+ EMPTY = Algorithm.new([])
32
24
 
33
- def eql?(other)
34
- self.class.equal?(other.class) && @moves == other.moves
35
- end
25
+ # Creates a one move algorithm.
26
+ def self.move(move)
27
+ Algorithm.new([move])
28
+ end
36
29
 
37
- alias == eql?
30
+ attr_reader :moves
38
31
 
39
- def hash
40
- @hash ||= ([self.class] + @moves).hash
41
- end
32
+ def eql?(other)
33
+ self.class.equal?(other.class) && @moves == other.moves
34
+ end
42
35
 
43
- def length
44
- @moves.length
45
- end
36
+ alias == eql?
46
37
 
47
- def empty?
48
- @moves.empty?
49
- end
38
+ def hash
39
+ @hash ||= ([self.class] + @moves).hash
40
+ end
50
41
 
51
- def to_s
52
- @moves.join(' ')
53
- end
42
+ def length
43
+ @moves.length
44
+ end
54
45
 
55
- def inspect
56
- "Algorithm(#{self})"
57
- end
46
+ def empty?
47
+ @moves.empty?
48
+ end
58
49
 
59
- def apply_to(cube_state)
60
- case cube_state
61
- when SkewbState
62
- compiled_for_skewb.apply_to(cube_state)
63
- when CubeState
64
- compiled_for_cube(cube_state.n).apply_to(cube_state)
65
- else
66
- raise TypeError, "Unsupported cube state class #{cube_state.class}."
67
- end
68
- end
50
+ def to_s
51
+ @moves.join(' ')
52
+ end
69
53
 
70
- def inverse
71
- @inverse ||=
72
- begin
73
- alg = self.class.new(@moves.reverse.map(&:inverse))
74
- alg.inverse = self
75
- alg
76
- end
77
- end
54
+ def inspect
55
+ "Algorithm(#{self})"
56
+ end
78
57
 
79
- def +(other)
80
- self.class.new(@moves + other.moves)
58
+ def apply_to(cube_state)
59
+ case cube_state
60
+ when SkewbState
61
+ compiled_for_skewb.apply_to(cube_state)
62
+ when CubeState
63
+ compiled_for_cube(cube_state.n).apply_to(cube_state)
64
+ else
65
+ raise TypeError, "Unsupported cube state class #{cube_state.class}."
81
66
  end
67
+ end
82
68
 
83
- def <=>(other)
84
- [length, @moves] <=> [other.length, other.moves]
85
- end
69
+ def inverse
70
+ @inverse ||=
71
+ begin
72
+ alg = self.class.new(@moves.reverse.map(&:inverse))
73
+ alg.inverse = self
74
+ alg
75
+ end
76
+ end
86
77
 
87
- # Returns the cancelled version of the given algorithm.
88
- # Note that the cube size is important to know which fat moves cancel
89
- def cancelled(cube_size)
90
- CancellationHelper.cancel(self, cube_size)
91
- end
78
+ def +(other)
79
+ self.class.new(@moves + other.moves)
80
+ end
92
81
 
93
- # Returns the number of moves that cancel if you concat the algorithm to the right of self.
94
- # Note that the cube size is important to know which fat moves cancel
95
- def cancellations(other, cube_size, metric = :htm)
96
- CubeState.check_cube_size(cube_size)
97
- AbstractMove.check_move_metric(metric)
98
- cancelled = cancelled(cube_size)
99
- other_cancelled = other.cancelled(cube_size)
100
- together_cancelled = (self + other).cancelled(cube_size)
101
- cancelled.move_count(cube_size, metric) +
102
- other_cancelled.move_count(cube_size, metric) -
103
- together_cancelled.move_count(cube_size, metric)
104
- end
82
+ def <=>(other)
83
+ [length, @moves] <=> [other.length, other.moves]
84
+ end
105
85
 
106
- # Rotates the algorithm, e.g. applying "y" to "R U" becomes "F U".
107
- # Applying rotation r to alg a is equivalent to r' a r.
108
- # Note that this is not implemented for all moves.
109
- def rotate_by(rotation)
110
- raise TypeError unless rotation.is_a?(Rotation)
111
- return self if rotation.direction.zero?
86
+ # Returns the cancelled version of the given algorithm.
87
+ # Note that the cube size is important to know which fat moves cancel
88
+ def cancelled(cube_size)
89
+ CancellationHelper.cancel(self, cube_size)
90
+ end
112
91
 
113
- self.class.new(@moves.map { |m| m.rotate_by(rotation) })
114
- end
92
+ # Returns the number of moves that cancel if you concat the algorithm to the right of self.
93
+ # Note that the cube size is important to know which fat moves cancel
94
+ def cancellations(other, cube_size, metric = :htm)
95
+ CubeState.check_cube_size(cube_size)
96
+ AbstractMove.check_move_metric(metric)
97
+ cancelled = cancelled(cube_size)
98
+ other_cancelled = other.cancelled(cube_size)
99
+ together_cancelled = (self + other).cancelled(cube_size)
100
+ cancelled.move_count(cube_size, metric) +
101
+ other_cancelled.move_count(cube_size, metric) -
102
+ together_cancelled.move_count(cube_size, metric)
103
+ end
115
104
 
116
- # Mirrors the algorithm and uses the given face as the normal of the mirroring.
117
- # E.g. mirroring "R U F" with "R" as the normal face, we get "L U' F'".
118
- def mirror(normal_face)
119
- raise TypeError unless normal_face.is_a?(Face)
105
+ # Rotates the algorithm, e.g. applying "y" to "R U" becomes "F U".
106
+ # Applying rotation r to alg a is equivalent to r' a r.
107
+ # Note that this is not implemented for all moves.
108
+ def rotate_by(rotation)
109
+ raise TypeError unless rotation.is_a?(Rotation)
110
+ return self if rotation.direction.zero?
120
111
 
121
- self.class.new(@moves.map { |m| m.mirror(normal_face) })
122
- end
112
+ self.class.new(@moves.map { |m| m.rotate_by(rotation) })
113
+ end
123
114
 
124
- # Cube size is needed to decide whether 'u' is a slice move (like on bigger cubes) or a
125
- # fat move (like on 3x3).
126
- def move_count(cube_size, metric = :htm)
127
- raise TypeError unless cube_size.is_a?(Integer)
115
+ # Mirrors the algorithm and uses the given face as the normal of the mirroring.
116
+ # E.g. mirroring "R U F" with "R" as the normal face, we get "L U' F'".
117
+ def mirror(normal_face)
118
+ raise TypeError unless normal_face.is_a?(Face)
128
119
 
129
- AbstractMove.check_move_metric(metric)
130
- return 0 if empty?
120
+ self.class.new(@moves.map { |m| m.mirror(normal_face) })
121
+ end
131
122
 
132
- @moves.map { |m| m.move_count(cube_size, metric) }.reduce(:+)
133
- end
123
+ # Cube size is needed to decide whether 'u' is a slice move (like on bigger cubes) or a
124
+ # fat move (like on 3x3).
125
+ def move_count(cube_size, metric = :htm)
126
+ raise TypeError unless cube_size.is_a?(Integer)
134
127
 
135
- def *(other)
136
- raise TypeError unless other.is_a?(Integer)
137
- raise ArgumentError if other.negative?
128
+ AbstractMove.check_move_metric(metric)
129
+ return 0 if empty?
138
130
 
139
- self.class.new(@moves * other)
140
- end
131
+ @moves.map { |m| m.move_count(cube_size, metric) }.reduce(:+)
132
+ end
141
133
 
142
- def compiled_for_skewb
143
- @compiled_for_skewb ||= CompiledSkewbAlgorithm.for_moves(@moves)
144
- end
134
+ def *(other)
135
+ raise TypeError unless other.is_a?(Integer)
136
+ raise ArgumentError if other.negative?
145
137
 
146
- def compiled_for_cube(cube_size)
147
- (@compiled_for_cube ||= {})[cube_size] ||=
148
- CompiledCubeAlgorithm.for_moves(cube_size, @moves)
149
- end
138
+ self.class.new(@moves * other)
139
+ end
150
140
 
151
- protected
141
+ def compiled_for_skewb
142
+ @compiled_for_skewb ||= CompiledSkewbAlgorithm.for_moves(@moves)
143
+ end
152
144
 
153
- attr_writer :inverse
145
+ def compiled_for_cube(cube_size)
146
+ (@compiled_for_cube ||= {})[cube_size] ||=
147
+ CompiledCubeAlgorithm.for_moves(cube_size, @moves)
154
148
  end
149
+
150
+ protected
151
+
152
+ attr_writer :inverse
153
+ end
155
154
  end