Skip to content

Instantly share code, notes, and snippets.

@redbullmarky
Created February 20, 2019 13:43
Show Gist options
  • Select an option

  • Save redbullmarky/b710809b4f4589777a09d7daf5a0c5c4 to your computer and use it in GitHub Desktop.

Select an option

Save redbullmarky/b710809b4f4589777a09d7daf5a0c5c4 to your computer and use it in GitHub Desktop.
Rubiks cube solver
<body onload="APP=APP(); APP.main() "
onresize="APP.setCanvasSize()" >
<h2>Rubik&#8217;s Cube.</h2>
<div id="left">
<!-- <canvas id="canvas" width="600" height="600"> -->
<canvas id="canvas" style="width: 100%;" >
Oops, your browser does not support the canvas element.
</canvas>
</div>
<div id="right">
<h3>Controls: </h3>
<p>
Rubik&#8217;s cube can run in two modes:
</p>
<ul>
<li><b>Random:</b>&nbsp;Make 10 random moves, then &#8220;
solve&#8221; the randomized cube by reversing them.</li>
<li><b>Manual:</b>&nbsp; Make moves by hand using three controls:
<ul>
<li>Axis: Sets x, y or z to the axis of rotation</li>
<li>Plane: Sets -1, 0, 1 as the coordinate of the move&#8217;s plane along the axis.</li>
<li>Rotation: Either + or - 90 degrees</li>
</ul>
</li>
</ul>
<hr>
<button name="pause" value="Pause" onclick="APP.setPause(this.value)">Pause</button>
<button name="demo" value="Manual" onclick="APP.setDemo(this.value);APP.setPause()">Manual</button>
<select name="speed" onchange="APP.setSpeed(this.value)">
<option value = "240" selected>Slow</option>
<option value = "120">Medium</option>
<option value = "60">Fast</option>
</select>
<br /><br />
<form>
<table>
<thead><b>Manual Controls
<i class='small'>(click 'Manual' again to return to demo mode)</i>
</b></thead>
<tr>
<td>Axis:</td>
<td>
<input type="radio" id='x' name="axis" value="x"
onchange="APP.setAxis(0)" checked="checked" />
<label for="x">x</label>
</td>
<td>
<input type="radio" id='y' name="axis" value="y"
onchange="APP.setAxis(1)" />
<label for="y">y</label>
</td>
<td>
<input type="radio" id='z' name="axis" value="z"
onchange="APP.setAxis(2)" />
<label for="z">z</label>
</td>
</tr>
<tr>
<td>Plane:</td>
<td>
<input type="radio" id='-1' name="plane" value="-1"
onchange="APP.setPlane(-1)" />
<label for="-1">-1</label>
</td>
<td>
<input type="radio" id='0' name="plane" value="0"
onchange="APP.setPlane(0)" />
<label for="0">0</label>
</td>
<td>
<input type="radio" id='+1' name="plane" value="+1"
onchange="APP.setPlane(1)" checked="checked"/>
<label for="+1">+1</label>
</td>
</tr>
<tr>
<td>Rotation:</td>
<td>
<input type="radio" id='-1'name="rotation" value="-90"
onchange="APP.setRotation(-90)" />
<label for="-1">-1</label>
</td>
<td>
<input type="radio" id='+1' name="rotation" value="+90"
onchange="APP.setRotation(+90)" checked="checked"/>
<label for="+1">+1</label>
</td>
</tr>
</table>
</form>
<button name="Move" value="Move" onclick="APP.setDoMove()">Move</button>
<button name="Random" value="Random" onclick="APP.setDoRandomMove()">Random</button>
<button name="Solve" value="Solve" onclick="APP.setSolve()">Solve</button>
<button name="Reset" value="Reset" onclick="APP.resetView()">Reset</button>
<ul>
<li>Rotate: Click / Drag </li>
<li>Reset: To Initial View</li>
<li>Resize: Cube Is Responsive!</li>
</ul>
</div>
</body>
APP=function() {
var gl = glx.initGL("canvas", 1),
program = glx.initShaders(gl, "vertexShader", "fragmentShader"),
vPosition = gl.getAttribLocation(program, "vPosition"),
vColor = gl.getAttribLocation(program, "vColor"),
cube = glx.createCube(.49, // rubik's colors:
[ [1,1,1], [1,0,0], [0,0,1], [1,.5,0], [0,1,0], [1,1,0] ]),
moves = [],
move,
matrices = [],
solving = false,
demo = true,
uiFramesPerMove = 120,
uiDoMove = false,
uiRandomMove = false,
uiMove = [ [1,0,0], +1, +90 ],
uiEye = [10,5,10];
function init() {
var typedArray,
cubeVertexBuf = gl.createBuffer(),
cubeColorBuf = gl.createBuffer(),
cubeIndexBuf = gl.createBuffer();
typedArray = new Float32Array(cube.vertices);
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexBuf);
gl.bufferData(gl.ARRAY_BUFFER, typedArray, gl.STATIC_DRAW);
gl.enableVertexAttribArray(vPosition);
gl.vertexAttribPointer(vPosition, cube.cols, gl.FLOAT, false, 0, 0);
typedArray = new Float32Array(cube.colors);
gl.bindBuffer(gl.ARRAY_BUFFER, cubeColorBuf);
gl.bufferData(gl.ARRAY_BUFFER, typedArray, gl.STATIC_DRAW);
gl.enableVertexAttribArray(vColor);
gl.vertexAttribPointer(vColor, cube.colorcols, gl.FLOAT, false, 0, 0);
typedArray = new Uint16Array(cube.indices);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeIndexBuf);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, typedArray, gl.STATIC_DRAW);
initMatrices();
}
function initMatrices () {
matrices = [];
moves = [];
for (var x=-1; x <= 1; x++) {
for (var y=-1; y <= 1; y++) {
for (var z=-1; z <= 1; z++) {
matrices.push(
mat4.translate(
mat4.identity(mat4.create()),
[x, y, z]));
}
}
}
}
function createRandomMove() {
var iaxis = glx.randomInt(3),
axis = [0,0,0],
plane = glx.randomFromTo(-1,1),
rot = 90*(2*glx.randomInt(2) - 1);
axis[iaxis] = 1;
return createForwardMove(axis, plane, rot);
}
function createReverseMove() {
var move = moves.pop();
move[2] = - move[2];
return createMove(move[0],move[1],move[2],move[3]);
}
function createForwardMove(axis, plane, rot) {
moves.push([axis,plane,rot]);
return createMove(axis, plane, rot);
}
function createMove(axis, plane, rot) {
var imat = axis.indexOf(1)+12,
incr = glx.degToRad(rot) / uiFramesPerMove,
stopframe = glx.animFrame + uiFramesPerMove;
function done () {
return glx.animFrame > stopframe;
}
function next(mat) {
if ( !done() && (mat[imat] == plane) ) {
var rmat = mat4.identity(mat4.create());
mat4.rotate(rmat, incr, axis);
mat4.multiply(rmat, mat, mat);
if (glx.animFrame == stopframe)
glx.vmod(mat, Math.round);
}
}
return {
done: done,
next: next
};
}
function setUniformMatrix(uniformName, matrix) {
gl.uniformMatrix4fv(
gl.getUniformLocation(program, uniformName), false, matrix);
}
function checkMove () {
if( !move || move.done() ) {
if (demo) {
solving = (solving && moves.length != 0) ||
(!solving && moves.length == 10);
if (solving)
move=createReverseMove();
else
move=createRandomMove();
} else { // manual
if (uiDoMove) {
if (uiRandomMove)
move=createRandomMove();
else
move = createForwardMove(uiMove[0], uiMove[1], uiMove[2]);
uiDoMove = false;
uiRandomMove = false;
} else if (solving && moves.length != 0) {
move=createReverseMove();
} else {
if (move && move.done()) {
//setPause(true);
glx.animPause = true;
}
}
}
}
}
function display() {
checkMove();
doDisplay(true);
}
function redisplay() {
doDisplay(false);
}
function doDisplay(checkMove) {
var pMatrix = mat4.perspective(45, glx.aspectRatio(gl), 0.1, 100.0),
laMatrix = mat4.lookAt(uiEye, [0,0,0], [0,1,0]);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
setUniformMatrix("pMatrix", pMatrix);
setUniformMatrix("laMatrix", laMatrix);
for (var i=0; i < matrices.length; i++) {
if(checkMove && move) move.next(matrices[i]);
setUniformMatrix("mvMatrix", matrices[i]);
gl.drawElements(gl.TRIANGLES, cube.indices.length,
gl.UNSIGNED_SHORT, 0);
};
}
function main() {
init();
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);
glx.animate(display);
}
// UI: enclose all UI within a closure-module
var uiStartX,
uiStartA,
uiR;
gl.canvas.addEventListener("mousedown", mouseDown, false);
gl.canvas.addEventListener("mouseup", mouseUp, false);
function mouseDown(e) {
uiStartX = e.offsetX;
uiR = Math.sqrt(10*10 + 10*10);
uiStartA = glx.radToDeg(Math.acos(uiEye[0]/uiR));
gl.canvas.addEventListener("mousemove", mouseDrag, false);
}
function mouseUp(e) {
gl.canvas.removeEventListener("mousemove", mouseDrag, false);
}
function mouseDrag(e) {
var dx = e.offsetX - uiStartX,
da = Math.round(360*(dx/gl.canvas.height));
console.log("mouseDrag: dx: "+dx+" r: "+uiR+" a: "+uiStartA+" da: "+da);
uiEye[0] = uiR*Math.cos(glx.degToRad(uiStartA+da));
uiEye[2] = uiR*Math.sin(glx.degToRad(uiStartA+da));
if (glx.animPause) redisplay();
}
function setCanvasSize() {
glx.resizeCanvas(gl, 1);
if (glx.animPause) redisplay();
}
function uiStr() {
return "[" + vec3.str(uiMove[0]) + ", " + uiMove[1] + ", " + uiMove[2]+ "]"
}
function setAxis(i) {
var uiAxis = [0,0,0];
uiAxis[i] = 1;
uiMove[0] = uiAxis;
console.log("setAxis: "+vec3.str(uiAxis)+" "+ uiStr());
}
function setPlane(i) {
uiMove[1] = i;
console.log("setPlane: "+i+" "+ uiStr());
}
function setRotation(i) {
uiMove[2] = i;
console.log("setRotation: "+i+" "+ uiStr());
}
function setDoMove() {
uiDoMove = true;
uiRandomMove = false;
solving = false;
console.log("setDoMove");
glx.animPause = false;
}
function setDoRandomMove() {
uiDoMove = true;
uiRandomMove = true;
solving = false;
console.log("setDoRandomMove");
glx.animPause = false;
}
function setPause(s) {
var names = ["Pause","Resume"];
s = s?s:names[1]; // default to switching back to Pause
glx.animPause=(s==names[0]);
console.log("setPause: "+glx.animPause);
document.getElementsByName("pause")[0].value=
(glx.animPause)?names[1]:names[0];
}
function setDemo(s) {
var names = ["Demo","Manual"];
demo=(s==names[0]);
document.getElementsByName("demo")[0].value=
(demo)?names[1]:names[0];
initMatrices();
move = null;
solving = false;
glx.animPause = !demo;
if (glx.animPause) redisplay();
console.log("setDemo: "+demo+" pause:"+glx.animPause);
}
function setSpeed(s) {
s = s ? s : document.getElementsByName("speed")[0].value;
uiFramesPerMove = parseInt(s,10);
console.log("setSpeed: "+uiFramesPerMove);
}
function setSolve() {
solving = true;
uiDoMove = false;
glx.animPause = false;
}
function resetView() {
uiEye = [10,5,10];
if (glx.animPause) redisplay();
}
return {
// dbg
dbg: function () {
window.gl = gl;
window.program = program;
window.cube = cube;
window.can = gl.canvas;
window.vp = gl.getParameter( gl.VIEWPORT );
window.matrices = matrices;
window.axis = document.getElementsByName("axis");
window.plane = document.getElementsByName("plane");
window.rotation = document.getElementsByName("rotation");
},
// interface
main: main,
setAxis: setAxis,
setPlane: setPlane,
setRotation: setRotation,
setCanvasSize: setCanvasSize,
setPause: setPause,
setDemo: setDemo,
setSpeed: setSpeed,
setDoMove: setDoMove,
resetView: resetView,
setDoRandomMove: setDoRandomMove,
setSolve: setSolve
};
};
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/131045/glMatrix.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/131045/glx.js"></script>
body{
font-size:1.5em;
background:#2980b9;
color:#2c3e50;
}
body:before {
content: "";
position: fixed;
top: -10px;
left: 0;
width: 100%;
height: 10px;
-webkit-box-shadow: 0px 0px 10px rgba(0,0,0,.8);
-moz-box-shadow: 0px 0px 10px rgba(0,0,0,.8);
box-shadow: 0px 0px 10px rgba(0,0,0,.8);
z-index: 100;
}
h2{
text-align:center;
padding:1em;
}
#left {
float: left;
width: 50%;
margin: 0;
padding: 0;
-webkit-box-shadow: 0px 0px 10px rgba(0,0,0,.8);
-moz-box-shadow: 0px 0px 10px rgba(0,0,0,.8);
box-shadow: 0px 0px 10px rgba(0,0,0,.8);
}
#right {
float: right;
width: 47%;
margin: 0;
padding: 0 0 0 10px;
border-left: 1px dashed #000;
}
#right h3 {
margin-top: 0;
padding-top: 0;
}
hr{
border: 1px dashed #000;
}
button{
border:none;
width:8vw;
padding:.5em;
margin:1%;
font-size:1rem;
color:#fff;
background:#c0392b;
box-shadow:1px 1px 1px 1px rgba(0,0,0,0.4);
-webkit-box-shadow:1px 1px 1px 1px rgba(0,0,0,0.4);
-moz-box-shadow:1px 1px 1px 1px rgba(0,0,0,0.4);
}
select{
color:#fff;
background:#95a5a6;
padding:.5em;
margin:1%;
font-size:1rem;
border:none;
width:20%;
box-shadow:1px 1px 1px 1px rgba(0,0,0,0.4);
-webkit-box-shadow:1px 1px 1px 1px rgba(0,0,0,0.4);
-moz-box-shadow:1px 1px 1px 1px rgba(0,0,0,0.4);
}
input[type=radio] {
border: 0px;
height:1.5vh;
width:50%;
}
label{
display:inline-block;
font-weight:bold;
}
table{
width:60%;
}
tr, td{
font-size:1rem;
padding-bottom:1em;
margin-bottom:2%;
}
thead{
text-align:center;
}
.small{
font-size:.5rem;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment