@vscubing/cubing
Version:
A collection of JavaScript cubing libraries.
404 lines (395 loc) • 10.9 kB
JavaScript
import {
packageVersion
} from "./chunks/chunk-BSOKOGXQ.js";
// src/bin/puzzle-geometry-bin.ts
import { basename } from "node:path";
import { argv } from "node:process";
import {
argument,
constant,
flag,
integer,
map,
merge,
message,
multiple,
object,
option,
optional,
or,
string
} from "@optique/core";
import { run } from "@optique/run";
import { Move } from "@vscubing/cubing/alg";
import {
getPG3DNamedPuzzles,
PuzzleGeometry,
parsePuzzleDescription
} from "@vscubing/cubing/puzzle-geometry";
import { PrintableShellCommand } from "printable-shell-command";
var puzzleList = getPG3DNamedPuzzles();
function antiBool(optionName, description) {
return optional(
map(
flag(optionName, {
description
}),
(v) => !v
)
);
}
var subcommandDefaults = {
commentStyle: constant("hash"),
forceQuiet: constant(void 0)
};
var args = run(
merge(
object({
verbosity: optional(
or(
map(multiple(flag("--verbose", "-v")), (v) => v.length),
map(flag("--quiet", "-q"), () => 0)
)
)
}),
or(
object({
...subcommandDefaults,
subcommand: constant("KSolve"),
subcommandFlag: flag("--ksolve", {
description: message`Print KSolve (\`.tws\`).`
})
}),
object({
...subcommandDefaults,
subcommand: constant("SVG"),
subcommandFlag: flag("--svg", {
description: message`Print SVG. Forces \`--quiet\`.`
}),
commentStyle: constant("none"),
svg3D: optional(
option("--3d", {
description: message`Use 3D format for SVG file.`
})
),
verbosity: constant(0)
}),
object({
...subcommandDefaults,
subcommand: constant("GAP"),
subcommandFlag: flag("--gap", {
description: message`Print GAP output.`
})
}),
object({
...subcommandDefaults,
subcommand: constant("Mathematica"),
subcommandFlag: flag("--mathematica", {
description: message`Print Mathematica output.`
}),
commentStyle: constant("Pascal")
}),
object({
...subcommandDefaults,
subcommand: constant("Schreier-Sims"),
subcommandFlag: flag("--ss", {
description: message`Perform Schrier-Sims calculation.`
})
}),
object({
...subcommandDefaults,
subcommand: constant("Canonical string analysis"),
subcommandFlag: flag("--canon", {
description: message`Print canonical string analysis.`
})
}),
object({
...subcommandDefaults,
subcommand: constant("3D"),
subcommandFlag: flag("--3d", {
description: message`Print 3D information.`
})
})
),
object({
// This doesn't apply to SVG, but we place it here so that it doesn't print once for each non-SVG subcommand in the help string.
optimizeOrbits: option("--optimize", {
description: message`Optimize output (when possible).`
}),
addRotations: option("--rotations", {
description: message`Include full-puzzle rotations as moves.`
}),
allMoves: option("--allmoves", {
description: message`Includes all moves (i.e., slice moves for 3x3x3).`
}),
outerBlockMoves: option("--outerblockmoves", {
description: message`Use outer block moves rather than slice moves.`
}),
vertexMoves: option("--vertexmoves", {
description: message`For tetrahedral puzzles, prefer vertex moves to face moves.`
}),
includeCornerOrbits: antiBool(
"--nocorners",
message`Ignore all corners.`
),
excludeOrbits: optional(
map(
option("--omit", string({ metavar: "COMMA_SEPARATED_ORBITS" }), {
description: message`Omit orbits.`
}),
(s) => s.split(",")
)
),
includeEdgeOrbits: antiBool("--noedges", message`Ignore all edges.`),
includeCenterOrbits: antiBool(
"--nocenters",
message`Ignore all centers.`
),
grayCorners: option("--graycorners", {
description: message`Gray corners.`
}),
grayEdges: option("--grayedges", { description: message`Gray edges.` }),
grayCenters: option("--graycenters", {
description: message`Gray centers.`
}),
fixedOrientation: option("--noorientation", {
description: message`Ignore orientations.`
}),
orientCenters: option("--orientcenters", {
description: message`Give centers an orientation.`
})
}),
optional(
or(
object({
puzzleOrientation: optional(
map(
option(
"--puzzleorientation",
string({ metavar: "JSON_STRING" }),
{
description: message`For 3D formats, give puzzle orientation.`
}
),
(s) => JSON.parse(s)
)
),
puzzleOrientations: constant(void 0)
}),
object({
puzzleOrientation: constant(void 0),
puzzleOrientations: optional(
map(
option(
"--puzzleorientations",
string({ metavar: "JSON_STRING" }),
{
description: message`For 3D formats, give puzzle orientations.`
}
),
(s) => JSON.parse(s)
)
)
})
)
),
object({
fixedPieceType: optional(
or(
map(
flag("--fixcorner", {
description: message`Auto-select a subset of moves to keep a corner fixed in place.`
}),
() => "v"
),
map(
flag("--fixedge", {
description: message`Auto-select a subset of moves to keep an edge fixed in place.`
}),
() => "e"
),
map(
flag("--fixcenter", {
description: message`Auto-select a subset of moves to keep a center fixed in place.`
}),
() => "f"
)
)
),
// TODO: this doesn't make sense for all subcommands?
scrambleAmount: optional(
option("--scramble", integer({ min: 0 }), {
description: message`Scramble solved position.`
})
),
moveList: optional(
map(
option("--moves", string({ metavar: "COMMA_SEPARATED_MOVES" }), {
description: message`Restrict moves to this list. Example: \"U2,F,r\").`
}),
(s) => s.split(",").map((m) => Move.fromString(m))
)
),
puzzle: map(
argument(string({ metavar: "PUZZLE" }), {
description: message`The puzzle can be given as a geometric description or by name.
The geometric description starts with one of:
- \`c\` (cube),\n
- \`t\` (tetrahedron),\n
- \`d\` (dodecahedron),\n
- \`i\` (icosahedron),\n
- \`o\` (octahedron),\n
then a space, then a series of cuts. Each cut begins with one of:
- \`f\` (for a cut parallel to faces),\n
- \`v\` (for a cut perpendicular to a ray from the center through a corner),\n
- \`e\` (for a cut perpendicular to a ray from the center through an edge),\n
followed by a decimal number giving a distance, where 1 is the distance
between the center of the puzzle and the center of a face.
Example description: \`c f 0 v 0.577350269189626 e 0\`. Corresponds to: https://alpha.twizzle.net/explore/?puzzle=2x2x2+%2B+dino+%2B+little+chop
The recognized puzzle names are: ${Object.keys(puzzleList).map((p) => JSON.stringify(p)).join(", ")}`
}),
(s) => {
const parsed = parsePuzzleDescription(puzzleList[s] ?? s);
if (parsed === null) {
throw new Error("Could not parse puzzle description!");
}
return parsed;
}
)
})
),
{
programName: basename(argv[1]),
description: message`
Examples:
puzzle-geometry --ss 2x2x2\n
puzzle-geometry --ss --fixcorner 2x2x2\n
puzzle-geometry --ss --moves U,F2,r 4x4x4\n
puzzle-geometry --ksolve --optimize --moves U,F,R megaminx\n
puzzle-geometry --gap --noedges megaminx
`,
help: "option",
completion: {
mode: "option",
name: "plural"
},
version: {
mode: "option",
value: packageVersion
}
}
);
if (args.verbosity !== 0) {
const cmd = () => {
const [command, ...args2] = argv;
return new PrintableShellCommand(command, args2).getPrintableCommand({
argumentLineWrapping: "inline"
});
};
switch (args.commentStyle) {
case "hash": {
console.log(`# ${cmd()}`);
break;
}
case "none": {
break;
}
case "Pascal": {
console.log(`(* ${cmd()} *)`);
break;
}
default:
throw new Error("Invalid comment style.");
}
}
function buildPuzzleGeometry() {
const {
verbosity,
optimizeOrbits,
addRotations,
allMoves,
outerBlockMoves,
vertexMoves,
includeCornerOrbits,
includeCenterOrbits,
includeEdgeOrbits,
excludeOrbits,
grayCorners,
grayEdges,
grayCenters,
fixedOrientation,
orientCenters,
puzzleOrientation,
puzzleOrientations,
fixedPieceType,
scrambleAmount,
moveList
} = args;
const options = {
verbosity,
optimizeOrbits,
addRotations,
allMoves,
outerBlockMoves,
vertexMoves,
includeCornerOrbits,
includeCenterOrbits,
includeEdgeOrbits,
excludeOrbits,
grayCorners,
grayEdges,
grayCenters,
fixedOrientation,
orientCenters,
puzzleOrientation,
puzzleOrientations,
fixedPieceType,
scrambleAmount,
moveList
};
const puzzleGeometry = new PuzzleGeometry(args.puzzle, options);
puzzleGeometry.allstickers();
puzzleGeometry.genperms();
return puzzleGeometry;
}
switch (args.subcommand) {
case "KSolve": {
console.log(buildPuzzleGeometry().writeksolve());
break;
}
case "SVG": {
console.log(
buildPuzzleGeometry().generatesvg(
void 0,
void 0,
void 0,
args.svg3D
)
);
break;
}
case "GAP": {
console.log(buildPuzzleGeometry().writegap());
break;
}
case "Mathematica": {
console.log(buildPuzzleGeometry().writemathematica());
break;
}
case "Schreier-Sims": {
buildPuzzleGeometry().writeSchreierSims(console.log);
break;
}
case "Canonical string analysis": {
buildPuzzleGeometry().showcanon(console.log);
break;
}
case "3D": {
console.log(JSON.stringify(buildPuzzleGeometry().get3d(), null, " "));
break;
}
default:
throw new Error("Invalid subcommand.");
}
//# sourceMappingURL=puzzle-geometry-bin.js.map