The solver's position key reads stackAt (each tile oldest first). New tool tools/verify-levels.mjs checks course/10-spellgrid/levels/proofs.json: each level's shortest solution, and that it exhausts without its idea (neverRule) or on the naive engine. Levels 4-10, one course idea each, with test_10_level_N_solution in both tracks, scenarios and Lesson 10's list; l1 no longer matches l10, and proofs.json is not listed as a level. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
64 lines
2.8 KiB
JavaScript
64 lines
2.8 KiB
JavaScript
#!/usr/bin/env node
|
|
// Search a SpellGrid level (Module 10) for solutions, by brute force, with the course's
|
|
// TypeScript reference engine. Used in Module 13 to check that a level has the solution
|
|
// its designer meant and no other; tools/verify-levels.mjs runs it on every level's proof.
|
|
//
|
|
// node tools/solve-spellgrid.mjs [level] [--dir <dir>] [--max N] [--naive] [--never-rule "X IS Y"] [--all]
|
|
// npm run solve:spellgrid -- l1 --never-rule "DEATHRATTLE IS DOUBLE"
|
|
//
|
|
// Stages the Module 10 TypeScript reference with its cards and levels, as play-spellgrid
|
|
// does, copies tools/solver/solve.ts beside it and runs it there. `level` is a level of
|
|
// course/10-spellgrid/levels by name or prefix (`l1`), or a path to a level file; default `l1`.
|
|
// The other options are the solver's: see tools/solver/solve.ts.
|
|
import { cpSync, existsSync } from "node:fs";
|
|
import { spawnSync } from "node:child_process";
|
|
import { dirname, join, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { fail, isMain } from "./lib.mjs";
|
|
import { levelNames, resolveLevel, stagePlay } from "./play-spellgrid.mjs";
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const WITH_VALUE = ["--max", "--never-rule"];
|
|
const SWITCHES = ["--naive", "--all"];
|
|
|
|
export function parseArgs(argv) {
|
|
const opts = { level: "l1", dir: null, solver: [] };
|
|
const rest = [];
|
|
for (let i = 0; i < argv.length; i++) {
|
|
if (argv[i] === "--dir") opts.dir = argv[++i];
|
|
else if (WITH_VALUE.includes(argv[i])) opts.solver.push(argv[i], argv[++i]);
|
|
else if (SWITCHES.includes(argv[i])) opts.solver.push(argv[i]);
|
|
else rest.push(argv[i]);
|
|
}
|
|
[opts.level = "l1"] = rest;
|
|
return opts;
|
|
}
|
|
|
|
/**
|
|
* Stage the Module 10 TypeScript reference with its cards and levels into `dir` (default: a new
|
|
* temp directory), copy the solver beside it and install its dependencies. Returns the directory.
|
|
*/
|
|
export function stageSolver(dir) {
|
|
dir = resolve(stagePlay("ts", dir));
|
|
cpSync(join(here, "solver", "solve.ts"), join(dir, "solve.ts"));
|
|
if (!existsSync(join(dir, "node_modules"))) {
|
|
console.error(`Installing the TypeScript reference's dependencies in ${dir} ...`);
|
|
const r = spawnSync("npm", ["install", "--no-audit", "--no-fund", "--loglevel=error"], { cwd: dir, stdio: ["ignore", "inherit", "inherit"] });
|
|
if (r.status !== 0) fail("npm install failed");
|
|
}
|
|
return dir;
|
|
}
|
|
|
|
function main() {
|
|
const opts = parseArgs(process.argv.slice(2));
|
|
const dir = stageSolver(opts.dir);
|
|
const level = resolveLevel(join(dir, "levels"), opts.level);
|
|
if (!level) {
|
|
fail(`no level "${opts.level}". Levels: ${levelNames(join(dir, "levels")).join(", ")}`);
|
|
}
|
|
const r = spawnSync("npx", ["tsx", "solve.ts", level.path, ...opts.solver], { cwd: dir, stdio: "inherit" });
|
|
process.exit(r.status ?? 1);
|
|
}
|
|
|
|
if (isMain(import.meta.url)) main();
|