UNPKG

scramby

Version:

Generates scrambles for twisty puzzles.

516 lines (456 loc) 16.8 kB
/* scramble_NNN.js NxNxN Scramble Generator in Javascript. Code taken from the official WCA scrambler. Ported by Lucas Garron, November 24, 2011. */ 'use strict'; var Raphael; if (typeof window !== 'undefined') { Raphael = require('raphael'); } function randInt(min, max) { if (max === undefined) { max = min; min = 0; } if (typeof min !== 'number' || typeof max !== 'number') { throw new TypeError('Expected all arguments to be numbers'); } return Math.floor(Math.random() * (max - min) + min); } // We use an anonymous wrapper (and call it immediately) in order to avoid leaving the generator hanging around in the top-level scope. var generate_NNN_scrambler = function generate_NNN_scrambler(size, seqlen, mult) { // Default settings //var size=3; //var seqlen=30; var numcub = 1; //var mult=false; var cubeorient = false; var colorString = 'yobwrg'; //In dlburf order. May use any colours in colorList below // list of available colours var colorList = new Array('y', 'yellow.jpg', 'yellow', 'b', 'blue.jpg', 'blue', 'r', 'red.jpg', 'red', 'w', 'white.jpg', 'white', 'g', 'green.jpg', 'green', 'o', 'orange.jpg', 'orange', 'p', 'purple.jpg', 'purple', '0', 'grey.jpg', 'grey' // used for unrecognised letters, or when zero used. ); var colors = new Array(); //stores colours used var seq = new Array(); // move sequences var posit = new Array(); // facelet array var flat2posit; //lookup table for drawing cube var colorPerm = new Array(); //dlburf face colour permutation for each cube orientation colorPerm[0] = new Array(0, 1, 2, 3, 4, 5); colorPerm[1] = new Array(0, 2, 4, 3, 5, 1); colorPerm[2] = new Array(0, 4, 5, 3, 1, 2); colorPerm[3] = new Array(0, 5, 1, 3, 2, 4); colorPerm[4] = new Array(1, 0, 5, 4, 3, 2); colorPerm[5] = new Array(1, 2, 0, 4, 5, 3); colorPerm[6] = new Array(1, 3, 2, 4, 0, 5); colorPerm[7] = new Array(1, 5, 3, 4, 2, 0); colorPerm[8] = new Array(2, 0, 1, 5, 3, 4); colorPerm[9] = new Array(2, 1, 3, 5, 4, 0); colorPerm[10] = new Array(2, 3, 4, 5, 0, 1); colorPerm[11] = new Array(2, 4, 0, 5, 1, 3); colorPerm[12] = new Array(3, 1, 5, 0, 4, 2); colorPerm[13] = new Array(3, 2, 1, 0, 5, 4); colorPerm[14] = new Array(3, 4, 2, 0, 1, 5); colorPerm[15] = new Array(3, 5, 4, 0, 2, 1); colorPerm[16] = new Array(4, 0, 2, 1, 3, 5); colorPerm[17] = new Array(4, 2, 3, 1, 5, 0); colorPerm[18] = new Array(4, 3, 5, 1, 0, 2); colorPerm[19] = new Array(4, 5, 0, 1, 2, 3); colorPerm[20] = new Array(5, 0, 4, 2, 3, 1); colorPerm[21] = new Array(5, 1, 0, 2, 4, 3); colorPerm[22] = new Array(5, 3, 1, 2, 0, 4); colorPerm[23] = new Array(5, 4, 3, 2, 1, 0); // get all the form settings from the url parameters function parse() { /* var s=""; var urlquery=location.href.split("?") if(urlquery.length>1){ var urlterms=urlquery[1].split("&") for( var i=0; i<urlterms.length; i++){ var urllr=urlterms[i].split("="); if(urllr[0]==="size") { if(urllr[1]-0 >= 2 ) size=urllr[1]-0; } else if(urllr[0]==="len") { if(urllr[1]-0 >= 1 ) seqlen=urllr[1]-0; } else if(urllr[0]==="num"){ if(urllr[1]-0 >= 1 ) numcub=urllr[1]-0; } else if(urllr[0]==="multi") { mult=(urllr[1]==="on"); } else if(urllr[0]==="cubori") { cubeorient=(urllr[1]==="on"); } else if(urllr[0]==="col") { if(urllr[1].length===6) colorString = urllr[1]; } } }*/ // build lookup table var i, j; flat2posit = new Array(12 * size * size); for (i = 0; i < flat2posit.length; i++) { flat2posit[i] = -1; }for (i = 0; i < size; i++) { for (j = 0; j < size; j++) { flat2posit[4 * size * (3 * size - i - 1) + size + j] = i * size + j; //D flat2posit[4 * size * (size + i) + size - j - 1] = (size + i) * size + j; //L flat2posit[4 * size * (size + i) + 4 * size - j - 1] = (2 * size + i) * size + j; //B flat2posit[4 * size * i + size + j] = (3 * size + i) * size + j; //U flat2posit[4 * size * (size + i) + 2 * size + j] = (4 * size + i) * size + j; //R flat2posit[4 * size * (size + i) + size + j] = (5 * size + i) * size + j; //F } } /* 19 32 16 48 35 31 60 51 44 28 80 63 67 47 83 64 92 79 95 76 0 12 3 15 */ // expand colour string into 6 actual html color names for (var k = 0; k < 6; k++) { colors[k] = colorList.length - 3; // gray for (var i = 0; i < colorList.length; i += 3) { if (colorString.charAt(k) === colorList[i]) { colors[k] = i; break; } } } } // append set of moves along an axis to current sequence in order function appendmoves(sq, axsl, tl, la) { for (var sl = 0; sl < tl; sl++) { // for each move type if (axsl[sl]) { // if it occurs var q = axsl[sl] - 1; // get semi-axis of this move var sa = la; var m = sl; if (sl + sl + 1 >= tl) { // if on rear half of this axis sa += 3; // get semi-axis (i.e. face of the move) m = tl - 1 - m; // slice number counting from that face q = 2 - q; // opposite direction when looking at that face } // store move sq[sq.length] = (m * 6 + sa) * 4 + q; } } } // generate sequence of scambles function scramble() { //tl=number of allowed moves (twistable layers) on axis -- middle layer ignored var tl = size; if (mult || (size & 1) != 0) tl--; //set up bookkeeping var axsl = new Array(tl); // movement of each slice/movetype on this axis var axam = new Array(0, 0, 0); // number of slices moved each amount var la; // last axis moved // for each cube scramble for (var n = 0; n < numcub; n++) { // initialise this scramble la = -1; seq[n] = new Array(); // moves generated so far // reset slice/direction counters for (var i = 0; i < tl; i++) { axsl[i] = 0; }axam[0] = axam[1] = axam[2] = 0; var moved = 0; // while generated sequence not long enough while (seq[n].length + moved < seqlen) { var ax, sl, q; do { do { // choose a random axis ax = randInt(3); // choose a random move type on that axis sl = randInt(tl); // choose random amount q = randInt(3); } while (ax === la && axsl[sl] != 0); // loop until have found an unused movetype } while (ax === la && // loop while move is reducible: reductions only if on same axis as previous moves !mult && // multislice moves have no reductions so always ok tl === size && ( // only even-sized cubes have reductions (odds have middle layer as reference) 2 * axam[0] === tl || // reduction if already have half the slices move in same direction 2 * axam[1] === tl || 2 * axam[2] === tl || 2 * (axam[q] + 1) === tl && // reduction if move makes exactly half the slices moved in same direction and axam[0] + axam[1] + axam[2] - axam[q] > 0) // some other slice also moved ); // if now on different axis, dump cached moves from old axis if (ax != la) { appendmoves(seq[n], axsl, tl, la); // reset slice/direction counters for (var i = 0; i < tl; i++) { axsl[i] = 0; }axam[0] = axam[1] = axam[2] = 0; moved = 0; // remember new axis la = ax; } // adjust counters for this move axam[q]++; // adjust direction count moved++; axsl[sl] = q + 1; // mark the slice has moved amount } // dump the last few moves appendmoves(seq[n], axsl, tl, la); // do a random cube orientation if necessary seq[n][seq[n].length] = cubeorient ? randInt(24) : 0; } } var cubeSize = size; var border = 2; var width = 40 / cubeSize; var gap = 4; function colorGet(col) { if (col === 'r') return '#FF0000'; if (col === 'o') return '#FF8000'; if (col === 'b') return '#0000FF'; if (col === 'g') return '#00FF00'; if (col === 'y') return '#FFFF00'; if (col === 'w') return '#FFFFFF'; if (col === 'x') return '#000000'; } var scalePoint = function scalePoint(w, h, ptIn) { var defaultWidth = border * 2 + width * 4 * cubeSize + gap * 3; var defaultHeight = border * 2 + width * 3 * cubeSize + gap * 2; var scale = Math.min(w / defaultWidth, h / defaultHeight); var x = Math.floor(ptIn[0] * scale + (w - defaultWidth * scale) / 2) + 0.5; var y = Math.floor(ptIn[1] * scale + (h - defaultHeight * scale) / 2) + 0.5; return [x, y]; }; function drawSquare(r, canvasWidth, canvasHeight, cx, cy, w, fillColor) { var arrx = [cx - w, cx - w, cx + w, cx + w]; var arry = [cy - w, cy + w, cy + w, cy - w]; var pathString = ''; for (var i = 0; i < arrx.length; i++) { var scaledPoint = scalePoint(canvasWidth, canvasHeight, [arrx[i], arry[i]]); pathString += (i === 0 ? 'M' : 'L') + scaledPoint[0] + ',' + scaledPoint[1]; } pathString += 'z'; r.path(pathString).attr({ fill: colorGet(fillColor), stroke: '#000' }); } var drawScramble = function drawScramble(parentElement, state, w, h) { initializeDrawing(); var colorString = 'wrgoby'; // UFRLBD parentElement.innerHTML = ''; var r = Raphael(parentElement, w, h); var s = '', i, f, d = 0, q; var ori = 0; d = 0; s = '<table border=0 cellpadding=0 cellspacing=0>'; for (i = 0; i < 3 * size; i++) { s += '<tr>'; for (f = 0; f < 4 * size; f++) { if (flat2posit[d] < 0) { s += '<td></td>'; } else { var c = colorPerm[ori][state[flat2posit[d]]]; var col = colorList[colors[c] + 0]; drawSquare(r, w, h, border + width / 2 + f * width + gap * Math.floor(f / cubeSize), border + width / 2 + i * width + gap * Math.floor(i / cubeSize), width / 2, col); //s+="<td style='background-color:"+colorList[colors[c]+2]+"'><img src='scrbg/"+colorList[colors[c]+1]+"' width=10 border=1 height=10><\/td>"; } d++; } s += '</tr>'; } s += '</table>'; return s; }; function scramblestring(n) { var s = '', j; for (var i = 0; i < seq[n].length - 1; i++) { if (i != 0) s += ' '; var k = seq[n][i] >> 2; j = k % 6; k = (k - j) / 6; if (k && size <= 5 && !mult) { s += 'dlburf'.charAt(j); // use lower case only for inner slices on 4x4x4 or 5x5x5 } else { if (size <= 5 && mult) { s += 'DLBURF'.charAt(j); if (k) s += 'w'; // use w only for double layers on 4x4x4 and 5x5x5 } else { if (k) s += k + 1; s += 'DLBURF'.charAt(j); } } j = seq[n][i] & 3; if (j != 0) s += " 2'".charAt(j); } // add cube orientation if (cubeorient) { var ori = seq[n][seq[n].length - 1]; s = 'Top:' + colorList[2 + colors[colorPerm[ori][3]]] + '&nbsp;&nbsp;&nbsp;Front:' + colorList[2 + colors[colorPerm[ori][5]]] + '<br>' + s; } return s; } function imagestring(nr) { var s = '', i, f, d = 0, q; // initialise colours for (i = 0; i < 6; i++) { for (f = 0; f < size * size; f++) { posit[d++] = i; } } // do move sequence for (i = 0; i < seq[nr].length - 1; i++) { q = seq[nr][i] & 3; f = seq[nr][i] >> 2; d = 0; while (f > 5) { f -= 6; d++; } do { doslice(f, d, q + 1); d--; } while (mult && d >= 0); } // build string containing cube var ori = seq[nr][seq[nr].length - 1]; d = 0; var imageheight = 160; // height of cube images in pixels (160px is a good height for fitting 5 images on a sheet of paper) var stickerheight = Math.floor(imageheight / (size * 3)); if (stickerheight < 5) { stickerheight = 5; } // minimum sticker size of 5px, takes effect when cube size reaches 11 s = "<div style='width:" + stickerheight * size * 4 + 'px; height:' + stickerheight * size * 3 + "px;'>"; for (i = 0; i < 3 * size; i++) { s += "<div style='float: left; display: block; height: " + stickerheight + 'px; width: ' + stickerheight * size * 4 + "px; line-height: 0px;'>"; for (f = 0; f < 4 * size; f++) { if (true) { s += "<div style='overflow: hidden; display: block; float: left; height: " + stickerheight + 'px; width: ' + stickerheight + "px;'></div>"; } else { var c = colorPerm[ori][posit[flat2posit[d]]]; s += "<div style='overflow: hidden; display: block; float: left; border: 1px solid #000; height: " + (stickerheight * 1 - 2) + 'px; width: ' + (stickerheight * 1 - 2) + "px;'><img src='scrbg/" + colorList[colors[c] + 1] + "' /></div>"; } d++; } s += '</div>'; } s += '</div>'; return s; } function doslice(f, d, q) { //do move of face f, layer d, q quarter turns var f1, f2, f3, f4; var s2 = size * size; var c, i, j, k; if (f > 5) f -= 6; // cycle the side facelets for (k = 0; k < q; k++) { for (i = 0; i < size; i++) { if (f === 0) { f1 = 6 * s2 - size * d - size + i; f2 = 2 * s2 - size * d - 1 - i; f3 = 3 * s2 - size * d - 1 - i; f4 = 5 * s2 - size * d - size + i; } else if (f === 1) { f1 = 3 * s2 + d + size * i; f2 = 3 * s2 + d - size * (i + 1); f3 = s2 + d - size * (i + 1); f4 = 5 * s2 + d + size * i; } else if (f === 2) { f1 = 3 * s2 + d * size + i; f2 = 4 * s2 + size - 1 - d + size * i; f3 = d * size + size - 1 - i; f4 = 2 * s2 - 1 - d - size * i; } else if (f === 3) { f1 = 4 * s2 + d * size + size - 1 - i; f2 = 2 * s2 + d * size + i; f3 = s2 + d * size + i; f4 = 5 * s2 + d * size + size - 1 - i; } else if (f === 4) { f1 = 6 * s2 - 1 - d - size * i; f2 = size - 1 - d + size * i; f3 = 2 * s2 + size - 1 - d + size * i; f4 = 4 * s2 - 1 - d - size * i; } else if (f === 5) { f1 = 4 * s2 - size - d * size + i; f2 = 2 * s2 - size + d - size * i; f3 = s2 - 1 - d * size - i; f4 = 4 * s2 + d + size * i; } c = posit[f1]; posit[f1] = posit[f2]; posit[f2] = posit[f3]; posit[f3] = posit[f4]; posit[f4] = c; } /* turn face */ if (d === 0) { for (i = 0; i + i < size; i++) { for (j = 0; j + j < size - 1; j++) { f1 = f * s2 + i + j * size; f3 = f * s2 + (size - 1 - i) + (size - 1 - j) * size; if (f < 3) { f2 = f * s2 + (size - 1 - j) + i * size; f4 = f * s2 + j + (size - 1 - i) * size; } else { f4 = f * s2 + (size - 1 - j) + i * size; f2 = f * s2 + j + (size - 1 - i) * size; } c = posit[f1]; posit[f1] = posit[f2]; posit[f2] = posit[f3]; posit[f3] = posit[f4]; posit[f4] = c; } } } } } /* * Some helper functions. */ var getRandomScramble = function getRandomScramble() { scramble(); imagestring(0); return { state: posit, scrambleString: scramblestring(0) }; }; var drawingInitialized = false; var initializeDrawing = function initializeDrawing(continuation) { if (!drawingInitialized) { parse(); drawingInitialized = true; } if (continuation) { setTimeout(continuation, 0); } }; var initializeFull = function initializeFull(continuation, _) { initializeDrawing(); if (continuation) { setTimeout(continuation, 0); } }; return { initialize: initializeFull, getRandomScramble: getRandomScramble, drawScramble: drawScramble }; }; var scramblers = { '444': generate_NNN_scrambler(4, 40, true), '555': generate_NNN_scrambler(5, 60, true), '666': generate_NNN_scrambler(6, 70, true), '777': generate_NNN_scrambler(7, 100, true) }; module.exports = scramblers;