scramby
Version:
Generates scrambles for twisty puzzles.
582 lines (519 loc) • 17 kB
JavaScript
/*
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(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(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(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]]] +
' 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() {
scramble()
imagestring(0)
return {
state: posit,
scrambleString: scramblestring(0)
}
}
var drawingInitialized = false
var initializeDrawing = function(continuation) {
if (!drawingInitialized) {
parse()
drawingInitialized = true
}
if (continuation) {
setTimeout(continuation, 0)
}
}
var initializeFull = function(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