- lessons 8 and 9 updated for the once-per-move rule, per-object negated conversion and the new ordering tests - tools/solve-spellgrid.mjs: breadth-first search over a SpellGrid level with the TypeScript reference; --never-rule and --naive - README, harness.md and the mentor skill know the new beat and module Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
56 lines
2.6 KiB
JavaScript
56 lines
2.6 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 11 to check that a level has the solution
|
|
// its designer meant and no other.
|
|
//
|
|
// 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, readdirSync } from "node:fs";
|
|
import { spawnSync } from "node:child_process";
|
|
import { basename, dirname, join, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { fail, isMain } from "./lib.mjs";
|
|
import { 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;
|
|
}
|
|
|
|
function main() {
|
|
const opts = parseArgs(process.argv.slice(2));
|
|
const dir = resolve(stagePlay("ts", opts.dir));
|
|
const level = resolveLevel(join(dir, "levels"), opts.level);
|
|
if (!level) {
|
|
const names = readdirSync(join(dir, "levels")).filter((f) => f.endsWith(".json")).map((f) => basename(f, ".json"));
|
|
fail(`no level "${opts.level}". Levels: ${names.join(", ")}`);
|
|
}
|
|
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");
|
|
}
|
|
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();
|