Skip to content

Instantly share code, notes, and snippets.

@romaincoeur
Created January 17, 2021 17:55
Show Gist options
  • Save romaincoeur/8e586463d9504c86b3f8768177146661 to your computer and use it in GitHub Desktop.
Save romaincoeur/8e586463d9504c86b3f8768177146661 to your computer and use it in GitHub Desktop.
Rubik's cube in Javascript
- var faces = ['left', 'right', 'top', 'bottom', 'back', 'front'], pieces = 56 // n^3 - (n-2)^3
#scene.scene
#pivot.pivot.centered(style = 'transform: rotateX(-35deg) rotateY(-135deg);')
#cube.cube
while pieces--
.piece
each face in faces
div(class = 'element ' + face)
.credits
.text(style = 'position: initial') Wanna make solver, but I'm too lazy to translate 100 lines of my Cpp's solver to JS...
#guide
- var n = 4
while n--
div(id = 'anchor' + n class = 'anchor' style = 'transform: translateZ(3px) translateY(-33.33%) rotate(' + (-90 * n) + 'deg) translateY(66.67%)')
var colors = ['blue', 'green', 'black', 'yellow', 'orange', 'red'],
pieces = document.getElementsByClassName('piece');
// console.log('pieces', pieces)
// Returns j-th adjacent face of i-th face
function mx(i, j) {
return ([2, 4, 3, 5][j % 4 |0] + i % 2 * ((j|0) % 4 * 2 + 3) + 2 * (i / 2 |0)) % 6;
}
function getAxis(face) {
return String.fromCharCode('X'.charCodeAt(0) + face / 2); // X, Y or Z
}
// Moves each of 26 pieces to their places, assigns IDs and attaches stickers
function assembleCube() {
function moveto(face) {
id = id + (1 << face);
pieces[i].children[face].appendChild(document.createElement('div'))
.setAttribute('class', 'sticker ' + colors[face]);
return 'translate' + getAxis(face) + '(' + (face % 2 * 4 - 2) + 'em)';
}
for (var id, x, i = 0; id = 0, i < 26; i++) {
x = mx(i, i % 18);
pieces[i].style.transform = 'rotateX(0deg)' + moveto(i % 6) +
(i > 5 ? moveto(x) + (i > 17 ? moveto(mx(x, x + 2)) : '') : '');
pieces[i].setAttribute('id', 'piece' + id);
}
}
function getPieceBy(face, index, corner) {
return document.getElementById('piece' +
((1 << face) + (1 << mx(face, index)) + (1 << mx(face, index + 1)) * corner));
}
// Swaps stickers of the face (by clockwise) stated times, thereby rotates the face
function swapPieces(face, times) {
for (var i = 0; i < 6 * times; i++) {
var piece1 = getPieceBy(face, i / 2, i % 2),
piece2 = getPieceBy(face, i / 2 + 1, i % 2);
for (var j = 0; j < 5; j++) {
var sticker1 = piece1.children[j < 4 ? mx(face, j) : face].firstChild,
sticker2 = piece2.children[j < 4 ? mx(face, j + 1) : face].firstChild,
className = sticker1 ? sticker1.className : '';
if (className)
sticker1.className = sticker2.className,
sticker2.className = className;
}
}
}
// Animates rotation of the face (by clockwise if cw), and then swaps stickers
function animateRotation(face, cw, currentTime) {
var k = .3 * (face % 2 * 2 - 1) * (2 * cw - 1),
qubes = Array(9).fill(pieces[face]).map(function (value, index) {
return index ? getPieceBy(face, index / 2, index % 2) : value;
});
(function rotatePieces() {
var passed = Date.now() - currentTime,
style = 'rotate' + getAxis(face) + '(' + k * passed * (passed < 300) + 'deg)';
qubes.forEach(function (piece) {
piece.style.transform = piece.style.transform.replace(/rotate.\(\S+\)/, style);
});
if (passed >= 300)
return swapPieces(face, 3 - 2 * cw);
requestAnimationFrame(rotatePieces);
})();
}
// Events
function mousedown(md_e) {
var startXY = pivot.style.transform.match(/-?\d+\.?\d*/g).map(Number),
element = md_e.target.closest('.element'),
face = [].indexOf.call((element || cube).parentNode.children, element);
function mousemove(mm_e) {
if (element) {
var gid = /\d/.exec(document.elementFromPoint(mm_e.pageX, mm_e.pageY).id);
if (gid && gid.input.includes('anchor')) {
mouseup();
var e = element.parentNode.children[mx(face, Number(gid) + 3)].hasChildNodes();
animateRotation(mx(face, Number(gid) + 1 + 2 * e), e, Date.now());
}
} else pivot.style.transform =
'rotateX(' + (startXY[0] - (mm_e.pageY - md_e.pageY) / 2) + 'deg)' +
'rotateY(' + (startXY[1] + (mm_e.pageX - md_e.pageX) / 2) + 'deg)';
}
function mouseup() {
document.body.appendChild(guide);
scene.removeEventListener('mousemove', mousemove);
document.removeEventListener('mouseup', mouseup);
scene.addEventListener('mousedown', mousedown);
}
(element || document.body).appendChild(guide);
scene.addEventListener('mousemove', mousemove);
document.addEventListener('mouseup', mouseup);
scene.removeEventListener('mousedown', mousedown);
}
document.ondragstart = function() { return false; }
window.addEventListener('load', assembleCube);
scene.addEventListener('mousedown', mousedown);
$base-color: #0A0A0A;
$ground-color: #2F2F31;
$element-size: 2em;
$sticker-size: 95%;
$rounded: 10%;
$cube-scale: 1.9;
$faces: (left: (0, -90, 180), right: (0, 90, 90), back: (0, 180, -90), front: (0, 0, 0), bottom: (-90, 0, -90), top: (90, 0, 180));
$colors: (blue: #001ca8, green: #006E16, white: #DDD, yellow: #E0AE00, orange: #FF5000, red: #DF0500);
html, body {
height: 100%;
overflow: hidden;
background: radial-gradient(circle, white, rgba(black, .5)) {
color: $ground-color;
blend-mode: overlay; }
}
.credits {
width: 100%;
top: 90%;
}
.text {
text-align: center;
font: { family: Helvetica; size: .8rem; }
color: grey;
pointer-events: none;
}
.centered {
position: absolute;
top: 0; bottom: 0; left: 0; right: 0;
margin: auto;
}
.scene {
width: 100%;
height: 100%;
perspective: 1200px;
transform-style: preserve-3d;
> .pivot {
width: 0;
height: 0;
transition: .18s;
}
.anchor {
width: $element-size;
height: $element-size * 3;
}
div {
position: absolute;
transform-style: inherit;
}
}
#piece4 > .element.top > .sticker {
background-image: URL('http://i63.tinypic.com/25hh1xu.png');
background-size: cover;
}
.cube {
font-size: $cube-scale * 100%;
margin-left: -$element-size / 2;
margin-top: -$element-size / 2;
> .piece {
width: $element-size - .1em;
height: $element-size - .1em;
> .element {
width: 100%;
height: 100%;
background: $base-color;
outline: 1px solid transparent; // firefox aliasing
border: .05em solid $base-color { radius: $rounded }
@each $face, $angles in $faces {
&.#{$face} {
transform: rotateX + '(' + nth($angles, 1) + 'deg)'
rotateY + '(' + nth($angles, 2) + 'deg)'
rotateZ + '(' + nth($angles, 3) + 'deg)' translateZ($element-size / 2);
}
}
> .sticker {
@extend .centered;
transform: translateZ(2px);
width: $sticker-size;
height: $sticker-size;
border-radius: $rounded;
outline: 1px solid transparent; // firefox aliasing
box-shadow: inset .05em .05em .2rem 0 rgba(white, .25),
inset -.05em -.05em .2rem 0 rgba(black, .25);
@each $color, $value in $colors {
&.#{$color} { background-color: $value }
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment