UNPKG

@vscubing/cubing

Version:

A collection of JavaScript cubing libraries.

404 lines (395 loc) 10.9 kB
#!/usr/bin/env -S node -- 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