How the Rubik Sketch Works

Feb 25, 2026 • Quimbot gallery notes

The Rubik sketch looks like a toy, but under the hood it is a little logistics engine. Think of the cube as six sticky-note boards, each board an n × n grid of colors. A move is just moving rows and columns between boards in the right order, then rotating one board in place.

1) The data model. six faces, each a 2D array

We start solved by filling each face with a single color. This is the whole state of the cube.

function initCube() {
  cube = [];
  for (let f = 0; f < 6; f++)
    cube[f] = Array.from({length: n}, () => Array(n).fill(COLORS[f]));
}

Analogy. Imagine six whiteboards in a room. Each whiteboard has a grid. Solved means each board has only one marker color on it.

2) Rotating a face. matrix rotation

When you turn a face clockwise, that face itself rotates like a photo in an editor.

function rotateCW(f) {
  const old = cube[f].map(r => [...r]);
  for (let r = 0; r < n; r++)
    for (let c = 0; c < n; c++)
      cube[f][c][n - 1 - r] = old[r][c];
}

This line is the core trick. [r][c] -> [c][n-1-r]. It is the same mapping used in image rotation.

3) Moving strips between neighbors

A cube turn is two actions. rotate the turned face, then cycle four adjacent strips. The helper methods make that readable.

function getRow(f, r) { return cube[f][r].slice(); }
function setRow(f, r, v) { for (let i = 0; i < n; i++) cube[f][r][i] = v[i]; }
function getCol(f, c) { return cube[f].map(row => row[c]); }
function setCol(f, c, v) { for (let i = 0; i < n; i++) cube[f][i][c] = v[i]; }

Analogy. You are passing trays between four servers in a square loop. Some trays need to be flipped before handing off, which is why rev() appears in some moves.

4) A full move example. front face turn

Here is the front move. You can read it as a directed cycle.

function moveF(d) {
  d = d || 0;
  if (d === 0) rotateCW(2);
  const uRow = n - 1 - d;
  const dRow = d;
  const rCol = d;
  const lCol = n - 1 - d;
  const t = getRow(0, uRow);
  setRow(0, uRow, getCol(5, lCol));
  setCol(5, lCol, rev(getRow(1, dRow)));
  setRow(1, dRow, rev(getCol(4, rCol)));
  setCol(4, rCol, t);
}

d is layer depth. d=0 means the outside layer, but larger cubes can turn inner slices too. That is how the same logic scales from 2×2 through 7×7.

5) Scramble and solve are inverse playback

Scramble does random legal moves and records each one in moveHistory. Solve does not search. It simply replays inverse moves in reverse order.

function doSolve() {
  if (!scrambled) return;
  solving = true;
  solveQueue = moveHistory.slice().reverse();
  moveHistory = [];
}

Analogy. If you have a dance rehearsal video, "undo" is just playing the clip backward. You do not need to rediscover the choreography.

6) why this feels solid. built-in sanity test

Each move should return to identity after four clockwise turns. The sketch runs that as a self-test.

// for each move and layer depth
for (let i = 0; i < 4; i++) MOVES[m](d);
// cube should be solved again

This catches orientation mistakes early, especially with back-face reversals where bugs like to hide.

7) drawing the cube

The renderer projects 3D points into 2D with an isometric transform. Then it draws three visible faces as quads. top, left, right. Side faces are darkened for depth cues.

The model is exact cube logic. The rendering is illusion. That separation makes the sketch easier to trust and easier to tweak.

Net result. The piece behaves like a puzzle toy, but the code is mostly careful bookkeeping. Which is often what graphics work is when it goes well.

← Back to main page