UNPKG

@vscubing/cubing

Version:

A collection of JavaScript cubing libraries.

1,781 lines (1,766 loc) 146 kB
import { Move, QuantumMove } from "../chunks/chunk-T3WO4S5D.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/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/PGPuzzles.ts var PGPuzzles = { "2x2x2": "c f 0", "3x3x3": "c f 0.333333333333333", "4x4x4": "c f 0.5 f 0", "5x5x5": "c f 0.6 f 0.2", "6x6x6": "c f 0.666666666666667 f 0.333333333333333 f 0", "7x7x7": "c f 0.714285714285714 f 0.428571428571429 f 0.142857142857143", "8x8x8": "c f 0.75 f 0.5 f 0.25 f 0", "9x9x9": "c f 0.777777777777778 f 0.555555555555556 f 0.333333333333333 f 0.111111111111111", "10x10x10": "c f 0.8 f 0.6 f 0.4 f 0.2 f 0", "11x11x11": "c f 0.818181818181818 f 0.636363636363636 f 0.454545454545455 f 0.272727272727273 f 0.0909090909090909", "12x12x12": "c f 0.833333333333333 f 0.666666666666667 f 0.5 f 0.333333333333333 f 0.166666666666667 f 0", "13x13x13": "c f 0.846153846153846 f 0.692307692307692 f 0.538461538461538 f 0.384615384615385 f 0.230769230769231 f 0.0769230769230769", "20x20x20": "c f 0 f .1 f .2 f .3 f .4 f .5 f .6 f .7 f .8 f .9", "30x30x30": "c f 0 f .066667 f .133333 f .2 f .266667 f .333333 f .4 f .466667 f .533333 f .6 f .666667 f .733333 f .8 f .866667 f .933333", "40x40x40": "c f 0 f .05 f .1 f .15 f .2 f .25 f .3 f .35 f .4 f .45 f .5 f .55 f .6 f .65 f .7 f .75 f .8 f .85 f .9 f .95", skewb: "c v 0", "master skewb": "c v 0.275", "professor skewb": "c v 0 v 0.38", "compy cube": "c v 0.915641442663986", helicopter: "c e 0.707106781186547", "curvy copter": "c e 0.83", dino: "c v 0.577350269189626", "little chop": "c e 0", pyramorphix: "t e 0", mastermorphix: "t e 0.346184634065199", pyraminx: "t v 0.333333333333333 v 1.66666666666667", tetraminx: "t v 0.333333333333333", "master pyraminx": "t v 0 v 1 v 2", "master tetraminx": "t v 0 v 1", "professor pyraminx": "t v -0.2 v 0.6 v 1.4 v 2.2", "professor tetraminx": "t v -0.2 v 0.6 v 1.4", "royal pyraminx": "t v -0.333333333333333 v 0.333333333333333 v 1 v 1.66666666666667 v 2.33333333333333", "royal tetraminx": "t v -0.333333333333333 v 0.333333333333333 v 1 v 1.66666666666667", "emperor pyraminx": "t v -0.428571428571429 v 0.142857142857143 v 0.714285714285714 v 1.28571428571429 v 1.85714285714286 v 2.42857142857143", "emperor tetraminx": "t v -0.428571428571429 v 0.142857142857143 v 0.714285714285714 v 1.28571428571429 v 1.85714285714286", "Jing pyraminx": "t f 0", "master pyramorphix": "t e 0.866025403784437", megaminx: "d f 0.7", gigaminx: "d f 0.64 f 0.82", teraminx: "d f 0.64 f 0.76 f 0.88", petaminx: "d f 0.64 f 0.73 f 0.82 f 0.91", examinx: "d f 0.64 f 0.712 f 0.784 f 0.856 f 0.928", zetaminx: "d f 0.64 f 0.7 f 0.76 f 0.82 f 0.88 f 0.94", yottaminx: "d f 0.64 f 0.6914 f 0.7429 f 0.7943 f 0.8457 f 0.8971 f 0.9486", pentultimate: "d f 0", "master pentultimate": "d f 0.1", "elite pentultimate": "d f 0 f 0.145905", // exact value for starminx is sqrt(5(5-2 sqrt(5))/3) starminx: "d v 0.937962370425399", "starminx 2": "d f 0.23606797749979", "pyraminx crystal": "d f 0.447213595499989", chopasaurus: "d v 0", "big chop": "d e 0", "skewb diamond": "o f 0", FTO: "o f 0.333333333333333", "master FTO": "o f 0.5 f 0", "Christopher's jewel": "o v 0.577350269189626", octastar: "o e 0", "Trajber's octahedron": "o v 0.433012701892219", "radio chop": "i f 0", icosamate: "i v 0", "Regular Astrominx": "i v 0.18759247376021", "Regular Astrominx + Big Chop": "i v 0.18759247376021 e 0", Redicosahedron: "i v 0.794654472291766", "Redicosahedron with centers": "i v 0.84", Icosaminx: "i v 0.73", "Eitan's star": "i f 0.61803398874989", "2x2x2 + dino": "c f 0 v 0.577350269189626", "2x2x2 + little chop": "c f 0 e 0", "dino + little chop": "c v 0.577350269189626 e 0", "2x2x2 + dino + little chop": "c f 0 v 0.577350269189626 e 0", "megaminx + chopasaurus": "d f 0.61803398875 v 0", "starminx combo": "d f 0.23606797749979 v 0.937962370425399" }; // 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);