cube-state-engine
Version:
An efficient representation in memory for tracking the Rubik's cube state on each movement.
358 lines (317 loc) • 9.6 kB
TypeScript
class CubeEngine {
MOVES = [];
// States object for the rotation
STATES = {
UPPER: [
// (White)
[COLOR.W[0], COLOR.W[1], COLOR.W[2]],
[COLOR.W[3], COLOR.W[4], COLOR.W[5]],
[COLOR.W[6], COLOR.W[7], COLOR.W[8]],
],
LEFT: [
// (Orange)
[COLOR.O[0], COLOR.O[1], COLOR.O[2]],
[COLOR.O[3], COLOR.O[4], COLOR.O[5]],
[COLOR.O[6], COLOR.O[7], COLOR.O[8]],
],
FRONT: [
// (Green)
[COLOR.G[0], COLOR.G[1], COLOR.G[2]],
[COLOR.G[3], COLOR.G[4], COLOR.G[5]],
[COLOR.G[6], COLOR.G[7], COLOR.G[8]],
],
RIGHT: [
// (Red)
[COLOR.R[0], COLOR.R[1], COLOR.R[2]],
[COLOR.R[3], COLOR.R[4], COLOR.R[5]],
[COLOR.R[6], COLOR.R[7], COLOR.R[8]],
],
BACK: [
// (Blue)
[COLOR.B[0], COLOR.B[1], COLOR.B[2]],
[COLOR.B[3], COLOR.B[4], COLOR.B[5]],
[COLOR.B[6], COLOR.B[7], COLOR.B[8]],
],
DOWN: [
// (Yellow)
[COLOR.Y[0], COLOR.Y[1], COLOR.Y[2]],
[COLOR.Y[3], COLOR.Y[4], COLOR.Y[5]],
[COLOR.Y[6], COLOR.Y[7], COLOR.Y[8]],
],
};
/**
* Rotates the (UPPER) layer clockwise or counterclockwise.
*/
rotateU(clockwise = true) {
if (clockwise) {
this.#rotateU(true);
this.MOVES.push("U");
} else {
this.#rotateU(false);
this.MOVES.push("U'");
}
}
#rotateU(clockwise = true) {
if (clockwise) {
this.STATES.UPPER = this.#switchMatrix(this.STATES.UPPER, true);
const tempFront = [...this.STATES.FRONT[0]];
const tempRight = [...this.STATES.RIGHT[0]];
const tempLeft = [...this.STATES.LEFT[0]];
const tempBack = [...this.STATES.BACK[0]];
this.STATES.FRONT[0] = [...tempRight];
this.STATES.LEFT[0] = [...tempFront];
this.STATES.BACK[0] = [...tempLeft];
this.STATES.RIGHT[0] = [...tempBack];
} else {
this.STATES.UPPER = this.#switchMatrix(this.STATES.UPPER, false);
const tempFront = [...this.STATES.FRONT[0]];
const tempRight = [...this.STATES.RIGHT[0]];
const tempLeft = [...this.STATES.LEFT[0]];
const tempBack = [...this.STATES.BACK[0]];
this.STATES.FRONT[0] = [...tempLeft];
this.STATES.LEFT[0] = [...tempBack];
this.STATES.BACK[0] = [...tempRight];
this.STATES.RIGHT[0] = [...tempFront];
}
}
/**
* Rotates the (FRONT) layer clockwise or counterclockwise.
*/
rotateF(clockwise = true) {
if (clockwise) {
this.#rotateF(true);
this.MOVES.push("F");
} else {
this.#rotateF(false);
this.MOVES.push("F'");
}
}
#rotateF(clockwise = true) {
if (clockwise) {
this.#rotateX(true);
this.#rotateU(true);
this.#rotateX(false);
} else {
this.#rotateX(true);
this.#rotateU(false);
this.#rotateX(false);
}
}
/**
* Rotates the (RIGHT) layer clockwise or counterclockwise.
*/
rotateR(clockwise = true) {
if (clockwise) {
this.#rotateR(true);
this.MOVES.push("R");
} else {
this.#rotateR(false);
this.MOVES.push("R'");
}
}
#rotateR(clockwise = true) {
if (clockwise) {
this.#rotateY(true);
this.#rotateX(true);
this.#rotateU(true);
this.#rotateX(false);
this.#rotateY(false);
} else {
this.#rotateY(true);
this.#rotateX(true);
this.#rotateU(false);
this.#rotateX(false);
this.#rotateY(false);
}
}
/**
* Rotates the (LEFT) layer clockwise or counterclockwise.
*/
rotateL(clockwise = true) {
if (clockwise) {
this.#rotateL(true);
this.MOVES.push("L");
} else {
this.#rotateL(false);
this.MOVES.push("L'");
}
}
#rotateL(clockwise = true) {
if (clockwise) {
this.#rotateY(false);
this.#rotateX(true);
this.#rotateU(true);
this.#rotateX(false);
this.#rotateY(true);
} else {
this.#rotateY(false);
this.#rotateX(true);
this.#rotateU(false);
this.#rotateX(false);
this.#rotateY(true);
}
}
/**
* Rotates the (DOWN) layer clockwise or counterclockwise.
*/
rotateD(clockwise = true) {
if (clockwise) {
this.#rotateD(true);
this.MOVES.push("D");
} else {
this.#rotateD(false);
this.MOVES.push("D'");
}
}
#rotateD(clockwise = true) {
if (clockwise) {
this.#rotateX(true);
this.#rotateF(true);
this.#rotateX(false);
} else {
this.#rotateX(true);
this.#rotateF(false);
this.#rotateX(false);
}
}
/**
* Rotates the (x) axis clockwise or counterclockwise.
*/
rotateX(clockwise = true) {
if (clockwise) {
this.#rotateX(true);
this.MOVES.push("x");
} else {
this.#rotateX(false);
this.MOVES.push("x'");
}
}
#rotateX(clockwise = true) {
const tempFront = structuredClone(this.STATES.FRONT);
const tempDown = structuredClone(this.STATES.DOWN);
const tempUpper = structuredClone(this.STATES.UPPER);
const tempBack = structuredClone(this.STATES.BACK);
const tempLeft = structuredClone(this.STATES.LEFT);
const tempRight = structuredClone(this.STATES.RIGHT);
if (clockwise) {
// Balance the rotation
this.STATES.LEFT = this.#switchMatrix(tempLeft, false);
this.STATES.RIGHT = this.#switchMatrix(tempRight, true);
// Rotate mid X axis
this.STATES.FRONT = [...tempDown];
this.STATES.UPPER = [...tempFront];
// Special permutation (BACK view elements)
this.STATES.BACK = this.#specialFlip(tempUpper);
this.STATES.DOWN = this.#specialFlip(tempBack);
} else {
this.STATES.LEFT = this.#switchMatrix(tempLeft, true);
this.STATES.RIGHT = this.#switchMatrix(tempRight, false);
this.STATES.FRONT = [...tempUpper];
this.STATES.DOWN = [...tempFront];
this.STATES.BACK = this.#specialFlip(tempDown);
this.STATES.UPPER = this.#specialFlip(tempBack);
}
}
/**
* Rotates the (y) axis clockwise or counterclockwise.
*/
rotateY(clockwise = true) {
if (clockwise) {
this.#rotateY(true);
this.MOVES.push("y");
} else {
this.#rotateY(false);
this.MOVES.push("y'");
}
}
#rotateY(clockwise = true) {
const tempFront = structuredClone(this.STATES.FRONT);
const tempRight = structuredClone(this.STATES.RIGHT);
const tempBack = structuredClone(this.STATES.BACK);
const tempLeft = structuredClone(this.STATES.LEFT);
if (clockwise) {
this.STATES.UPPER = this.#switchMatrix(this.STATES.UPPER, true);
this.STATES.DOWN = this.#switchMatrix(this.STATES.DOWN, false);
this.STATES.FRONT = [...tempRight];
this.STATES.RIGHT = [...tempBack];
this.STATES.LEFT = [...tempFront];
this.STATES.BACK = [...tempLeft];
} else {
this.STATES.UPPER = this.#switchMatrix(this.STATES.UPPER, false);
this.STATES.DOWN = this.#switchMatrix(this.STATES.DOWN, true);
this.STATES.FRONT = [...tempLeft];
this.STATES.RIGHT = [...tempFront];
this.STATES.LEFT = [...tempBack];
this.STATES.BACK = [...tempRight];
}
}
/**
* Rotate the entire face in the direction set
*/
#switchMatrix(matrix, clockwise = true) {
const clone = structuredClone(matrix);
const tempMatrix = [...clone[0], ...clone[1], ...clone[2]];
if (clockwise) {
return [
[tempMatrix[6], tempMatrix[3], tempMatrix[0]],
[tempMatrix[7], tempMatrix[4], tempMatrix[1]],
[tempMatrix[8], tempMatrix[5], tempMatrix[2]],
];
} else {
return [
[tempMatrix[2], tempMatrix[5], tempMatrix[8]],
[tempMatrix[1], tempMatrix[4], tempMatrix[7]],
[tempMatrix[0], tempMatrix[3], tempMatrix[6]],
];
}
}
#specialFlip(matrix) {
return structuredClone(matrix)
.reverse()
.map((row) => [...row].reverse());
}
/**
* Logs the current state of the cube.
*/
state() {
return {
...this.STATES,
};
}
/**
* Indicates if the cube is solve or not in all layers.
*/
isSolved() {
const temp = {
...this.STATES,
};
const layersSolved = Object.keys(temp).map((layer) => {
const mixedMatrix = [
...temp[layer][0],
...temp[layer][1],
...temp[layer][2],
];
const centerColor = mixedMatrix[4];
return mixedMatrix.every((currentColor) => currentColor === centerColor);
});
return layersSolved.every((isLayerSolved) => isLayerSolved);
}
/**
* Returns the history of all movements made.
*
* @param {boolean} asString - If true, returns the history as a string; otherwise, returns it as an array.
* @returns {string|array} The history of movements as an array or string.
*/
getMoves(asString = true) {
return asString ? this.MOVES.join(" ") : this.MOVES;
}
}
const COLOR = {
W: ["W", "W", "W", "W", "W", "W", "W", "W", "W"],
G: ["G", "G", "G", "G", "G", "G", "G", "G", "G"],
R: ["R", "R", "R", "R", "R", "R", "R", "R", "R"],
B: ["B", "B", "B", "B", "B", "B", "B", "B", "B"],
O: ["O", "O", "O", "O", "O", "O", "O", "O", "O"],
Y: ["Y", "Y", "Y", "Y", "Y", "Y", "Y", "Y", "Y"],
};
export { COLOR, CubeEngine };