twisty_puzzles 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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