UNPKG

@vscubing/cubing

Version:

A collection of JavaScript cubing libraries.

1,849 lines (1,833 loc) 145 kB
import { Move, QuantumMove } from "../chunks/chunk-QVWFSWHJ.js"; // src/cubing/puzzle-geometry/colors.ts function defaultPlatonicColorSchemes() { return { // the colors should use the same naming convention as the nets, above. 4: { F: "#44ee00" /* Green */, D: "#f4f400" /* Yellow */, L: "#ff0000" /* Red */, R: "#2266ff" /* Blue */ }, 6: { U: "#ffffff" /* White */, F: "#44ee00" /* Green */, R: "#ff0000" /* Red */, D: "#f4f400" /* Yellow */, B: "#2266ff" /* Blue */, L: "#ff8000" /* Orange */ }, 8: { U: "#ffffff" /* White */, F: "#44ee00" /* Green */, R: "#ff0000" /* Red */, D: "#f4f400" /* Yellow */, BB: "#2266ff" /* Blue */, L: "#8800dd" /* Purple */, BL: "#ff8000" /* Orange */, BR: "#888888" /* MediumGray */ }, 12: { U: "#ffffff" /* White */, F: "#008800" /* DarkGreen */, R: "#ff0000" /* Red */, C: "#e8d0a0" /* Cream */, A: "#3399ff" /* Aqua */, L: "#8800dd" /* Purple */, E: "#ff66cc" /* Pink */, BF: "#99ff00" /* Lime */, BR: "#0000ff" /* BoldBlue */, BL: "#f4f400" /* Yellow */, I: "#ff8000" /* Orange */, D: "#888888" /* MediumGray */ }, 20: { R: "#f4f400" /* Yellow */, C: "#d41f69" /* Cerise */, F: "#008800" /* DarkGreen */, E: "#5c5c5c" /* DarkGray */, L: "#8800dd" /* Purple */, U: "#ffffff" /* White */, A: "#007a89" /* Teal */, G: "#ff0000" /* Red */, I: "#7d3b11" /* Brown */, S: "#b9a1ff" /* Lavender */, H: "#3399ff" /* Aqua */, J: "#5ec4b6" /* SeaGreen */, B: "#44ee00" /* Green */, K: "#e8d0a0" /* Cream */, D: "#aaaaaa" /* LightGray */, M: "#ff66cc" /* Pink */, O: "#292929" /* harcoal */, P: "#ff8000" /* Orange */, N: "#980000" /* Burgundy */, Q: "#0000ff" /* BoldBlue */ } }; } // src/cubing/puzzle-geometry/FaceNameSwizzler.ts var FaceNameSwizzler = class { constructor(facenames, gripnames_arg) { this.facenames = facenames; if (gripnames_arg) { this.gripnames = gripnames_arg; } for (let i = 0; this.prefixFree && i < facenames.length; i++) { for (let j = 0; this.prefixFree && j < facenames.length; j++) { if (i !== j && facenames[i].startsWith(facenames[j])) { this.prefixFree = false; } } } } prefixFree = true; gripnames = []; setGripNames(names) { this.gripnames = names; } // split a string into face names and return a list of // indices. splitByFaceNames(s) { const r = []; let at = 0; while (at < s.length) { if (at > 0 && at < s.length && s[at] === "_") { at++; } let currentMatch = -1; for (let i = 0; i < this.facenames.length; i++) { if (s.substr(at).startsWith(this.facenames[i]) && (currentMatch < 0 || this.facenames[i].length > this.facenames[currentMatch].length)) { currentMatch = i; } } if (currentMatch >= 0) { r.push(currentMatch); at += this.facenames[currentMatch].length; } else { throw new Error(`Could not split ${s} into face names.`); } } return r; } // cons a grip from an array of numbers. joinByFaceIndices(list) { let sep = ""; const r = []; for (let i = 0; i < list.length; i++) { r.push(sep); r.push(this.facenames[list[i]]); if (!this.prefixFree) { sep = "_"; } } return r.join(""); } /* * Try to match something the user gave us with some geometric * feature. We used to have strict requirements: * * a) The set of face names are prefix free * b) When specifying a corner, all coincident planes were * specified * * But, to allow megaminx to have more reasonable and * conventional names, and to permit shorter canonical * names, we are relaxing these requirements and adding * new syntax. Now: * * a) Face names need not be syntax free. * b) When parsing a geometric name, we use greedy * matching, so the longest name that matches the * user string at the current position is the one * assumed to match. * c) Underscores are permitted to separate face names * (both in user input and in geometric * descriptions). * d) Default names of corner moves where corners have * more than three corners, need only include three * of the corners. * * This code is not performance-sensitive so we can do it a * slow and simple way. */ spinmatch(userinput, longname) { if (userinput === longname) { return true; } try { const e1 = this.splitByFaceNames(userinput); const e2 = this.splitByFaceNames(longname); if (e1.length !== e2.length && e1.length < 3) { return false; } for (let i = 0; i < e1.length; i++) { for (let j = 0; j < i; j++) { if (e1[i] === e1[j]) { return false; } } let found = false; for (let j = 0; j < e2.length; j++) { if (e1[i] === e2[j]) { found = true; break; } } if (!found) { return false; } } return true; } catch { return false; } } /* same as above, but permit both to have v's on the end. */ spinmatchv(userinput, longname) { if (userinput.endsWith("v") && longname.endsWith("v")) { return this.spinmatch( userinput.slice(0, userinput.length - 1), longname.slice(0, longname.length - 1) ); } else { return this.spinmatch(userinput, longname); } } unswizzle(s) { if ((s.endsWith("v") || s.endsWith("w")) && s[0] <= "Z") { s = s.slice(0, s.length - 1); } const upperCaseGrip = s.toUpperCase(); for (let i = 0; i < this.gripnames.length; i++) { const g = this.gripnames[i]; if (this.spinmatch(upperCaseGrip, g)) { return g; } } return s; } }; // src/cubing/puzzle-geometry/notation-mapping/FaceRenamingMapper.ts var FaceRenamingMapper = class { constructor(internalNames, externalNames) { this.internalNames = internalNames; this.externalNames = externalNames; } // TODO: consider putting a cache in front of this convertString(grip, a, b) { let suffix = ""; if ((grip.endsWith("v") || grip.endsWith("v")) && grip <= "_") { suffix = grip.slice(grip.length - 1); grip = grip.slice(0, grip.length - 1); } const upper = grip.toUpperCase(); let isLowerCase = false; if (grip !== upper) { isLowerCase = true; grip = upper; } grip = b.joinByFaceIndices(a.splitByFaceNames(grip)); if (isLowerCase) { grip = grip.toLowerCase(); } return grip + suffix; } convert(move, a, b) { const grip = move.family; const ngrip = this.convertString(grip, a, b); if (grip === ngrip) { return move; } else { return new Move( new QuantumMove(ngrip, move.innerLayer, move.outerLayer), move.amount ); } } notationToInternal(move) { const r = this.convert(move, this.externalNames, this.internalNames); return r; } notationToExternal(move) { return this.convert(move, this.internalNames, this.externalNames); } }; // src/cubing/puzzle-geometry/notation-mapping/FTONotationMapper.ts var FTONotationMapper = class { constructor(child, sw) { this.child = child; this.sw = sw; } notationToInternal(move) { if (move.family === "T" && move.innerLayer === void 0 && move.outerLayer === void 0) { return new Move( new QuantumMove("FLRv", move.innerLayer, move.outerLayer), move.amount ); } else { const r = this.child.notationToInternal(move); return r; } } // we never rewrite click moves to these moves. notationToExternal(move) { let fam = move.family; if (fam.length > 0 && fam[fam.length - 1] === "v") { fam = fam.substring(0, fam.length - 1); } if (this.sw.spinmatch(fam, "FLUR")) { return new Move( new QuantumMove("T", move.innerLayer, move.outerLayer), move.amount ); } return this.child.notationToExternal(move); } }; // src/cubing/puzzle-geometry/notation-mapping/MegaminxScramblingNotationMapper.ts var MegaminxScramblingNotationMapper = class { constructor(child) { this.child = child; } notationToInternal(move) { if (move.innerLayer === void 0 && move.outerLayer === void 0) { if (Math.abs(move.amount) === 1) { if (move.family === "R++") { return new Move(new QuantumMove("L", 3, 2), -2 * move.amount); } else if (move.family === "R--") { return new Move(new QuantumMove("L", 3, 2), 2 * move.amount); } else if (move.family === "D++") { return new Move(new QuantumMove("U", 3, 2), -2 * move.amount); } else if (move.family === "D--") { return new Move(new QuantumMove("U", 3, 2), 2 * move.amount); } if (move.family === "R_PLUSPLUS_") { return new Move(new QuantumMove("L", 3, 2), -2 * move.amount); } else if (move.family === "D_PLUSPLUS_") { return new Move(new QuantumMove("U", 3, 2), -2 * move.amount); } } if (move.family === "y") { return new Move("Uv", move.amount); } if (move.family === "x" && Math.abs(move.amount) === 2) { return new Move("ERv", move.amount / 2); } } return this.child.notationToInternal(move); } // we never rewrite click moves to these moves. notationToExternal(move) { if (move.family === "ERv" && Math.abs(move.amount) === 1) { return new Move( new QuantumMove("x", move.innerLayer, move.outerLayer), move.amount * 2 ); } if (move.family === "ILv" && Math.abs(move.amount) === 1) { return new Move( new QuantumMove("x", move.innerLayer, move.outerLayer), -move.amount * 2 ); } if (move.family === "Uv") { return new Move( new QuantumMove("y", move.innerLayer, move.outerLayer), move.amount ); } if (move.family === "Dv") { return new Move("y", -move.amount); } return this.child.notationToExternal(move); } }; // src/cubing/puzzle-geometry/notation-mapping/NullMapper.ts var NullMapper = class { notationToInternal(move) { return move; } notationToExternal(move) { return move; } }; // src/cubing/puzzle-geometry/notation-mapping/NxNxNCubeMapper.ts var NxNxNCubeMapper = class { constructor(slices) { this.slices = slices; } notationToInternal(move) { const grip = move.family; if (!(move.innerLayer || move.outerLayer)) { if (grip === "x") { move = new Move("Rv", move.amount); } else if (grip === "y") { move = new Move("Uv", move.amount); } else if (grip === "z") { move = new Move("Fv", move.amount); } if ((this.slices & 1) === 1) { if (grip === "E") { move = new Move( new QuantumMove("D", (this.slices + 1) / 2), move.amount ); } else if (grip === "M") { move = new Move( new QuantumMove("L", (this.slices + 1) / 2), move.amount ); } else if (grip === "S") { move = new Move( new QuantumMove("F", (this.slices + 1) / 2), move.amount ); } } if (this.slices > 2) { if (grip === "e") { move = new Move( new QuantumMove("D", this.slices - 1, 2), move.amount ); } else if (grip === "m") { move = new Move( new QuantumMove("L", this.slices - 1, 2), move.amount ); } else if (grip === "s") { move = new Move( new QuantumMove("F", this.slices - 1, 2), move.amount ); } } } return move; } // do we want to map slice moves to E/M/S instead of 2U/etc.? notationToExternal(move) { const grip = move.family; if (!(move.innerLayer || move.outerLayer)) { if (grip === "Rv") { return new Move("x", move.amount); } else if (grip === "Uv") { return new Move("y", move.amount); } else if (grip === "Fv") { return new Move("z", move.amount); } else if (grip === "Lv") { return new Move("x", -move.amount); } else if (grip === "Dv") { return new Move("y", -move.amount); } else if (grip === "Bv") { return new Move("z", -move.amount); } } return move; } }; // src/cubing/puzzle-geometry/notation-mapping/PyraminxNotationMapper.ts var pyraminxFamilyMap = { U: "frl", L: "fld", R: "fdr", B: "dlr", u: "FRL", l: "FLD", r: "FDR", b: "DLR", Uv: "FRLv", Lv: "FLDv", Rv: "FDRv", Bv: "DLRv", D: "D", F: "F", BL: "L", BR: "R" }; var tetraminxFamilyMap = { U: "FRL", L: "FLD", R: "FDR", B: "DLR", u: "frl", l: "fld", r: "fdr", b: "dlr", Uv: "FRLv", Lv: "FLDv", Rv: "FDRv", Bv: "DLRv", D: "D", F: "F", BL: "L", BR: "R", d: "d", f: "f", bl: "l", br: "r" }; var pyraminxFamilyMapWCA = { U: "FRL", L: "FLD", R: "FDR", B: "DLR" }; var pyraminxExternalQuantumY = new QuantumMove("y"); var pyraminxInternalQuantumY = new QuantumMove("Dv"); var PyraminxNotationMapper = class { constructor(child) { this.child = child; } wcaHack = false; map = pyraminxFamilyMap; notationToInternal(move) { if (this.wcaHack && move.innerLayer === 2 && move.outerLayer === null) { const newFamilyWCA = pyraminxFamilyMapWCA[move.family]; if (newFamilyWCA) { return new Move( new QuantumMove(newFamilyWCA, move.innerLayer, move.outerLayer), move.amount ); } } const newFamily = this.map[move.family]; if (newFamily) { return new Move( new QuantumMove(newFamily, move.innerLayer, move.outerLayer), move.amount ); } else if (pyraminxExternalQuantumY.isIdentical(move.quantum)) { return new Move(pyraminxInternalQuantumY, -move.amount); } else { return null; } } // we never rewrite click moves to these moves. notationToExternal(move) { if (this.wcaHack && move.innerLayer === 2 && move.outerLayer === null) { for (const [external, internal] of Object.entries(pyraminxFamilyMapWCA)) { if (this.child.spinmatch(move.family, internal)) { return new Move( new QuantumMove(external, move.innerLayer, move.outerLayer), move.amount ); } } } for (const [external, internal] of Object.entries(this.map)) { if (this.child.spinmatch(move.family, internal)) { return new Move( new QuantumMove(external, move.innerLayer, move.outerLayer), move.amount ); } } if (pyraminxInternalQuantumY.isIdentical(move.quantum)) { return new Move(pyraminxExternalQuantumY, -move.amount); } else { return null; } } }; var TetraminxNotationMapper = class extends PyraminxNotationMapper { wcaHack = true; constructor(child) { super(child); this.map = tetraminxFamilyMap; } }; // src/cubing/puzzle-geometry/notation-mapping/SkewbNotationMapper.ts var skewbFamilyMap = { U: "UBL", UL: "ULF", F: "UFR", UR: "URB", B: "DBL", D: "DFR", L: "DLF", R: "DRB", Uv: "UBLv", ULv: "ULFv", Fv: "UFRv", URv: "URBv", Bv: "DBLv", Dv: "DFRv", Lv: "DLFv", Rv: "DRBv" }; var skewbExternalQuantumX = new QuantumMove("x"); var skewbInternalQuantumX = new QuantumMove("Rv"); var skewbInternalQuantumXPrime = new QuantumMove("Lv"); var skewbExternalQuantumY = new QuantumMove("y"); var skewbInternalQuantumY = new QuantumMove("Uv"); var skewbInternalQuantumYPrime = new QuantumMove("Dv"); var skewbExternalQuantumZ = new QuantumMove("z"); var skewbInternalQuantumZ = new QuantumMove("Fv"); var skewbInternalQuantumZPrime = new QuantumMove("Bv"); var SkewbNotationMapper = class { constructor(child) { this.child = child; } notationToInternal(move) { if (move.innerLayer || move.outerLayer) { return null; } const newFamily = skewbFamilyMap[move.family]; if (newFamily) { return new Move( new QuantumMove(newFamily, move.outerLayer, move.innerLayer), move.amount ); } if (skewbExternalQuantumX.isIdentical(move.quantum)) { return new Move(skewbInternalQuantumX, move.amount); } if (skewbExternalQuantumY.isIdentical(move.quantum)) { return new Move(skewbInternalQuantumY, move.amount); } if (skewbExternalQuantumZ.isIdentical(move.quantum)) { return new Move(skewbInternalQuantumZ, move.amount); } return null; } // we never rewrite click moves to these moves. notationToExternal(move) { for (const [external, internal] of Object.entries(skewbFamilyMap)) { if (this.child.spinmatchv(move.family, internal)) { return new Move( new QuantumMove(external, move.innerLayer, move.outerLayer), move.amount ); } } if (skewbInternalQuantumX.isIdentical(move.quantum)) { return new Move(skewbExternalQuantumX, move.amount); } if (skewbInternalQuantumXPrime.isIdentical(move.quantum)) { return new Move(skewbExternalQuantumX, -move.amount); } if (skewbInternalQuantumY.isIdentical(move.quantum)) { return new Move(skewbExternalQuantumY, move.amount); } if (skewbInternalQuantumYPrime.isIdentical(move.quantum)) { return new Move(skewbExternalQuantumY, -move.amount); } if (skewbInternalQuantumZ.isIdentical(move.quantum)) { return new Move(skewbExternalQuantumZ, move.amount); } if (skewbInternalQuantumZPrime.isIdentical(move.quantum)) { return new Move(skewbExternalQuantumZ, -move.amount); } return null; } }; // src/cubing/puzzle-geometry/notation-mapping/NotationMapper.ts function remapKPuzzleDefinition(internalDefinition, notationMapper) { const externalDefinition = { ...internalDefinition, moves: {} }; for (const [internalMoveName, transformationData] of Object.entries( internalDefinition.moves )) { let prefix = internalMoveName; let suffix = ""; if (["v", "w"].includes(internalMoveName.at(-1))) { prefix = internalMoveName.slice(0, -1); suffix = internalMoveName.slice(-1); } const externalPrefix = notationMapper.notationToExternal( Move.fromString(prefix) ); if (!externalPrefix) { continue; } const externalMoveName = externalPrefix + suffix; if (!externalMoveName) { throw new Error( `Missing external move name for: ${internalMoveName.toString()}` ); } externalDefinition.moves[externalMoveName.toString()] = transformationData; } return externalDefinition; } // src/cubing/puzzle-geometry/Options.ts var PuzzleGeometryFullOptions = class { verbosity = 0; // verbosity (console.log) allMoves = false; // generate all slice moves in ksolve outerBlockMoves = false; // generate outer block moves vertexMoves = false; // generate vertex moves addRotations = false; // add symmetry information to ksolve output moveList = null; // move list to generate fixedOrientation = false; // eliminate any orientations fixedPieceType = null; // fix a piece? orientCenters = false; // orient centers? // TODO: Group these into a single object? includeCornerOrbits = true; // include corner orbits includeCenterOrbits = true; // include center orbits includeEdgeOrbits = true; // include edge orbits // Overrides the previous options. excludeOrbits = []; // exclude these orbits optimizeOrbits = false; // optimize PermOri grayCorners = false; // make corner sets gray grayCenters = false; // make center sets gray grayEdges = false; // make edge sets gray puzzleOrientation = null; // single puzzle orientation from options puzzleOrientations = null; // puzzle orientation override object from options // TODO: is this needed? scrambleAmount = 0; // scramble? constructor(options = {}) { const optionsWithoutUndefined = Object.fromEntries( Object.entries(options).filter( ([_, value]) => typeof value !== "undefined" ) ); Object.assign(this, optionsWithoutUndefined); } }; // src/cubing/puzzle-geometry/Perm.ts var zeroCache = []; var iotaCache = []; function zeros(n) { if (!zeroCache[n]) { const c = Array(n); for (let i = 0; i < n; i++) { c[i] = 0; } zeroCache[n] = c; } return zeroCache[n]; } function iota(n) { if (!iotaCache[n]) { const c = Array(n); for (let i = 0; i < n; i++) { c[i] = i; } iotaCache[n] = c; } return iotaCache[n]; } function identity(n) { return new Perm(iota(n)); } function factorial(a) { let r = BigInt(1); while (a > 1) { r *= BigInt(a); a--; } return r; } function gcd(a, b) { if (a > b) { const t = a; a = b; b = t; } while (a > 0) { const m = b % a; b = a; a = m; } return b; } function lcm(a, b) { return a / gcd(a, b) * b; } var Perm = class _Perm { n; // length p; // The permutation itself constructor(a) { this.n = a.length; this.p = a; } toString() { return `Perm[${this.p.join(" ")}]`; } mul(p2) { const c = Array(this.n); for (let i = 0; i < this.n; i++) { c[i] = p2.p[this.p[i]]; } return new _Perm(c); } rmul(p2) { const c = Array(this.n); for (let i = 0; i < this.n; i++) { c[i] = this.p[p2.p[i]]; } return new _Perm(c); } inv() { const c = Array(this.n); for (let i = 0; i < this.n; i++) { c[this.p[i]] = i; } return new _Perm(c); } compareTo(p2) { for (let i = 0; i < this.n; i++) { if (this.p[i] !== p2.p[i]) { return this.p[i] - p2.p[i]; } } return 0; } toGap() { const cyc = []; const seen = new Array(this.n); for (let i = 0; i < this.p.length; i++) { if (seen[i] || this.p[i] === i) { continue; } const incyc = []; for (let j = this.p[i]; !seen[j]; j = this.p[j]) { incyc.push(1 + j); seen[j] = true; } cyc.push(`(${incyc.reverse().join(",")})`); } return cyc.join(""); } toMathematica() { const cyc = []; const seen = new Array(this.n); for (let i = 0; i < this.p.length; i++) { if (seen[i] || this.p[i] === i) { continue; } const incyc = []; for (let j = this.p[i]; !seen[j]; j = this.p[j]) { incyc.push(1 + j); seen[j] = true; } cyc.push(`{${incyc.reverse().join(",")}}`); } return `Cycles[{${cyc.join(",")}}]`; } order() { let r = 1; const seen = new Array(this.n); for (let i = 0; i < this.p.length; i++) { if (seen[i] || this.p[i] === i) { continue; } let cs = 0; for (let j = i; !seen[j]; j = this.p[j]) { cs++; seen[j] = true; } r = lcm(r, cs); } return r; } }; // src/cubing/puzzle-geometry/PermOriSet.ts var PGOrbitDef = class { constructor(size, mod) { this.size = size; this.mod = mod; } reassemblySize() { return factorial(this.size) * BigInt(this.mod) ** BigInt(this.size); } }; var lastGlobalDefinitionCounter = 0; function externalName(mapper, moveString) { const mv = Move.fromString(moveString); const mv2 = mapper.notationToExternal(mv); if (mv2 === null || mv === mv2) { return moveString; } return mv2.toString(); } var PGOrbitsDef = class _PGOrbitsDef { constructor(orbitnames, orbitdefs, solved, movenames, moveops, isRotation, forcenames) { this.orbitnames = orbitnames; this.orbitdefs = orbitdefs; this.solved = solved; this.movenames = movenames; this.moveops = moveops; this.isRotation = isRotation; this.forcenames = forcenames; } toKTransformationData(t) { const ktransformationData = {}; for (let i = 0; i < this.orbitnames.length; i++) { ktransformationData[this.orbitnames[i]] = t.orbits[i].toKTransformationOrbitData(); } return ktransformationData; } toKPatternData(t) { const kpatternData = {}; for (let i = 0; i < this.orbitnames.length; i++) { kpatternData[this.orbitnames[i]] = t.orbits[i].toKPatternOrbitData(); } return kpatternData; } // TODO: remove this static transformToKTransformationData(orbitnames, t) { const mp = {}; for (let j = 0; j < orbitnames.length; j++) { mp[orbitnames[j]] = t.orbits[j].toKTransformationOrbitData(); } return mp; } describeSet(s, r, mapper) { const n = this.orbitdefs[s].size; const m = new Array(n); for (let i = 0; i < n; i++) { m[i] = []; } for (let i = 0; i < this.movenames.length; i++) { if (this.isRotation[i]) { continue; } let mvname = this.movenames[i]; if (!this.forcenames[i]) { mvname = externalName(mapper, mvname); if (mvname[mvname.length - 1] === "'") { mvname = mvname.substring(0, mvname.length - 1); } } const pd = this.moveops[i].orbits[s]; for (let j = 0; j < n; j++) { if (pd.perm[j] !== j || pd.ori[j] !== 0) { m[j].push(mvname); } } } for (let j = 0; j < n; j++) { r.push(`# ${j + 1} ${m[j].join(" ")}`); } } toKsolve(name, mapper = new NullMapper()) { const result = []; result.push(`Name ${name}`); result.push(""); for (let i = 0; i < this.orbitnames.length; i++) { result.push( `Set ${this.orbitnames[i]} ${this.orbitdefs[i].size} ${this.orbitdefs[i].mod}` ); this.describeSet(i, result, mapper); } result.push(""); result.push("Solved"); for (let i = 0; i < this.orbitnames.length; i++) { this.solved.orbits[i].appendDefinition( result, this.orbitnames[i], false, false ); } result.push("End"); for (let i = 0; i < this.movenames.length; i++) { result.push(""); let name2 = this.movenames[i]; if (!this.forcenames[i]) { name2 = externalName(mapper, this.movenames[i]); } let doinv = false; if (name2[name2.length - 1] === "'") { doinv = true; name2 = name2.substring(0, name2.length - 1); } result.push(`Move ${name2}`); for (let j = 0; j < this.orbitnames.length; j++) { if (doinv) { this.moveops[i].orbits[j].inv().appendDefinition(result, this.orbitnames[j], true); } else { this.moveops[i].orbits[j].appendDefinition( result, this.orbitnames[j], true ); } } result.push("End"); } return result; } // TODO: return type. toKPuzzleDefinition(includemoves) { const orbits = []; const defaultPatternData = {}; for (let i = 0; i < this.orbitnames.length; i++) { orbits.push({ orbitName: this.orbitnames[i], numPieces: this.orbitdefs[i].size, numOrientations: this.orbitdefs[i].mod }); const defaultPatternFrom = this.solved.orbits[i].toKTransformationOrbitData(); defaultPatternData[this.orbitnames[i]] = { pieces: defaultPatternFrom.permutation, orientation: defaultPatternFrom.orientationDelta }; } const moves = {}; if (includemoves) { for (let i = 0; i < this.movenames.length; i++) { moves[this.movenames[i]] = this.toKTransformationData(this.moveops[i]); } } return { name: `PG3D #${++lastGlobalDefinitionCounter}`, orbits, defaultPattern: defaultPatternData, moves }; } optimize() { const neworbitnames = []; const neworbitdefs = []; const newsolved = []; const newmoveops = []; for (let j = 0; j < this.moveops.length; j++) { newmoveops.push([]); } for (let i = 0; i < this.orbitdefs.length; i++) { const om = this.orbitdefs[i].mod; const n = this.orbitdefs[i].size; const du = new DisjointUnion(n); const changed = new Array(this.orbitdefs[i].size); for (let k = 0; k < n; k++) { changed[k] = false; } for (let j = 0; j < this.moveops.length; j++) { for (let k = 0; k < n; k++) { if (this.moveops[j].orbits[i].perm[k] !== k || this.moveops[j].orbits[i].ori[k] !== 0) { if (!this.isRotation[j]) { changed[k] = true; } du.union(k, this.moveops[j].orbits[i].perm[k]); } } } let keepori = true; if (om > 1) { keepori = false; const duo = new DisjointUnion(this.orbitdefs[i].size * om); for (let j = 0; j < this.moveops.length; j++) { for (let k = 0; k < n; k++) { if (this.moveops[j].orbits[i].perm[k] !== k || this.moveops[j].orbits[i].ori[k] !== 0) { for (let o = 0; o < om; o++) { duo.union( k * om + o, this.moveops[j].orbits[i].perm[k] * om + (o + this.moveops[j].orbits[i].ori[k]) % om ); } } } } for (let j = 0; !keepori && j < n; j++) { for (let o = 1; o < om; o++) { if (duo.find(j * om) === duo.find(j * om + o)) { keepori = true; } } } for (let j = 0; !keepori && j < n; j++) { for (let k = 0; k < j; k++) { if (this.solved.orbits[i].perm[j] === this.solved.orbits[i].perm[k]) { keepori = true; } } } } let nontriv = -1; let multiple = false; for (let j = 0; j < this.orbitdefs[i].size; j++) { if (changed[j]) { const h = du.find(j); if (nontriv < 0) { nontriv = h; } else if (nontriv !== h) { multiple = true; } } } for (let j = 0; j < this.orbitdefs[i].size; j++) { if (!changed[j]) { continue; } const h = du.find(j); if (h !== j) { continue; } const no = []; const on = []; let nv = 0; for (let k = 0; k < this.orbitdefs[i].size; k++) { if (du.find(k) === j) { no[nv] = k; on[k] = nv; nv++; } } if (multiple) { neworbitnames.push(`${this.orbitnames[i]}_p${j}`); } else { neworbitnames.push(this.orbitnames[i]); } if (keepori) { neworbitdefs.push(new PGOrbitDef(nv, this.orbitdefs[i].mod)); newsolved.push(this.solved.orbits[i].remapVS(no, nv)); for (let k = 0; k < this.moveops.length; k++) { newmoveops[k].push(this.moveops[k].orbits[i].remap(no, on, nv)); } } else { neworbitdefs.push(new PGOrbitDef(nv, 1)); newsolved.push(this.solved.orbits[i].remapVS(no, nv).killOri()); for (let k = 0; k < this.moveops.length; k++) { newmoveops[k].push( this.moveops[k].orbits[i].remap(no, on, nv).killOri() ); } } } } return new _PGOrbitsDef( neworbitnames, neworbitdefs, new VisibleState(newsolved), this.movenames, newmoveops.map((_) => new PGTransform(_)), this.isRotation, this.forcenames ); } // replace the solved state with a new scrambled state. scramble(n) { this.solved = this.solved.mul(this.getScrambleTransformation(n)); } // generate a new "random" position based on an entropy pool // this should be significantly faster and more random than just // doing a large number of random moves, especially on big puzzles. getScrambleTransformation(n) { if (n < 100) { n = 100; } const pool = []; for (let i = 0; i < this.moveops.length; i++) { pool[i] = this.moveops[i]; } for (let i = 0; i < pool.length; i++) { const j = Math.floor(Math.random() * pool.length); const t = pool[i]; pool[i] = pool[j]; pool[j] = t; } if (n < pool.length) { n = pool.length; } for (let i = 0; i < n; i++) { const ri = Math.floor(Math.random() * pool.length); const rj = Math.floor(Math.random() * pool.length); const rm = Math.floor(Math.random() * this.moveops.length); pool[ri] = pool[ri].mul(pool[rj]).mul(this.moveops[rm]); if (Math.random() < 0.1) { pool[ri] = pool[ri].mul(this.moveops[rm]); } } let s = pool[0]; for (let i = 1; i < pool.length; i++) { s = s.mul(pool[i]); } return s; } reassemblySize() { let n = BigInt(1); for (let i = 0; i < this.orbitdefs.length; i++) { n *= this.orbitdefs[i].reassemblySize(); } return n; } }; var PGOrbit = class _PGOrbit { constructor(perm, ori, orimod) { this.perm = perm; this.ori = ori; this.orimod = orimod; } static ktransformationCache = []; static e(n, mod) { return new _PGOrbit(iota(n), zeros(n), mod); } mul(b) { const n = this.perm.length; const newPerm = new Array(n); if (this.orimod === 1) { for (let i = 0; i < n; i++) { newPerm[i] = this.perm[b.perm[i]]; } return new _PGOrbit(newPerm, this.ori, this.orimod); } else { const newOri = new Array(n); for (let i = 0; i < n; i++) { newPerm[i] = this.perm[b.perm[i]]; newOri[i] = (this.ori[b.perm[i]] + b.ori[i]) % this.orimod; } return new _PGOrbit(newPerm, newOri, this.orimod); } } inv() { const n = this.perm.length; const newPerm = new Array(n); const newOri = new Array(n); for (let i = 0; i < n; i++) { newPerm[this.perm[i]] = i; newOri[this.perm[i]] = (this.orimod - this.ori[i]) % this.orimod; } return new _PGOrbit(newPerm, newOri, this.orimod); } equal(b) { const n = this.perm.length; for (let i = 0; i < n; i++) { if (this.perm[i] !== b.perm[i] || this.ori[i] !== b.ori[i]) { return false; } } return true; } // in-place mutator killOri() { const n = this.perm.length; for (let i = 0; i < n; i++) { this.ori[i] = 0; } this.orimod = 1; return this; } toPerm() { const o = this.orimod; if (o === 1) { return new Perm(this.perm); } const n = this.perm.length; const newPerm = new Array(n * o); for (let i = 0; i < n; i++) { for (let j = 0; j < o; j++) { newPerm[i * o + j] = o * this.perm[i] + (this.ori[i] + j) % o; } } return new Perm(newPerm); } // returns tuple of sets of identical pieces in this orbit identicalPieces() { const done = []; const n = this.perm.length; const r = []; for (let i = 0; i < n; i++) { const v = this.perm[i]; if (done[v] === void 0) { const s = [i]; done[v] = true; for (let j = i + 1; j < n; j++) { if (this.perm[j] === v) { s.push(j); } } r.push(s); } } return r; } order() { return this.toPerm().order(); } isIdentity() { const n = this.perm.length; if (this.perm === iota(n) && this.ori === zeros(n)) { return true; } for (let i = 0; i < n; i++) { if (this.perm[i] !== i || this.ori[i] !== 0) { return false; } } return true; } zeroOris() { const n = this.perm.length; if (this.ori === zeros(n)) { return true; } for (let i = 0; i < n; i++) { if (this.ori[i] !== 0) { return false; } } return true; } remap(no, on, nv) { const newPerm = new Array(nv); const newOri = new Array(nv); for (let i = 0; i < nv; i++) { newPerm[i] = on[this.perm[no[i]]]; newOri[i] = this.ori[no[i]]; } return new _PGOrbit(newPerm, newOri, this.orimod); } remapVS(no, nv) { const newPerm = new Array(nv); const newOri = new Array(nv); let nextNew = 0; const reassign = []; for (let i = 0; i < nv; i++) { const ov = this.perm[no[i]]; if (reassign[ov] === void 0) { reassign[ov] = nextNew++; } newPerm[i] = reassign[ov]; newOri[i] = this.ori[no[i]]; } return new _PGOrbit(newPerm, newOri, this.orimod); } appendDefinition(result, name, useVS, concise = true) { if (concise && this.isIdentity()) { return; } result.push(name); result.push(this.perm.map((_) => _ + 1).join(" ")); if (!this.zeroOris()) { if (useVS) { const newori = new Array(this.ori.length); for (let i = 0; i < newori.length; i++) { newori[this.perm[i]] = this.ori[i]; } result.push(newori.join(" ")); } else { result.push(this.ori.join(" ")); } } } toKTransformationOrbitData() { const n = this.perm.length; if (this.isIdentity()) { if (!_PGOrbit.ktransformationCache[n]) { _PGOrbit.ktransformationCache[n] = { permutation: iota(n), orientationDelta: zeros(n) }; } return _PGOrbit.ktransformationCache[n]; } else { return { permutation: this.perm, orientationDelta: this.ori }; } } toKPatternOrbitData() { const n = this.perm.length; return { pieces: this.perm, orientation: this.ori, orientationMod: zeros(n) }; } }; var PGTransformBase = class { constructor(orbits) { this.orbits = orbits; } internalMul(b) { const newOrbits = []; for (let i = 0; i < this.orbits.length; i++) { newOrbits.push(this.orbits[i].mul(b.orbits[i])); } return newOrbits; } internalInv() { const newOrbits = []; for (const orbit of this.orbits) { newOrbits.push(orbit.inv()); } return newOrbits; } equal(b) { for (let i = 0; i < this.orbits.length; i++) { if (!this.orbits[i].equal(b.orbits[i])) { return false; } } return true; } killOri() { for (const orbit of this.orbits) { orbit.killOri(); } return this; } toPerm() { const perms = []; let n = 0; for (const orbit of this.orbits) { const p = orbit.toPerm(); perms.push(p); n += p.n; } const newPerm = new Array(n); n = 0; for (const p of perms) { for (let j = 0; j < p.n; j++) { newPerm[n + j] = n + p.p[j]; } n += p.n; } return new Perm(newPerm); } identicalPieces() { const r = []; let n = 0; for (const orbit of this.orbits) { const o = orbit.orimod; const s = orbit.identicalPieces(); for (let j = 0; j < s.length; j++) { r.push(s[j].map((_) => _ * o + n)); } n += o * orbit.perm.length; } return r; } order() { let r = 1; for (const orbit of this.orbits) { r = lcm(r, orbit.order()); } return r; } }; var PGTransform = class _PGTransform extends PGTransformBase { mul(b) { return new _PGTransform(this.internalMul(b)); } mulScalar(n) { if (n === 0) { return this.e(); } let t = this; if (n < 0) { t = t.inv(); n = -n; } while ((n & 1) === 0) { t = t.mul(t); n >>= 1; } if (n === 1) { return t; } let s = t; let r = this.e(); while (n > 0) { if (n & 1) { r = r.mul(s); } if (n > 1) { s = s.mul(s); } n >>= 1; } return r; } inv() { return new _PGTransform(this.internalInv()); } e() { return new _PGTransform( this.orbits.map((_) => PGOrbit.e(_.perm.length, _.orimod)) ); } }; var VisibleState = class _VisibleState extends PGTransformBase { mul(b) { return new _VisibleState(this.internalMul(b)); } }; var DisjointUnion = class { constructor(n) { this.n = n; this.heads = new Array(n); for (let i = 0; i < n; i++) { this.heads[i] = i; } } heads; find(v) { let h = this.heads[v]; if (this.heads[h] === h) { return h; } h = this.find(this.heads[h]); this.heads[v] = h; return h; } union(a, b) { const ah = this.find(a); const bh = this.find(b); if (ah < bh) { this.heads[bh] = ah; } else if (ah > bh) { this.heads[ah] = bh; } } }; function showcanon(g, disp) { const n = g.moveops.length; if (n > 30) { throw new Error("Canon info too big for bitmask"); } const orders = []; const commutes = []; for (let i = 0; i < n; i++) { const permA = g.moveops[i]; orders.push(permA.order()); let bits = 0; for (let j = 0; j < n; j++) { if (j === i) { continue; } const permB = g.moveops[j]; if (permA.mul(permB).equal(permB.mul(permA))) { bits |= 1 << j; } } commutes.push(bits); } let curlev = {}; curlev[0] = 1; for (let d = 0; d < 100; d++) { let sum = 0; const nextlev = {}; let uniq = 0; for (const sti in curlev) { const st = +sti; const cnt = curlev[st]; sum += cnt; uniq++; for (let mv = 0; mv < orders.length; mv++) { if ((st >> mv & 1) === 0 && (st & commutes[mv] & (1 << mv) - 1) === 0) { const nst = st & commutes[mv] | 1 << mv; if (nextlev[nst] === void 0) { nextlev[nst] = 0; } nextlev[nst] += (orders[mv] - 1) * cnt; } } } disp(`${d}: canonseq ${sum} states ${uniq}`); curlev = nextlev; } } // src/cubing/puzzle-geometry/Quat.ts var eps = 1e-9; function centermassface(face) { let s = new Quat(0, 0, 0, 0); for (let i = 0; i < face.length; i++) { s = s.sum(face[i]); } return s.smul(1 / face.length); } function solvethreeplanes(p1, p2, p3, planes) { const p = planes[p1].intersect3(planes[p2], planes[p3]); if (!p) { return p; } for (let i = 0; i < planes.length; i++) { if (i !== p1 && i !== p2 && i !== p3) { const dt = planes[i].b * p.b + planes[i].c * p.c + planes[i].d * p.d; if (planes[i].a > 0 && dt > planes[i].a || planes[i].a < 0 && dt < planes[i].a) { return false; } } } return p; } var Quat = class _Quat { constructor(a, b, c, d) { this.a = a; this.b = b; this.c = c; this.d = d; } mul(q) { return new _Quat( this.a * q.a - this.b * q.b - this.c * q.c - this.d * q.d, this.a * q.b + this.b * q.a + this.c * q.d - this.d * q.c, this.a * q.c - this.b * q.d + this.c * q.a + this.d * q.b, this.a * q.d + this.b * q.c - this.c * q.b + this.d * q.a ); } toString() { return `Q[${this.a},${this.b},${this.c},${this.d}]`; } dist(q) { return Math.hypot(this.a - q.a, this.b - q.b, this.c - q.c, this.d - q.d); } len() { return Math.hypot(this.a, this.b, this.c, this.d); } cross(q) { return new _Quat( 0, this.c * q.d - this.d * q.c, this.d * q.b - this.b * q.d, this.b * q.c - this.c * q.b ); } dot(q) { return this.b * q.b + this.c * q.c + this.d * q.d; } normalize() { const d = Math.sqrt(this.dot(this)); return new _Quat(this.a / d, this.b / d, this.c / d, this.d / d); } makenormal() { return new _Quat(0, this.b, this.c, this.d).normalize(); } normalizeplane() { const d = Math.hypot(this.b, this.c, this.d); return new _Quat(this.a / d, this.b / d, this.c / d, this.d / d); } smul(m) { return new _Quat(this.a * m, this.b * m, this.c * m, this.d * m); } sum(q) { return new _Quat(this.a + q.a, this.b + q.b, this.c + q.c, this.d + q.d); } sub(q) { return new _Quat(this.a - q.a, this.b - q.b, this.c - q.c, this.d - q.d); } angle() { return 2 * Math.acos(this.a); } invrot() { return new _Quat(this.a, -this.b, -this.c, -this.d); } det3x3(a00, a01, a02, a10, a11, a12, a20, a21, a22) { return a00 * (a11 * a22 - a12 * a21) + a01 * (a12 * a20 - a10 * a22) + a02 * (a10 * a21 - a11 * a20); } rotateplane(q) { const t = q.mul(new _Quat(0, this.b, this.c, this.d)).mul(q.invrot()); t.a = this.a; return t; } // return any vector orthogonal to the given one. Find the smallest // component (in absolute value) and return the cross product of that // axis with the given vector. orthogonal() { const ab = Math.abs(this.b); const ac = Math.abs(this.c); const ad = Math.abs(this.d); if (ab < ac && ab < ad) { return this.cross(new _Quat(0, 1, 0, 0)).normalize(); } else if (ac < ab && ac < ad) { return this.cross(new _Quat(0, 0, 1, 0)).normalize(); } else { return this.cross(new _Quat(0, 0, 0, 1)).normalize(); } } // return the Quaternion that will rotate the this vector // to the b vector through rotatepoint. pointrotation(b) { const a = this.normalize(); b = b.normalize(); if (a.sub(b).len() < eps) { return new _Quat(1, 0, 0, 0); } let h = a.sum(b); if (h.len() < eps) { h = h.orthogonal(); } else { h = h.normalize(); } const r = a.cross(h); r.a = a.dot(h); return r; } // given two vectors, return the portion of the first that // is not in the direction of the second. unproject(b) { return this.sum(b.smul(-this.dot(b) / (this.len() * b.len()))); } rotatepoint(q) { return q.mul(this).mul(q.invrot()); } rotateface(face) { return face.map((_) => _.rotatepoint(this)); } intersect3(p2, p3) { const det = this.det3x3( this.b, this.c, this.d, p2.b, p2.c, p2.d, p3.b, p3.c, p3.d ); if (Math.abs(det) < eps) { return false; } return new _Quat( 0, this.det3x3(this.a, this.c, this.d, p2.a, p2.c, p2.d, p3.a, p3.c, p3.d) / det, this.det3x3(this.b, this.a, this.d, p2.b, p2.a, p2.d, p3.b, p3.a, p3.d) / det, this.det3x3(this.b, this.c, this.a, p2.b, p2.c, p2.a, p3.b, p3.c, p3.a) / det ); } side(x) { if (x > eps) { return 1; } if (x < -eps) { return -1; } return 0; } /** * Cuts a face by this plane, or returns null if there * is no intersection. * @param face The face to cut. */ cutface(face) { const d = this.a; let seen = 0; let r = null; for (let i = 0; i < face.length; i++) { seen |= 1 << this.side(face[i].dot(this) - d) + 1; } if ((seen & 5) === 5) { r = []; const inout = face.map((_) => this.side(_.dot(this) - d)); for (let s = -1; s <= 1; s += 2) { const nface = []; for (let k = 0; k < face.length; k++) { if (inout[k] === s || inout[k] === 0) { nface.push(face[k]); } const kk = (k + 1) % face.length; if (inout[k] + inout[kk] === 0 && inout[k] !== 0) { const vk = face[k].dot(this) - d; const vkk = face[kk].dot(this) - d; const r2 = vk / (vk - vkk); const pt = face[k].smul(1 - r2).sum(face[kk].smul(r2)); nface.push(pt); } } r.push(nface); } } return r; } cutfaces(faces) { const nfaces = []; for (let j = 0; j < faces.length; j++) { const face = faces[j]; const t = this.cutface(face); if (t) { nfaces.push(t[0]); nfaces.push(t[1]); } else { nfaces.push(face); } } return nfaces; } faceside(face) { const d = this.a; for (let i = 0; i < face.length; i++) { const s = this.side(face[i].dot(this) - d); if (s !== 0) { return s; } } throw new Error("Could not determine side of plane in faceside"); } sameplane(p) { const a = this.normalize(); const b = p.normalize(); return a.dist(b) < eps || a.dist(b.smul(-1)) < eps; } makecut(r) { return new _Quat(r, this.b, this.c, this.d); } }; // src/cubing/puzzle-geometry/PlatonicGenerator.ts var eps2 = 1e-9; function cube() { const s5 = Math.sqrt(0.5); return [new Quat(s5, s5, 0, 0), new Quat(s5, 0, s5, 0)]; } function tetrahedron() { return [new Quat(0.5, 0.5, 0.5, 0.5), new Quat(0.5, 0.5, 0.5, -0.5)]; } function dodecahedron() { const d36 = 2 * Math.PI / 10; let dx = 0.5 + 0.3 * Math.sqrt(5); let dy = 0.5 + 0.1 * Math.sqrt(5); const dd = Math.sqrt(dx * dx + dy * dy); dx /= dd; dy /= dd; return [ new Quat(Math.cos(d36), dx * Math.sin(d36), dy * Math.sin(d36), 0), new Quat(0.5, 0.5, 0.5, 0.5) ]; } function icosahedron() { let dx = 1 / 6 + Math.sqrt(5) / 6; let dy = 2 / 3 + Math.sqrt(5) / 3; const dd = Math.sqrt(dx * dx + dy * dy); dx /= dd; dy /= dd; const ang = 2 * Math.PI / 6; return [ new Quat(Math.cos(ang), dx * Math.sin(ang), dy * Math.sin(ang), 0), new Quat(Math.cos(ang), -dx * Math.sin(ang), dy * Math.sin(ang), 0) ]; } function octahedron() { const s5 = Math.sqrt(0.5); return [new Quat(0.5, 0.5, 0.5, 0.5), new Quat(s5, 0, 0, s5)]; } function closure(g) { const q = [new Quat(1, 0, 0, 0)]; for (let i = 0; i < q.length; i++) { for (let j = 0; j < g.length; j++) { const ns = g[j].mul(q[i]); const negns = ns.smul(-1); let wasseen = false; for (let k = 0; k < q.length; k++) { if (ns.dist(q[k]) < eps2 || negns.dist(q[k]) < eps2) { wasseen = true; break; } }