├── LICENSE ├── README.md ├── js ├── flat.js ├── rubiks.js └── solver.js └── rubiks.html /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ryan Stringham 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rubiks-solver 2 | ============= 3 | 4 | Rubik's cube solver in javascript 5 | 6 | 7 | This is an implementation of Thistlewaite's algorithm in javascript: 8 | (http://en.wikipedia.org/wiki/Optimal_solutions_for_Rubik's_Cube#Thistlethwaite.27s_algorithm) 9 | 10 | The Rubik's cube has 20 cubicles, the cubicles are fixed positions on the cube where cubies reside 11 | Each cubie is named after the cubicle it belongs in. A cubicle is named by the faces it has. 12 | The faces are labeled as: {U: up, D: down, R: right, L: left, F: front, B: back} 13 | 14 | To solve a cube you pass it a string of the current state of the cube that looks like: 15 | UF UR UB UL DF DR DB DL FR FL BR BL UFR URB UBL ULF DRF DFL DLB DBR (<-- is an already solved cube) 16 | 17 | The first 12 pairs correspond to the cubicle of the Rubik's cube 18 | For a scrambled cube you put the cubie that is in the cubicle in the order presented above. 19 | An example of a scramble cube is: 20 | BR DF UR LB BD FU FL DL RD FR LU BU UBL FDR FRU BUR ULF LDF RDB DLB 21 | 22 | The state of the Rubik's cube is the position of the cubies at each of the 20 non-center locations 23 | 24 | We number the cubies in the following order: 25 | 26 | ------------------- 27 | | | | | 28 | | | | | 29 | | | | | 30 | ------------------- 31 | | | | | 32 | | 11 | B | 10 | 33 | | | | | 34 | ------------------- 35 | | | | | 36 | | | | | 37 | | | | | 38 | ======================================================= 39 | | | | | | | | | | | 40 | | | | | 14 | 2 | 13 | | | | 41 | | | | | | | | | | | 42 | ------------------------------------------------------- 43 | | | | | | | | | | | 44 | | | L | | 3 | U | 1 | | R | | 45 | | | | | | | | | | | 46 | ------------------------------------------------------- 47 | | | | | | | | | | | 48 | | | | | 15 | 0 | 12 | | | | 49 | | | | | | | | | | | 50 | ======================================================= 51 | | | | | 52 | | | | | 53 | | | | | 54 | ------------------- 55 | | | | | 56 | | 9 | F | 8 | 57 | | | | | 58 | ------------------- 59 | | | | | 60 | | | | | 61 | | | | | 62 | =================== 63 | | | | | 64 | | 17 | 4 | 16 | 65 | | | | | 66 | ------------------- 67 | | | | | 68 | | 7 | D | 5 | 69 | | | | | 70 | ------------------- 71 | | | | | 72 | | 18 | 6 | 19 | 73 | | | | | 74 | ------------------- -------------------------------------------------------------------------------- /js/flat.js: -------------------------------------------------------------------------------- 1 | var FlatCube = function(containerId, size, down){ 2 | var WHITE="#ffffff", YELLOW="#ffff00" , GREEN="#009900" , BLUE="#000099", RED="#cc0000", ORANGE="#ff8000", CLEAR = "#000000"; 3 | var colors = [GREEN,RED,WHITE,ORANGE,BLUE,YELLOW]; 4 | 5 | 6 | this.container = document.getElementById(containerId); 7 | 8 | while(this.container.firstChild){ 9 | this.container.removeChild(this.container.firstChild); 10 | } 11 | 12 | this.container.style.position = 'absolute'; 13 | if(down){ 14 | this.container.style.top = size*45/28 + 45 + 'px'; 15 | this.container.style.left = '15px'; 16 | } else { 17 | this.container.style.top = '15px'; 18 | this.container.style.left = size + 30 + 'px'; 19 | } 20 | 21 | this.faceSize = size/3 || 100; 22 | 23 | this.container.style.width = (this.faceSize*3)+'px'; 24 | this.container.style.height = (this.faceSize*4.75)+'px'; 25 | 26 | this.faces = []; 27 | 28 | var tops = [0, 1, 1, 1, 2, 3]; 29 | var lefts = [1, 0, 1, 2, 1, 1]; 30 | 31 | for(var i=0; i<6; i++){ 32 | this.faces.push(new FlatFace(this, this.faceSize, tops[i]*this.faceSize, lefts[i]*this.faceSize, colors[i])); 33 | } 34 | this.faces.forEach(function(face){ 35 | this.container.appendChild(face.container); 36 | }, this); 37 | 38 | this.message = document.createElement('div'); 39 | this.message.style.position = 'absolute'; 40 | this.message.style.left = this.faceSize/8 + 'px'; 41 | this.message.style.top = this.faceSize*4.03 + 'px'; 42 | this.message.style.color = '#ff0000'; 43 | this.message.style.fontSize = this.faceSize/9 + 'px'; 44 | this.container.appendChild(this.message); 45 | 46 | this.picker = new FlatColorPicker(colors, this.faceSize/4); 47 | this.picker.container.style.right = 0; 48 | this.picker.container.style.left = 0; 49 | this.picker.container.style.bottom = 0; 50 | this.container.appendChild(this.picker.container); 51 | }; 52 | 53 | FlatCube.prototype.getState = function() { 54 | 55 | var me = this; 56 | var result = ""; 57 | var cubicles = ["UF", "UR", "UB", "UL", "DF", "DR", "DB", "DL", "FR", "FL", "BR", "BL", "UFR", "URB", "UBL", "ULF", "DRF", "DFL", "DLB", "DBR"] 58 | var colorToFace = {}; 59 | 60 | var getFaceColor = function(c, direction){ 61 | //direction is a string 'x', 'y', or 'z' 62 | return result; 63 | } 64 | 65 | faceNames.forEach(function(face){ 66 | colorToFace[me.faces[faceToIndex[face]]].stickers[5].getColor() = face; 67 | }); 68 | 69 | cubicles.forEach(function(cubicle){ 70 | var c = me.getCubie(cubicle); 71 | var cubieName = ""; 72 | lucid.array.forEach(cubicle.split(''), function(face){ 73 | var color = getFaceColor(c, faceToDirection[face]); 74 | cubieName += colorToFace[color]; 75 | }); 76 | result += cubieName + " "; 77 | }) 78 | 79 | return result.trim(); 80 | } 81 | 82 | FlatCube.prototype.update = function() { 83 | if(this.message.firstChild){ 84 | this.message.removeChild(this.message.firstChild); 85 | } 86 | if(this.cube){ 87 | this.cube.updateColors(); 88 | if(!this.cube.isSolvable()){ 89 | this.message.appendChild(document.createTextNode(this.cube.solver.currentState)); 90 | } 91 | } 92 | }; 93 | 94 | FlatCube.prototype.setColors = function(top, front, right, colors){ 95 | // var FRONT=4, TOP=1, BOTTOM=2, LEFT=0, RIGHT=5, BACK=3; 96 | 97 | var stickers = this.getStickers(top, front, right); 98 | colors.forEach(function(color, i){ 99 | color && stickers[i] && stickers[i].setColor(color); 100 | }); 101 | } 102 | 103 | FlatCube.prototype.getColors = function(top, front, right){ 104 | return this.getStickers(top,front,right).map(function(sticker){ 105 | return sticker && sticker.color; 106 | }); 107 | } 108 | 109 | FlatCube.prototype.getStickers = function(top, front, right){ 110 | var faceToIndex = { 111 | 'B':0, 112 | 'L':1, 113 | 'U':2, 114 | 'R':3, 115 | 'F':4, 116 | 'D':5, 117 | }; 118 | var FRONT=4, TOP=1, BOTTOM=2, LEFT=0, RIGHT=5, BACK=3; 119 | 120 | var me = this; 121 | var colors = [null,null,null,null,null,null]; 122 | var getColor = function(face, sticker){ 123 | return me.faces[face].stickers[sticker]; 124 | } 125 | 126 | numFaces = Math.abs(top)+Math.abs(front)+Math.abs(right); 127 | if(numFaces == 3){ 128 | if(top == 1 && right == 1 && front == 1){ 129 | colors[TOP] = getColor(2,8); 130 | colors[FRONT] = getColor(4,2); 131 | colors[RIGHT] = getColor(3,6); 132 | } 133 | else if(top == 1 && right == 1 && front == -1){ 134 | colors[TOP] = getColor(2,2); 135 | colors[RIGHT] = getColor(3,0); 136 | colors[BACK] = getColor(0,8); 137 | } 138 | else if(top == 1 && right == -1 && front == 1){ 139 | colors[TOP] = getColor(2,6); 140 | colors[FRONT] = getColor(4,0); 141 | colors[LEFT] = getColor(1,8); 142 | } 143 | else if(top == 1 && right == -1 && front == -1){ 144 | colors[TOP] = getColor(2,0); 145 | colors[BACK] = getColor(0,6); 146 | colors[LEFT] = getColor(1,2); 147 | } 148 | else if(top == -1 && right == 1 && front == 1){ 149 | colors[FRONT] = getColor(4,8); 150 | colors[RIGHT] = getColor(3,8); 151 | colors[BOTTOM] = getColor(5,2); 152 | } 153 | else if(top == -1 && right == 1 && front == -1){ 154 | colors[RIGHT] = getColor(3,2); 155 | colors[BACK] = getColor(0,2); 156 | colors[BOTTOM] = getColor(5,8); 157 | } 158 | else if(top == -1 && right == -1 && front == 1){ 159 | colors[FRONT] = getColor(4,6); 160 | colors[LEFT] = getColor(1,6); 161 | colors[BOTTOM] = getColor(5,0); 162 | } 163 | else if(top == -1 && right == -1 && front == -1){ 164 | colors[BACK] = getColor(0,0); 165 | colors[LEFT] = getColor(1,0); 166 | colors[BOTTOM] = getColor(5,6); 167 | } 168 | } 169 | else if(numFaces == 2){ 170 | if(top == 1){ 171 | if(front == 1){ 172 | colors[FRONT] = getColor(4,1); 173 | colors[TOP] = getColor(2,7); 174 | } 175 | else if(front == -1){ 176 | colors[BACK] = getColor(0,7); 177 | colors[TOP] = getColor(2,1); 178 | } 179 | else if(right == 1){ 180 | colors[RIGHT] = getColor(3,3); 181 | colors[TOP] = getColor(2,5); 182 | }else if(right == -1){ 183 | colors[LEFT] = getColor(1,5); 184 | colors[TOP] = getColor(2,3); 185 | } 186 | } 187 | else if(top == -1){ 188 | if(front == 1){ 189 | colors[FRONT] = getColor(4,7); 190 | colors[BOTTOM] = getColor(5,1); 191 | } 192 | else if(front == -1){ 193 | colors[BACK] = getColor(0,1); 194 | colors[BOTTOM] = getColor(5,7); 195 | } 196 | else if(right == 1){ 197 | colors[RIGHT] = getColor(3,5); 198 | colors[BOTTOM] = getColor(5,5); 199 | }else if(right == -1){ 200 | colors[LEFT] = getColor(1,3); 201 | colors[BOTTOM] = getColor(5,3); 202 | } 203 | } 204 | else if(front == 1){ 205 | if(right==1){ 206 | colors[FRONT] = getColor(4,5); 207 | colors[RIGHT] = getColor(3,7); 208 | }else if(right==-1){ 209 | colors[FRONT] = getColor(4,3); 210 | colors[LEFT] = getColor(1,7); 211 | } 212 | } 213 | else if(front == -1){ 214 | if(right==1){ 215 | colors[BACK] = getColor(0,5); 216 | colors[RIGHT] = getColor(3,1); 217 | }else if(right==-1){ 218 | colors[BACK] = getColor(0,3); 219 | colors[LEFT] = getColor(1,1); 220 | } 221 | } 222 | } 223 | else if(numFaces == 1){ 224 | //center 225 | if(top==1) 226 | colors[TOP] = getColor(2,4); 227 | else if(top== -1) 228 | colors[BOTTOM] = getColor(5,4); 229 | else if(front==1) 230 | colors[FRONT] = getColor(4,4); 231 | else if(front== -1) 232 | colors[BACK] = getColor(0,4); 233 | else if(right == 1) 234 | colors[RIGHT] = getColor(3,4); 235 | else if(right == -1) 236 | colors[LEFT] = getColor(1,4); 237 | } 238 | return colors; 239 | } 240 | 241 | FlatCube.prototype.getColor = function() { 242 | return this.picker.getColor(); 243 | }; 244 | 245 | var FlatFace = function(cube, size, top, left, color){ 246 | this.container = document.createElement('div'); 247 | this.container.style.position = 'absolute'; 248 | this.container.style.width = size + 'px'; 249 | this.container.style.height = size + 'px'; 250 | this.container.style.top = top + 'px'; 251 | this.container.style.left = left + 'px'; 252 | this.cube = cube; 253 | this.stickers = []; 254 | var tops = [0, 0, 0, 1, 1, 1, 2, 2, 2]; 255 | var lefts = [0, 1, 2, 0, 1, 2, 0, 1, 2]; 256 | for(var i=0; i<9; i++){ 257 | var sticker = new FlatSticker(size/3, tops[i]*size/3, lefts[i]*size/3, color); 258 | this.stickers.push(sticker); 259 | this.container.appendChild(sticker.container); 260 | } 261 | 262 | var me = this; 263 | this.stickers.forEach(function(sticker){ 264 | sticker.container.onclick = function(){ 265 | if(me.cube.getColor()){ 266 | sticker.setColor(me.cube.getColor()); 267 | me.cube.update(); 268 | } 269 | } 270 | }, this); 271 | }; 272 | 273 | var FlatSticker = function(size, top, left, color){ 274 | this.container = document.createElement('div'); 275 | this.container.style.width = (size-2) + 'px'; 276 | this.container.style.height = (size-2) + 'px'; 277 | this.container.style.border = '1px solid black'; 278 | this.container.style.position = 'absolute'; 279 | this.container.style.top = top + 'px'; 280 | this.container.style.left = left + 'px'; 281 | 282 | this.setColor(color); 283 | }; 284 | 285 | FlatSticker.prototype.setColor = function(color) { 286 | this.color = color; 287 | this.container.style.backgroundColor = color; 288 | }; 289 | 290 | var FlatColorPicker = function(colors, size){ 291 | var me = this; 292 | size*=1.5; 293 | this.container = document.createElement('div'); 294 | this.container.style.position = 'absolute'; 295 | // this.container.style.width = (size*4) + 'px'; 296 | this.container.style.height = (size*1.5) + 'px'; 297 | this.container.style.border = '1px solid black'; 298 | this.container.onclick = function(){me.setSelection(-1)} 299 | this.size = size; 300 | this.colors = colors; 301 | 302 | this.choices = []; 303 | var tops = [0,0,0,0,0,0]; 304 | var lefts = [0,1,2,3,4,5]; 305 | for(var i=0; i<6; i++){ 306 | this.choices.push(new FlatSticker(size, .25*size, .25*size + 1.29*lefts[i]*size, colors[i])) 307 | this.choices[this.choices.length-1].container.style.cursor = 'pointer'; 308 | } 309 | this.choices.forEach(function(choice, i){ 310 | this.container.appendChild(choice.container); 311 | choice.container.onclick = function(e){ 312 | me.setSelection(i); 313 | e.stopPropagation(); 314 | } 315 | }, this); 316 | }; 317 | 318 | FlatColorPicker.prototype.setSelection = function(index) { 319 | this.selection = index; 320 | this.choices.forEach(function(choice, i){ 321 | if(i == index){ 322 | choice.container.style.width = (this.size-6) + 'px'; 323 | choice.container.style.height = (this.size-6) + 'px'; 324 | choice.container.style.border = '3px solid black'; 325 | } else { 326 | choice.container.style.width = (this.size-2) + 'px'; 327 | choice.container.style.height = (this.size-2) + 'px'; 328 | choice.container.style.border = '1px solid black'; 329 | } 330 | }, this); 331 | }; 332 | 333 | FlatColorPicker.prototype.getColor = function(){ 334 | return this.selection < 0 ? '' : this.colors[this.selection]; 335 | } 336 | 337 | var clock90 = "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABe0lEQVR42u2Y4a2EIBCE6cASKIESLIUSLMESLIESLMESKMESrgQeP3jJxdzJrSDgMl8yPy7mojPskgUhAAAAAAAAADcivbSX8dq83Ims1+o1eaknmx6CCRsxHNPutYQQqZhaxmevV6LxTzKEIEz4T1Gmm4wftYSgY+ZdyR7fChg/tsYYMV8kgLHQqn+TPjF/ewC6ovHj3mC+PGNvPqbbyt71GoCs3PPVA9geZD57AFOGD7JhUBrfRl0Zfk9hDG4ygCGx9ClTXM5pMhtzwoqrhNDXFgK4uvomMrLeHb6r2fs5T2KmdgD2QtkPDZjPEoC88FLViPksAehKpZ/DvKvxIbIh81kC2Ii9n8rS2iBEednM8UaXEsDYewCq9wAEKgB7QN8BTBwDoJwDVo4BUM/jA7cAqEdhdsOQIgbw4lgFOzEEdnvBlSspVq1w5VLEcqsCI+pciT2uClia//WygrV5EcztvZo/mwu6Mf+P7tn8+37QrXkAAAAAAAAezh/ic2r7MgbjxQAAAABJRU5ErkJggg==')"; 338 | var counter90 = "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABeklEQVR42u2aYc3DIBCGcYAEJCChUpBQCZVQCUiohEqohEqoBD6WsGRZ8nU9euwYvE/y/liyZfBwsCuZUgAAAAAAAIBC2JgxZonZYsJJ1hgf42JMLRPwGZ95DH6O2T9M+FO2JE9LCggECSa9NzDniJmkRIQLEnRa8VA4R6oIEQH/SRgYSp2a9ZtnRDiR4L488fdqGCQEPCV4wcm/xkkIqC2udwGh5Hb4FQFHqYMx/FDW2gUs6Xd8eFktm15PF1rlKxlrE0Dt4u52kwd3x3h3xXMHY29UxFSDAI5B6MxqYK2CXAGecRG85FkQKpCgM7bDVsshyCXBZny3qUEApwTqVnC1COCSYCTEczZCM8N4KGdBkc5QmokovTmG3gXY3gUoVADOgL4FjL0LWCSeB2pBZ9xHdN0Eja2t/kEUYHvd+4/sPZc++72gNDmXo6a1/U+R4FWDUCQY1ShXJMyqcc4k7Er4D1XSEqzqiHcJTnXIU8KsAAAAAAAAABT+AGABavtiD0FIAAAAAElFTkSuQmCC')"; 339 | var clock180 = "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAABSxJREFUeJztmUtMG1cUhv/xi3fGU8DITYqLaUFRBYlwUaUUhS6ANV04W7qCJd3BDrojK6ruYGOq7vCi2TZGggVUReEZlwUUbEJauaVJxlOw8aN4urDHDGZs5nHHIHU+6Qozc+499/z3dWYGMDAwMDAwMDAwMPhfQpXJjwvAF7nyYe5vMbYAHAJYypVt/bqlLzSAUQCbAHgNJQxgGlkRleLTFIFKaAATAFhoC1yq+CBfCF+uTlkZhT6BF5ZpZIUuhk9kWxZcABahf+CFS6NXoi++Ajvd6UV5Rr1YGRL1pTB43QUY0iEgNcUH6eB1FeC2BH9d0YXeWxDYjQngws2u+RsXYPEWBEVMAIvC4EdROo2VwxaAZ8imuVFkU10Xsinyw1z7gxp96AINbVPfB/lZHMlskhgTKjuwCeCBSp80gB9V+iUqgNrR96F0yioXteITE2BUhWMfKeconuSUTQClj7SbIDPygLbgiQjgUuFU7ZovRGvwRAQYUuiQ1NQnETwRAZR2xHUDPnUVYFGBs00C/qYV+CuLAEqcTZBwWE5MhNtbItye7pAWIEq4Pd2R811AyToq13cGYpCeAaTO/7JBWgA74fZ0h7QAD7U2QFGU22yx9plMZg8p+4rqmuHWju6Dzp4BvrmtY01sK2fNbkJ+YM8AfJn7zbR2dK/V0IxbuBk9jqwf7QX7kX2yLIRpvOuacba0e4ULMY4NhXc2nmQy5+uFxiaT2dPU7J5qvNfSJ7Y/2guOp5MJv3DNWlHpvd/9eF5cN5U4Y3fXlz/leT4kZwYcyrARGETuIai6jh4WBw8AdofTY7ZYpUaKaW7rCIiDB4AamnG3dT0KFI6uyWT2tHU9CoiDF+zvdz+er6iuGc77bGjyAsD+9ur4y+XnVCS867dVVjFVtXe8gLwlsCTDRszXAJCInS7IrVBdRw/bHU7JKWyrrGLqnffGxNdaPumat1VWMcXaa+/6fIaiKHex+2L0EoDOZM7XgyuB1v3t1fHrKtD1jkvT+OXy8/ci4d38NBbPDGtFpbdwWe38stgf3tl4Km6z1l4/DADRN3/5AeCjB59NdfYM8M6Wdm8qccaenf7jB+QJsA1ly8AOYA4AeJ4PJc/iV9avFqy2iksje7QX7D//N71wwr4Zjx5H8r5qacYDAOlkwr+7sTIS49gQkBVsb+Pnfp7nQ4D8t8JzACYV9HMQ2eeCb+QYc2+PF4T1XEMz7s6egXfi+6/3grMlquc31HQqIbW5IhmPzR4EX0i2IfcYnJNpJ0b2q+34CVcqQJxyrOz9RClyBXgFZSJsQcH3A8bhnCp13+n6eKzUfS0oSYQmZdoJwXNyjCmKcn/Q1pE/tmIcG9rfXh3/+/dwftTtDqdHfLSRRIkArwB8e42NouABQDiPBUK/rvXHT7inkcPf+oWNCwDuMA19V2trR2kqPIniJ4Li4KXgef5iU0tebGoWq63YuZ+/brVVFs0NiqFUAA7Sm5vq4NOpZEj8f+Nd14zZYu2rrqPHxMnRKceuS9k3t3UEzBZrXx3TMCVlfx1KP44C2bzgK1xsippGPp1M+GMcGxKSG2dLu7cwJQaA0+jbWSl7u8PpsTucgWL216H2afB7ZPcDItM+vLPxJJU4kzzDAWB3Y2VESFzU2JfCrKyrl/gJwAyAZCkjPpMJpRLx9+n6Js/rveBs/IT77ooNz0fe/fnHAkVR7po7F2lujGNDB8EXI6nE2Q9a7Etx615hURTlNpktbj6TYaUeg7XaGxgYGBgYGBgY5PgPHc4RtaXi8ysAAAAASUVORK5CYII=)"; 340 | 341 | var RubiksCubeControls = function(id, cube, width){ 342 | var me = this; 343 | 344 | this.cube = cube; 345 | 346 | this.container = document.getElementById(id); 347 | this.container.style.position = 'absolute'; 348 | this.container.style.border = '1px solid black'; 349 | 350 | this.buttons = {}; 351 | 352 | this.cube.addUpdateCallback(function(){ 353 | for(var face in me.buttons){ 354 | var color = me.cube.getFaceColor(face.substr(0,1)); 355 | me.buttons[face].style.backgroundColor = color; 356 | } 357 | }); 358 | 359 | var addButton = function(name, background){ 360 | var color = me.cube.getFaceColor(name.substr(0,1)); 361 | var button = document.createElement('div'); 362 | 363 | me.buttons[name] = button; 364 | me.container.appendChild(button); 365 | 366 | button.addEventListener('click', function(){ 367 | me.cube.makeMove(name); 368 | }); 369 | button.setAttribute('name',name); 370 | button.style.backgroundColor = color; 371 | button.style.position = 'absolute'; 372 | button.style.border = '1px solid black'; 373 | button.style.backgroundImage = background; 374 | button.style.backgroundSize = 'contain'; 375 | 376 | } 377 | 378 | addButton('L',clock90); 379 | addButton('R',clock90); 380 | addButton('U',clock90); 381 | addButton('D',clock90); 382 | addButton('F',clock90); 383 | addButton('B',clock90); 384 | addButton('L\'',counter90); 385 | addButton('R\'',counter90); 386 | addButton('U\'',counter90); 387 | addButton('D\'',counter90); 388 | addButton('F\'',counter90); 389 | addButton('B\'',counter90); 390 | 391 | this.solveButton = document.createElement('div'); 392 | this.solveButton.appendChild(document.createTextNode('Solve')); 393 | this.solveButton.addEventListener('click', function(){ 394 | me.progress.display = ''; 395 | me.cube.solve(function(data){ 396 | setProgress(data); 397 | }); 398 | me.setSolution(''); 399 | }); 400 | 401 | this.solveSlowButton = document.createElement('div'); 402 | this.solveSlowButton.appendChild(document.createTextNode('Solve Step')); 403 | this.solveSlowButton.addEventListener('click', function(){ 404 | me.cube.getSolutionAsync(function(solution){me.setSolution(solution);},function(data){ 405 | setProgress(data); 406 | }); 407 | }); 408 | 409 | function setProgress(data){ 410 | me.progress.style.width = data*100 + '%'; 411 | if(data == 1){ 412 | me.progress.style.width = '0%'; 413 | } 414 | } 415 | 416 | this.overlay = document.createElement('div'); 417 | 418 | this.stepButton = document.createElement('div'); 419 | this.stepButton.addEventListener('click', function(){ 420 | me.nextMove(); 421 | }); 422 | 423 | this.scrambleButton = document.createElement('div'); 424 | this.scrambleButton.appendChild(document.createTextNode('Scramble')); 425 | this.scrambleButton.addEventListener('click', function(){ 426 | me.cube.scramble(); 427 | }); 428 | 429 | this.progress = document.createElement('div'); 430 | this.progress.style.position = 'absolute'; 431 | this.progress.style.top = 0; 432 | this.progress.style.left = 0; 433 | this.progress.style.bottom = 0; 434 | this.progress.style.width = '0%'; 435 | this.progress.style.backgroundColor = 'rgba(0,0,255,0.2)'; 436 | 437 | this.overlay.appendChild(this.stepButton); 438 | this.container.appendChild(this.scrambleButton); 439 | this.container.appendChild(this.overlay); 440 | this.container.appendChild(this.progress); 441 | this.container.appendChild(this.solveButton); 442 | this.container.appendChild(this.solveSlowButton); 443 | if(width){ 444 | this.setWidth(width); 445 | } 446 | } 447 | 448 | RubiksCubeControls.prototype.setSolution = function(solution) { 449 | if(solution.length > 0){ 450 | this.solution = solution.split(' '); 451 | this.updateStepButton(); 452 | this.overlay.style.display = ''; 453 | } else { 454 | this.solution = []; 455 | this.overlay.style.display = 'none'; 456 | } 457 | } 458 | 459 | RubiksCubeControls.prototype.updateStepButton = function() { 460 | if(this.solution && this.solution.length > 0){ 461 | var move = this.solution[0]; 462 | var color = this.cube.getFaceColor(move.substr(0,1)); 463 | this.stepButton.style.backgroundColor = color; 464 | var bgImg = move.length == 1 ? clock90 : move[1] == '2' ? clock180 : counter90; 465 | this.stepButton.style.backgroundImage = bgImg; 466 | if(this.stepButton.firstChild){ 467 | this.stepButton.removeChild(this.stepButton.firstChild); 468 | } 469 | this.stepButton.appendChild(document.createTextNode(this.solution.length)); 470 | } 471 | } 472 | RubiksCubeControls.prototype.nextMove = function() { 473 | var move = this.solution.shift(); 474 | this.cube.makeMove(move); 475 | if(this.solution.length > 0){ 476 | this.updateStepButton(); 477 | } else { 478 | this.overlay.style.display = 'none'; 479 | } 480 | } 481 | RubiksCubeControls.prototype.setWidth = function(width) { 482 | this.container.style.width = (width-2) + 'px'; 483 | this.container.style.height = (width*15/28 - 2) + 'px'; 484 | this.container.style.left = '15px'; 485 | this.container.style.top = 30 + width + 'px'; 486 | var buttonWidth = width/8; 487 | var current = 0; 488 | for(var name in this.buttons){ 489 | this.buttons[name].style.width = (buttonWidth-2) + 'px'; 490 | this.buttons[name].style.height = (buttonWidth-2) + 'px'; 491 | this.buttons[name].style.left = buttonWidth*2/7 + 9/7*buttonWidth*(current < 6 ? current : current - 6) + 'px'; 492 | this.buttons[name].style.top = width/28 + (current < 6 ? 0 : (buttonWidth + width/28)) + 'px'; 493 | current++; 494 | } 495 | 496 | function styleSolve(button, left, bottom){ 497 | button.style.position = 'absolute'; 498 | button.style.cursor = 'pointer'; 499 | button.style.bottom = (bottom || width*3/28) + 'px'; 500 | button.style.left = left + 'px'; 501 | button.style.width = buttonWidth*25/7 - 2 + 'px'; 502 | button.style.height = buttonWidth/2 + 'px'; 503 | button.style.lineHeight = buttonWidth/2 + 'px'; 504 | button.style.textAlign = 'center'; 505 | button.style.border = '1px solid black'; 506 | button.style.fontSize = buttonWidth/3+'px'; 507 | button.style.backgroundColor = '#fafafa'; 508 | 509 | } 510 | 511 | styleSolve(this.solveButton, width/28); 512 | styleSolve(this.solveSlowButton, width*29/56); 513 | styleSolve(this.scrambleButton, width/28, width/58); 514 | 515 | this.scrambleButton.style.width = ''; 516 | this.scrambleButton.style.right = (width/28 - 2) + 'px'; 517 | 518 | 519 | if(!this.solution || this.solution.length == 0){ 520 | this.overlay.style.display = 'none'; 521 | } 522 | this.overlay.style.position = 'absolute'; 523 | this.overlay.style.left = 0; 524 | this.overlay.style.right = 0; 525 | this.overlay.style.top = 0; 526 | this.overlay.style.bottom = 0; 527 | this.overlay.style.backgroundColor = 'rgba(0,0,0,.5)'; 528 | 529 | 530 | this.stepButton.style.backgroundSize = 'contain'; 531 | this.stepButton.style.position = 'absolute'; 532 | this.stepButton.style.border = '1px solid black'; 533 | this.stepButton.style.width = buttonWidth * 16/7 - 2 + 'px'; 534 | this.stepButton.style.height = buttonWidth * 16/7 - 2 + 'px'; 535 | this.stepButton.style.top = width/28 + 'px'; 536 | this.stepButton.style.left = width/28 + (buttonWidth + width/28)*2 + 'px'; 537 | }; -------------------------------------------------------------------------------- /js/rubiks.js: -------------------------------------------------------------------------------- 1 | /********************************** 2 | * Utility Functions 3 | **********************************/ 4 | function hexToRgb(hex) { 5 | if(hex.length == 7){ 6 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 7 | return result ? { 8 | r: parseInt(result[1], 16), 9 | g: parseInt(result[2], 16), 10 | b: parseInt(result[3], 16), 11 | a: 1 12 | } : null; 13 | } 14 | else if(hex.length == 9){ 15 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 16 | return result ? { 17 | r: parseInt(result[1], 16), 18 | g: parseInt(result[2], 16), 19 | b: parseInt(result[3], 16), 20 | a: parseInt(result[4], 16)/256 21 | } : null; 22 | } 23 | } 24 | 25 | function makeRotationAffine(x,y,z){ 26 | return multiplyAffine(multiplyAffine(makeRotateAffineX(x),makeRotateAffineY(y)),makeRotateAffineZ(z)); 27 | } 28 | 29 | /** 30 | * Search an array for the first element that satisfies a given condition and 31 | * return that element. 32 | */ 33 | var arrayFind = function(arr, f) { 34 | var i = arrayFindIndex(arr, f); 35 | return i < 0 ? null : arr[i]; 36 | }; 37 | 38 | 39 | /** 40 | * Search an array for the first element that satisfies a given condition and 41 | * return its index. 42 | */ 43 | arrayFindIndex = function(arr, f) { 44 | var l = arr.length; // must be fixed during loop... see docs 45 | var arr2 = arr; 46 | for (var i = 0; i < l; i++) { 47 | if (i in arr2 && f(arr2[i], i, arr)) { 48 | return i; 49 | } 50 | } 51 | return -1; 52 | }; 53 | 54 | /********************************** 55 | * Variables 56 | **********************************/ 57 | var xAutorotate = 0, yAutorotate = 0, zAutorotate = 0; 58 | 59 | /************************************************************ 60 | * MMMM MMMM OOOOOOOO DDDDDDDD EEEEEEEE LL 61 | * MM MM MM MM OO OO DD DD EE LL 62 | * MM MMMM MM OO OO DD DD EEEEE LL 63 | * MM MM MM OO OO DD DD EE LL 64 | * MM MM MM OOOOOOOO DDDDDDDD EEEEEEEE LLLLLLL 65 | ************************************************************/ 66 | 67 | /********************************** 68 | * 3D Translation Stuff 69 | *********************************/ 70 | 71 | // This represents an affine 4x4 matrix, stored as a 3x4 matrix with the last 72 | // row implied as [0, 0, 0, 1]. This is to avoid generally unneeded work, 73 | // skipping part of the homogeneous coordinates calculations and the 74 | // homogeneous divide. Unlike points, we use a constructor function instead 75 | // of object literals to ensure map sharing. The matrix looks like: 76 | // e0 e1 e2 e3 77 | // e4 e5 e6 e7 78 | // e8 e9 e10 e11 79 | // 0 0 0 1 80 | function AffineMatrix(e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11) { 81 | this.e0 = e0; 82 | this.e1 = e1; 83 | this.e2 = e2; 84 | this.e3 = e3; 85 | this.e4 = e4; 86 | this.e5 = e5; 87 | this.e6 = e6; 88 | this.e7 = e7; 89 | this.e8 = e8; 90 | this.e9 = e9; 91 | this.e10 = e10; 92 | this.e11 = e11; 93 | }; 94 | 95 | // Matrix multiplication of AffineMatrix |a| x |b|. This is unrolled, 96 | // and includes the calculations with the implied last row. 97 | function multiplyAffine(a, b) { 98 | // Avoid repeated property lookups by accessing into the local frame. 99 | var a0 = a.e0, a1 = a.e1, a2 = a.e2, a3 = a.e3, a4 = a.e4, a5 = a.e5; 100 | var a6 = a.e6, a7 = a.e7, a8 = a.e8, a9 = a.e9, a10 = a.e10, a11 = a.e11; 101 | var b0 = b.e0, b1 = b.e1, b2 = b.e2, b3 = b.e3, b4 = b.e4, b5 = b.e5; 102 | var b6 = b.e6, b7 = b.e7, b8 = b.e8, b9 = b.e9, b10 = b.e10, b11 = b.e11; 103 | return new AffineMatrix( 104 | a0 * b0 + a1 * b4 + a2 * b8, 105 | a0 * b1 + a1 * b5 + a2 * b9, 106 | a0 * b2 + a1 * b6 + a2 * b10, 107 | a0 * b3 + a1 * b7 + a2 * b11 + a3, 108 | a4 * b0 + a5 * b4 + a6 * b8, 109 | a4 * b1 + a5 * b5 + a6 * b9, 110 | a4 * b2 + a5 * b6 + a6 * b10, 111 | a4 * b3 + a5 * b7 + a6 * b11 + a7, 112 | a8 * b0 + a9 * b4 + a10 * b8, 113 | a8 * b1 + a9 * b5 + a10 * b9, 114 | a8 * b2 + a9 * b6 + a10 * b10, 115 | a8 * b3 + a9 * b7 + a10 * b11 + a11 116 | ); 117 | } 118 | function makeIdentityAffine() { 119 | return new AffineMatrix( 120 | 1, 0, 0, 0, 121 | 0, 1, 0, 0, 122 | 0, 0, 1, 0 123 | ); 124 | } 125 | // http://en.wikipedia.org/wiki/Rotation_matrix 126 | function makeRotateAffineX(theta) { 127 | var s = Math.sin(theta); 128 | var c = Math.cos(theta); 129 | return new AffineMatrix( 130 | 1, 0, 0, 0, 131 | 0, c, -s, 0, 132 | 0, s, c, 0 133 | ); 134 | } 135 | function makeRotateAffineY(theta) { 136 | var s = Math.sin(theta); 137 | var c = Math.cos(theta); 138 | return new AffineMatrix( 139 | c, 0, s, 0, 140 | 0, 1, 0, 0, 141 | -s, 0, c, 0 142 | ); 143 | } 144 | function makeRotateAffineZ(theta) { 145 | var s = Math.sin(theta); 146 | var c = Math.cos(theta); 147 | return new AffineMatrix( 148 | c, -s, 0, 0, 149 | s, c, 0, 0, 150 | 0, 0, 1, 0 151 | ); 152 | } 153 | 154 | // e0 e1 e2 e3 155 | // e4 e5 e6 e7 156 | // e8 e9 e10 e11 157 | // 0 0 0 1 158 | 159 | // a b c x (xa + yb + zc) 160 | // d e f * y = (xd + ye + zf) 161 | // g h i z (xg + yh + zi) 162 | 163 | // j k l (xa + yb + zc) 164 | // m n o * (xd + ye + zf) 165 | // p q r (xg + yh + zi) 166 | 167 | // Transform the point |p| by the AffineMatrix |t|. 168 | function transformPoint(t, p) { 169 | return { 170 | x: t.e0 * p.x + t.e1 * p.y + t.e2 * p.z + t.e3, 171 | y: t.e4 * p.x + t.e5 * p.y + t.e6 * p.z + t.e7, 172 | z: t.e8 * p.x + t.e9 * p.y + t.e10 * p.z + t.e11 173 | }; 174 | } 175 | 176 | // Average a list of points, returning a new "centroid" point. 177 | function averagePoints(ps) { 178 | var avg = {x: 0, y: 0, z: 0}; 179 | for (var i = 0, il = ps.length; i < il; ++i) { 180 | var p = ps[i]; 181 | avg.x += p.x; 182 | avg.y += p.y; 183 | avg.z += p.z; 184 | } 185 | 186 | var f = 1 / il; 187 | 188 | avg.x *= f; 189 | avg.y *= f; 190 | avg.z *= f; 191 | 192 | return avg; 193 | } 194 | 195 | function averageUnRotatedPoints(ps) { 196 | var avg = {x: 0, y: 0, z: 0}; 197 | for (var i = 0, il = ps.length; i < il; ++i) { 198 | var p = ps[i]; 199 | avg.x += p.xo; 200 | avg.y += p.yo; 201 | avg.z += p.zo; 202 | } 203 | 204 | var f = 1 / il; 205 | 206 | avg.x *= f; 207 | avg.y *= f; 208 | avg.z *= f; 209 | 210 | return avg; 211 | } 212 | 213 | /********************************** 214 | * 3D Point 215 | **********************************/ 216 | var Point = function (parent, xyz, project, rubiks) { 217 | this.project = project; 218 | this.rubiks = rubiks; 219 | this.xo = xyz[0]; 220 | this.yo = xyz[1]; 221 | this.zo = xyz[2]; 222 | this.cube = parent; 223 | }; 224 | Point.prototype.projection = function () { 225 | var p = transformPoint(this.cube.rotationAffine, {x:this.xo, y:this.yo, z:this.zo}); 226 | // this.rubiks.cameraAffines.forEach(function(affine){ 227 | // p = transformPoint(affine, p); 228 | // }); 229 | p = transformPoint(this.rubiks.cameraAffine, p); 230 | p = transformPoint(this.rubiks.customAffine, p); 231 | if(this.rubiks.affinediff){ 232 | p = transformPoint(this.rubiks.affinediff, p); 233 | } 234 | this.x = p.x; 235 | this.y = p.y; 236 | this.z = p.z; 237 | var x = p.x; 238 | var y = p.y; 239 | var z = p.z; 240 | if (this.project) { 241 | // ---- point visible ---- 242 | this.visible = (350 + z > 0); 243 | // ---- 3D to 2D projection ---- 244 | this.X = ((75/2) + x * (250 / (z + 350)))*(this.rubiks.width / 75); 245 | this.Y = ((75/2) + y * (250 / (z + 350)))*(this.rubiks.width / 75); 246 | } 247 | }; 248 | /********************************** 249 | * Face Object 250 | **********************************/ 251 | var Face = function (cube, index, normalVector, color, rubiks) { 252 | // ---- Rubiks Cube ---- 253 | this.rubiks = rubiks; 254 | // ---- parent cube ---- 255 | this.cube = cube; 256 | // ---- coordinates ---- 257 | this.p0 = cube.points[index[0]]; 258 | this.p1 = cube.points[index[1]]; 259 | this.p2 = cube.points[index[2]]; 260 | this.p3 = cube.points[index[3]]; 261 | // ---- normal vector ---- 262 | this.normal = new Point(this.cube, normalVector, false, rubiks); 263 | // ---- color ---- 264 | this.color = color; 265 | }; 266 | Face.prototype.distanceToCamera = function () { 267 | // ---- distance to camera ---- 268 | var dx = (this.p0.x + this.p1.x + this.p2.x + this.p3.x ) * 0.25; 269 | var dy = (this.p0.y + this.p1.y + this.p2.y + this.p3.y ) * 0.25; 270 | var dz = (350 + 250) + (this.p0.z + this.p1.z + this.p2.z + this.p3.z ) * 0.25; 271 | this.distance = Math.sqrt(dx * dx + dy * dy + dz * dz); 272 | }; 273 | Face.prototype.draw = function () { 274 | var ctx = this.rubiks.ctx; 275 | // ---- shape face ---- 276 | ctx.beginPath(); 277 | ctx.moveTo(this.p0.X, this.p0.Y); 278 | ctx.lineTo(this.p1.X, this.p1.Y); 279 | ctx.lineTo(this.p2.X, this.p2.Y); 280 | ctx.lineTo(this.p3.X, this.p3.Y); 281 | ctx.closePath(); 282 | // ---- light ---- 283 | this.normal.projection(); 284 | var light = ( 285 | false ? 286 | this.normal.y + this.normal.z * 0.5 : 287 | this.normal.z 288 | ); 289 | var r = g = b = light; 290 | light += (1-light)*.8; 291 | var rgb = hexToRgb(this.color); 292 | // ---- fill ---- 293 | ctx.fillStyle = "rgba(" + 294 | Math.round(rgb.r*light) + "," + 295 | Math.round(rgb.g*light) + "," + 296 | Math.round(rgb.b*light) + "," + rgb.a + ")"; 297 | ctx.fill(); 298 | }; 299 | Face.prototype.getRenderData = function(){ 300 | 301 | this.normal.projection(); 302 | var light = ( 303 | false ? 304 | this.normal.y + this.normal.z * 0.5 : 305 | this.normal.z 306 | ); 307 | var r = g = b; 308 | var rgb = hexToRgb(this.color); 309 | r = Math.round(rgb.r*light).toString(16); 310 | g = Math.round(rgb.g*light).toString(16); 311 | b = Math.round(rgb.b*light).toString(16); 312 | r = r.length == 1 ? '0' + r : r; 313 | g = g.length == 1 ? '0' + g : g; 314 | b = b.length == 1 ? '0' + b : b; 315 | var fillColor = "#" + r + g + b; 316 | return { 317 | FillColor:fillColor, 318 | StrokeColor:null, 319 | LineWidth:null, 320 | Actions:[ 321 | { 322 | Action:'move', 323 | x:this.p0.X, 324 | y:this.p0.Y 325 | },{ 326 | Action:'line', 327 | x: this.p1.X, 328 | y:this.p1.Y 329 | },{ 330 | Action:'line', 331 | x: this.p2.X, 332 | y:this.p2.Y 333 | },{ 334 | Action:'line', 335 | x: this.p3.X, 336 | y:this.p3.Y 337 | },{ 338 | Action:'close' 339 | } 340 | ] 341 | } 342 | } 343 | /********************************** 344 | * Cube Object 345 | **********************************/ 346 | var Cube = function(x, y, z, w, rubiks, colors) { 347 | this.rubiks = rubiks; 348 | // ---- create points ---- 349 | this.w = w; 350 | this.points = []; 351 | var p = [ 352 | [x-w, y-w, z-w], 353 | [x+w, y-w, z-w], 354 | [x+w, y+w, z-w], 355 | [x-w, y+w, z-w], 356 | [x-w, y-w, z+w], 357 | [x+w, y-w, z+w], 358 | [x+w, y+w, z+w], 359 | [x-w, y+w, z+w] 360 | ]; 361 | for (var i in p) this.points.push( 362 | new Point(this, p[i], true, rubiks) 363 | ); 364 | 365 | // ---- faces coordinates ---- 366 | var f = [ 367 | [0,1,2,3], 368 | [0,4,5,1], 369 | [3,2,6,7], 370 | [0,3,7,4], 371 | [1,5,6,2], 372 | [5,4,7,6] 373 | ]; 374 | // ---- faces normals ---- 375 | var nv = [ 376 | [0,0,1], 377 | [0,1,0], 378 | [0,-1,0], 379 | [1,0,0], 380 | [-1,0,0], 381 | [0,0,-1] 382 | ]; 383 | // ---- cube transparency ---- 384 | this.alpha = 1; 385 | // ---- push faces ---- 386 | this.faces = []; 387 | for (var i in f) { 388 | this.faces.push( 389 | new Face(this, f[i], nv[i], colors[i], rubiks) 390 | ); 391 | } 392 | this.rotationAffine = makeIdentityAffine(); 393 | this.rotateX = 0; 394 | this.rotateY = 0; 395 | this.rotateZ = 0; 396 | }; 397 | 398 | RubiksCube.prototype.update = function(){ 399 | if(this.flatCube && !this.rotating){ 400 | this.blocks.forEach(function(block){ 401 | var p = block.getPosition(); 402 | var colors = block.getColors(); 403 | this.flatCube.setColors( 404 | p.y < 0 ? 1 : p.y > 0 ? -1 : 0, 405 | p.x < 0 ? -1 : p.x > 0 ? 1 : 0, 406 | p.z < 0 ? -1 : p.z > 0 ? 1 : 0, 407 | colors 408 | ); 409 | }, this); 410 | } 411 | if(!this.rotating){ 412 | this.updateCallbacks.forEach(function(f){f();}); 413 | } 414 | } 415 | 416 | RubiksCube.prototype.addUpdateCallback = function(f){ 417 | this.updateCallbacks.push(f); 418 | } 419 | 420 | Cube.prototype.updateColors = function(colors){ 421 | this.faces.forEach(function(face){ 422 | if(colors[0] && face.normal.zo == 1) 423 | face.color = colors[0]; 424 | else if(colors[1] && face.normal.yo == 1) 425 | face.color = colors[1]; 426 | else if(colors[2] && face.normal.yo == -1) 427 | face.color = colors[2]; 428 | else if(colors[3] && face.normal.xo == 1) 429 | face.color = colors[3]; 430 | else if(colors[4] && face.normal.xo == -1) 431 | face.color = colors[4]; 432 | else if(colors[5] && face.normal.zo == -1) 433 | face.color = colors[5]; 434 | }); 435 | } 436 | 437 | Cube.prototype.getColors = function(){ 438 | var colors = [null,null,null,null,null,null]; 439 | this.faces.forEach(function(face){ 440 | if(face.normal.zo == 1) 441 | colors[0] = face.color; 442 | else if(face.normal.yo == 1) 443 | colors[1] = face.color; 444 | else if(face.normal.yo == -1) 445 | colors[2] = face.color; 446 | else if(face.normal.xo == 1) 447 | colors[3] = face.color; 448 | else if(face.normal.xo == -1) 449 | colors[4] = face.color; 450 | else if(face.normal.zo == -1) 451 | colors[5] = face.color; 452 | }); 453 | return colors; 454 | } 455 | 456 | Cube.prototype.getPosition = function() { 457 | var points = []; 458 | for(var i=0; i 0){ 582 | e = e.touches[0]; 583 | } 584 | this.lastPos = {x:e.clientX,y:e.clientY}; 585 | this.affinediff = makeIdentityAffine(); 586 | } 587 | var onmouseup = function(){ 588 | if(this.mouseDown){ 589 | this.mouseDown = false; 590 | if(this.affinediff){ 591 | this.customAffine = multiplyAffine(this.affinediff, this.customAffine); 592 | } 593 | this.affinediff = null; 594 | } 595 | } 596 | var onmousemove = function(e){ 597 | if(this.mouseDown){ 598 | e.preventDefault(); 599 | if(e.touches && e.touches.length > 0){ 600 | e = e.touches[0]; 601 | } 602 | var moved = {x:e.clientX - this.lastPos.x, y:e.clientY-this.lastPos.y}; 603 | this.lastPos = {x:e.clientX,y:e.clientY}; 604 | this.affinediff = multiplyAffine(multiplyAffine(makeRotateAffineX(moved.y/100), makeRotateAffineY(-moved.x/100)),this.affinediff); 605 | } 606 | } 607 | this.canvas.addEventListener('mousedown', onmousedown.bind(this)); 608 | this.canvas.addEventListener('touchstart', onmousedown.bind(this)); 609 | document.addEventListener('mouseup', onmouseup.bind(this)); 610 | document.addEventListener('touchend', onmouseup.bind(this)); 611 | document.addEventListener('touchcancel', onmouseup.bind(this)); 612 | document.addEventListener('mousemove', onmousemove.bind(this)); 613 | document.addEventListener('touchmove', onmousemove.bind(this)); 614 | } 615 | 616 | RubiksCube.prototype.solve = function(progress) { 617 | var me = this; 618 | if(this.isSolvable()){ 619 | // this.makeMoves(this.solver.solve(this.getState(), progress)); 620 | this.solver.solveAsync(this.getState(), function(solution){ 621 | me.makeMoves(solution); 622 | }, progress); 623 | } 624 | } 625 | 626 | RubiksCube.prototype.getSolutionAsync = function(callback, progress) { 627 | if(this.isSolvable()){ 628 | this.solver.solveAsync(this.getState(), callback, progress); 629 | } 630 | callback(''); 631 | } 632 | 633 | RubiksCube.prototype.getSolution = function() { 634 | if(this.isSolvable()){ 635 | return this.solver.solve(this.getState()); 636 | } 637 | return ''; 638 | } 639 | 640 | RubiksCube.prototype.isSolvable = function(){ 641 | return !this.rotating && this.solver.setState(this.getState()); 642 | } 643 | 644 | RubiksCube.prototype.scramble = function(num) { 645 | var moves = 'u d f b l r'.split(' '); 646 | var me = this; 647 | if(this.rotating){ 648 | return; 649 | } 650 | num = num || 50; 651 | // shift(); 652 | var turnSpeed = this.turnSpeed; 653 | this.turnSpeed = 100; 654 | for(var i=0; i 1/2 ? "'" : "")); 657 | } 658 | var checkAgain = function(){ 659 | setTimeout(function() { 660 | if(me.queue.length == 0){ 661 | me.turnSpeed = turnSpeed; 662 | } else { 663 | checkAgain(); 664 | } 665 | }, 100); 666 | } 667 | checkAgain(); 668 | }; 669 | 670 | RubiksCube.prototype.updateSize = function(size) { 671 | this.width = size; 672 | this.canvas.width = size; 673 | this.canvas.height = size; 674 | }; 675 | 676 | RubiksCube.prototype.tick = function() { 677 | this.cameraX += ((this.cx - this.cameraX) * 0.05); 678 | this.cameraY += ((this.cy - this.cameraY) * 0.05); 679 | this.cameraZ += ((this.cz - this.cameraZ) * 0.05); 680 | if (xAutorotate != 0) this.cx += xAutorotate; 681 | if (yAutorotate != 0) this.cy += yAutorotate; 682 | if (zAutorotate != 0) this.cz += zAutorotate; 683 | 684 | }; 685 | 686 | RubiksCube.prototype.updateColors = function() { 687 | if(this.rotating){ 688 | return; 689 | } 690 | this.blocks.forEach(function(block){ 691 | var p = block.getPosition(); 692 | var colors = this.flatCube.getColors( 693 | p.y < 0 ? 1 : p.y > 0 ? -1 : 0, 694 | p.x < 0 ? -1 : p.x > 0 ? 1 : 0, 695 | p.z < 0 ? -1 : p.z > 0 ? 1 : 0 696 | ); 697 | block.updateColors(colors); 698 | }, this); 699 | if(!this.rotating){ 700 | this.updateCallbacks.forEach(function(f){f();}); 701 | } 702 | }; 703 | 704 | RubiksCube.prototype.render = function(){ 705 | this.cameraAffine = makeRotationAffine(this.cameraX,this.cameraY,this.cameraZ); 706 | 707 | this.blocks.forEach(function(block){ 708 | block.setRotationAffine(); 709 | }); 710 | 711 | this.points.forEach(function(point){ 712 | point.projection(); 713 | }); 714 | 715 | this.faces.forEach(function(face){ 716 | face.distanceToCamera(); 717 | }); 718 | 719 | this.faces.sort(function (p0, p1) { 720 | return p1.distance - p0.distance; 721 | }); 722 | 723 | this.ctx.fillStyle = "#fafafa"; 724 | this.ctx.fillRect(0, 0, this.width, this.width); 725 | for(var i=0; i 0) 794 | result.push(block); 795 | }); 796 | } 797 | else if(face == 'U'){ 798 | this.blocks.forEach(function(block){ 799 | if(block.getPosition().y < 0) 800 | result.push(block); 801 | }); 802 | } 803 | else if(face == 'D'){ 804 | this.blocks.forEach(function(block){ 805 | if(block.getPosition().y > 0) 806 | result.push(block); 807 | }); 808 | } 809 | else if(face == 'L'){ 810 | this.blocks.forEach(function(block){ 811 | if(block.getPosition().z < 0) 812 | result.push(block); 813 | }); 814 | } 815 | else if(face == 'R'){ 816 | this.blocks.forEach(function(block){ 817 | if(block.getPosition().z > 0) 818 | result.push(block); 819 | }); 820 | } else if(face == 'X' || face == 'Y' || face == 'Z'){ 821 | result = this.blocks; 822 | } 823 | return result; 824 | }; 825 | RubiksCube.prototype.makeMoves = function(moves) { 826 | moves = moves.split(/\s/); 827 | for(var i=0; i 0) 903 | this.makeMove(this.queue.shift()); 904 | }; 905 | 906 | RubiksCube.prototype.getFaceColor = function(face){ 907 | 908 | function getOutside(c){ 909 | var index = -1; 910 | var max = 0; 911 | for(var i=0; i max){ 914 | index = i; 915 | max = Math.abs(p.x); 916 | } 917 | if(Math.abs(p.y) > max){ 918 | index = i; 919 | max = Math.abs(p.y); 920 | } 921 | if(Math.abs(p.z) > max){ 922 | index = i; 923 | max = Math.abs(p.z); 924 | } 925 | } 926 | return c.faces[index].color; 927 | } 928 | 929 | switch(face){ 930 | case 'D': 931 | return getOutside(arrayFind(this.blocks, function(block){ 932 | var p = block.getPosition(); 933 | return (p.x == 0 && p.y > 0 && p.z == 0); 934 | })); 935 | case 'U': 936 | return getOutside(arrayFind(this.blocks, function(block){ 937 | var p = block.getPosition(); 938 | return (p.x == 0 && p.y < 0 && p.z == 0); 939 | })); 940 | case 'L': 941 | return getOutside(arrayFind(this.blocks, function(block){ 942 | var p = block.getPosition(); 943 | return (p.x == 0 && p.y == 0 && p.z < 0); 944 | })); 945 | case 'R': 946 | return getOutside(arrayFind(this.blocks, function(block){ 947 | var p = block.getPosition(); 948 | return (p.x == 0 && p.y == 0 && p.z > 0); 949 | })); 950 | case 'F': 951 | return getOutside(arrayFind(this.blocks, function(block){ 952 | var p = block.getPosition(); 953 | return (p.x > 0 && p.y == 0 && p.z == 0); 954 | })); 955 | case 'B': 956 | return getOutside(arrayFind(this.blocks, function(block){ 957 | var p = block.getPosition(); 958 | return (p.x < 0 && p.y == 0 && p.z == 0); 959 | })); 960 | } 961 | } 962 | // var faceNames = ['L', 'U', 'D', 'B', 'F', 'R']; 963 | RubiksCube.prototype.getCubie = function(position){ 964 | var cubes = this.getBlocks(position[0]); 965 | switch(position){ 966 | //Edge piece 967 | case "UF": 968 | case "DF": 969 | return arrayFind(cubes, function(c){ 970 | var p = c.getPosition(); 971 | return p.z == 0 && p.x > 0; 972 | }); 973 | case "UB": 974 | case "DB": 975 | return arrayFind(cubes, function(c){ 976 | var p = c.getPosition(); 977 | return p.z == 0 && p.x < 0; 978 | }); 979 | case "UR": 980 | case "DR": 981 | return arrayFind(cubes, function(c){ 982 | var p = c.getPosition(); 983 | return p.z > 0 && p.x == 0; 984 | }); 985 | case "UL": 986 | case "DL": 987 | return arrayFind(cubes, function(c){ 988 | var p = c.getPosition(); 989 | return p.z < 0 && p.x == 0; 990 | }); 991 | case "FR": 992 | case "BR": 993 | return arrayFind(cubes, function(c){ 994 | var p = c.getPosition(); 995 | return p.z > 0 && p.y == 0; 996 | }); 997 | case "FL": 998 | case "BL": 999 | return arrayFind(cubes, function(c){ 1000 | var p = c.getPosition(); 1001 | return p.z < 0 && p.y == 0; 1002 | }); 1003 | //Corner Cubie 1004 | case "UFR": 1005 | case "DRF": 1006 | return arrayFind(cubes, function(c){ 1007 | var p = c.getPosition(); 1008 | return p.z > 0 && p.x > 0; 1009 | }); 1010 | case "URB": 1011 | case "DBR": 1012 | return arrayFind(cubes, function(c){ 1013 | var p = c.getPosition(); 1014 | return p.z > 0 && p.x < 0; 1015 | }); 1016 | case "UBL": 1017 | case "DLB": 1018 | return arrayFind(cubes, function(c){ 1019 | var p = c.getPosition(); 1020 | return p.z < 0 && p.x < 0; 1021 | }); 1022 | case "ULF": 1023 | case "DFL": 1024 | return arrayFind(cubes, function(c){ 1025 | var p = c.getPosition(); 1026 | return p.z < 0 && p.x > 0; 1027 | }); 1028 | } 1029 | } 1030 | 1031 | RubiksCube.prototype.faceNames = ['L', 'U', 'D', 'B', 'F', 'R']; 1032 | 1033 | RubiksCube.prototype.getState = function(){ 1034 | var me = this; 1035 | var result = ""; 1036 | var cubicles = ["UF", "UR", "UB", "UL", "DF", "DR", "DB", "DL", "FR", "FL", "BR", "BL", "UFR", "URB", "UBL", "ULF", "DRF", "DFL", "DLB", "DBR"] 1037 | var colorToFace = {}; 1038 | var faceToDirection = { 1039 | 'F':'x', 1040 | 'B':'x', 1041 | 'U':'y', 1042 | 'D':'y', 1043 | 'R':'z', 1044 | 'L':'z' 1045 | }; 1046 | var getFaceColor = function(c, direction){ 1047 | //direction is a string 'x', 'y', or 'z' 1048 | var result=null; 1049 | var max = 0; 1050 | c.faces.forEach(function(f){ 1051 | p = averageUnRotatedPoints([f.p0,f.p1, f.p2, f.p3]); 1052 | if(Math.abs(p[direction]) > max){ 1053 | max = Math.abs(p[direction]); 1054 | result = f.color; 1055 | } 1056 | }); 1057 | return result; 1058 | } 1059 | this.faceNames.forEach(function(face){ 1060 | colorToFace[me.getFaceColor(face)] = face; 1061 | }); 1062 | 1063 | cubicles.forEach(function(cubicle){ 1064 | var c = me.getCubie(cubicle); 1065 | var cubieName = ""; 1066 | cubicle.split('').forEach(function(face){ 1067 | var color = getFaceColor(c, faceToDirection[face]); 1068 | cubieName += colorToFace[color]; 1069 | }); 1070 | result += cubieName + " "; 1071 | }) 1072 | 1073 | return result.trim(); 1074 | } -------------------------------------------------------------------------------- /js/solver.js: -------------------------------------------------------------------------------- 1 | // SSSSSSSS OOOOOO LL VV VV EEEEEEE RRRRRR 2 | // SS OO OO LL VV VV EE RR RR 3 | // SSSSSSSS 00 00 LL VV VV EEEEEEE RRRRR 4 | // SS 00 00 LL VVVV EE RR RR 5 | // SSSSSSSS 000000 LLLLLL VV EEEEEEE RR RR 6 | 7 | /* 8 | This is an implementation of Thistlewaite's algorithm in javascript: 9 | (http://en.wikipedia.org/wiki/Optimal_solutions_for_Rubik's_Cube#Thistlethwaite.27s_algorithm) 10 | 11 | The Rubik's cube has 20 cubicles, the cubicles are fixed positions on the cube where cubies reside 12 | Each cubie is named after the cubicle it belongs in. A cubicle is named by the faces it has. 13 | The faces are labeled as: {U: up, D: down, R: right, L: left, F: front, B: back} 14 | 15 | To solve a cube you pass it a string of the current state of the cube that looks like: 16 | UF UR UB UL DF DR DB DL FR FL BR BL UFR URB UBL ULF DRF DFL DLB DBR (<-- is an already solved cube) 17 | 18 | The first 12 pairs correspond to the cubicle of the Rubik's cube 19 | For a scrambled cube you put the cubie that is in the cubicle in the order presented above. 20 | An example of a scramble cube is: 21 | BR DF UR LB BD FU FL DL RD FR LU BU UBL FDR FRU BUR ULF LDF RDB DLB 22 | 23 | */ 24 | 25 | /*The state of the Rubik's cube is the position of the cubies at each of the 20 non-center locations 26 | * We number the cubies in the following order: 27 | * 28 | * ------------------- 29 | * | | | | 30 | * | | | | 31 | * | | | | 32 | * ------------------- 33 | * | | | | 34 | * | 11 | B | 10 | 35 | * | | | | 36 | * ------------------- 37 | * | | | | 38 | * | | | | 39 | * | | | | 40 | * ======================================================= 41 | * | | | | | | | | | | 42 | * | | | | 14 | 2 | 13 | | | | 43 | * | | | | | | | | | | 44 | * ------------------------------------------------------- 45 | * | | | | | | | | | | 46 | * | | L | | 3 | U | 1 | | R | | 47 | * | | | | | | | | | | 48 | * ------------------------------------------------------- 49 | * | | | | | | | | | | 50 | * | | | | 15 | 0 | 12 | | | | 51 | * | | | | | | | | | | 52 | * ======================================================= 53 | * | | | | 54 | * | | | | 55 | * | | | | 56 | * ------------------- 57 | * | | | | 58 | * | 9 | F | 8 | 59 | * | | | | 60 | * ------------------- 61 | * | | | | 62 | * | | | | 63 | * | | | | 64 | * =================== 65 | * | | | | 66 | * | 17 | 4 | 16 | 67 | * | | | | 68 | * ------------------- 69 | * | | | | 70 | * | 7 | D | 5 | 71 | * | | | | 72 | * ------------------- 73 | * | | | | 74 | * | 18 | 6 | 19 | 75 | * | | | | 76 | * ------------------- 77 | */ 78 | 79 | // /********************************************************************** 80 | // * 81 | // * A cube 'state' is a Array with 40 entries, the first 20 82 | // * are a permutation of {0,...,19} and describe which cubie is at 83 | // * a certain position (regarding the input ordering). The first 84 | // * twelve are for edges, the last eight for corners. 85 | // * 86 | // * The last 20 entries are for the orientations, each describing 87 | // * how often the cubie at a certain position has been turned 88 | // * counterclockwise away from the correct orientation. Again the 89 | // * first twelve are edges, the last eight are corners. The values 90 | // * are 0 or 1 for edges and 0, 1 or 2 for corners. 91 | // * 92 | // **********************************************************************/ 93 | 94 | RubiksCubeSolver = function(){ 95 | this.phase = 0; 96 | this.currentState = null; 97 | this.goalState = null; 98 | } 99 | 100 | RubiksCubeSolver.prototype.applyMove = function(move, inState) { 101 | var affectedCubies = [ 102 | [0, 1, 2, 3, 0, 1, 2, 3], // U 103 | [4, 7, 6, 5, 4, 5, 6, 7], // D 104 | [0, 9, 4, 8, 0, 3, 5, 4], // F 105 | [2, 10, 6, 11, 2, 1, 7, 6], // B 106 | [3, 11, 7, 9, 3, 2, 6, 5], // L 107 | [1, 8, 5, 10, 1, 0, 4, 7], // R 108 | ]; 109 | var turns = move % 3 + 1; 110 | var face = Math.floor(move / 3); 111 | var state = inState.slice(); 112 | while(turns--> 0){ 113 | var oldState = state.slice(); 114 | for(var i=0; i<8; i++ ){ 115 | var isCorner = i > 3; 116 | var target = affectedCubies[face][i] + isCorner*12; 117 | var killer = affectedCubies[face][(i&3)==3 ? i-3 : i+1] + isCorner*12; 118 | var orientationDelta = (i<4) ? (face>1 && face<4) : (face<2) ? 0 : 2 - (i&1); 119 | state[target] = oldState[killer]; 120 | state[target+20] = oldState[killer+20] + orientationDelta; 121 | if(turns == 0) 122 | state[target+20] %= 2 + isCorner; 123 | } 124 | } 125 | return state; 126 | } 127 | 128 | RubiksCubeSolver.prototype.inverse = function(move) { 129 | return move + 2 - 2 * (move % 3); 130 | } 131 | 132 | RubiksCubeSolver.prototype.getId = function(state) { 133 | //--- Phase 1: Edge orientations. 134 | if(this.phase < 2) 135 | return JSON.stringify(state.slice(20,32)); 136 | 137 | //-- Phase 2: Corner orientations, E slice edges. 138 | if(this.phase < 3){ 139 | var result = state.slice(31,40); 140 | for(var e=0; e<12; e++) 141 | result[0] |= (Math.floor(state[e] / 8)) << e; 142 | return JSON.stringify(result); 143 | } 144 | 145 | //--- Phase 3: Edge slices M and S, corner tetrads, overall parity. 146 | if(this.phase < 4){ 147 | var result = [0,0,0]; 148 | for(var e=0; e<12; e++) 149 | result[0] |= ((state[e] > 7) ? 2 : (state[e] & 1)) << (2*e); 150 | for(var c=0; c<8; c++) 151 | result[1] |= ((state[c+12]-12) & 5) << (3*c); 152 | for(var i=12; i<20; i++) 153 | for(var j=i+1; j<20; j++) 154 | result[2] ^= state[i] > state[j]; 155 | return JSON.stringify(result); 156 | } 157 | 158 | //--- Phase 4: The rest. 159 | return JSON.stringify(state); 160 | } 161 | 162 | // //---------------------------------------------------------------------- 163 | 164 | RubiksCubeSolver.prototype.setState = function(cube) { 165 | cube = cube.split(' '); 166 | if(cube.length != 20){ 167 | this.currentState = "Not enough cubies provided"; 168 | return false; 169 | } 170 | //--- Prepare current (start) and goal state. 171 | var goal = ["UF", "UR", "UB", "UL", "DF", "DR", "DB", "DL", "FR", "FL", "BR", "BL", "UFR", "URB", "UBL", "ULF", "DRF", "DFL", "DLB", "DBR"]; 172 | this.currentState = new Array(40); 173 | this.goalState = new Array(40); 174 | for(var i=0; i<40; i++){ 175 | this.currentState[i] = 0; 176 | this.goalState[i] = 0; 177 | } 178 | for(var i=0; i<20; i++){ 179 | 180 | //--- Goal state. 181 | this.goalState[i] = i; 182 | 183 | //--- Current (start) state. 184 | var cubie = cube[i]; 185 | while((this.currentState[i] = goal.indexOf(cubie)) == -1){ 186 | cubie = cubie.substr(1) + cubie[0]; 187 | this.currentState[i+20]++; 188 | if(this.currentState[i+20] > 2){ 189 | this.currentState = "Cannot solve: Invalid painting of cube."; 190 | return false; 191 | } 192 | } 193 | goal[goal.indexOf(cubie)] = ""; 194 | } 195 | return this.verifyState(); 196 | }; 197 | 198 | RubiksCubeSolver.prototype.verifyState = function() { 199 | if(!Array.isArray(this.currentState)) 200 | return false; 201 | //orientation of edges 202 | var sum = 0; 203 | this.currentState.slice(20,32).forEach(function(edge){ 204 | sum+=edge; 205 | }); 206 | if(sum % 2 != 0){ 207 | //edge orientation 208 | this.currentState = "Cannot solve: Edges not oriented correctly."; 209 | return false; 210 | } 211 | sum = 0; 212 | //orientation of corners 213 | this.currentState.slice(32,40).forEach(function(edge){ 214 | sum+=edge; 215 | }); 216 | if(sum % 3 != 0){ 217 | //corner orientation 218 | this.currentState = "Cannot solve: Corners not oriented correctly"; 219 | return false; 220 | } 221 | 222 | var getParity = function(a){ 223 | var count = 0; 224 | for(var i = 0; i a[i]){ 227 | count++; 228 | var temp = a[i]; 229 | a[i] = a[j]; 230 | a[j] = temp; 231 | } 232 | } 233 | } 234 | return count; 235 | } 236 | //check for parity 237 | sum = 0; 238 | //edge parity 239 | sum += getParity(this.currentState.slice(0,12)); 240 | //corner parity 241 | sum += getParity(this.currentState.slice(12,20)); 242 | if (sum % 2 != 0){ 243 | this.currentState = "Cannot solve: Parity error only one set of corners or edges swapped." ; 244 | return false; 245 | } 246 | 247 | return true; 248 | }; 249 | 250 | 251 | RubiksCubeSolver.prototype.solve = function(cube) { 252 | this.solution = ""; 253 | this.phase = 0; 254 | 255 | if(cube){ 256 | if(!this.setState(cube)) 257 | return false; 258 | } 259 | else if(!this.verifyState()) 260 | return false; 261 | 262 | while(++this.phase < 5){ 263 | this.startPhase(); 264 | } 265 | this.prepareSolution(); 266 | return this.solution; 267 | }; 268 | 269 | RubiksCubeSolver.prototype.solveAsync = function(cube, callback, progress) { 270 | this.solution = ''; 271 | this.phase = 1; 272 | if(cube){ 273 | if(!this.setState(cube)){ 274 | callback(false); 275 | return; 276 | } 277 | } else if(!this.verifyState()){ 278 | callback(false); 279 | return; 280 | } 281 | 282 | var nextPhase = function(){ 283 | if(this.phase < 5){ 284 | this.startPhase(); 285 | progress && progress(this.phase/5); 286 | this.phase++; 287 | setTimeout(nextPhase.bind(this), 0); 288 | } else { 289 | progress && progress(1); 290 | this.prepareSolution(); 291 | callback(this.solution); 292 | } 293 | } 294 | 295 | nextPhase.bind(this)(); 296 | }; 297 | 298 | RubiksCubeSolver.prototype.startPhase = function() { 299 | //--- Compute ids for current and goal state, skip phase if equal. 300 | var currentId = this.getId(this.currentState), goalId = this.getId(this.goalState); 301 | if(currentId == goalId) 302 | return; 303 | //--- Initialize the BFS queue. 304 | var q = []; 305 | q.push(this.currentState); 306 | q.push(this.goalState); 307 | 308 | //--- Initialize the BFS tables. 309 | var predecessor = {}; 310 | var direction = {}, lastMove = {}; 311 | direction[currentId] = 1; 312 | direction[goalId] = 2; 313 | 314 | //--- Begin BFS search 315 | while(1){ 316 | //--- Get state from queue, compute its ID and get its direction. 317 | var oldState = q.shift(); 318 | var oldId = this.getId(oldState); 319 | var oldDir = direction[oldId]; 320 | 321 | //--- Apply all applicable moves to it and handle the new state. 322 | var applicableMoves = [0, 262143, 259263, 74943, 74898]; 323 | for(var move=0; move<18; move++){ 324 | if(applicableMoves[this.phase] & (1 << move)){ 325 | 326 | //--- Apply the move. 327 | var newState = this.applyMove(move, oldState); 328 | var newId = this.getId(newState); 329 | var newDir = direction[newId]; 330 | 331 | //--- Have we seen this state (id) from the other direction already? 332 | //--- I.e. have we found a connection? 333 | if( newDir && newDir != oldDir ){ 334 | //--- Make oldId represent the forwards and newId the backwards search state. 335 | if(oldDir > 1){ 336 | var temp = newId; 337 | var newId = oldId; 338 | var oldId = temp; 339 | move = this.inverse(move); 340 | } 341 | 342 | //--- Reconstruct the connecting algorithm. 343 | var algorithm = [move]; 344 | while(oldId != currentId){ 345 | algorithm.unshift(lastMove[oldId]); 346 | oldId = predecessor[ oldId ]; 347 | } 348 | while(newId != goalId){ 349 | algorithm.push(this.inverse(lastMove[newId])); 350 | newId = predecessor[newId]; 351 | } 352 | 353 | //--- append to the solution and apply the algorithm. 354 | for(var i=0; i 2 | 3 | Rubik's Cube 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | HTML5 CANVAS 16 |
17 |
18 | 19 | 99 | 100 | 101 | --------------------------------------------------------------------------------