rubiks_cube 0.0.1

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