UNPKG

rubiks-cube-solver

Version:

Outputs a solution using the Fridrich Method for a given cube state.

1,753 lines (1,441 loc) 131 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') exports["rubiksCubeSolver"] = factory(); else root["rubiksCubeSolver"] = factory(); })(this, function() { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // identity function for calling harmony imports with the correct context /******/ __webpack_require__.i = function(value) { return value; }; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 33); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_gl_vec3_cross__ = __webpack_require__(12); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_gl_vec3_cross___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_gl_vec3_cross__); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__models_Face__ = __webpack_require__(14); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__models_Vector__ = __webpack_require__(4); // maps each face with the notation for their middle moves const _middlesMatchingFace = { f: 's', r: 'mprime', u: 'eprime', d: 'e', l: 'm', b: 'sprime' }; /** * @param {string} move - The notation of a move, e.g. rPrime. * @return {string} */ const getFaceOfMove = (move) => { if (typeof move !== 'string') { throw new TypeError('move must be a string'); } let faceLetter = move[0].toLowerCase(); if (faceLetter === 'f') return 'front'; if (faceLetter === 'r') return 'right'; if (faceLetter === 'u') return 'up'; if (faceLetter === 'd') return 'down'; if (faceLetter === 'l') return 'left'; if (faceLetter === 'b') return 'back'; }; /* harmony export (immutable) */ __webpack_exports__["a"] = getFaceOfMove; /** * Almost useless. Almost. * @param {string} face - The string identifying a face. * @return {string} */ const getMoveOfFace = (face) => { if (typeof face !== 'string') { throw new TypeError('face must be a string'); } face = face.toLowerCase(); if (!['front', 'right', 'up', 'down', 'left', 'back'].includes(face)) { throw new Error(`${face} is not valid face`); } return face[0]; }; /* harmony export (immutable) */ __webpack_exports__["f"] = getMoveOfFace; const getMiddleMatchingFace = (face) => { face = face.toLowerCase()[0]; return _middlesMatchingFace[face]; }; /* harmony export (immutable) */ __webpack_exports__["g"] = getMiddleMatchingFace; const getFaceMatchingMiddle = (middle) => { middle = middle.toLowerCase(); for (let face of Object.keys(_middlesMatchingFace)) { let testMiddle = _middlesMatchingFace[face]; if (middle === testMiddle) { return face; } } }; /* unused harmony export getFaceMatchingMiddle */ /** * @param {string|array} notations - The move notation. * @param {object} options - Move options. * @prop {boolean} options.upperCase - Turn all moves to upper case (i.e. no "double" moves). * * @return {string|array} -- whichever was initially given. */ const transformNotations = (notations, options = {}) => { let normalized = normalizeNotations(notations); if (options.upperCase) { normalized = normalized.map(n => n[0].toUpperCase() + n.slice(1)); } if (options.orientation) { normalized = orientMoves(normalized, options.orientation); } if (options.reverse) { normalized = _reverseNotations(normalized); } return typeof notations === 'string' ? normalized.join(' ') : normalized; }; /* harmony export (immutable) */ __webpack_exports__["d"] = transformNotations; /** * @param {array|string} notations - The notations to noramlize. * @return {array} */ const normalizeNotations = (notations) => { if (typeof notations === 'string') { notations = notations.split(' '); } notations = notations.filter(notation => notation !== ''); return notations.map(notation => { let isPrime = notation.toLowerCase().includes('prime'); let isDouble = notation.includes('2'); notation = notation[0]; if (isDouble) notation = notation[0] + '2'; else if (isPrime) notation = notation + 'prime'; return notation; }); }; /* harmony export (immutable) */ __webpack_exports__["h"] = normalizeNotations; /** * Finds the direction from an origin face to a target face. The origin face * will be oriented so that it becomes FRONT. An orientation object must be * provided that specifies any of these faces (exclusively): TOP, RIGHT, DOWN, * LEFT. * If FRONT or BACK is provided along with one of those faces, it will be * ignored. If FRONT or BACK is the only face provided, the orientation is * ambiguous and an error will be thrown. * * Example: * getDirectionFromFaces('back', 'up', { down: 'right' }) * Step 1) orient the BACK face so that it becomes FRONT. * Step 2) orient the DOWN face so that it becomes RIGHT. * Step 3) Find the direction from BACK (now FRONT) to UP (now LEFT). * Step 4) Returns 'left'. * * @param {string} origin - The origin face. * @param {string} target - The target face. * @param {object} orientation - The object that specifies the cube orientation. * @return {string|number} */ const getDirectionFromFaces = (origin, target, orientation) => { orientation = _toLowerCase(orientation); orientation = _prepOrientationForDirection(orientation, origin); let fromFace = new __WEBPACK_IMPORTED_MODULE_1__models_Face__["a" /* Face */](origin); let toFace = new __WEBPACK_IMPORTED_MODULE_1__models_Face__["a" /* Face */](target); let rotations = _getRotationsForOrientation(orientation); _rotateFacesByRotations([fromFace, toFace], rotations); let axis = new __WEBPACK_IMPORTED_MODULE_2__models_Vector__["a" /* Vector */](__WEBPACK_IMPORTED_MODULE_0_gl_vec3_cross___default()([], fromFace.normal(), toFace.normal())).getAxis(); let direction = __WEBPACK_IMPORTED_MODULE_2__models_Vector__["a" /* Vector */].getAngle(fromFace.normal(), toFace.normal()); if (axis === 'x' && direction > 0) return 'down'; if (axis === 'x' && direction < 0) return 'up'; if (axis === 'y' && direction > 0) return 'right'; if (axis === 'y' && direction < 0) return 'left'; if (direction === 0) { return 'front'; } else if (direction === Math.PI) { return 'back'; } }; /* harmony export (immutable) */ __webpack_exports__["c"] = getDirectionFromFaces; /** * See `getDirectionFromFaces`. Almost identical, but instead of finding a * direction from an origin face and target face, this finds a target face from * an origin face and direction. * @param {string} origin - The origin face. * @param {string} direction - The direction. * @param {object} orientation - The orientation object. * @return {string} */ const getFaceFromDirection = (origin, direction, orientation) => { orientation = _toLowerCase(orientation); orientation = _prepOrientationForDirection(orientation, origin); let fromFace = new __WEBPACK_IMPORTED_MODULE_1__models_Face__["a" /* Face */](origin); let rotations = _getRotationsForOrientation(orientation); _rotateFacesByRotations([fromFace], rotations); let directionFace = new __WEBPACK_IMPORTED_MODULE_1__models_Face__["a" /* Face */](direction); let { axis, angle } = __WEBPACK_IMPORTED_MODULE_2__models_Vector__["a" /* Vector */].getRotationFromNormals(fromFace.normal(), directionFace.normal()); fromFace.rotate(axis, angle); // at this point fromFace is now the target face, but we still need to revert // the orientation to return the correct string let reversedRotations = rotations.map(rotation => __WEBPACK_IMPORTED_MODULE_2__models_Vector__["a" /* Vector */].reverseRotation(rotation)).reverse(); _rotateFacesByRotations([fromFace], reversedRotations); return fromFace.toString(); }; /* harmony export (immutable) */ __webpack_exports__["e"] = getFaceFromDirection; /** * Finds a move that rotates the given face around its normal, by the angle * described by normal1 -> normal2. * @param {string} face - The face to rotate. * @param {string} from - The origin face. * @param {string} to - The target face. * @return {string} */ const getRotationFromTo = (face, from, to) => { const rotationFace = new __WEBPACK_IMPORTED_MODULE_1__models_Face__["a" /* Face */](face); const fromFace = new __WEBPACK_IMPORTED_MODULE_1__models_Face__["a" /* Face */](from); const toFace = new __WEBPACK_IMPORTED_MODULE_1__models_Face__["a" /* Face */](to); let rotationAxis = rotationFace.vector.getAxis(); let [fromAxis, toAxis] = [fromFace.vector.getAxis(), toFace.vector.getAxis()]; if ([fromAxis.toLowerCase(), toAxis.toLowerCase()].includes(rotationAxis.toLowerCase())) { throw new Error(`moving ${rotationFace} from ${fromFace} to ${toFace} is not possible.`); } let move = getMoveOfFace(face).toUpperCase(); let angle = __WEBPACK_IMPORTED_MODULE_2__models_Vector__["a" /* Vector */].getAngle(fromFace.normal(), toFace.normal()); if (rotationFace.vector.getMagnitude() < 0) { angle *= -1; } if (angle === 0) { return ''; } else if (Math.abs(angle) === Math.PI) { return `${move} ${move}`; } else if (angle < 0) { return `${move}`; } else if (angle > 0) { return `${move}Prime`; } }; /* harmony export (immutable) */ __webpack_exports__["b"] = getRotationFromTo; /** * Returns an array of transformed notations so that if done when the cube's * orientation is default (FRONT face is FRONT, RIGHT face is RIGHT, etc.), the * moves will have the same effect as performing the given notations on a cube * oriented by the specified orientation. * * Examples: * orientMoves(['R', 'U'], { front: 'front', up: 'up' }) === ['R', 'U'] * orientMoves(['R', 'U'], { front: 'front', down: 'right' }) === ['U', 'L'] * orientMoves(['R', 'U', 'LPrime', 'D'], { up: 'back', right: 'down' }) === ['D', 'B', 'UPrime', 'F'] * * @param {array} notations - An array of notation strings. * @param {object} orientation - The orientation object. */ const orientMoves = (notations, orientation) => { orientation = _toLowerCase(orientation); let rotations = _getRotationsForOrientation(orientation); rotations.reverse().map(rotation => __WEBPACK_IMPORTED_MODULE_2__models_Vector__["a" /* Vector */].reverseRotation(rotation)); return notations.map(notation => { let isPrime = notation.toLowerCase().includes('prime'); let isDouble = notation.includes('2'); let isWithMiddle = notation[0] === notation[0].toLowerCase(); let isMiddle = ['m', 'e', 's'].includes(notation[0].toLowerCase()); if (isDouble) { notation = notation.replace('2', ''); } let face; if (isMiddle) { let faceStr = getFaceOfMove(getFaceMatchingMiddle(notation)); face = new __WEBPACK_IMPORTED_MODULE_1__models_Face__["a" /* Face */](faceStr); } else { let faceStr = getFaceOfMove(notation[0]); face = new __WEBPACK_IMPORTED_MODULE_1__models_Face__["a" /* Face */](faceStr); } _rotateFacesByRotations([face], rotations); let newNotation; // this will always be lower case if (isMiddle) { newNotation = getMiddleMatchingFace(face.toString()); } else { newNotation = face.toString()[0]; } if (!isWithMiddle) newNotation = newNotation.toUpperCase(); if (isDouble) newNotation = newNotation + '2'; if (isPrime && !isMiddle) newNotation += 'prime'; return newNotation; }); }; /* unused harmony export orientMoves */ //----------------- // Helper functions //----------------- /** * Returns an object with all keys and values lowercased. Assumes all keys and * values are strings. * @param {object} object - The object to map. */ function _toLowerCase(object) { let ret = {}; Object.keys(object).forEach(key => { ret[key.toLowerCase()] = object[key].toLowerCase(); }); return ret; } /** * This function is specificly for `getDirectionFromFaces` and * `getFaceFromDirection`. It removes all keys that are either 'front' or 'back' * and sets the given front face to orientation.front. * @param {object} orientation - The orientation object. * @param {string} front - The face to set as front. */ function _prepOrientationForDirection(orientation, front) { let keys = Object.keys(orientation); if (keys.length <= 1 && ['front', 'back'].includes(keys[0])) { throw new Error(`Orientation object "${orientation}" is ambiguous. Please specify one of these faces: "up", "right", "down", "left"`); } // remove "front" and "back" from provided orientation object let temp = orientation; orientation = {}; keys.forEach(key => { if (['front', 'back'].includes(key)) { return; } orientation[key] = temp[key]; }); orientation.front = front.toLowerCase(); return orientation; } /** * @param {object} orientation - The orientation object. * @return {array} */ function _getRotationsForOrientation(orientation) { if (Object.keys(orientation) <= 1) { throw new Error(`Orientation object "${orientation}" is ambiguous. Please specify 2 faces.`); } let keys = Object.keys(orientation); let origins = keys.map(key => new __WEBPACK_IMPORTED_MODULE_1__models_Face__["a" /* Face */](orientation[key])); let targets = keys.map(key => new __WEBPACK_IMPORTED_MODULE_1__models_Face__["a" /* Face */](key)); // perform the first rotation, and save it let rotation1 = __WEBPACK_IMPORTED_MODULE_2__models_Vector__["a" /* Vector */].getRotationFromNormals( origins[0].normal(), origins[0].orientTo(targets[0]).normal() ); // perform the first rotation on the second origin face origins[1].rotate(rotation1.axis, rotation1.angle); // peform the second rotation, and save it let rotation2 = __WEBPACK_IMPORTED_MODULE_2__models_Vector__["a" /* Vector */].getRotationFromNormals( origins[1].normal(), origins[1].orientTo(targets[1]).normal() ); // if the rotation angle is PI, there are 3 possible axes that can perform the // rotation. however only one axis will perform the rotation while keeping // the first origin face on the target. this axis is the same as the origin // face's normal. if (Math.abs(rotation2.angle) === Math.PI) { let rotation2Axis = new __WEBPACK_IMPORTED_MODULE_1__models_Face__["a" /* Face */](keys[0]).vector.getAxis(); rotation2.axis = rotation2Axis; } return [rotation1, rotation2]; } /** * @param {array} - Array of Face objects to rotate. * @param {array} - Array of rotations to apply to faces. * @return {null} */ function _rotateFacesByRotations(faces, rotations) { for (let face of faces) { for (let rotation of rotations) { face.rotate(rotation.axis, rotation.angle); } } } /** * @param {array} notations * @return {array} */ function _reverseNotations(notations) { const reversed = []; for (let notation of notations) { let isPrime = notation.includes('prime'); notation = isPrime ? notation[0] : notation[0] + 'prime'; reversed.push(notation); } return typeof moves === 'string' ? reversed.join(' ') : reversed; } /* unused harmony default export */ var _unused_webpack_default_export = ({ getFaceOfMove, getMoveOfFace, getMiddleMatchingFace, getFaceMatchingMiddle, transformNotations, normalizeNotations, getDirectionFromFaces, getRotationFromTo, getFaceFromDirection, orientMoves }); /***/ }), /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return RubiksCube; }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__Cubie__ = __webpack_require__(6); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__algorithm_shortener__ = __webpack_require__(2); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__utils__ = __webpack_require__(0); const SOLVED_STATE = 'fffffffffrrrrrrrrruuuuuuuuudddddddddlllllllllbbbbbbbbb'; class RubiksCube { /** * Factory method. Returns an instance of a solved Rubiks Cube. */ static Solved() { return new RubiksCube(SOLVED_STATE); } /** * Factory method. * @param {string|array} moves */ static FromMoves(moves) { const cube = RubiksCube.Solved(); cube.move(moves); return cube; } /** * Factory method. Returns an instance of a scrambled Rubiks Cube. */ static Scrambled() { let cube = RubiksCube.Solved(); let randomMoves = RubiksCube.getRandomMoves(25); cube.move(randomMoves); return cube; } /** * @param {string|array} notations - The list of moves to reverse. * @return {string|array} -- whichever was initially given. */ static reverseMoves(moves) { return RubiksCube.transformMoves(moves, { reverse: true }); } /** * @param {string|array} moves - The moves to transform; * @param {object} options * @prop {boolean} options.upperCase - Turn lowercase moves into uppercase. * @prop {object} options.orientation - An object describing the orientation * from which to makes the moves. See src/js/utils#orientMoves. * * @return {string|array} -- whichever was initially given. */ static transformMoves(moves, options = {}) { return __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__utils__["d" /* transformNotations */])(moves, options); } static getRandomMoves(length = 25) { let randomMoves = []; let totalMoves = [ 'F', 'Fprime', 'R', 'Rprime', 'U', 'Uprime', 'D', 'Dprime', 'L', 'Lprime', 'B', 'Bprime' ]; while (randomMoves.length < length) { for (let i = 0; i < length - randomMoves.length; i++) { let idx = ~~(Math.random() * totalMoves.length); randomMoves.push(totalMoves[idx]); } randomMoves = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__algorithm_shortener__["a" /* algorithmShortener */])(randomMoves).split(' '); } return randomMoves.join(' '); } /** * @param {string} cubeState - The string representing the Rubik's Cube. * * The cube state are represented as: * 'FFFFFFFFFRRRRRRRRRUUUUUUUUUDDDDDDDDDLLLLLLLLLBBBBBBBBB' * * where: * F stands for the FRONT COLOR * R stands for the RIGHT COLOR * U stands for the UP COLOR * D stands for the DOWN COLOR * L stands for the LEFT COLOR * B stands for the BACK COLOR * * and the faces are given in the order of: * FRONT, RIGHT, UP, DOWN, LEFT, BACK * * The order of each color per face is ordered by starting from the top left * corner and moving to the bottom right, as if reading lines of text. * * See this example: http://2.bp.blogspot.com/_XQ7FznWBAYE/S9Sbric1KNI/AAAAAAAAAFs/wGAb_LcSOwo/s1600/rubik.png */ constructor(cubeState) { if (cubeState.length !== 9 * 6) { throw new Error('Wrong number of colors provided'); } this._notationToRotation = { f: { axis: 'z', mag: -1 }, r: { axis: 'x', mag: -1 }, u: { axis: 'y', mag: -1 }, d: { axis: 'y', mag: 1 }, l: { axis: 'x', mag: 1 }, b: { axis: 'z', mag: 1 }, m: { axis: 'x', mag: 1 }, e: { axis: 'y', mag: 1 }, s: { axis: 'z', mag: -1 } }; this._build(cubeState); } /** * Grab all the cubes on a given face, and return them in order from top left * to bottom right. * @param {string} face - The face to grab. * @return {array} */ getFace(face) { if (typeof face !== 'string') { throw new Error(`"face" must be a string (received: ${face})`); } face = face.toLowerCase()[0]; // The 3D position of cubies and the way they're ordered on each face // do not play nicely. Below is a shitty way to reconcile the two. // The way the cubies are sorted depends on the row and column they // occupy on their face. Cubies on a higher row will have a lower sorting // index, but rows are not always denoted by cubies' y position, and // "higher rows" do not always mean "higher axis values". let row, col, rowOrder, colOrder; let cubies; // grab correct cubies if (face === 'f') { [row, col, rowOrder, colOrder] = ['Y', 'X', -1, 1]; cubies = this._cubies.filter(cubie => cubie.getZ() === 1); } else if (face === 'r') { [row, col, rowOrder, colOrder] = ['Y', 'Z', -1, -1]; cubies = this._cubies.filter(cubie => cubie.getX() === 1); } else if (face === 'u') { [row, col, rowOrder, colOrder] = ['Z', 'X', 1, 1]; cubies = this._cubies.filter(cubie => cubie.getY() === 1); } else if (face === 'd') { [row, col, rowOrder, colOrder] = ['Z', 'X', -1, 1]; cubies = this._cubies.filter(cubie => cubie.getY() === -1); } else if (face === 'l') { [row, col, rowOrder, colOrder] = ['Y', 'Z', -1, 1]; cubies = this._cubies.filter(cubie => cubie.getX() === -1); } else if (face === 'b') { [row, col, rowOrder, colOrder] = ['Y', 'X', -1, -1]; cubies = this._cubies.filter(cubie => cubie.getZ() === -1); } else if (['m', 'e', 's'].includes(face)) { return this._getMiddleCubiesForMove(face); } // order cubies from top left to bottom right return cubies.sort((first, second) => { let firstCubieRow = first[`get${row}`]() * rowOrder; let firstCubieCol = first[`get${col}`]() * colOrder; let secondCubieRow = second[`get${row}`]() * rowOrder; let secondCubieCol = second[`get${col}`]() * colOrder; if (firstCubieRow < secondCubieRow) { return -1; } else if (firstCubieRow > secondCubieRow) { return 1; } else { return firstCubieCol < secondCubieCol ? -1 : 1; } }); } /** * @param {array} faces - The list of faces the cubie belongs on. */ getCubie(faces) { return this._cubies.find(cubie => { if (faces.length != cubie.faces().length) { return false; } for (let face of faces) { if (!cubie.faces().includes(face)) { return false; } } return true; }); } /** * Finds and returns all cubies with three colors. * @return {array} */ corners() { return this._cubies.filter(cubie => cubie.isCorner()); } /** * Finds and returns all cubies with two colors. * @return {array} */ edges() { return this._cubies.filter(cubie => cubie.isEdge()); } /** * Finds and returns all cubies with one color. * @return {array} */ middles() { return this._cubies.filter(cubie => cubie.isMiddle()); } /** * Gets the rotation axis and magnitude of rotation based on notation. * Then finds all cubes on the correct face, and rotates them around the * rotation axis. * @param {string|array} notations - The move notation. * @param {object} options - Move options. * @prop {boolean} options.upperCase - Turn all moves to upper case (i.e. no "double" moves). */ move(notations, options = {}) { if (typeof notations === 'string') { notations = notations.split(' '); } notations = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__utils__["d" /* transformNotations */])(notations, options); for (let notation of notations) { let move = notation[0]; if (!move) { continue; } let isPrime = notation.toLowerCase().includes('prime'); let isWithMiddle = move === move.toLowerCase(); let isDoubleMove = notation.includes('2'); let { axis, mag } = this._getRotationForFace(move); let cubesToRotate = this.getFace(move); if (isPrime) mag *= -1; if (isDoubleMove) mag *= 2; if (isWithMiddle) { let middleMove = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__utils__["g" /* getMiddleMatchingFace */])(move); let middleCubies = this._getMiddleCubiesForMove(middleMove); cubesToRotate = [...cubesToRotate, ...middleCubies]; } for (let cubie of cubesToRotate) { cubie.rotate(axis, mag); } } } isSolved() { return this.toString() === SOLVED_STATE; } toString() { let cubeState = ''; let faces = ['front', 'right', 'up', 'down', 'left', 'back']; for (let face of faces) { let cubies = this.getFace(face); for (let cubie of cubies) { cubeState += cubie.getColorOfFace(face); } } return cubeState; } clone() { return new RubiksCube(this.toString()); } /** * Create a "virtual" cube, with individual "cubies" having a 3D coordinate * position and 1 or more colors attached to them. */ _build(cubeState) { this._cubies = []; this._populateCube(); let parsedColors = this._parseColors(cubeState); for (let face of Object.keys(parsedColors)) { let colors = parsedColors[face]; this._colorFace(face, colors); } } /** * Populates the "virtual" cube with 26 "empty" cubies by their position. * @return {null} */ _populateCube() { for (let x = -1; x <= 1; x++) { for (let y = -1; y <= 1; y++) { for (let z = -1; z <= 1; z++) { // no cubie in the center of the rubik's cube if (x === 0 && y === 0 && z === 0) { continue; } let cubie = new __WEBPACK_IMPORTED_MODULE_0__Cubie__["a" /* Cubie */]({ position: [x, y, z] }); this._cubies.push(cubie); } } } } /** * @return {object} - A map with faces for keys and colors for values */ _parseColors(cubeState) { let faceColors = { front: [], right: [], up: [], down: [], left: [], back: [] }; let currentFace; for (let i = 0; i < cubeState.length; i++) { let color = cubeState[i]; if (i < 9) { currentFace = 'front'; } else if (i < 9 * 2) { currentFace = 'right'; } else if (i < 9 * 3) { currentFace = 'up'; } else if (i < 9 * 4) { currentFace = 'down'; } else if (i < 9 * 5) { currentFace = 'left'; } else { currentFace = 'back'; } faceColors[currentFace].push(color); } return faceColors; } /** * @param {array} face - An array of the cubies on the given face. * @param {array} colors - An array of the colors on the given face. */ _colorFace(face, colors) { let cubiesToColor = this.getFace(face); for (let i = 0; i < colors.length; i++) { cubiesToColor[i].colorFace(face, colors[i]); } } /** * @return {object} - The the rotation axis and magnitude for the given face. */ _getRotationForFace(face) { if (typeof face !== 'string') { throw new Error(`"face" must be a string (received: ${face})`); } face = face.toLowerCase(); return { axis: this._notationToRotation[face].axis, mag: this._notationToRotation[face].mag * Math.PI / 2 }; } _getMiddleCubiesForMove(move) { move = move[0].toLowerCase(); let nonMiddles; if (move === 'm') { nonMiddles = ['left', 'right']; } else if (move === 'e') { nonMiddles = ['up', 'down']; } else if (move === 's') { nonMiddles = ['front', 'back']; } return this._cubies.filter(cubie => { return !cubie.hasFace(nonMiddles[0]) && !cubie.hasFace(nonMiddles[1]); }); } } /***/ }), /* 2 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return algorithmShortener; }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_array_element_combiner__ = __webpack_require__(19); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_array_element_combiner___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_array_element_combiner__); const parallelMoves = { F: 'B', R: 'L', U: 'D' }; /** * @param {array|string} notations - The array of move notations. * @return {string} */ const algorithmShortener = (notations) => { if (typeof notations === 'string') { notations = notations.split(' '); } const options = { compare(a, b) { return a[0] === b[0]; }, combine(a, b) { const aDir = a.includes('2') ? 2 : (a.includes('prime') ? -1 : 1); const bDir = b.includes('2') ? 2 : (b.includes('prime') ? -1 : 1); let totalDir = aDir + bDir; if (totalDir === 4) totalDir = 0; if (totalDir === -2) totalDir = 2; if (totalDir === 3) totalDir = -1; if (totalDir === 0) { return ''; } let dirString = totalDir === 2 ? '2' : (totalDir === -1 ? 'prime' : ''); return `${a[0]}${dirString}`; }, cancel(value) { return value === ''; }, ignore(a, b) { return (parallelMoves[a[0]] === b[0] || parallelMoves[b[0]] === a[0]); } }; return __WEBPACK_IMPORTED_MODULE_0_array_element_combiner___default()(notations, options).join(' '); }; /***/ }), /* 3 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return BaseSolver; }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__models_RubiksCube__ = __webpack_require__(1); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__utils___ = __webpack_require__(0); class BaseSolver { /** * Solves the first step following the Fridrich Method: the cross. Solves the * cross on the UP face by default. * * @param {string|RubiksCube} rubiksCube - This can either be a 54-character * long string representing the cube state (in this case it will have to * "build" another rubik's Cube), or an already built RubiksCube object. */ constructor(rubiksCube, options = {}) { this.cube = typeof rubiksCube === 'string' ? new __WEBPACK_IMPORTED_MODULE_0__models_RubiksCube__["a" /* RubiksCube */](rubiksCube) : rubiksCube; this.options = options; this.partition = {}; this.partitions = []; this.totalMoves = []; this._afterEachCallbacks = []; } /** * @param {string|array} notation - A string of move(s) to execute and store. * @param {object} options - The options to pass to RubiksCube#move. */ move(notations, options) { if (typeof notations === 'string') { notations = notations.split(' '); } this.cube.move(notations, options); // this step is also in RubiksCube#move, but it is important we do it here // as well. The notations need to be saved to the partition correctly. notations = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_1__utils___["d" /* transformNotations */])(notations, options); for (let notation of notations) { this.totalMoves.push(notation); } } afterEach(callback) { this._afterEachCallbacks.push(callback); } /** * @param {...*} callbackArgs - The arguments to call the function with. */ _triggerAfterEach(...callbackArgs) { this._afterEachCallbacks.forEach(fn => fn(...callbackArgs)); } /** * Solves the edge and/or corner and returns information about the state * about them right before they are solved. It's important to construct the * object in steps for debugging, so that we can still have access to e.g. * the case number if the solve method fails. */ _solve(cubies = {}) { this.partition = {}; this.partition.cubies = cubies; let { corner, edge } = cubies; this.partition.caseNumber = this._getCaseNumber({ corner, edge }); this._solveCase(this.partition.caseNumber, { corner, edge }); this.partition.moves = this.totalMoves; this.totalMoves = []; if (!this._overrideAfterEach) { this._triggerAfterEach(this.partition, this.phase); } return this.partition; } _solveCase(caseNumber, cubies = {}) { let { corner, edge } = cubies; this[`_solveCase${caseNumber}`]({ corner, edge }); } } /***/ }), /* 4 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Vector; }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_gl_vec3_angle__ = __webpack_require__(22); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_gl_vec3_angle___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0_gl_vec3_angle__); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_gl_vec3_cross__ = __webpack_require__(12); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_gl_vec3_cross___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_gl_vec3_cross__); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_gl_vec3_rotateX__ = __webpack_require__(26); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2_gl_vec3_rotateX___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2_gl_vec3_rotateX__); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_3_gl_vec3_rotateY__ = __webpack_require__(27); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_3_gl_vec3_rotateY___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_3_gl_vec3_rotateY__); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4_gl_vec3_rotateZ__ = __webpack_require__(28); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_4_gl_vec3_rotateZ___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_4_gl_vec3_rotateZ__); const rotate = { x: __WEBPACK_IMPORTED_MODULE_2_gl_vec3_rotateX___default.a, y: __WEBPACK_IMPORTED_MODULE_3_gl_vec3_rotateY___default.a, z: __WEBPACK_IMPORTED_MODULE_4_gl_vec3_rotateZ___default.a }; class Vector { /** * Factory method. * @param {string} vector - Space-deliminated x, y, and z values. * @return {Vector} */ static FromString(vector) { return new Vector(vector.split(' ').map(value => parseInt(value))); } /** * @param {array} vector1 - Vector 1. * @param {array} vector2 - Vector 2. * @return {boolean} */ static areEqual(vector1, vector2) { return vector1[0] === vector2[0] && vector1[1] === vector2[1] && vector1[2] === vector2[2]; } /** * Helper method. gl-vec3's angle function always returns positive but in many * cases we want the angle in the direction from one vector to another. To get * the sign of the angle, cross the two vectors and determine the direction the * crossed vector, um, directs in. For example, the vector [0, -1, 0] would * shoot negatively along the y-axis. * * @param {array} v1 - Vector 1. * @param {array} v2 - Vector 2. * @return {number} */ static getAngle(v1, v2) { let _angle = __WEBPACK_IMPORTED_MODULE_0_gl_vec3_angle___default()(v1, v2); let crossVector = __WEBPACK_IMPORTED_MODULE_1_gl_vec3_cross___default()([], v1, v2); let sign = new Vector(crossVector).getMagnitude(); return sign ? _angle * sign : _angle; } /** * Finds the rotation axis and angle to get from one normal to another. * @param {array} normal1 - The from normal. * @param {array} normal2 - The to normal. * @return {object} - Stores the rotation axis and angle */ static getRotationFromNormals(normal1, normal2) { let axis = new Vector(__WEBPACK_IMPORTED_MODULE_1_gl_vec3_cross___default()([], normal1, normal2)).getAxis(); let angle = Vector.getAngle(normal1, normal2); // when normal1 is equal to or opposite from normal2, it means 2 things: 1) // the cross axis is undefined and 2) the angle is either 0 or PI. This // means that rotating around the axis parallel to normal1 will not result // in any change, while rotating around either of the other two will work // properly. if (!axis) { let axes = ['x', 'y', 'z']; axes.splice(axes.indexOf(new Vector(normal1).getAxis()), 1); axis = axes[0]; } return { axis, angle }; } /** * @param {object} rotation - The rotation to reverse. * @return {object} */ static reverseRotation(rotation) { rotation.angle *= -1; return rotation; } /** * @param {array} [vector] - Contains x, y, and z values. */ constructor(vector) { this.set(vector); } /** * @return {array} */ toArray() { return this.vector; } /** * @param {array} vector - The new vector to store. */ set(vector) { if (typeof vector === 'undefined') { return; } this.vector = vector.map(value => Math.round(value)); } /** * @param {number} value - The value to store. */ setX(value) { this.vector[0] = value; } /** * @param {number} value - The value to store. */ setY(value) { this.vector[1] = value; } /** * @param {number} value - The value to store. */ setZ(value) { this.vector[2] = value; } /** * @return {number} */ getX() { return this.toArray()[0]; } /** * @return {number} */ getY() { return this.toArray()[1]; } /** * @return {number} */ getZ() { return this.toArray()[2]; } /** * Kind of a flimsy method. If this vector points parallel to an axis, this * returns true. A hacky way to find this is to count the number of 0's and * return true if and only if the count is 2. * @return {boolean} */ isAxis() { let count = 0; for (let value of this.vector) { if (value === 0) { count += 1; } } return count === 2; } /** * Kind of a flimsy method. If this vector points parallel to an axis, return * that axis. * @return {string} */ getAxis() { if (!this.isAxis()) { return; } if (this.vector[0] !== 0) return 'x'; if (this.vector[1] !== 0) return 'y'; if (this.vector[2] !== 0) return 'z'; } /** * Kind of a flimsy method. If this vector points parallel to an axis, return * the magnitude of the value along that axis. (Basically, return whether it * is positive or negative.) * @return {number} */ getMagnitude() { if (!this.isAxis()) { return; } return this[`get${this.getAxis().toUpperCase()}`](); } /** * @param {string} axis - The axis to rotate around. * @param {number} angle - The angle of rotation. * @return {Vector} */ rotate(axis, angle) { axis = axis.toLowerCase(); this.set(rotate[axis]([], this.vector, [0, 0, 0], angle)); return this; } } /***/ }), /* 5 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return F2LCaseBaseSolver; }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__F2LBaseSolver__ = __webpack_require__(15); class F2LCaseBaseSolver extends __WEBPACK_IMPORTED_MODULE_0__F2LBaseSolver__["a" /* F2LBaseSolver */] { solve({ corner, edge }) { return this._solve({ corner, edge }); } } /***/ }), /* 6 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Cubie; }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__Vector__ = __webpack_require__(4); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__Face__ = __webpack_require__(14); class Cubie { /** * Factory method. Returns an instance of a cubie identified by the faces it * sits on. * @param {array} faces - A list of all the faces this cubie sits on. */ static FromFaces(faces) { let position = new __WEBPACK_IMPORTED_MODULE_0__Vector__["a" /* Vector */]([0, 0, 0]); let colorMap = {}; for (let face of faces) { if (!face) { continue; } let temp = new __WEBPACK_IMPORTED_MODULE_1__Face__["a" /* Face */](face); let axis = temp.vector.getAxis().toUpperCase(); position[`set${axis}`](temp.vector.getMagnitude()); colorMap[face.toLowerCase()] = temp.toString()[0].toLowerCase(); } return new Cubie({ position: position.toArray(), colorMap }); } /** * @param {object} [options] * @param {object} options.position - The cubie's position. * @param {object} options.colorMap - A map with faces as keys and colors * as values. For example: { 'front' : 'f' }. */ constructor({ position, colorMap = {} }) { this.position(position); this.colorMap = {}; Object.keys(colorMap).forEach(face => { let color = colorMap[face]; this.colorFace(face, color); }); } /** * @return {Cubie} */ clone() { return new Cubie({ position: this.position(), colorMap: this.colorMap }); } /** * Getter/setter for the vector position. * @param {array} [position] - The new position to store. * @return {array} */ position(position) { if (typeof position === 'undefined') { return this.vector ? this.vector.toArray() : this.vector; } this.vector = new __WEBPACK_IMPORTED_MODULE_0__Vector__["a" /* Vector */](position); } /** * @return {number} */ getX() { return this.vector.getX(); } /** * @return {number} */ getY() { return this.vector.getY(); } /** * @return {number} */ getZ() { return this.vector.getZ(); } /** * @return {boolean} */ isCorner() { return Object.keys(this.colorMap).length === 3; } /** * @return {boolean} */ isEdge() { return Object.keys(this.colorMap).length === 2; } /** * @return {boolean} */ isMiddle() { return Object.keys(this.colorMap).length === 1; } /** * @return {array} */ colors() { return Object.keys(this.colorMap).map(face => this.colorMap[face]); } /** * @param {string} color - Check if the cubie has this color. * @return {boolean} */ hasColor(color) { color = color.toLowerCase(); for (let face of Object.keys(this.colorMap)) { if (this.colorMap[face] === color) { return true; } } return false; } /** * @param {string} face - Check if the cubie has this face. * @return {boolean} */ hasFace(face) { face = face.toLowerCase(); return Object.keys(this.colorMap).includes(face); } /** * Sets a color on a given face or normal of a cubie. * @param {string} face - The face of the cubie we want to set the color on. * @param {string} color - The color we want to set. * @return {Cubie} */ colorFace(face, color) { face = face.toLowerCase(); color = color.toLowerCase(); this.colorMap[face] = color; return this; } /** * @param {string} face - The color on the face this cubie sits on. * @return {string} */ getColorOfFace(face) { face = face.toLowerCase(); return this.colorMap[face]; } /** * @param {string} color - Find the face that this color sits on. * @return {string} */ getFaceOfColor(color) { color = color.toLowerCase(); return Object.keys(this.colorMap).find(cubieColor => { return this.colorMap[cubieColor] === color; }); } /** * Return all the faces this cubie sits on. * @return {array} */ faces() { return Object.keys(this.colorMap); } /** * Rotates the position vector around `axis` by `angle`. Updates the internal * position vector and the normal-color map. * @param {string} axis - The axis of rotation. * @param {number} angle - The magnitude of rotation. * @return {null} */ rotate(axis, angle) { // update position vector after rotation this.vector.rotate(axis, angle); // update normal-color map let newMap = {}; // need to completely overwrite the old one // go through each normal, rotate it, and assign the new normal the old color for (let face of Object.keys(this.colorMap)) { let color = this.colorMap[face]; let faceModel = new __WEBPACK_IMPORTED_MODULE_1__Face__["a" /* Face */](face); let newNormal = faceModel.rotate(axis, angle).normal().join(' '); let newFace = __WEBPACK_IMPORTED_MODULE_1__Face__["a" /* Face */].FromNormal(newNormal).toString().toLowerCase(); newMap[newFace] = color; } this.colorMap = {}; Object.keys(newMap).forEach(face => this.colorFace(face, newMap[face])); } } /***/ }), /* 7 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return CrossSolver; }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__BaseSolver__ = __webpack_require__(3); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__models_RubiksCube__ = __webpack_require__(1); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__utils__ = __webpack_require__(0); const CROSS_COLOR = 'u'; const R = (moves) => __WEBPACK_IMPORTED_MODULE_1__models_RubiksCube__["a" /* RubiksCube */].reverseMoves(moves); class CrossSolver extends __WEBPACK_IMPORTED_MODULE_0__BaseSolver__["a" /* BaseSolver */] { constructor(...args) { super(...args); this.phase = 'cross'; } solve() { let crossEdges = this._getCrossEdges(); for (let edge of crossEdges) { let partition = this._solve({ edge }); this.partitions.push(partition); } return this.partitions; } isSolved() { let edges = this._getCrossEdges(); for (let edge of edges) { if (!this.isEdgeSolved(edge)) { return false; } } return true; } isEdgeSolved(edge) { let otherColor = edge.colors().find(color => color !== 'u'); let otherFace = edge.faces().find(face => face !== 'up'); const matchesMiddle = otherFace[0] === otherColor; const isOnCrossFace = edge.getColorOfFace('up') === 'u'; return isOnCrossFace && matchesMiddle; } /** * Finds all edges that have 'F' as a color. * @return {array} */ _getCrossEdges() { return this.cube.edges().filter(edge => edge.hasColor(CROSS_COLOR)); } /** * 6 Cases! * 1) The edge's UP color is on the UP face. * 2) the edge's UP color is on the DOWN face. * 3) The edge's UP color is not on the UP or DOWN face and the other color is on the UP face. * 4) The edge's UP color is not on the UP or DOWN face and the other color is on the DOWN face. * 5) The edge's UP color is not on the UP or DOWN face and the other color is on the RELATIVE RIGHT face. * 6) The edge's UP color is not on the UP or DOWN face and the other color is on the RELATIVE LEFT face. * * @param {cubie} edge */ _getCaseNumber({ edge }) { if (edge.getColorOfFace('up') === CROSS_COLOR) { return 1; } else if (edge.getColorOfFace('down') === CROSS_COLOR) { return 2; } if (edge.faces().includes('up')) { return 3; } else if (edge.faces().includes('down')) { return 4; } let crossFace = edge.getFaceOfColor(CROSS_COLOR); let otherFace = edge.getFaceOfColor(edge.colors().find(color => color !== CROSS_COLOR)); let direction = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__utils__["c" /* getDirectionFromFaces */])(crossFace, otherFace, { up: 'up' }); if (direction === 'right') { return 5; } else if (direction === 'left') { return 6; } } _solveCase1({ edge }) { if (this.isEdgeSolved(edge)) { return; } let face = edge.faces().find(face => face !== 'up'); this.move(`${face} ${face}`, { upperCase: true }); this._solveCase2({ edge }); } _solveCase2({ edge }) { let solveMoves = this._case1And2Helper({ edge }, 2); this.move(solveMoves, { upperCase: true }); } _solveCase3({ edge }) { let prepMove = this._case3And4Helper({ edge }, 3); this.move(prepMove, { upperCase: true }); this._solveCase5({ edge }); } _solveCase4({ edge }) { let prepMove = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__utils__["b" /* getRotationFromTo */])( 'down', edge.getFaceOfColor('u'), __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__utils__["a" /* getFaceOfMove */])(edge.getColorOfFace('down')) ); this.move(prepMove, { upperCase: true }); let edgeToMiddle = R(edge.getFaceOfColor('u')); this.move(edgeToMiddle, { upperCase: true }); this._solveCase5({ edge }); } _solveCase5({ edge }) { let solveMoves = this._case5And6Helper({ edge }, 5); this.move(solveMoves, { upperCase: true }); } _solveCase6({ edge }) { let solveMoves = this._case5And6Helper({ edge }, 6); this.move(solveMoves, { upperCase: true }); } _case1And2Helper({ edge }, caseNum) { let crossColorFace = caseNum === 1 ? 'up' : 'down'; let currentFace = edge.faces().find(face => face !== crossColorFace); let targetFace = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__utils__["a" /* getFaceOfMove */])(edge.getColorOfFace(currentFace)); let solveMoves = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__utils__["b" /* getRotationFromTo */])(crossColorFace, currentFace, targetFace); if (caseNum === 2) { let edgeToCrossFace = __webpack_require__.i(__WEBPACK_IMPORTED_MODULE_2__utils__["f" /* getMoveOfFace */])(targetFace); solveMoves += ` ${edgeToCrossFace} ${edgeToCrossFace}`; } return solveMoves; } _case3And4Helper({ edge }, caseNum) { let prepMove = edge.faces().find(face => !['up', 'down'].includes(face)); if (caseNum === 4) { prepMove = R(prepMove); } return prepMove; } _case5And6Helper({ edge }, caseNum) { let otherCo