├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── img ├── cube_algorithm.jpg ├── cube_blank.jpg ├── cube_m_slice.jpg ├── cube_orientation.jpg ├── cube_permutation.jpg ├── cube_scramble_1.jpg ├── cube_scramble_2.jpg ├── cube_solved.jpg └── video_thumbnail.jpg ├── lib ├── rubiks_cube.rb └── rubiks_cube │ ├── algorithms.rb │ ├── bicycle_solution.rb │ ├── cube.rb │ ├── cubie.rb │ ├── solution.rb │ ├── sticker_state_transform.rb │ ├── two_cycle_solution.rb │ └── version.rb ├── rubiks_cube.gemspec └── spec ├── rubiks_cube ├── algorithms_spec.rb ├── bicycle_solution_spec.rb ├── cube_spec.rb ├── cubie_spec.rb ├── solution_spec.rb ├── sticker_state_transform_spec.rb └── two_cycle_solution_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.gem 3 | *.rbc 4 | .bundle 5 | .config 6 | .yardoc 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.1.0 4 | - 2.0.0 5 | - 1.9.3 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 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) 14 | 15 | ## Running The Tests 16 | 17 | ```bash 18 | $ git clone https://github.com/chrishunt/rubiks-cube.git 19 | $ cd rubiks-cube 20 | $ bundle install 21 | $ bundle exec rake 22 | ``` 23 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in rubiks_cube.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | rubiks_cube (1.3.0) 5 | 6 | GEM 7 | remote: https://rubygems.org/ 8 | specs: 9 | cane (2.6.0) 10 | parallel 11 | cane-hashcheck (1.2.0) 12 | cane 13 | diff-lcs (1.2.5) 14 | parallel (0.7.1) 15 | rake (10.3.2) 16 | rspec (3.1.0) 17 | rspec-core (~> 3.1.0) 18 | rspec-expectations (~> 3.1.0) 19 | rspec-mocks (~> 3.1.0) 20 | rspec-core (3.1.2) 21 | rspec-support (~> 3.1.0) 22 | rspec-expectations (3.1.0) 23 | diff-lcs (>= 1.2.0, < 2.0) 24 | rspec-support (~> 3.1.0) 25 | rspec-mocks (3.1.0) 26 | rspec-support (~> 3.1.0) 27 | rspec-support (3.1.0) 28 | 29 | PLATFORMS 30 | ruby 31 | 32 | DEPENDENCIES 33 | bundler (~> 1.7.2) 34 | cane-hashcheck (~> 1.2.0) 35 | rake (~> 10.3.2) 36 | rspec (~> 3.1.0) 37 | rubiks_cube! 38 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 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. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rubik's Cube 2 | 3 | [![Gem Version](http://img.shields.io/gem/v/rubiks_cube.svg?style=flat)](http://rubygems.org/gems/rubiks_cube) 4 | [![Travis CI](http://img.shields.io/travis/chrishunt/rubiks-cube.svg?style=flat)](https://travis-ci.org/chrishunt/rubiks-cube) 5 | 6 | Solve your Rubik's Cube with an **easy** two-cycle solution 7 | 8 | ## Description 9 | 10 | Can you solve the Rubik's Cube? Do you want to learn how? GREAT! 11 | 12 | We will teach you a very simple two-cycle solution for solving the Rubik's 13 | Cube. It's so simple that I used this exact solution to solve the cube 14 | blindfolded in the [2005 Rubik's World 15 | Championship](http://worldcubeassociation.org/results/p.php?i=2005HUNT01) with 16 | a time of 5 minutes and 40 seconds. After you've mastered this solution, you 17 | will average less than 2 minutes and will have enough understanding to progress 18 | to the [Fridrich CFOP](http://en.wikipedia.org/wiki/Fridrich_Method) method. 19 | You'll be solving the Rubik's Cube in less than 20 seconds in no time. WOW! 20 | 21 | 22 | ![](https://raw.github.com/chrishunt/rubiks-cube/master/img/video_thumbnail.jpg) 23 | 24 | 25 | ## Sections 26 | 27 | 1. [Usage](#usage) 28 | 1. [Setting Rubik's Cube State](#setting-rubiks-cube-state) 29 | - [Examples](#examples) 30 | - [Solved Cube](#solved-cube) 31 | - [Slightly Scrambled Cube](#slightly-scrambled-cube) 32 | - [Fully Scrambled Cube](#fully-scrambled-cube) 33 | 1. [Turning a Rubik's Cube](#turning-a-rubiks-cube) 34 | 1. [Solving a Rubik's Cube](#solving-a-rubiks-cube) 35 | - [Two Cycle Solution](#two-cycle-solution) 36 | - [How It Works: Permutation](#how-it-works-permutation) 37 | - [How It Works: Orientation](#how-it-works-orientation) 38 | 1. [Algorithms](#algorithms) 39 | - [Algorithm Notation](#algorithm-notation) 40 | - [M Slice](#m-slice) 41 | 1. [Installation](#installation) 42 | 1. [Contributing](#contributing) 43 | 1. [License](#license) 44 | 45 | ## Usage 46 | 47 | ```ruby 48 | require 'rubiks_cube' 49 | 50 | cube = RubiksCube::Cube.new 51 | cube.solved? #=> true 52 | 53 | scramble = "U D B2 U B D2 B2 F' R' U2 F U' L2 F L2 B2 L2 R' U R' U' D R2 F2 B2" 54 | 55 | cube.perform! scramble 56 | cube.solved? #=> false 57 | 58 | solution = RubiksCube::TwoCycleSolution.new(cube) 59 | 60 | solution.length #=> 458 61 | 62 | puts solution.pretty 63 | 64 | # Setup: L2 65 | # Fix: R U R' U' R' F R2 U' R' U' R U R' F' 66 | # Undo: L2 67 | # 68 | # Setup: M2 D L2 69 | # Fix: R U R' U' R' F R2 U' R' U' R U R' F' 70 | # Undo: L2 D' M2 71 | # 72 | # Setup: U' F' U 73 | # Fix: R U R' U' R' F R2 U' R' U' R U R' F' 74 | # Undo: U' F U 75 | # ... 76 | ``` 77 | 78 | ## Setting Rubik's Cube State 79 | 80 | If you have a scrambled Rubik's Cube sitting in front of you right now, you 81 | may not know how to get it back to the solved position. That's OK! We can set 82 | the Rubik's Cube state manually and learn how to solve it. 83 | 84 | Starting with the **F** face, enter the color of each sticker from top-left to 85 | bottom-right. You may use any characters you like to describe the color of your 86 | cube, but make sure all six colors are unique. Whitespace is not important. 87 | 88 | The cube state must be entered in the following order: 89 | 90 | 1. **F**: Front Face 91 | 1. **R**: Right Face 92 | 1. **B**: Back Face 93 | 1. **L**: Left Face 94 | 1. **U**: Up Face (top) 95 | 1. **D**: Down Face (bottom) 96 | 97 | See the [examples](#examples) below for more help. 98 | 99 | ### Examples 100 | 101 | Entering cube state manually can be confusing at first. Here are some examples 102 | to help you out. 103 | 104 | #### Solved Cube 105 | 106 | ![](https://raw.github.com/chrishunt/rubiks-cube/master/img/cube_solved.jpg) 107 | 108 | A Rubik's Cube is solved by default. 109 | 110 | ```ruby 111 | require 'rubiks_cube' 112 | 113 | cube = RubiksCube::Cube.new 114 | 115 | cube.state = <<-STATE 116 | G G G G G G G G G 117 | Y Y Y Y Y Y Y Y Y 118 | B B B B B B B B B 119 | W W W W W W W W W 120 | R R R R R R R R R 121 | O O O O O O O O O 122 | STATE 123 | 124 | cube.solved? #=> true 125 | ``` 126 | 127 | #### Slightly Scrambled Cube 128 | 129 | ![](https://raw.github.com/chrishunt/rubiks-cube/master/img/cube_scramble_1.jpg) 130 | 131 | Our cube is now slightly scrambled. 132 | 133 | ```ruby 134 | require 'rubiks_cube' 135 | 136 | cube = RubiksCube::Cube.new 137 | 138 | cube.state = <<-STATE 139 | G G Y G G R G G G 140 | R R Y Y Y Y Y Y Y 141 | B B B B B B B B W 142 | W W W W W W O W W 143 | R R R R R G R Y G 144 | O O O O O O B O O 145 | STATE 146 | 147 | cube.solved? #=> false 148 | ``` 149 | 150 | #### Fully Scrambled Cube 151 | 152 | ![](https://raw.github.com/chrishunt/rubiks-cube/master/img/cube_scramble_2.jpg) 153 | 154 | Here's a fully scrambled cube. This looks hard to solve. We can start our cube 155 | in this state if we like and have the solver tell us a solution. 156 | 157 | Here's the scramble we used to mix the cube if you'd like to try it yourself: 158 | 159 | ``` 160 | U D B2 U B D2 B2 F' R' U2 F U' L2 F L2 B2 L2 R' U R' U' D R2 F2 B2 161 | ``` 162 | 163 | ```ruby 164 | require 'rubiks_cube' 165 | 166 | cube = RubiksCube::Cube.new 167 | 168 | cube.state = <<-STATE 169 | R G O R G O O W G 170 | G W W B Y W Y Y Y 171 | G B B G B W R Y B 172 | Y G Y B W G R Y W 173 | O R R O R O G Y W 174 | B R O R O O W B B 175 | STATE 176 | 177 | cube.solved? #=> false 178 | ``` 179 | 180 | ## Turning a Rubik's Cube 181 | 182 | Each Rubik's Cube face ( **L**, **R**, **F**, **B**, **D**, **U** ) can be 183 | turned clockwise manually by calling the appropriate method on the cube. For 184 | example, if we'd like to turn the right face twice, the down face once, and the 185 | back face three times: 186 | 187 | ```ruby 188 | require 'rubiks_cube' 189 | 190 | cube = RubiksCube::Cube.new 191 | 192 | cube.r.r.d.b.b.b 193 | ``` 194 | 195 | Most people will prefer to use standard Rubik's Cube [algorithm 196 | notation](#algorithm-notation) for turning the cube. Here's the same example 197 | with with cube notation: 198 | 199 | ```ruby 200 | require 'rubiks_cube' 201 | 202 | cube = RubiksCube::Cube.new 203 | 204 | cube.perform! "R2 D B'" 205 | ``` 206 | 207 | Performing face turns on the cube changes the state. 208 | 209 | ```ruby 210 | require 'rubiks_cube' 211 | 212 | cube = RubiksCube::Cube.new 213 | 214 | cube.perform!( 215 | "U D2 F2 L' R' D' B' U' D L D U2 B' L2 F2 R' U D F2 B' R' F2 U F2 B" 216 | ) 217 | 218 | cube.solved? #=> false 219 | 220 | cube.state 221 | # "DB UF RB RF LB DL UR RD FD UB LF UL FDL DFR UBR BDR UFL LDB FUR LBU" 222 | ``` 223 | 224 | ## Solving a Rubik's Cube 225 | 226 | We've provided a simple two-cycle solution to help you solve the Rubik's Cube. 227 | This solution is very inefficient, but wonderful for humans. Using the 228 | two-cycle solution, you can quickly learn how to solve the Rubik's Cube without 229 | using the computer. 230 | 231 | ### Two Cycle Solution 232 | 233 | The two-cycle solution is a popular solution used to solve the Rubik's Cube 234 | blindfolded. It requires little memorization and takes little time to learn. 235 | Solutions usually range from 400-600 turns, but most of those turns are quickly 236 | executed. We can easily achieve times of less than 4 minutes with this 237 | solution. 238 | 239 | The two-cycle solution solves the cube by swapping two cubies at a time until 240 | all cubies are in the correct location. This is the permutation step. After the 241 | cubies are permuted, we then rotate two cubies at a time (in their current 242 | location) until all are oriented correctly. This is the orientation step. We 243 | call this the *two-cycle* solution because everything is done in pairs. 244 | 245 | You can use the `rubiks_cube` gem to *learn* the two-cycle solution. For each 246 | step, we provide setup moves, the [fixing algorithm](#algorithms) (either 247 | changing permutation or orientation), and the undo moves. Pay close attention 248 | to how the cube moves and you will be solving by yourself in no time. 249 | 250 | See [Usage](#usage) for an example of the `TwoCycleSolution` 251 | 252 | #### How It Works: Permutation 253 | 254 | ![](https://raw.github.com/chrishunt/rubiks-cube/master/img/cube_permutation.jpg) 255 | 256 | The permutation step is completed using only two algorithms. One swaps two 257 | edges and the other swaps two corners. The diagram above shows which edges and 258 | corners are swapped. 259 | 260 | When a solution is calculated, we present a 'setup' algorithm (which gets the 261 | cubies into a position where they can be swapped), then we present one of the 262 | two swapping algorithms, followed by an 'undo' algorithm that reverses the 263 | setup move. 264 | 265 | #### How It Works: Orientation 266 | 267 | ![](https://raw.github.com/chrishunt/rubiks-cube/master/img/cube_orientation.jpg) 268 | 269 | The orientation step is completed using only two algorithms. One flips two 270 | edges and the other rotates two corners (one clockwise and the other 271 | counter-clockwise). The diagram above shows which edges and corners are 272 | rotated. 273 | 274 | When a solution is calculated, we present a 'setup' algorithm (which gets the 275 | cubies into a position where they can be rotated), then we present one of the 276 | two rotation algorithms, followed by an 'undo' algorithm that reverses the 277 | setup move. 278 | 279 | ## Algorithms 280 | 281 | All algorithms can be found in 282 | [`RubiksCube::Algorithms`](https://github.com/chrishunt/rubiks-cube/blob/master/lib/rubiks_cube/algorithms.rb) 283 | 284 | ### Algorithm Notation 285 | 286 | ![](https://raw.github.com/chrishunt/rubiks-cube/master/img/cube_algorithm.jpg) 287 | 288 | Rubik's Cube algorithm notation is easy to understand, but may look confusing 289 | at first. Each face is represented by a letter: 290 | 291 | - **L**: Left Face 292 | - **R**: Right Face 293 | - **F**: Front Face 294 | - **B**: Back Face 295 | - **U**: Up Face (top) 296 | - **D**: Down Face (bottom) 297 | 298 | When we see a letter in an algorithm, then we turn that face 90 degrees 299 | clockwise. To determine which direction is clockwise, rotate the cube so that 300 | you are looking at the face, then make the turn. 301 | 302 | Faces may be followed by one of two modifiers: 303 | 304 | - **'**: Rotate the face 90 degrees *counter*-clockwise 305 | - **2**: Rotate the face 180 degrees (two turns) 306 | 307 | For example, if we want to apply the algorithm `F2 B D' R`, then we would take 308 | these steps: 309 | 310 | 1. Rotate **F** face 180 degrees (two turns) 311 | 2. Rotate **B** face 90 degrees clockwise 312 | 3. Rotate **D** face 90 degrees *counter*-clockwise 313 | 4. Rotate **R** face 90 degrees clockwise 314 | 315 | #### M Slice 316 | 317 | ![](https://raw.github.com/chrishunt/rubiks-cube/master/img/cube_m_slice.jpg) 318 | 319 | There is one special algorithm notation that does not map to a face. This is 320 | called the M slice. The M slice is the middle vertical layer of the Rubik's 321 | Cube. 322 | 323 | When you see **M**, then rotate this slice 90 degrees clockwise. To figure out 324 | which direction is clockwise, look at the **L** face. 325 | 326 | For example, if we want to apply the algorithm `M2 F M2`, then we would take 327 | these steps: 328 | 329 | 1. Rotate **M** slice 180 degrees (two turns) 330 | 2. Rotate **F** face 90 degrees clockwise 331 | 3. Rotate **M** slice 180 degrees (two turns) 332 | 333 | ## Installation 334 | 335 | ```bash 336 | $ gem install rubiks_cube 337 | ``` 338 | 339 | ## Contributing 340 | 341 | Please see the [Contributing 342 | Document](https://github.com/chrishunt/rubiks-cube/blob/master/CONTRIBUTING.md) 343 | 344 | ## License 345 | 346 | Copyright (C) 2013 Chris Hunt, [MIT 347 | License](https://github.com/chrishunt/rubiks-cube/blob/master/LICENSE.txt) 348 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 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 19 | -------------------------------------------------------------------------------- /img/cube_algorithm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrishunt/rubiks-cube/3b276ac7dbc8c2afd02d351316d4eb6b1e0d14e0/img/cube_algorithm.jpg -------------------------------------------------------------------------------- /img/cube_blank.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrishunt/rubiks-cube/3b276ac7dbc8c2afd02d351316d4eb6b1e0d14e0/img/cube_blank.jpg -------------------------------------------------------------------------------- /img/cube_m_slice.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrishunt/rubiks-cube/3b276ac7dbc8c2afd02d351316d4eb6b1e0d14e0/img/cube_m_slice.jpg -------------------------------------------------------------------------------- /img/cube_orientation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrishunt/rubiks-cube/3b276ac7dbc8c2afd02d351316d4eb6b1e0d14e0/img/cube_orientation.jpg -------------------------------------------------------------------------------- /img/cube_permutation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrishunt/rubiks-cube/3b276ac7dbc8c2afd02d351316d4eb6b1e0d14e0/img/cube_permutation.jpg -------------------------------------------------------------------------------- /img/cube_scramble_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrishunt/rubiks-cube/3b276ac7dbc8c2afd02d351316d4eb6b1e0d14e0/img/cube_scramble_1.jpg -------------------------------------------------------------------------------- /img/cube_scramble_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrishunt/rubiks-cube/3b276ac7dbc8c2afd02d351316d4eb6b1e0d14e0/img/cube_scramble_2.jpg -------------------------------------------------------------------------------- /img/cube_solved.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrishunt/rubiks-cube/3b276ac7dbc8c2afd02d351316d4eb6b1e0d14e0/img/cube_solved.jpg -------------------------------------------------------------------------------- /img/video_thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrishunt/rubiks-cube/3b276ac7dbc8c2afd02d351316d4eb6b1e0d14e0/img/video_thumbnail.jpg -------------------------------------------------------------------------------- /lib/rubiks_cube.rb: -------------------------------------------------------------------------------- 1 | require 'rubiks_cube/version' 2 | require 'rubiks_cube/sticker_state_transform' 3 | require 'rubiks_cube/cube' 4 | require 'rubiks_cube/cubie' 5 | require 'rubiks_cube/algorithms' 6 | require 'rubiks_cube/solution' 7 | require 'rubiks_cube/two_cycle_solution' 8 | require 'rubiks_cube/bicycle_solution' 9 | -------------------------------------------------------------------------------- /lib/rubiks_cube/algorithms.rb: -------------------------------------------------------------------------------- 1 | module RubiksCube 2 | # Permutation and Orientation algorithms for two-cycle solution 3 | module Algorithms 4 | def self.reverse(algorithm) 5 | algorithm.split.map do |move| 6 | case modifier = move[-1] 7 | when "'" 8 | move[0] 9 | when "2" 10 | move 11 | else 12 | "#{move}'" 13 | end 14 | end.reverse.join ' ' 15 | end 16 | 17 | module Permutation 18 | Edge = "R U R' U' R' F R2 U' R' U' R U R' F'" 19 | Corner = "U F R U' R' U' R U R' F' R U R' U' R' F R F' U'" 20 | 21 | module Setup 22 | Edge = { 23 | 0 => "M2 D L2", 24 | 2 => "M2 D' L2", 25 | 3 => "", 26 | 4 => "U' F U", 27 | 5 => "U' F' U", 28 | 6 => "U B U'", 29 | 7 => "U B' U'", 30 | 8 => "D' L2", 31 | 9 => "D2 L2", 32 | 10 => "D L2", 33 | 11 => "L2" 34 | } 35 | 36 | Corner = { 37 | 1 => "R2 D' R2", 38 | 2 => "", 39 | 3 => "B2 D' R2", 40 | 4 => "D R2", 41 | 5 => "R2", 42 | 6 => "D' R2", 43 | 7 => "D2 R2" 44 | } 45 | end 46 | end 47 | 48 | module Orientation 49 | Edge = "M' U M' U M' U2 M U M U M U2" 50 | Corner = "R' D R F D F' U' F D' F' R' D' R U" 51 | 52 | module Setup 53 | Edge = { 54 | 1 => "R B", 55 | 2 => "", 56 | 3 => "L' B'", 57 | 4 => "L2 B'", 58 | 5 => "R2 B", 59 | 6 => "B", 60 | 7 => "B'", 61 | 8 => "D2 B2", 62 | 9 => "D B2", 63 | 10 => "B2", 64 | 11 => "D' B2" 65 | } 66 | 67 | Corner = { 68 | 1 => "", 69 | 2 => "R'", 70 | 3 => "B2 R2", 71 | 4 => "D2 R2", 72 | 5 => "D R2", 73 | 6 => "R2", 74 | 7 => "D' R2" 75 | } 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/rubiks_cube/bicycle_solution.rb: -------------------------------------------------------------------------------- 1 | module RubiksCube 2 | # Alias for TwoCycleSolution 3 | BicycleSolution = TwoCycleSolution 4 | end 5 | -------------------------------------------------------------------------------- /lib/rubiks_cube/cube.rb: -------------------------------------------------------------------------------- 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 state=(stickers) 15 | @state = build_state_from_string( 16 | StickerStateTransform.new(stickers).to_cube 17 | ) 18 | end 19 | 20 | def ==(other) 21 | state == other.state 22 | end 23 | 24 | def state 25 | @state.join ' ' 26 | end 27 | 28 | def edges 29 | @state[0..11] 30 | end 31 | 32 | def corners 33 | @state[12..-1] 34 | end 35 | 36 | def solved? 37 | state == SOLVED_STATE.join(' ') 38 | end 39 | 40 | def edge_permuted?(edge) 41 | cubie_permuted? :edges, edge 42 | end 43 | 44 | def corner_permuted?(corner) 45 | cubie_permuted? :corners, corner 46 | end 47 | 48 | def permuted_location_for(cubie) 49 | while (location = SOLVED_STATE.index cubie.state) == nil 50 | cubie = cubie.rotate 51 | end 52 | 53 | location -= 12 if location >= 12 54 | location 55 | end 56 | 57 | def perform!(algorithm) 58 | algorithm.split.each { |move| perform_move! move } 59 | algorithm 60 | end 61 | 62 | def r 63 | turn [1, 5, 9, 6], [13, 17, 18, 14] 64 | rotate [13, 14, 14, 17, 17, 18] 65 | self 66 | end 67 | 68 | def l 69 | turn [3, 7, 11, 4], [12, 15, 19, 16] 70 | rotate [12, 12, 15, 16, 19, 19] 71 | self 72 | end 73 | 74 | def u 75 | turn [0, 1, 2, 3], [12, 13, 14, 15] 76 | self 77 | end 78 | 79 | def d 80 | turn [8, 11, 10, 9], [16, 19, 18, 17] 81 | self 82 | end 83 | 84 | def f 85 | turn [0, 4, 8, 5], [12, 16, 17, 13] 86 | rotate [0, 4, 8, 5], [12, 13, 13, 16, 16, 17] 87 | self 88 | end 89 | 90 | def b 91 | turn [2, 6, 10, 7], [14, 18, 19, 15] 92 | rotate [2, 6, 10, 7], [14, 15, 15, 18, 18, 19] 93 | self 94 | end 95 | 96 | def m 97 | turn [0, 2, 10, 8] 98 | rotate [0, 2, 10, 8] 99 | self 100 | end 101 | 102 | [:edge, :corner].each do |cubie| 103 | [:orientation, :permutation].each do |step| 104 | define_method "incorrect_#{cubie}_#{step}_locations" do 105 | send "incorrect_#{step}_locations_for", cubie 106 | end 107 | 108 | define_method "has_correct_#{cubie}_#{step}?" do 109 | send("incorrect_#{cubie}_#{step}_locations").empty? 110 | end 111 | end 112 | end 113 | 114 | private 115 | 116 | def build_state_from_string(state) 117 | state.split.map { |state| RubiksCube::Cubie.new state } 118 | end 119 | 120 | def cubie_permuted?(type, cubie) 121 | send(type).index(cubie) == permuted_location_for(cubie) 122 | end 123 | 124 | def incorrect_permutation_locations_for(type) 125 | send("#{type}s").each_with_index.map do |cubie, location| 126 | location unless location == permuted_location_for(cubie) 127 | end.compact 128 | end 129 | 130 | def incorrect_orientation_locations_for(type) 131 | send("#{type}s").each_with_index.map do |cubie, location| 132 | oriented_state = SOLVED_STATE.fetch( 133 | if type == :corner 134 | location + 12 135 | else 136 | location 137 | end 138 | ) 139 | 140 | location unless cubie.state == oriented_state 141 | end.compact 142 | end 143 | 144 | def turn(*sequences) 145 | sequences.each do |sequence| 146 | location = sequence.shift 147 | first_cubie = @state.fetch(location) 148 | 149 | sequence.each do |next_location| 150 | @state[location] = @state.fetch(next_location) 151 | location = next_location 152 | end 153 | 154 | @state[location] = first_cubie 155 | end 156 | end 157 | 158 | def rotate(*sequences) 159 | sequences.each do |cubies| 160 | cubies.each { |cubie| @state[cubie].rotate! } 161 | end 162 | end 163 | 164 | def perform_move!(move) 165 | operation = "#{move[0].downcase}" 166 | 167 | case modifier = move[-1] 168 | when "'" 169 | 3.times { send operation } 170 | when "2" 171 | 2.times { send operation } 172 | else 173 | send operation 174 | end 175 | end 176 | end 177 | end 178 | -------------------------------------------------------------------------------- /lib/rubiks_cube/cubie.rb: -------------------------------------------------------------------------------- 1 | # Generic cubie piece, either edge cubie or corner cubie 2 | module RubiksCube 3 | Cubie = Struct.new(:state) do 4 | def ==(other) 5 | state == other.state 6 | end 7 | 8 | def rotate! 9 | self.state = state.split('').rotate.join 10 | self 11 | end 12 | 13 | def rotate 14 | Cubie.new(state.dup).rotate! 15 | end 16 | 17 | def to_s 18 | state 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/rubiks_cube/solution.rb: -------------------------------------------------------------------------------- 1 | module RubiksCube 2 | # Abstract interface for a RubiksCube solution 3 | # 4 | # Must implement: 5 | # solution: array or string of moves necessary to solve the cube 6 | # pretty: human readable string of solution 7 | class Solution 8 | attr_reader :cube 9 | 10 | # Array or String of moves necessary to solve the cube 11 | def solution 12 | raise "#solution unimplemented in #{self.class.name}" 13 | end 14 | 15 | # Human readable string of solution 16 | def pretty 17 | raise "#pretty unimplemented in #{self.class.name}" 18 | end 19 | 20 | def initialize(cube) 21 | @cube = Cube.new(cube.state) 22 | end 23 | 24 | def state 25 | cube.state 26 | end 27 | 28 | def solved? 29 | cube.solved? 30 | end 31 | 32 | def length 33 | Array(solution).flatten.join(' ').split.count 34 | end 35 | end 36 | end 37 | 38 | -------------------------------------------------------------------------------- /lib/rubiks_cube/sticker_state_transform.rb: -------------------------------------------------------------------------------- 1 | module RubiksCube 2 | # Transform manual sticker state entry to cube state 3 | class StickerStateTransform 4 | attr_reader :state 5 | 6 | CENTER_LOCATIONS = [ 4, 13, 22, 31, 40, 49 ] 7 | 8 | CORNER_LOCATIONS = [ 9 | 42, 0 , 29, 44, 9, 2 , 38, 18, 11, 36, 27, 20, 10 | 45, 35, 6 , 47, 8, 15, 53, 17, 24, 51, 26, 33 11 | ] 12 | 13 | EDGE_LOCATIONS = [ 14 | 43, 1 , 41, 10, 37, 19, 39, 28, 15 | 3 , 32, 5 , 12, 21, 14, 23, 30, 16 | 46, 7 , 50, 16, 52, 25, 48, 34 17 | ] 18 | 19 | def initialize(state) 20 | @state = state.gsub(/\s/, '').split('') 21 | end 22 | 23 | def to_cube 24 | (edges + corners).join(' ').gsub(/./) { |c| color_mapping.fetch(c, c) } 25 | end 26 | 27 | private 28 | 29 | def color_mapping 30 | Hash[state.values_at(*CENTER_LOCATIONS).zip %w(F R B L U D)] 31 | end 32 | 33 | def edges 34 | state.values_at(*EDGE_LOCATIONS).each_slice(2).map(&:join) 35 | end 36 | 37 | def corners 38 | state.values_at(*CORNER_LOCATIONS).each_slice(3).map(&:join) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/rubiks_cube/two_cycle_solution.rb: -------------------------------------------------------------------------------- 1 | module RubiksCube 2 | # Very inefficient two-cycle solving algorithm (aka bicycle solution) 3 | # Useful for learning and blindfold 4 | class TwoCycleSolution < Solution 5 | def solution 6 | @solution ||= begin 7 | solution = [] 8 | solution << solution_for(:permutation) 9 | solution << solution_for(:orientation) 10 | solution.flatten 11 | end 12 | end 13 | 14 | def pretty 15 | solution.each_slice(3).map do |setup, correction, undo| 16 | step = [] 17 | step << "Setup:\t#{setup}" unless setup.empty? 18 | step << "Fix:\t#{correction}" 19 | step << "Undo:\t#{undo}" unless undo.empty? 20 | step.join "\n" 21 | end.join("\n\n").strip 22 | end 23 | 24 | private 25 | 26 | def solution_for(step) 27 | [:edge, :corner].map do |cubie| 28 | solve_for cubie, step 29 | end 30 | end 31 | 32 | def solve_for(cubie, step) 33 | solution = [] 34 | solution << [perform(cubie, step)] until finished_with?(cubie, step) 35 | solution 36 | end 37 | 38 | def finished_with?(cubie, step) 39 | cube.public_send "has_correct_#{cubie}_#{step}?" 40 | end 41 | 42 | def perform(cubie, step) 43 | algorithms_for(cubie, step).map { |algorithm| cube.perform! algorithm } 44 | end 45 | 46 | def next_orientation_location_for(cubie) 47 | locations = incorrect_locations_for(cubie, :orientation) 48 | locations.delete 0 49 | locations.first 50 | end 51 | 52 | def next_permutation_location_for(cubie) 53 | buffer_cubie = send("permutation_buffer_#{cubie}") 54 | 55 | if cube.public_send("#{cubie}_permuted?", buffer_cubie) 56 | incorrect_locations_for(cubie, :permutation).first 57 | else 58 | cube.permuted_location_for buffer_cubie 59 | end 60 | end 61 | 62 | def permutation_buffer_edge 63 | cube.edges[1] 64 | end 65 | 66 | def permutation_buffer_corner 67 | cube.corners[0] 68 | end 69 | 70 | def incorrect_locations_for(cubie, step) 71 | cube.public_send "incorrect_#{cubie}_#{step}_locations" 72 | end 73 | 74 | def algorithms_for(cubie, step) 75 | location = send("next_#{step}_location_for", cubie) 76 | setup = setup_algorithms_for(cubie, step, location) 77 | correction = correction_algorithm_for(cubie, step) 78 | undo = RubiksCube::Algorithms.reverse(setup) 79 | 80 | [ setup, correction, undo ] 81 | end 82 | 83 | def correction_algorithm_for(cubie, step) 84 | load_algorithms step, cubie 85 | end 86 | 87 | def setup_algorithms_for(cubie, step, location) 88 | load_algorithms(step, :setup, cubie).fetch(location) 89 | end 90 | 91 | def load_algorithms(*modules) 92 | [ 'RubiksCube', 93 | 'Algorithms', 94 | *modules.map(&:capitalize) 95 | ].inject(Kernel) { |klass, mod| klass.const_get mod } 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /lib/rubiks_cube/version.rb: -------------------------------------------------------------------------------- 1 | module RubiksCube 2 | VERSION = '1.3.0' 3 | end 4 | -------------------------------------------------------------------------------- /rubiks_cube.gemspec: -------------------------------------------------------------------------------- 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{Solve your Rubik's Cube with a two-cycle solution} 12 | spec.summary = %q{Solve your Rubik's Cube with a two-cycle solution} 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.7.2' 22 | spec.add_development_dependency 'rake', '~> 10.3.2' 23 | spec.add_development_dependency 'rspec', '~> 3.1.0' 24 | spec.add_development_dependency 'cane-hashcheck', '~> 1.2.0' 25 | end 26 | -------------------------------------------------------------------------------- /spec/rubiks_cube/algorithms_spec.rb: -------------------------------------------------------------------------------- 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 12 | -------------------------------------------------------------------------------- /spec/rubiks_cube/bicycle_solution_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module RubiksCube 4 | describe BicycleSolution do 5 | it 'is an alias for TwoCycleSolution' do 6 | cube = double('RubiksCube::Cube') 7 | allow(TwoCycleSolution).to receive(:new) 8 | 9 | BicycleSolution.new(cube) 10 | 11 | expect(TwoCycleSolution).to have_received(:new).with(cube) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/rubiks_cube/cube_spec.rb: -------------------------------------------------------------------------------- 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 '#state=' do 27 | it 'changes the state of the cube' do 28 | expect { subject.state = 'a b c' }.to change { subject.state } 29 | end 30 | end 31 | 32 | describe '==' do 33 | it 'returns true when two cubes have the same state' do 34 | expect(subject).to eq described_class.new(state) 35 | end 36 | 37 | it 'returns false when two cubes do not have the same state' do 38 | expect(subject).to_not eq described_class.new(state).d 39 | end 40 | end 41 | 42 | describe '#edges' do 43 | it 'returns all the edges' do 44 | expect(subject.edges).to eq( 45 | subject.state.split[0..11].map { |cubie| RubiksCube::Cubie.new cubie } 46 | ) 47 | end 48 | end 49 | 50 | describe '#corners' do 51 | it 'returns all the corners' do 52 | expect(subject.corners).to eq( 53 | subject.state.split[12..-1].map { |cubie| RubiksCube::Cubie.new cubie } 54 | ) 55 | end 56 | end 57 | 58 | describe '#incorrect_edge_permutation_locations' do 59 | context 'with unpermuted edges' do 60 | let(:state) { 61 | 'UF UL UB UR FL FR BR BL DF DL DB DR UFL URF UBR ULB DLF DFR DRB DBL' 62 | } 63 | 64 | it 'returns the location of all unpermuted edges' do 65 | expect( 66 | subject.incorrect_edge_permutation_locations 67 | ).to eq [1, 3, 9, 11] 68 | end 69 | end 70 | 71 | context 'with permuted edges' do 72 | it 'returns an empty array' do 73 | expect(subject.incorrect_edge_permutation_locations).to eq [] 74 | end 75 | end 76 | end 77 | 78 | describe '#incorrect_corner_permutation_locations' do 79 | context 'with unpermuted corners' do 80 | let(:state){ 81 | 'UF UR UB UL FL FR BR BL DF DR DB DL ULB UBR URF UFL DLF DFR DRB DBL' 82 | } 83 | 84 | it 'returns the locations of all unpermuted corners' do 85 | expect( 86 | subject.incorrect_corner_permutation_locations 87 | ).to eq [0, 1, 2, 3] 88 | end 89 | end 90 | 91 | context 'with permuted corners' do 92 | it 'returns an empty array' do 93 | expect(subject.incorrect_corner_permutation_locations).to eq [] 94 | end 95 | end 96 | end 97 | 98 | describe '#incorrect_edge_orientation_locations' do 99 | context 'with edges that are not oriented' do 100 | let(:state) { 101 | 'FU UR BU UL FL RF RB LB FD DR DB DL UFL URF UBR ULB DLF DFR DRB DBL' 102 | } 103 | 104 | it 'returns the locations of all unoriented edges' do 105 | expect( 106 | subject.incorrect_edge_orientation_locations 107 | ).to eq [0, 2, 5, 6, 7, 8] 108 | end 109 | end 110 | 111 | context 'with oriented edges' do 112 | it 'returns an empty array' do 113 | expect(subject.incorrect_edge_orientation_locations).to eq [] 114 | end 115 | end 116 | end 117 | 118 | describe '#incorrect_corner_orientation_locations' do 119 | context 'with corners that are not oriented' do 120 | let(:state) { 121 | 'UF UR UB UL FL FR BR BL DF DR DB DL FLU FUR UBR ULB DLF DFR DRB DBL' 122 | } 123 | 124 | it 'returns the locations of all unoriented corners' do 125 | expect(subject.incorrect_corner_orientation_locations).to eq [0, 1] 126 | end 127 | end 128 | 129 | context 'with oriented corners' do 130 | it 'returns an empty array' do 131 | expect(subject.incorrect_corner_orientation_locations).to eq [] 132 | end 133 | end 134 | end 135 | 136 | describe '#permuted_location_for' do 137 | RubiksCube::Cube::SOLVED_STATE.each_with_index do |cubie, location| 138 | it "returns the correct location for the '#{cubie}' cubie" do 139 | # Both corner and edge index begins at zero 140 | location -= 12 if location >= 12 141 | 142 | cubie = RubiksCube::Cubie.new cubie 143 | 144 | expect(subject.permuted_location_for cubie).to eq location 145 | end 146 | end 147 | 148 | context 'when the cubie has been rotated' do 149 | let(:cubie) { subject.corners.first } 150 | 151 | before { cubie.rotate! } 152 | 153 | it 'still finds the correct location' do 154 | expect(subject.permuted_location_for cubie).to eq 0 155 | end 156 | 157 | it 'does not rotate the cubie' do 158 | original_state = cubie.state 159 | 160 | subject.permuted_location_for cubie 161 | 162 | expect(cubie.state).to eq original_state 163 | end 164 | end 165 | end 166 | 167 | describe '#solved?' do 168 | it 'returns true when cube is solved' do 169 | expect(subject).to be_solved 170 | end 171 | 172 | it 'returns false when cube is not solved' do 173 | subject.l 174 | expect(subject).to_not be_solved 175 | end 176 | end 177 | 178 | describe '#edge_permuted?' do 179 | it 'returns true when the cubie is permuted' do 180 | subject.u 181 | 182 | permuted_edge = subject.edges[4] 183 | unpermuted_edge = subject.edges[0] 184 | 185 | expect(subject.edge_permuted? unpermuted_edge).to be false 186 | expect(subject.edge_permuted? permuted_edge).to be true 187 | end 188 | end 189 | 190 | describe '#corner_permuted?' do 191 | it 'returns true when the cubie is permuted' do 192 | subject.f 193 | 194 | permuted_corner = subject.corners[2] 195 | unpermuted_corner = subject.corners[1] 196 | 197 | expect(subject.corner_permuted? unpermuted_corner).to be false 198 | expect(subject.corner_permuted? permuted_corner).to be true 199 | end 200 | end 201 | 202 | describe '#has_correct_edge_permutation?' do 203 | context 'when the edges are permuted, but corners are not' do 204 | let(:state) { 205 | 'UF UR UB UL FL FR BR BL DF DR DB DL ULB UBR URF UFL DBL DRB DFR DLF' 206 | } 207 | 208 | it 'returns true' do 209 | expect(subject).to have_correct_edge_permutation 210 | end 211 | end 212 | 213 | context 'when the edges are not permuted, but corners are' do 214 | let(:state) { 215 | 'UL UF UB UR FL FR BR BL DF DR DB DL UFL URF UBR ULB DLF DFR DRB DBL' 216 | } 217 | 218 | it 'returns false' do 219 | expect(subject).to_not have_correct_edge_permutation 220 | end 221 | end 222 | end 223 | 224 | describe '#has_correct_corner_permutation?' do 225 | context 'when the corners are permuted, but the edges are not' do 226 | let(:state) { 227 | 'UL UF UB UR FL FR BR BL DF DR DB DL UFL URF UBR ULB DLF DFR DRB DBL' 228 | } 229 | 230 | it 'returns true' do 231 | expect(subject).to have_correct_corner_permutation 232 | end 233 | end 234 | 235 | context 'when the corners are not permuted, but the edges are permuted' do 236 | let(:state) { 237 | 'UF UR UB UL FL FR BR BL DF DR DB DL ULB UBR URF UFL DBL DRB DFR DLF' 238 | } 239 | 240 | it 'returns false' do 241 | expect(subject).to_not have_correct_corner_permutation 242 | end 243 | end 244 | end 245 | 246 | describe '#has_correct_edge_orientation?' do 247 | context 'when the edges are oriented, but the corners are not' do 248 | let(:state) { 249 | 'UF UR UB UL FL FR BR BL DF DR DB DL LUF RFU UBR ULB DLF DFR DRB DBL' 250 | } 251 | 252 | it 'returns true' do 253 | expect(subject).to have_correct_edge_orientation 254 | end 255 | end 256 | 257 | context 'when the edges are not oriented, but the corners are' do 258 | let(:state) { 259 | 'FU UR BU UL FL FR BR BL DF DR DB DL UFL URF UBR ULB DLF DFR DRB DBL' 260 | } 261 | 262 | it 'returns false' do 263 | expect(subject).to_not have_correct_edge_orientation 264 | end 265 | end 266 | end 267 | 268 | describe '#has_correct_corner_orientation?' do 269 | context 'when the corners are oriented, but the edges are not' do 270 | let(:state) { 271 | 'FU UR BU UL FL FR BR BL DF DR DB DL UFL URF UBR ULB DLF DFR DRB DBL' 272 | } 273 | 274 | it 'returns true' do 275 | expect(subject).to have_correct_corner_orientation 276 | end 277 | end 278 | 279 | context 'when the corners are not oriented, but the edges are' do 280 | let(:state) { 281 | 'UF UR UB UL FL FR BR BL DF DR DB DL LUF RFU UBR ULB DLF DFR DRB DBL' 282 | } 283 | 284 | it 'returns false' do 285 | expect(subject).to_not have_correct_corner_orientation 286 | end 287 | end 288 | end 289 | 290 | describe '#perform!' do 291 | it 'performs the algorithm on the cube' do 292 | subject.perform! "U2 D' L R F B" 293 | 294 | expect(subject.state).to eq( 295 | described_class.new.u.u.d.d.d.l.r.f.b.state 296 | ) 297 | end 298 | 299 | it 'returns the algorithm that was performed' do 300 | algorithm = "F2 U' L" 301 | 302 | expect(subject.perform! algorithm).to eq algorithm 303 | end 304 | end 305 | 306 | describe 'face turns' do 307 | shared_examples_for 'a face turn' do 308 | it "rotates the face 90 degrees clockwise" do 309 | subject.public_send direction 310 | expect(subject.state).to eq expected_state 311 | end 312 | 313 | it 'is chainable' do 314 | expect(subject.public_send direction).to eq subject 315 | end 316 | end 317 | 318 | describe '#r' do 319 | let(:direction) { 'r' } 320 | let(:expected_state) { 321 | 'UF FR UB UL FL DR UR BL DF BR DB DL UFL FRD FUR ULB DLF BDR BRU DBL' 322 | } 323 | 324 | it_should_behave_like 'a face turn' 325 | end 326 | 327 | describe '#l' do 328 | let(:direction) { 'l' } 329 | let(:expected_state) { 330 | 'UF UR UB BL UL FR BR DL DF DR DB FL BUL URF UBR BLD FLU DFR DRB FDL' 331 | } 332 | 333 | it_should_behave_like 'a face turn' 334 | end 335 | 336 | describe '#u' do 337 | let(:direction) { 'u' } 338 | let(:expected_state) { 339 | 'UR UB UL UF FL FR BR BL DF DR DB DL URF UBR ULB UFL DLF DFR DRB DBL' 340 | } 341 | 342 | it_should_behave_like 'a face turn' 343 | end 344 | 345 | describe '#d' do 346 | let(:direction) { 'd' } 347 | let(:expected_state) { 348 | 'UF UR UB UL FL FR BR BL DL DF DR DB UFL URF UBR ULB DBL DLF DFR DRB' 349 | } 350 | 351 | it_should_behave_like 'a face turn' 352 | end 353 | 354 | describe '#f' do 355 | let(:direction) { 'f' } 356 | let(:expected_state) { 357 | 'LF UR UB UL FD FU BR BL RF DR DB DL LFD LUF UBR ULB RDF RFU DRB DBL' 358 | } 359 | 360 | it_should_behave_like 'a face turn' 361 | end 362 | 363 | describe '#b' do 364 | let(:direction) { 'b' } 365 | let(:expected_state) { 366 | 'UF UR RB UL FL FR BD BU DF DR LB DL UFL URF RBD RUB DLF DFR LDB LBU' 367 | } 368 | 369 | it_should_behave_like 'a face turn' 370 | end 371 | 372 | describe '#m' do 373 | let(:direction) { 'm' } 374 | let(:expected_state) { 375 | 'BU UR BD UL FL FR BR BL FU DR FD DL UFL URF UBR ULB DLF DFR DRB DBL' 376 | } 377 | 378 | it_should_behave_like 'a face turn' 379 | end 380 | end 381 | end 382 | -------------------------------------------------------------------------------- /spec/rubiks_cube/cubie_spec.rb: -------------------------------------------------------------------------------- 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 false' 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 'RFU' 47 | expect(subject.rotate!.state).to eq 'FUR' 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 | end 67 | -------------------------------------------------------------------------------- /spec/rubiks_cube/solution_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RubiksCube::Solution do 4 | class TestSolution < RubiksCube::Solution 5 | def initialize(cube, solution) 6 | @solution = solution 7 | super cube 8 | end 9 | 10 | def solution; @solution; end 11 | end 12 | 13 | subject { TestSolution.new(double('Cube', state: 'nope'), solution) } 14 | 15 | describe '#length' do 16 | context 'when the solution is a single array' do 17 | let(:solution) { %w(a b c) } 18 | 19 | it 'returns the length of the solution' do 20 | expect(subject.length).to eq 3 21 | end 22 | end 23 | 24 | context 'when the solution contains multiple arrays' do 25 | let(:solution) { [%w(a b), %w(c d), [%w(e f)]] } 26 | 27 | it 'returns the length of the solution' do 28 | expect(subject.length).to eq 6 29 | end 30 | end 31 | 32 | context 'when the solution is a string' do 33 | let(:solution) { "a b c d" } 34 | 35 | it 'returns the length of the solution' do 36 | expect(subject.length).to eq 4 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/rubiks_cube/sticker_state_transform_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe RubiksCube::StickerStateTransform do 4 | subject { described_class.new state } 5 | 6 | let(:state) { nil } 7 | 8 | describe '#to_cube' do 9 | context 'when initialized with sticker state' do 10 | let(:state) {[ 11 | "g b b b w b o g b \ 12 | 13 | o w b o o g r r w r y y o y r r g o r w o y r w", 14 | "gw bg o yggowywyywrbbyrg " 15 | ].join(' ')} 16 | 17 | it 'transforms to cube state' do 18 | expect(subject.to_cube).to eq( 19 | "BD RF RB UF DF DR RU LB BU DL LU LF FUR FRD BLD ULB BDR FDL UFL BRU" 20 | ) 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/rubiks_cube/two_cycle_solution_spec.rb: -------------------------------------------------------------------------------- 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 '#solution' do 10 | shared_examples_for 'a cube that can be solved' do 11 | it 'solves the cube' do 12 | subject.solution 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.solution 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 | it 'returns the setup, algorithm, and undo moves' do 68 | cube.perform!([ 69 | RubiksCube::Algorithms::Permutation::Edge, 70 | RubiksCube::Algorithms::Permutation::Corner, 71 | RubiksCube::Algorithms::Orientation::Edge, 72 | ].join(' ')) 73 | 74 | expect(subject.solution).to eq([ 75 | "", RubiksCube::Algorithms::Permutation::Edge, "", 76 | "M2 D L2", RubiksCube::Algorithms::Permutation::Edge, "L2 D' M2", 77 | "R2 D' R2", RubiksCube::Algorithms::Permutation::Corner, "R2 D R2", 78 | "", RubiksCube::Algorithms::Permutation::Corner, "", 79 | "R B", RubiksCube::Algorithms::Orientation::Edge, "B' R'", 80 | "", RubiksCube::Algorithms::Orientation::Edge, "" 81 | ]) 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubiks_cube' 2 | --------------------------------------------------------------------------------