UNPKG

arcanumcube

Version:
734 lines (732 loc) 20.2 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/core.ts var core_exports = {}; __export(core_exports, { ArcanumCube: () => ArcanumCube, CUBE: () => CUBE, CUBE_ANGLES: () => CUBE_ANGLES, CUBE_SIZE: () => CUBE_SIZE, Cube: () => Cube, DOUBLE_TWIST_LIST: () => DOUBLE_TWIST_LIST, FACE: () => FACE, MatchGoalState: () => MatchGoalState, SIDE_MAX: () => SIDE_MAX, SIDE_MIDDLE: () => SIDE_MIDDLE, SIDE_MIN: () => SIDE_MIN, SINGLE_TWIST_LIST: () => SINGLE_TWIST_LIST, STICKER_COLOR: () => STICKER_COLOR, TWIST: () => TWIST, TWIST_LIST: () => TWIST_LIST, TWIST_RULE: () => TWIST_RULE, getArrayForTensor: () => getArrayForTensor, getCubeFromStickerIndex: () => getCubeFromStickerIndex, getCubePermutationGroup: () => getCubePermutationGroup, getNextStickerColors: () => getNextStickerColors, getRandomTwistList: () => getRandomTwistList, getStickerIndex: () => getStickerIndex, getStickerPermutationGroup: () => getStickerPermutationGroup }); module.exports = __toCommonJS(core_exports); var CUBE_SIZE = 3; var SIDE_MAX = CUBE_SIZE - 1; var SIDE_MIN = 0; var SIDE_MIDDLE = Math.floor(CUBE_SIZE / 2); var STICKER_COLOR = { UP: 0, FRONT: 1, RIGHT: 2, DOWN: 3, BACK: 4, LEFT: 5, PLAIN: 6 }; var CUBE = { AXIS: "axis", CENTER: "center", EDGE: "edge", CORNER: "corner", STICKER: "sticker" }; var CUBETYPE_LIST = [CUBE.AXIS, CUBE.CENTER, CUBE.EDGE, CUBE.CORNER]; var FACE_LIST = ["U", "F", "R", "D", "B", "L"]; var FACE = Object.assign( {}, ...FACE_LIST.map((k, i) => ({ [k]: i })) ); var TWIST = { U: "U", UR: "U'", F: "F", FR: "F'", R: "R", RR: "R'", D: "D", DR: "D'", B: "B", BR: "B'", L: "L", LR: "L'", M: "M", MR: "M'", E: "E", ER: "E'", S: "S", SR: "S'", U2: "U2", F2: "F2", R2: "R2", D2: "D2", B2: "B2", L2: "L2", M2: "M2", E2: "E2", S2: "S2" }; var SINGLE_TWIST_LIST = [ TWIST.U, TWIST.UR, TWIST.F, TWIST.FR, TWIST.R, TWIST.RR, TWIST.D, TWIST.DR, TWIST.B, TWIST.BR, TWIST.L, TWIST.LR, TWIST.M, TWIST.MR, TWIST.E, TWIST.ER, TWIST.S, TWIST.SR ]; var DOUBLE_TWIST_LIST = [ TWIST.U2, TWIST.F2, TWIST.R2, TWIST.D2, TWIST.B2, TWIST.L2, TWIST.M2, TWIST.E2, TWIST.S2 ]; var TWIST_LIST = [...SINGLE_TWIST_LIST, ...DOUBLE_TWIST_LIST]; var CUBE_ANGLES = [ [ // down 3x3 cubes [ [TWIST.B], // [0, 0, 90] [TWIST.B2], // [0, 0, 180] [TWIST.B2] // [0, 0, 180] ], [ [TWIST.UR, TWIST.SR], // [0, 90, 90] [TWIST.M2], // [180, 0, 0] [TWIST.U, TWIST.S] // [0, -90, -90] ], [ [TWIST.L2], // [180, 0, 0] [TWIST.M2], // [180, 0, 0] [TWIST.L, TWIST.UR, TWIST.F] // [90, 90, -90] ] ], [ // middle 3x3 cubes [ [TWIST.B], // [0, 0, 90] [TWIST.MR], // [-90, 0, 0] [TWIST.BR] // [0, 0, -90] ], [ [TWIST.SR], // [0, 0, 90] [], // [0, 0, 0] original axis [TWIST.S] // [0, 0, -90] ], [ [TWIST.M, TWIST.FR], // [90, 0, 90] [TWIST.M], // [90, 0, 0] [TWIST.M, TWIST.F] // [90, 0, -90] ] ], [ // up 3x3 cubes [ [], // [0, 0, 0] original corner [], // [0, 0, 0] original edge [TWIST.U] // [0, -90, 0] ], [ [TWIST.UR], // [0, 90, 0] [], // [0, 0, 0] original center [TWIST.U] // [0, -90, 0] ], [ [TWIST.UR], // [0, 90, 0] [TWIST.U2], // [0, 180, 0] [TWIST.U2] // [0, 180, 0] ] ] ]; var TWIST_RULE = { [TWIST.U]: { axis: [0, -1, 0], levels: [2], steps: 1 }, [TWIST.UR]: { axis: [0, 1, 0], levels: [2], steps: 1 }, [TWIST.F]: { axis: [0, 0, -1], levels: [2], steps: 1 }, [TWIST.FR]: { axis: [0, 0, 1], levels: [2], steps: 1 }, [TWIST.R]: { axis: [-1, 0, 0], levels: [2], steps: 1 }, [TWIST.RR]: { axis: [1, 0, 0], levels: [2], steps: 1 }, [TWIST.D]: { axis: [0, 1, 0], levels: [0], steps: 1 }, [TWIST.DR]: { axis: [0, -1, 0], levels: [0], steps: 1 }, [TWIST.B]: { axis: [0, 0, 1], levels: [0], steps: 1 }, [TWIST.BR]: { axis: [0, 0, -1], levels: [0], steps: 1 }, [TWIST.L]: { axis: [1, 0, 0], levels: [0], steps: 1 }, [TWIST.LR]: { axis: [-1, 0, 0], levels: [0], steps: 1 }, [TWIST.M]: { axis: [1, 0, 0], levels: [1], steps: 1 }, [TWIST.MR]: { axis: [-1, 0, 0], levels: [1], steps: 1 }, [TWIST.E]: { axis: [0, 1, 0], levels: [1], steps: 1 }, [TWIST.ER]: { axis: [0, -1, 0], levels: [1], steps: 1 }, [TWIST.S]: { axis: [0, 0, -1], levels: [1], steps: 1 }, [TWIST.SR]: { axis: [0, 0, 1], levels: [1], steps: 1 }, [TWIST.U2]: { axis: [0, -1, 0], levels: [2], steps: 2 }, [TWIST.F2]: { axis: [0, 0, -1], levels: [2], steps: 2 }, [TWIST.R2]: { axis: [-1, 0, 0], levels: [2], steps: 2 }, [TWIST.D2]: { axis: [0, 1, 0], levels: [0], steps: 2 }, [TWIST.B2]: { axis: [0, 0, 1], levels: [0], steps: 2 }, [TWIST.L2]: { axis: [1, 0, 0], levels: [0], steps: 2 }, [TWIST.M2]: { axis: [1, 0, 0], levels: [1], steps: 2 }, [TWIST.E2]: { axis: [0, 1, 0], levels: [1], steps: 2 }, [TWIST.S2]: { axis: [0, 0, -1], levels: [1], steps: 2 } }; function getStickerIndex(x, y, z, face) { let [px, py] = [0, 0]; if (face === FACE.U) { px = x; py = z; } else if (face === FACE.D) { px = x; py = SIDE_MAX - z; } else if (face === FACE.F) { px = x; py = SIDE_MAX - y; } else if (face === FACE.B) { px = SIDE_MAX - x; py = SIDE_MAX - y; } else if (face === FACE.R) { px = SIDE_MAX - z; py = SIDE_MAX - y; } else if (face === FACE.L) { px = z; py = SIDE_MAX - y; } return face * CUBE_SIZE * CUBE_SIZE + py * CUBE_SIZE + px; } function getCubeFromStickerIndex(index) { const face = Math.floor(index / (CUBE_SIZE * CUBE_SIZE)); const f = index - face * (CUBE_SIZE * CUBE_SIZE); const py = Math.floor(f / CUBE_SIZE); const px = f % CUBE_SIZE; let [x, y, z] = [0, 0, 0]; if (face === FACE.U) { x = px; z = py; } else if (face === FACE.D) { x = px; z = SIDE_MAX - py; } else if (face === FACE.F) { x = px; y = SIDE_MAX - py; } else if (face === FACE.B) { x = SIDE_MAX - px; y = SIDE_MAX - py; } else if (face === FACE.R) { z = SIDE_MAX - px; y = SIDE_MAX - py; } else if (face === FACE.L) { z = px; y = SIDE_MAX - py; } return [x, y, z, face]; } function getRandomTwistList(steps = 0) { const t = steps === 0 ? Math.floor(Math.random() * (30 - 15 + 1)) + 15 : steps; const len = SINGLE_TWIST_LIST.length; const isOffsetting = (a, b) => { return a !== b && (a + "'").substring(0, 2) === (b + "'").substring(0, 2); }; const list = []; let prev = ""; for (let i = 0; i < t; i++) { let s; while (isOffsetting(s = SINGLE_TWIST_LIST[Math.floor(Math.random() * len)], prev)) ; list.push(s); prev = s; } return list; } var SIDE_FACES = Object.freeze([ [1, 2, 4, 5], [2, 0, 5, 3], [0, 1, 3, 4], [2, 1, 5, 4], [0, 2, 3, 5], [1, 0, 4, 3] ]); function getInitialState(up, front) { const down = (up + 3) % 6; const side = SIDE_FACES[up]; const state = new Array(CUBE_SIZE * CUBE_SIZE).fill(up); for (let i = 0; i < 4; i++) { if (i == 2) { const downs = [...Array(CUBE_SIZE * CUBE_SIZE)].fill(down); state.push(...downs); } const sides = [...Array(CUBE_SIZE * CUBE_SIZE)].fill(side[(front + i) % 4]); state.push(...sides); } return state; } function isSameArrays(arr1, arr2) { if (arr1.length !== arr2.length) return false; return !arr1.some((v, i) => v !== arr2[i]); } var GOAL_STATE_LIST = []; for (let up = 0; up < 6; up++) { for (let front = 0; front < 4; front++) { GOAL_STATE_LIST.push(getInitialState(up, front)); } } Object.freeze(GOAL_STATE_LIST); function MatchGoalState(state) { return GOAL_STATE_LIST.some((s) => isSameArrays(state, s)); } var Cube = class { type; position; _stickers; _initialPosition; constructor(x, y, z) { this._stickers = []; this._initialPosition = { x, y, z }; this.position = { x, y, z }; let faces = 0; if (x === SIDE_MAX) faces++; if (x === SIDE_MIN) faces++; if (y === SIDE_MAX) faces++; if (y === SIDE_MIN) faces++; if (z === SIDE_MAX) faces++; if (z === SIDE_MIN) faces++; this.type = CUBETYPE_LIST[faces]; } getStickers() { return this._stickers; } init() { const { x, y, z } = this.position; const angles = CUBE_ANGLES[y][z][x]; this._stickers = []; if (this.type === CUBE.CENTER) { this._stickers = [{ face: FACE.U, color: STICKER_COLOR.PLAIN }]; } else if (this.type === CUBE.EDGE) { this._stickers = [ { face: FACE.U, color: STICKER_COLOR.PLAIN }, { face: FACE.B, color: STICKER_COLOR.PLAIN } ]; } else if (this.type === CUBE.CORNER) { this._stickers = [ { face: FACE.U, color: STICKER_COLOR.PLAIN }, { face: FACE.B, color: STICKER_COLOR.PLAIN }, { face: FACE.L, color: STICKER_COLOR.PLAIN } ]; } this.rotateStickerFace(angles, false, true); } reset() { const Faces = [FACE.U, FACE.B, FACE.L]; const { x, y, z } = this._initialPosition; this.position = { x, y, z }; const angles = CUBE_ANGLES[y][z][x]; for (let i = 0; i < this._stickers.length; i++) { this._stickers[i].face = Faces[i]; this._stickers[i].color = STICKER_COLOR.PLAIN; } this.rotateStickerFace(angles, false, true); } rotateStickerFace(twists, reverse = false, init = false) { const rotateMap = [ /* x */ [FACE.U, FACE.F, FACE.D, FACE.B], /* y */ [FACE.F, FACE.R, FACE.B, FACE.L], /* z */ [FACE.U, FACE.L, FACE.D, FACE.R] ]; function getNext(face, axis_index, twist) { const { axis, steps } = TWIST_RULE[twist]; const map = rotateMap[axis_index]; const i = map.indexOf(face); if (i < 0) return face; const angle = axis[axis_index]; const i2 = (i + 4 + (reverse ? -1 : 1) * angle * steps) % 4; return map[i2]; } for (const sticker of this._stickers) { for (const r of twists) { sticker.face = getNext(sticker.face, 0, r); sticker.face = getNext(sticker.face, 1, r); sticker.face = getNext(sticker.face, 2, r); } if (init && sticker.color === STICKER_COLOR.PLAIN) { sticker.color = sticker.face; } } } }; var ArcanumCube = class { /** output debug information to console */ debug; /** history of twisting */ _history; /** cube objects matrix */ _matrix; constructor(options) { this.debug = false; this._history = []; this._matrix = []; if (options) { if (options.debug != null) { this.debug = options.debug; } } } init() { this._matrix = []; const yarray = []; for (let y = SIDE_MIN; y <= SIDE_MAX; y++) { const zarray = []; for (let z = SIDE_MIN; z <= SIDE_MAX; z++) { const xarray = []; for (let x = SIDE_MIN; x <= SIDE_MAX; x++) { const cube = new Cube(x, y, z); cube.init(); xarray.push(cube); } zarray.push(xarray); } yarray.push(zarray); } this._matrix = yarray; } reset() { const list = []; for (let y = SIDE_MIN; y <= SIDE_MAX; y++) { for (let z = SIDE_MIN; z <= SIDE_MAX; z++) { for (let x = SIDE_MIN; x <= SIDE_MAX; x++) { const cube = this._matrix[y][z][x]; cube.reset(); list.push(cube); } } } list.forEach((cube) => { const n = cube.position; this._matrix[n.y][n.z][n.x] = cube; }); this._history = []; } // twist randomly several steps scramble(steps) { const list = getRandomTwistList(steps); if (this.debug) console.log("Scramble: " + list.join(", ")); this.twist(list, false); } undo(steps = 1) { const list = this.getUndoList(steps); this.twist(list, true); } isSolved() { return MatchGoalState(this.getStickerColors()); } getHistory() { return this._history; } getUndoList(steps = 1) { let t = steps; if (t < 0) t = 0; if (t > this._history.length) t = this._history.length; return this._history.slice(-t).reverse(); } twist(twist, reverse = false) { if (Array.isArray(twist)) { if (twist.length == 0) return; for (const c of twist) { this._twist(c, reverse); } } else { this._twist(twist, reverse); } } _twist(twist, reverse) { this.rotateMatrix(twist, reverse); if (this.debug) { this.dumpStickers(); } if (reverse) { this._history.pop(); } else { this._history.push(twist); } if (this.debug) console.log(this._history.join(" ")); } rotateMatrix(twist, reverse = false) { const rule = TWIST_RULE[twist]; const axis = rule.axis; const map = new Array(3); for (const l of rule.levels) { let i = axis.indexOf(-1); if (i === -1) i = axis.indexOf(1); const s = (reverse ? -1 : 1) * axis[i]; map[i] = Array(8).fill(l); map[(i + s + 3) % 3] = [0, 1, 2, 2, 2, 1, 0, 0]; map[(i - s + 3) % 3] = [0, 0, 0, 1, 2, 2, 2, 1]; const [x, y, z] = map; const tmp = []; for (let i2 = 0; i2 < 8; i2++) { const cube = this._matrix[y[i2]][z[i2]][x[i2]]; cube.rotateStickerFace([twist], reverse); tmp.push(cube); } for (let i2 = 0; i2 < 8; i2++) { const i22 = (i2 + rule.steps * 2) % 8; tmp[i2].position = { x: x[i22], y: y[i22], z: z[i22] }; this._matrix[y[i22]][z[i22]][x[i22]] = tmp[i2]; } } } getStickerColors() { const list = new Array(CUBE_SIZE * CUBE_SIZE * 6); for (let y = SIDE_MIN; y <= SIDE_MAX; y++) { for (let z = SIDE_MIN; z <= SIDE_MAX; z++) { for (let x = SIDE_MIN; x <= SIDE_MAX; x++) { const cube = this._matrix[y][z][x]; const stickers = cube.getStickers(); for (const sticker of stickers) { const index = getStickerIndex(x, y, z, sticker.face); list[index] = sticker.color; } } } } return list; } dumpStickers() { const list = this.getStickerColors(); const STYLE = "background-color:black; padding:1px 1px;"; const Col2Str = { [STICKER_COLOR.UP]: "color:white; " + STYLE, [STICKER_COLOR.FRONT]: "color:#00d800; " + STYLE, [STICKER_COLOR.RIGHT]: "color:#d80000; " + STYLE, [STICKER_COLOR.DOWN]: "color:yellow; " + STYLE, [STICKER_COLOR.BACK]: "color:#0000d8; " + STYLE, [STICKER_COLOR.LEFT]: "color:#ff8000; " + STYLE }; const RESET = "background-color:none; padding:0px 0px;"; const result = []; const attrs = []; const getLine = (faces) => { const line = []; for (let py = 0; py < 3; py++) { for (const f of faces) { if (f === -1) { line.push(" "); } else { line.push("%c\u25A0 %c\u25A0 %c\u25A0%c "); const index = f * CUBE_SIZE * CUBE_SIZE + py * CUBE_SIZE; attrs.push(Col2Str[list[index + 0]]); attrs.push(Col2Str[list[index + 1]]); attrs.push(Col2Str[list[index + 2]]); attrs.push(RESET); } } line.push("\n"); } line.push("\n"); result.push(line.join("")); }; getLine([-1, FACE.U]); getLine([FACE.L, FACE.F, FACE.R, FACE.B]); getLine([-1, FACE.D]); console.log(result.join("\n"), ...attrs); } }; var STICKER_PERMUTATION_GROUP_MAP = makeStickerPermutationGroupMap(); function makeStickerPermutationGroupMap() { const result = {}; TWIST_LIST.forEach((twist) => { const tmp = getCubePermutationGroup(twist).stickers; result[twist] = [tmp.before, tmp.after]; }); return result; } function getNextStickerColors(stickers, twist) { const result = [...stickers]; const [bef, aft] = STICKER_PERMUTATION_GROUP_MAP[twist]; for (let i = 0; i < bef.length; i++) { result[aft[i]] = stickers[bef[i]]; } return result; } function getArrayForTensor(stickers) { const array = [...Array(3)].map( () => [...Array(3)].map(() => Array(6 * 6).fill(0)) ); for (let i = 0; i < stickers.length; i++) { const face = Math.floor(i / (CUBE_SIZE * CUBE_SIZE)); const c = i - face * (CUBE_SIZE * CUBE_SIZE); const row = Math.floor(c / CUBE_SIZE); const column = c - row * CUBE_SIZE; const color = stickers[i]; array[row][column][color * 6 + face] = 1; } return array; } function getCubePermutationGroup(twist, reverse = false) { const rule = TWIST_RULE[twist]; const axis = rule.axis; const result = { before: [], after: [], stickers: { before: [], after: [] } }; const map = new Array(3); for (const l of rule.levels) { let i = axis.indexOf(-1); if (i === -1) i = axis.indexOf(1); const s = (reverse ? -1 : 1) * axis[i]; map[i] = Array(8).fill(l); map[(i + s + 3) % 3] = [0, 1, 2, 2, 2, 1, 0, 0]; map[(i - s + 3) % 3] = [0, 0, 0, 1, 2, 2, 2, 1]; const [x, y, z] = map; for (let i2 = 0; i2 < 8; i2++) { const i22 = (i2 + rule.steps * 2) % 8; result.before.push([x[i2], y[i2], z[i2]]); result.after.push([x[i22], y[i22], z[i22]]); const list = getStickerPermutationGroup( [x[i2], y[i2], z[i2]], [x[i22], y[i22], z[i22]], [twist], reverse ); result.stickers.before.push(...list.before); result.stickers.after.push(...list.after); } } return result; } function getStickerPermutationGroup(position, position2, twists, reverse = false) { const rotateMap = [ /* x */ [FACE.U, FACE.F, FACE.D, FACE.B], /* y */ [FACE.F, FACE.R, FACE.B, FACE.L], /* z */ [FACE.U, FACE.L, FACE.D, FACE.R] ]; function getNext(face, axis_index, twist) { const { axis, steps } = TWIST_RULE[twist]; const map = rotateMap[axis_index]; const i = map.indexOf(face); if (i < 0) return face; const angle = axis[axis_index]; const i2 = (i + 4 + (reverse ? -1 : 1) * angle * steps) % 4; return map[i2]; } const result = { before: [], after: [] }; const [x, y, z] = position; const [x2, y2, z2] = position2; const faces = []; if (x === SIDE_MAX) faces.push(FACE.R); if (x === SIDE_MIN) faces.push(FACE.L); if (y === SIDE_MAX) faces.push(FACE.U); if (y === SIDE_MIN) faces.push(FACE.D); if (z === SIDE_MAX) faces.push(FACE.F); if (z === SIDE_MIN) faces.push(FACE.B); for (const face of faces) { let f = face; result.before.push(getStickerIndex(x, y, z, f)); for (const t of twists) { f = getNext(f, 0, t); f = getNext(f, 1, t); f = getNext(f, 2, t); } result.after.push(getStickerIndex(x2, y2, z2, f)); } return result; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ArcanumCube, CUBE, CUBE_ANGLES, CUBE_SIZE, Cube, DOUBLE_TWIST_LIST, FACE, MatchGoalState, SIDE_MAX, SIDE_MIDDLE, SIDE_MIN, SINGLE_TWIST_LIST, STICKER_COLOR, TWIST, TWIST_LIST, TWIST_RULE, getArrayForTensor, getCubeFromStickerIndex, getCubePermutationGroup, getNextStickerColors, getRandomTwistList, getStickerIndex, getStickerPermutationGroup });