rubiks_cube 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6c52f18a7cefa561a5ddcf4e3ced978f5da8b921
4
+ data.tar.gz: d876ec06323fb7574a7c14d585bbe9b5d247dd5d
5
+ SHA512:
6
+ metadata.gz: 0bfdc210f6c713f895c216b943b9bfd5ec186963a066c88c91a1748b50a77f32c5628b2976d43c97e606c783dd8c9a8d843474a3c6718df29c6a313a2b39f7b5
7
+ data.tar.gz: 2a673c8aa861243b077c1e026fdcb81488c570949d6008758d2f7f7b47a779aead7b10304edfb502fed4e0f257eefbdb59f89356cbf33e959eb3e3f8b8b825af
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.swp
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,13 @@
1
+ # Contributing
2
+
3
+ Want to contribute? Awesome! Thank you so much.
4
+
5
+ ## How?
6
+
7
+ 1. [Fork it](https://help.github.com/articles/fork-a-repo)
8
+ 2. Create a feature branch (`git checkout -b my-new-feature`)
9
+ 3. Commit changes, **with tests** (`git commit -am 'Add some feature'`)
10
+ 4. Run the tests (`bundle exec rake`)
11
+ 5. Push to the branch (`git push origin my-new-feature`)
12
+ 6. Create new [Pull
13
+ Request](https://help.github.com/articles/using-pull-requests)
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rubiks_cube.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Chris Hunt
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # Rubik's Cube
2
+ ![](http://www.mikesfreegifs.com/main4/underconstruction/atwork89.gif)
3
+
4
+ ## Description
5
+
6
+ ## Usage
7
+
8
+ ## Installation
9
+
10
+ ## Contributing
11
+ Please see the [Contributing
12
+ Document](https://github.com/chrishunt/rubiks-cube/blob/master/CONTRIBUTING.md)
13
+
14
+ ## License
15
+ Copyright (C) 2013 Chris Hunt, [MIT
16
+ License](https://github.com/chrishunt/rubiks-cube/blob/master/LICENSE.txt)
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'cane/rake_task'
4
+ require 'cane/hashcheck'
5
+
6
+ desc 'Run all tests'
7
+ RSpec::Core::RakeTask.new(:spec) do |task|
8
+ task.rspec_opts = '--color --order random'
9
+ end
10
+
11
+ desc 'Check code quality'
12
+ Cane::RakeTask.new(:quality) do |task|
13
+ task.abc_max = 9
14
+ task.use Cane::HashCheck
15
+ end
16
+
17
+ task default: :spec
18
+ task default: :quality
@@ -0,0 +1,78 @@
1
+ module RubiksCube
2
+ module Algorithms
3
+ def self.reverse(algorithm)
4
+ algorithm.split.map do |move|
5
+ case modifier = move[-1]
6
+ when "'"
7
+ move[0]
8
+ when "2"
9
+ move
10
+ else
11
+ "#{move}'"
12
+ end
13
+ end.reverse.join ' '
14
+ end
15
+
16
+ module Permutation
17
+ Edge = "R U R' U' R' F R2 U' R' U' R U R' F'"
18
+ Corner = "U F R U' R' U' R U R' F' R U R' U' R' F R F' U'"
19
+
20
+ module Setup
21
+ Edge = {
22
+ 0 => "M2 D L2",
23
+ 2 => "M2 D' L2",
24
+ 3 => "",
25
+ 4 => "U' F U",
26
+ 5 => "U' F' U",
27
+ 6 => "U B U'",
28
+ 7 => "U B' U'",
29
+ 8 => "D' L2",
30
+ 9 => "D2 L2",
31
+ 10 => "D L2",
32
+ 11 => "L2"
33
+ }
34
+
35
+ Corner = {
36
+ 1 => "R2 D' R2",
37
+ 2 => "",
38
+ 3 => "B2 D' R2",
39
+ 4 => "D R2",
40
+ 5 => "R2",
41
+ 6 => "D' R2",
42
+ 7 => "D2 R2"
43
+ }
44
+ end
45
+ end
46
+
47
+ module Orientation
48
+ Edge = "M' U M' U M' U2 M U M U M U2"
49
+ Corner = "R' D R F D F' U' F D' F' R' D' R U"
50
+
51
+ module Setup
52
+ Edge = {
53
+ 1 => "R B",
54
+ 2 => "",
55
+ 3 => "L' B'",
56
+ 4 => "L2 B'",
57
+ 5 => "R2 B",
58
+ 6 => "B",
59
+ 7 => "B'",
60
+ 8 => "D2 B2",
61
+ 9 => "D B2",
62
+ 10 => "B2",
63
+ 11 => "D' B2"
64
+ }
65
+
66
+ Corner = {
67
+ 1 => "",
68
+ 2 => "R'",
69
+ 3 => "B2 R2",
70
+ 4 => "D2 R2",
71
+ 5 => "D R2",
72
+ 6 => "R2",
73
+ 7 => "D' R2"
74
+ }
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,8 @@
1
+ module RubiksCube
2
+ CornerCubie = Struct.new(:state) do
3
+ def rotate!
4
+ u, r, f = state.split ''
5
+ self.state = [f, u, r].join
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,203 @@
1
+ module RubiksCube
2
+ # Standard 3x3x3 Rubik's Cube with normal turn operations (l, r, u, d, f, b)
3
+ class Cube
4
+ SOLVED_STATE = %w(
5
+ UF UR UB UL FL FR BR BL DF DR DB DL UFL URF UBR ULB DLF DFR DRB DBL
6
+ )
7
+
8
+ def initialize(state = nil)
9
+ @state = build_state_from_string(
10
+ state.to_s.empty? ? SOLVED_STATE.join(' ') : state
11
+ )
12
+ end
13
+
14
+ def ==(other)
15
+ state == other.state
16
+ end
17
+
18
+ def state
19
+ @state.join ' '
20
+ end
21
+
22
+ def edges
23
+ @state[0..11]
24
+ end
25
+
26
+ def corners
27
+ @state[12..-1]
28
+ end
29
+
30
+ def solved?
31
+ state == SOLVED_STATE.join(' ')
32
+ end
33
+
34
+ def edge_permuted?(edge)
35
+ cubie_permuted? :edges, edge
36
+ end
37
+
38
+ def corner_permuted?(corner)
39
+ cubie_permuted? :corners, corner
40
+ end
41
+
42
+ def has_correct_edge_permutation?
43
+ incorrect_edge_permutation_locations.empty?
44
+ end
45
+
46
+ def has_correct_corner_permutation?
47
+ incorrect_corner_permutation_locations.empty?
48
+ end
49
+
50
+ def has_correct_edge_orientation?
51
+ incorrect_edge_orientation_locations.empty?
52
+ end
53
+
54
+ def has_correct_corner_orientation?
55
+ incorrect_corner_orientation_locations.empty?
56
+ end
57
+
58
+ def incorrect_edge_permutation_locations
59
+ unpermuted_locations_for :edges
60
+ end
61
+
62
+ def incorrect_corner_permutation_locations
63
+ unpermuted_locations_for :corners
64
+ end
65
+
66
+ def incorrect_edge_orientation_locations
67
+ unoriented_locations_for :edges
68
+ end
69
+
70
+ def incorrect_corner_orientation_locations
71
+ unoriented_locations_for :corners
72
+ end
73
+
74
+ def permuted_location_for(cubie)
75
+ while (location = SOLVED_STATE.index cubie.state) == nil
76
+ cubie = cubie.rotate
77
+ end
78
+
79
+ location -= 12 if location >= 12
80
+ location
81
+ end
82
+
83
+ def perform!(algorithm)
84
+ algorithm.split.each { |move| perform_move! move }
85
+ algorithm
86
+ end
87
+
88
+ def undo!(algorithm)
89
+ perform! reverse(algorithm)
90
+ end
91
+
92
+ def r
93
+ turn [1, 5, 9, 6]
94
+ turn [13, 17, 18, 14]
95
+ rotate [13, 13, 14, 17, 18, 18]
96
+ self
97
+ end
98
+
99
+ def l
100
+ turn [3, 7, 11, 4]
101
+ turn [12, 15, 19, 16]
102
+ rotate [12, 15, 15, 16, 16, 19]
103
+ self
104
+ end
105
+
106
+ def u
107
+ turn [0, 1, 2, 3]
108
+ turn [12, 13, 14, 15]
109
+ self
110
+ end
111
+
112
+ def d
113
+ turn [8, 11, 10, 9]
114
+ turn [16, 19, 18, 17]
115
+ self
116
+ end
117
+
118
+ def f
119
+ turn [0, 4, 8, 5]
120
+ rotate [0, 4, 8, 5]
121
+ turn [12, 16, 17, 13]
122
+ rotate [12, 12, 13, 16, 17, 17]
123
+ self
124
+ end
125
+
126
+ def b
127
+ turn [2, 6, 10, 7]
128
+ rotate [2, 6, 10, 7]
129
+ turn [14, 18, 19, 15]
130
+ rotate [14, 14, 15, 18, 19, 19]
131
+ self
132
+ end
133
+
134
+ def m
135
+ turn [0, 2, 10, 8]
136
+ rotate [0, 2, 10, 8]
137
+ self
138
+ end
139
+
140
+ private
141
+
142
+ def build_state_from_string(state)
143
+ state.split.map { |state| RubiksCube::Cubie.new state }
144
+ end
145
+
146
+ def cubie_permuted?(type, cubie)
147
+ send(type).index(cubie) == permuted_location_for(cubie)
148
+ end
149
+
150
+ def unpermuted_locations_for(type)
151
+ send(type).each_with_index.map do |cubie, location|
152
+ location unless location == permuted_location_for(cubie)
153
+ end.compact
154
+ end
155
+
156
+ def unoriented_locations_for(type)
157
+ send(type).each_with_index.map do |cubie, location|
158
+ oriented_state = SOLVED_STATE.fetch(
159
+ if type == :corners
160
+ location + 12
161
+ else
162
+ location
163
+ end
164
+ )
165
+
166
+ location unless cubie.state == oriented_state
167
+ end.compact
168
+ end
169
+
170
+ def turn(sequence)
171
+ current_cubie = sequence.shift
172
+ first_cubie = @state[current_cubie]
173
+
174
+ sequence.each do |cubie|
175
+ @state[current_cubie] = @state[cubie]
176
+ current_cubie = cubie
177
+ end
178
+
179
+ @state[current_cubie] = first_cubie
180
+ end
181
+
182
+ def rotate(cubies)
183
+ cubies.each { |cubie| @state[cubie].rotate! }
184
+ end
185
+
186
+ def perform_move!(move)
187
+ operation = "#{move[0].downcase}"
188
+
189
+ case modifier = move[-1]
190
+ when "'"
191
+ 3.times { send operation }
192
+ when "2"
193
+ 2.times { send operation }
194
+ else
195
+ send operation
196
+ end
197
+ end
198
+
199
+ def reverse(algorithm)
200
+ RubiksCube::Algorithms.reverse algorithm
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,29 @@
1
+ module RubiksCube
2
+ # Generic cubie piece, either edge cubie or corner cubie
3
+ class Cubie
4
+ def initialize(state)
5
+ @cubie = state.size == 2 ? EdgeCubie.new(state) : CornerCubie.new(state)
6
+ end
7
+
8
+ def ==(other)
9
+ state == other.state
10
+ end
11
+
12
+ def state
13
+ @cubie.state
14
+ end
15
+
16
+ def rotate!
17
+ @cubie.rotate!
18
+ self
19
+ end
20
+
21
+ def rotate
22
+ Cubie.new(state.dup).rotate!
23
+ end
24
+
25
+ def to_s
26
+ @cubie.state
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ module RubiksCube
2
+ EdgeCubie = Struct.new(:state) do
3
+ def rotate!
4
+ state.reverse!
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,120 @@
1
+ module RubiksCube
2
+ # Very inefficient two-cycle solving algorithm, useful for blindfold
3
+ class TwoCycleSolution
4
+ attr_reader :cube
5
+
6
+ def initialize(cube)
7
+ @cube = Cube.new(cube.state)
8
+ end
9
+
10
+ def state
11
+ cube.state
12
+ end
13
+
14
+ def solved?
15
+ cube.solved?
16
+ end
17
+
18
+ def solve!
19
+ @solution ||= begin
20
+ solution = []
21
+ solution << solution_for(:permutation)
22
+ solution << solution_for(:orientation)
23
+ solution.flatten
24
+ end
25
+ end
26
+
27
+ def solution
28
+ solve!
29
+ @solution
30
+ end
31
+
32
+ def solution_length
33
+ solution.flatten.join(' ').split.count
34
+ end
35
+
36
+ def pretty
37
+ solution.each_slice(3).map do |setup, correction, undo|
38
+ step = []
39
+ step << "Setup:\t#{setup}" unless setup.empty?
40
+ step << "Fix:\t#{correction}"
41
+ step << "Undo:\t#{undo}" unless undo.empty?
42
+ step.join "\n"
43
+ end.join("\n\n").strip
44
+ end
45
+
46
+ private
47
+
48
+ def solution_for(step)
49
+ [:edge, :corner].map do |cubie|
50
+ solve_for cubie, step
51
+ end
52
+ end
53
+
54
+ def solve_for(cubie, step)
55
+ solution = []
56
+ solution << [perform(cubie, step)] until finished_with?(cubie, step)
57
+ solution
58
+ end
59
+
60
+ def finished_with?(cubie, step)
61
+ cube.public_send "has_correct_#{cubie}_#{step}?"
62
+ end
63
+
64
+ def perform(cubie, step)
65
+ algorithms_for(cubie, step).map { |algorithm| cube.perform! algorithm }
66
+ end
67
+
68
+ def next_orientation_location_for(cubie)
69
+ incorrect_locations_for(cubie, :orientation).tap do |locations|
70
+ locations.delete(0)
71
+ end.first
72
+ end
73
+
74
+ def next_permutation_location_for(cubie)
75
+ buffer_cubie = send("permutation_buffer_#{cubie}")
76
+
77
+ if cube.public_send("#{cubie}_permuted?", buffer_cubie)
78
+ incorrect_locations_for(cubie, :permutation).first
79
+ else
80
+ cube.permuted_location_for buffer_cubie
81
+ end
82
+ end
83
+
84
+ def permutation_buffer_edge
85
+ cube.edges[1]
86
+ end
87
+
88
+ def permutation_buffer_corner
89
+ cube.corners[0]
90
+ end
91
+
92
+ def incorrect_locations_for(cubie, step)
93
+ cube.public_send "incorrect_#{cubie}_#{step}_locations"
94
+ end
95
+
96
+ def algorithms_for(cubie, step)
97
+ location = send("next_#{step}_location_for", cubie)
98
+ setup = setup_algorithms_for(cubie, step, location)
99
+ correction = correction_algorithm_for(cubie, step)
100
+ undo = RubiksCube::Algorithms.reverse(setup)
101
+
102
+ [ setup, correction, undo ]
103
+ end
104
+
105
+ def correction_algorithm_for(cubie, step)
106
+ load_algorithms step, cubie
107
+ end
108
+
109
+ def setup_algorithms_for(cubie, step, location)
110
+ load_algorithms(step, 'setup', cubie).fetch(location)
111
+ end
112
+
113
+ def load_algorithms(*classes)
114
+ Kernel.const_get(
115
+ "RubiksCube::Algorithms::" <<
116
+ classes.map(&:capitalize).flatten.join('::')
117
+ )
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,3 @@
1
+ module RubiksCube
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,7 @@
1
+ require 'rubiks_cube/version'
2
+ require 'rubiks_cube/cube'
3
+ require 'rubiks_cube/cubie'
4
+ require 'rubiks_cube/algorithms'
5
+ require 'rubiks_cube/edge_cubie'
6
+ require 'rubiks_cube/corner_cubie'
7
+ require 'rubiks_cube/two_cycle_solution'
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rubiks_cube/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'rubiks_cube'
8
+ spec.version = RubiksCube::VERSION
9
+ spec.authors = ['Chris Hunt']
10
+ spec.email = ['c@chrishunt.co']
11
+ spec.description = %q{Learn how to solve the Rubik's Cube. It's easy!}
12
+ spec.summary = %q{Learn how to solve the Rubik's Cube. It's easy!}
13
+ spec.homepage = 'https://github.com/chrishunt/rubiks-cube'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.3'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'rspec'
24
+ spec.add_development_dependency 'cane-hashcheck'
25
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe RubiksCube::Algorithms do
4
+ subject { described_class }
5
+
6
+ describe '.reverse' do
7
+ it 'reverses the algorithm' do
8
+ expect(subject.reverse "F' B L2 U' R").to eq "R' U L2 B' F"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,385 @@
1
+ require 'spec_helper'
2
+
3
+ describe RubiksCube::Cube do
4
+ subject { described_class.new state }
5
+
6
+ let(:state) { nil }
7
+
8
+ describe '#initialize' do
9
+ context 'when a state is provided' do
10
+ let(:state) { 'some state' }
11
+
12
+ it 'is initialized with the state' do
13
+ expect(subject.state).to eq state
14
+ end
15
+ end
16
+
17
+ context 'when no state is provided' do
18
+ let(:state) { nil }
19
+
20
+ it 'is initialized with the solved state' do
21
+ expect(subject.state).to eq RubiksCube::Cube::SOLVED_STATE.join ' '
22
+ end
23
+ end
24
+ end
25
+
26
+ describe '==' do
27
+ it 'returns true when two cubes have the same state' do
28
+ expect(subject).to eq described_class.new(state)
29
+ end
30
+
31
+ it 'returns false when two cubes do not have the same state' do
32
+ expect(subject).to_not eq described_class.new(state).d
33
+ end
34
+ end
35
+
36
+ describe '#edges' do
37
+ it 'returns all the edges' do
38
+ expect(subject.edges).to eq(
39
+ subject.state.split[0..11].map { |cubie| RubiksCube::Cubie.new cubie }
40
+ )
41
+ end
42
+ end
43
+
44
+ describe '#corners' do
45
+ it 'returns all the corners' do
46
+ expect(subject.corners).to eq(
47
+ subject.state.split[12..-1].map { |cubie| RubiksCube::Cubie.new cubie }
48
+ )
49
+ end
50
+ end
51
+
52
+ describe '#incorrect_edge_permutation_locations' do
53
+ context 'with unpermuted edges' do
54
+ let(:state) {
55
+ 'UF UL UB UR FL FR BR BL DF DL DB DR UFL URF UBR ULB DLF DFR DRB DBL'
56
+ }
57
+
58
+ it 'returns the location of all unpermuted edges' do
59
+ expect(
60
+ subject.incorrect_edge_permutation_locations
61
+ ).to eq [1, 3, 9, 11]
62
+ end
63
+ end
64
+
65
+ context 'with permuted edges' do
66
+ it 'returns an empty array' do
67
+ expect(subject.incorrect_edge_permutation_locations).to eq []
68
+ end
69
+ end
70
+ end
71
+
72
+ describe '#incorrect_corner_permutation_locations' do
73
+ context 'with unpermuted corners' do
74
+ let(:state){
75
+ 'UF UR UB UL FL FR BR BL DF DR DB DL ULB UBR URF UFL DLF DFR DRB DBL'
76
+ }
77
+
78
+ it 'returns the locations of all unpermuted corners' do
79
+ expect(
80
+ subject.incorrect_corner_permutation_locations
81
+ ).to eq [0, 1, 2, 3]
82
+ end
83
+ end
84
+
85
+ context 'with permuted corners' do
86
+ it 'returns an empty array' do
87
+ expect(subject.incorrect_corner_permutation_locations).to eq []
88
+ end
89
+ end
90
+ end
91
+
92
+ describe '#incorrect_edge_orientation_locations' do
93
+ context 'with edges that are not oriented' do
94
+ let(:state) {
95
+ 'FU UR BU UL FL RF RB LB FD DR DB DL UFL URF UBR ULB DLF DFR DRB DBL'
96
+ }
97
+
98
+ it 'returns the locations of all unoriented edges' do
99
+ expect(
100
+ subject.incorrect_edge_orientation_locations
101
+ ).to eq [0, 2, 5, 6, 7, 8]
102
+ end
103
+ end
104
+
105
+ context 'with oriented edges' do
106
+ it 'returns an empty array' do
107
+ expect(subject.incorrect_edge_orientation_locations).to eq []
108
+ end
109
+ end
110
+ end
111
+
112
+ describe '#incorrect_corner_orientation_locations' do
113
+ context 'with corners that are not oriented' do
114
+ let(:state) {
115
+ 'UF UR UB UL FL FR BR BL DF DR DB DL FLU FUR UBR ULB DLF DFR DRB DBL'
116
+ }
117
+
118
+ it 'returns the locations of all unoriented corners' do
119
+ expect(subject.incorrect_corner_orientation_locations).to eq [0, 1]
120
+ end
121
+ end
122
+
123
+ context 'with oriented corners' do
124
+ it 'returns an empty array' do
125
+ expect(subject.incorrect_corner_orientation_locations).to eq []
126
+ end
127
+ end
128
+ end
129
+
130
+ describe '#permuted_location_for' do
131
+ RubiksCube::Cube::SOLVED_STATE.each_with_index do |cubie, location|
132
+ it "returns the correct location for the '#{cubie}' cubie" do
133
+ # Both corner and edge index begins at zero
134
+ location -= 12 if location >= 12
135
+
136
+ cubie = RubiksCube::Cubie.new cubie
137
+
138
+ expect(subject.permuted_location_for cubie).to eq location
139
+ end
140
+ end
141
+
142
+ context 'when the cubie has been rotated' do
143
+ let(:cubie) { subject.corners.first }
144
+
145
+ before { cubie.rotate! }
146
+
147
+ it 'still finds the correct location' do
148
+ expect(subject.permuted_location_for cubie).to eq 0
149
+ end
150
+
151
+ it 'does not rotate the cubie' do
152
+ original_state = cubie.state
153
+
154
+ subject.permuted_location_for cubie
155
+
156
+ expect(cubie.state).to eq original_state
157
+ end
158
+ end
159
+ end
160
+
161
+ describe '#solved?' do
162
+ it 'returns true when cube is solved' do
163
+ expect(subject).to be_solved
164
+ end
165
+
166
+ it 'returns false when cube is not solved' do
167
+ subject.l
168
+ expect(subject).to_not be_solved
169
+ end
170
+ end
171
+
172
+ describe '#edge_permuted?' do
173
+ it 'returns true when the cubie is permuted' do
174
+ subject.u
175
+
176
+ permuted_edge = subject.edges[4]
177
+ unpermuted_edge = subject.edges[0]
178
+
179
+ expect(subject.edge_permuted? unpermuted_edge).to be_false
180
+ expect(subject.edge_permuted? permuted_edge).to be_true
181
+ end
182
+ end
183
+
184
+ describe '#corner_permuted?' do
185
+ it 'returns true when the cubie is permuted' do
186
+ subject.f
187
+
188
+ permuted_corner = subject.corners[2]
189
+ unpermuted_corner = subject.corners[1]
190
+
191
+ expect(subject.corner_permuted? unpermuted_corner).to be_false
192
+ expect(subject.corner_permuted? permuted_corner).to be_true
193
+ end
194
+ end
195
+
196
+ describe '#has_correct_edge_permutation?' do
197
+ context 'when the edges are permuted, but corners are not' do
198
+ let(:state) {
199
+ 'UF UR UB UL FL FR BR BL DF DR DB DL ULB UBR URF UFL DBL DRB DFR DLF'
200
+ }
201
+
202
+ it 'returns true' do
203
+ expect(subject).to have_correct_edge_permutation
204
+ end
205
+ end
206
+
207
+ context 'when the edges are not permuted, but corners are' do
208
+ let(:state) {
209
+ 'UL UF UB UR FL FR BR BL DF DR DB DL UFL URF UBR ULB DLF DFR DRB DBL'
210
+ }
211
+
212
+ it 'returns false' do
213
+ expect(subject).to_not have_correct_edge_permutation
214
+ end
215
+ end
216
+ end
217
+
218
+ describe '#has_correct_corner_permutation?' do
219
+ context 'when the corners are permuted, but the edges are not' do
220
+ let(:state) {
221
+ 'UL UF UB UR FL FR BR BL DF DR DB DL UFL URF UBR ULB DLF DFR DRB DBL'
222
+ }
223
+
224
+ it 'returns true' do
225
+ expect(subject).to have_correct_corner_permutation
226
+ end
227
+ end
228
+
229
+ context 'when the corners are not permuted, but the edges are permuted' do
230
+ let(:state) {
231
+ 'UF UR UB UL FL FR BR BL DF DR DB DL ULB UBR URF UFL DBL DRB DFR DLF'
232
+ }
233
+
234
+ it 'returns false' do
235
+ expect(subject).to_not have_correct_corner_permutation
236
+ end
237
+ end
238
+ end
239
+
240
+ describe '#has_correct_edge_orientation?' do
241
+ context 'when the edges are oriented, but the corners are not' do
242
+ let(:state) {
243
+ 'UF UR UB UL FL FR BR BL DF DR DB DL LUF RFU UBR ULB DLF DFR DRB DBL'
244
+ }
245
+
246
+ it 'returns true' do
247
+ expect(subject).to have_correct_edge_orientation
248
+ end
249
+ end
250
+
251
+ context 'when the edges are not oriented, but the corners are' do
252
+ let(:state) {
253
+ 'FU UR BU UL FL FR BR BL DF DR DB DL UFL URF UBR ULB DLF DFR DRB DBL'
254
+ }
255
+
256
+ it 'returns false' do
257
+ expect(subject).to_not have_correct_edge_orientation
258
+ end
259
+ end
260
+ end
261
+
262
+ describe '#has_correct_corner_orientation?' do
263
+ context 'when the corners are oriented, but the edges are not' do
264
+ let(:state) {
265
+ 'FU UR BU UL FL FR BR BL DF DR DB DL UFL URF UBR ULB DLF DFR DRB DBL'
266
+ }
267
+
268
+ it 'returns true' do
269
+ expect(subject).to have_correct_corner_orientation
270
+ end
271
+ end
272
+
273
+ context 'when the corners are not oriented, but the edges are' do
274
+ let(:state) {
275
+ 'UF UR UB UL FL FR BR BL DF DR DB DL LUF RFU UBR ULB DLF DFR DRB DBL'
276
+ }
277
+
278
+ it 'returns false' do
279
+ expect(subject).to_not have_correct_corner_orientation
280
+ end
281
+ end
282
+ end
283
+
284
+ describe '#perform!' do
285
+ it 'performs the algorithm on the cube' do
286
+ subject.perform! "U2 D' L R F B"
287
+
288
+ expect(subject.state).to eq(
289
+ described_class.new.u.u.d.d.d.l.r.f.b.state
290
+ )
291
+ end
292
+
293
+ it 'returns the algorithm that was performed' do
294
+ algorithm = "F2 U' L"
295
+
296
+ expect(subject.perform! algorithm).to eq algorithm
297
+ end
298
+ end
299
+
300
+ describe '#undo!' do
301
+ it 'performs the algorithm in reverse on the cube' do
302
+ subject.f.f.f.b.l.l.u.u.u.r
303
+
304
+ subject.undo! "F' B L2 U' R"
305
+
306
+ expect(subject).to be_solved
307
+ end
308
+ end
309
+
310
+ describe 'face turns' do
311
+ shared_examples_for 'a face turn' do
312
+ it "rotates the face 90 degrees clockwise" do
313
+ subject.public_send direction
314
+ expect(subject.state).to eq expected_state
315
+ end
316
+
317
+ it 'is chainable' do
318
+ expect(subject.public_send direction).to eq subject
319
+ end
320
+ end
321
+
322
+ describe '#r' do
323
+ let(:direction) { 'r' }
324
+ let(:expected_state) {
325
+ 'UF FR UB UL FL DR UR BL DF BR DB DL UFL FRD FUR ULB DLF BDR BRU DBL'
326
+ }
327
+
328
+ it_should_behave_like 'a face turn'
329
+ end
330
+
331
+ describe '#l' do
332
+ let(:direction) { 'l' }
333
+ let(:expected_state) {
334
+ 'UF UR UB BL UL FR BR DL DF DR DB FL BUL URF UBR BLD FLU DFR DRB FDL'
335
+ }
336
+
337
+ it_should_behave_like 'a face turn'
338
+ end
339
+
340
+ describe '#u' do
341
+ let(:direction) { 'u' }
342
+ let(:expected_state) {
343
+ 'UR UB UL UF FL FR BR BL DF DR DB DL URF UBR ULB UFL DLF DFR DRB DBL'
344
+ }
345
+
346
+ it_should_behave_like 'a face turn'
347
+ end
348
+
349
+ describe '#d' do
350
+ let(:direction) { 'd' }
351
+ let(:expected_state) {
352
+ 'UF UR UB UL FL FR BR BL DL DF DR DB UFL URF UBR ULB DBL DLF DFR DRB'
353
+ }
354
+
355
+ it_should_behave_like 'a face turn'
356
+ end
357
+
358
+ describe '#f' do
359
+ let(:direction) { 'f' }
360
+ let(:expected_state) {
361
+ 'LF UR UB UL FD FU BR BL RF DR DB DL LFD LUF UBR ULB RDF RFU DRB DBL'
362
+ }
363
+
364
+ it_should_behave_like 'a face turn'
365
+ end
366
+
367
+ describe '#b' do
368
+ let(:direction) { 'b' }
369
+ let(:expected_state) {
370
+ 'UF UR RB UL FL FR BD BU DF DR LB DL UFL URF RBD RUB DLF DFR LDB LBU'
371
+ }
372
+
373
+ it_should_behave_like 'a face turn'
374
+ end
375
+
376
+ describe '#m' do
377
+ let(:direction) { 'm' }
378
+ let(:expected_state) {
379
+ 'BU UR BD UL FL FR BR BL FU DR FD DL UFL URF UBR ULB DLF DFR DRB DBL'
380
+ }
381
+
382
+ it_should_behave_like 'a face turn'
383
+ end
384
+ end
385
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe RubiksCube::Cubie do
4
+ subject { described_class.new state }
5
+
6
+ let(:state) { 'UF' }
7
+
8
+ it 'is initialized with a state' do
9
+ expect(subject.state).to eq state
10
+ end
11
+
12
+ describe '==' do
13
+ context 'when the state equals the state of the other' do
14
+ it 'returns true' do
15
+ expect(subject == described_class.new(state)).to be_true
16
+ end
17
+ end
18
+
19
+ context 'when the state does not equal the state of the other' do
20
+ it 'returns true' do
21
+ expect(subject == described_class.new('UB')).to be_false
22
+ end
23
+ end
24
+ end
25
+
26
+ describe '#to_s' do
27
+ it 'converts the cubie to a string' do
28
+ expect(subject.to_s).to eq state
29
+ end
30
+ end
31
+
32
+ describe '#rotate!' do
33
+ context 'with an edge piece' do
34
+ let(:state) { 'UF' }
35
+
36
+ it 'flips the cubie' do
37
+ expect(subject.rotate!.state).to eq 'FU'
38
+ expect(subject.rotate!.state).to eq state
39
+ end
40
+ end
41
+
42
+ context 'with a corner piece' do
43
+ let(:state) { 'URF' }
44
+
45
+ it 'rotates the cubie once couter clockwise' do
46
+ expect(subject.rotate!.state).to eq 'FUR'
47
+ expect(subject.rotate!.state).to eq 'RFU'
48
+ expect(subject.rotate!.state).to eq state
49
+ end
50
+ end
51
+ end
52
+
53
+ describe '#rotate' do
54
+ it "returns a new cubie that's been rotated" do
55
+ expect(subject.rotate).to eq described_class.new(state).rotate!
56
+ end
57
+
58
+ it 'does not modify the cubie' do
59
+ original_state = subject.state.dup
60
+
61
+ subject.rotate
62
+
63
+ expect(subject.state).to eq original_state
64
+ end
65
+ end
66
+
67
+ end
@@ -0,0 +1,110 @@
1
+ require 'spec_helper'
2
+
3
+ describe RubiksCube::TwoCycleSolution do
4
+ subject { described_class.new cube }
5
+
6
+ let(:cube) { RubiksCube::Cube.new state }
7
+ let(:state) { nil }
8
+
9
+ describe '#solve!' do
10
+ shared_examples_for 'a cube that can be solved' do
11
+ it 'solves the cube' do
12
+ subject.solve!
13
+ expect(subject).to be_solved
14
+ end
15
+ end
16
+
17
+ it 'does not modify the original cube state' do
18
+ original_cube_state = cube.l.state
19
+
20
+ subject.solve!
21
+
22
+ expect(cube.state).to eq original_cube_state
23
+ end
24
+
25
+ context 'when edges need to be swapped' do
26
+ let(:state) {
27
+ 'UF UL UB UR FL FR BR BL DF DR DB DL UFL UBR URF ULB DLF DFR DRB DBL'
28
+ }
29
+
30
+ it_should_behave_like 'a cube that can be solved'
31
+ end
32
+
33
+ context 'when corners need to be swapped' do
34
+ let(:state) {
35
+ 'UL UR UB UF FL FR BR BL DF DR DB DL UBR URF UFL ULB DLF DFR DRB DBL'
36
+ }
37
+
38
+ it_should_behave_like 'a cube that can be solved'
39
+ end
40
+
41
+ context 'when two edges have incorrect orientation' do
42
+ before { cube.perform! RubiksCube::Algorithms::Orientation::Edge }
43
+
44
+ it_should_behave_like 'a cube that can be solved'
45
+ end
46
+
47
+ context 'when two corners have incorrect orientation' do
48
+ before { cube.perform! RubiksCube::Algorithms::Orientation::Corner }
49
+
50
+ it_should_behave_like 'a cube that can be solved'
51
+ end
52
+
53
+ [
54
+ "U2 L' F2 U' B D' R' F2 U F' R L2 F2 L' B L R2 B' D R L' U D' R2 F'",
55
+ "L U2 R' B2 U D R' U R' B2 L F R2 L' B' R' U2 L2 R2 U F' L' U' L U'",
56
+ "F' U2 D2 F2 R' D2 B L2 F L2 D2 F U2 F B L U' D' R' F D F' L2 F' B2",
57
+ "R2 U' D2 B L2 U' R D2 L2 U2 F2 D' B' L B' D L' B2 L' F' L' B2 F2 U' L2",
58
+ "L2 R D' U2 R L' B2 U' L2 D2 B2 D' R2 L' B' U2 B R2 F2 R' D' F2 D L U",
59
+ ].each do |scramble|
60
+ context "with scramble (#{scramble})" do
61
+ before { cube.perform! scramble }
62
+
63
+ it_should_behave_like 'a cube that can be solved'
64
+ end
65
+ end
66
+
67
+ describe '#solution' do
68
+ before do
69
+ cube.perform!([
70
+ RubiksCube::Algorithms::Permutation::Edge,
71
+ RubiksCube::Algorithms::Permutation::Corner,
72
+ RubiksCube::Algorithms::Orientation::Edge,
73
+ ].join ' ')
74
+ end
75
+
76
+ context 'when the cube has not been solved' do
77
+ it 'first solves the cube' do
78
+ subject.should_receive(:solve!)
79
+ subject.solution
80
+ end
81
+ end
82
+
83
+ context 'when the cube has been solved' do
84
+ before { subject.solve! }
85
+
86
+ it 'returns the setup, algorithm, and undo moves' do
87
+ expect(subject.solution).to eq([
88
+ "", RubiksCube::Algorithms::Permutation::Edge, "",
89
+ "M2 D L2", RubiksCube::Algorithms::Permutation::Edge, "L2 D' M2",
90
+ "R2 D' R2", RubiksCube::Algorithms::Permutation::Corner, "R2 D R2",
91
+ "", RubiksCube::Algorithms::Permutation::Corner, "",
92
+ "R B", RubiksCube::Algorithms::Orientation::Edge, "B' R'",
93
+ "", RubiksCube::Algorithms::Orientation::Edge, ""
94
+ ])
95
+ end
96
+ end
97
+ end
98
+
99
+ describe '#solution_length' do
100
+ it 'returns the length of the solution' do
101
+ cube.l.r
102
+ expect(subject.solution_length).to eq 634
103
+ end
104
+
105
+ it 'returns zero when cube already solved' do
106
+ expect(subject.solution_length).to be_zero
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1 @@
1
+ require 'rubiks_cube'
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubiks_cube
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Chris Hunt
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-05-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: cane-hashcheck
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Learn how to solve the Rubik's Cube. It's easy!
70
+ email:
71
+ - c@chrishunt.co
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - CONTRIBUTING.md
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - lib/rubiks_cube.rb
83
+ - lib/rubiks_cube/algorithms.rb
84
+ - lib/rubiks_cube/corner_cubie.rb
85
+ - lib/rubiks_cube/cube.rb
86
+ - lib/rubiks_cube/cubie.rb
87
+ - lib/rubiks_cube/edge_cubie.rb
88
+ - lib/rubiks_cube/two_cycle_solution.rb
89
+ - lib/rubiks_cube/version.rb
90
+ - rubiks_cube.gemspec
91
+ - spec/rubiks_cube/algorithms_spec.rb
92
+ - spec/rubiks_cube/cube_spec.rb
93
+ - spec/rubiks_cube/cubie_spec.rb
94
+ - spec/rubiks_cube/two_cycle_solution_spec.rb
95
+ - spec/spec_helper.rb
96
+ homepage: https://github.com/chrishunt/rubiks-cube
97
+ licenses:
98
+ - MIT
99
+ metadata: {}
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - '>='
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubyforge_project:
116
+ rubygems_version: 2.0.3
117
+ signing_key:
118
+ specification_version: 4
119
+ summary: Learn how to solve the Rubik's Cube. It's easy!
120
+ test_files:
121
+ - spec/rubiks_cube/algorithms_spec.rb
122
+ - spec/rubiks_cube/cube_spec.rb
123
+ - spec/rubiks_cube/cubie_spec.rb
124
+ - spec/rubiks_cube/two_cycle_solution_spec.rb
125
+ - spec/spec_helper.rb