UNPKG

aoc-copilot

Version:

Advent of Code automatic runner for examples and inputs

268 lines (267 loc) 12.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.NotImplemented = exports.logger = void 0; exports.dumpExample = dumpExample; exports.run = run; const node_path_1 = require("node:path"); const cheerio = __importStar(require("cheerio")); const examples_1 = require("./examples"); const site_1 = require("./site"); class NotImplemented extends Error { name; constructor(message) { super(message); this.name = 'NotImplemented'; } } exports.NotImplemented = NotImplemented; var LogOutput; (function (LogOutput) { LogOutput[LogOutput["LOG"] = 0] = "LOG"; LogOutput[LogOutput["ERROR"] = 1] = "ERROR"; })(LogOutput || (LogOutput = {})); class Logger { lines = []; log = (...args) => this.lines.push({ output: LogOutput.LOG, line: args }); error = (...args) => this.lines.push({ output: LogOutput.ERROR, line: args }); dump() { for (const line of this.lines) { switch (line.output) { case LogOutput.LOG: console.log(...line.line); break; case LogOutput.ERROR: console.error(...line.line); break; } } } } exports.logger = new Logger(); function getYearDay(filename) { const bn = (0, node_path_1.basename)(filename); if (!/^\d+$/.test(bn.substring(3, 7))) { throw new Error("File must be named like 'xxxYYDD*' for automatic year and day detection, e.g. 'aoc2301.js'."); } return { year: parseInt(`20${bn.substring(3, 5)}`), day: parseInt(bn.substring(5, 7)) }; } function preChecksPass() { if (!process.env.AOC_SESSION_COOKIE) { console.error(` Pre-checks failed: Missing session cookie A session cookie is required in order to log in to the adventofcode.com site. ` + site_1.cookieSteps); return false; } return true; } async function allPass(year, day, part, test, examples, solver) { let allPassed = true, passed = false; if (examples.length === 0) console.log(`Sorry, no examples found for ${year} day ${day} part ${part}`); for (let example of examples) { try { passed = await passes(example.inputs, year, day, example.part, test, solver, example.answer, example.additionalInfo); } catch (err) { passed = false; } if (!passed) allPassed = false; } return allPassed; ; } function dumpExample(inputs, expected, additionalInfo, exampleNumber) { if (exampleNumber == null) { console.log("\nExample input:"); } else { console.log("\nExample", exampleNumber, "input:"); } for (const input of inputs) console.log(input); if (additionalInfo != null) { console.log(`\nAdditional info:\n${Object.entries(additionalInfo).map(([k, v]) => `${k}: ${v}`).join('\n')}`); } console.log(`\nExpected answer: ${expected}`); } async function passes(inputs, year, day, part, test, solver, expected, additionalInfo) { const start = performance.now(); try { exports.logger.lines.length = 0; const answer = await solver(inputs, part, test, additionalInfo); const end = performance.now(); const elapsed = (end - start) / 1000; if (answer == expected) { console.log(`That's the right answer! ${answer} (${year} day ${day} part ${part}) (${test ? "example" : "regression"}) (${elapsed.toFixed(3)}s)`); return true; } else { console.log(`That's not the right answer. Expected: ${expected} actual: ${answer} (${year} day ${day} part ${part}) (${test ? "example" : "regression"}) (${elapsed.toFixed(3)}s)`); exports.logger.dump(); return false; } } catch (error) { if (error.name === 'NotImplemented') { dumpExample(inputs, expected, additionalInfo); return false; } else { console.error(error); throw error; } } } async function runInput(year, day, part, solver, examples, inputs, skipTests, forceSubmit) { if (skipTests || await allPass(year, day, part, true, examples.filter(e => e.part == part), solver)) { exports.logger.lines.length = 0; const start = performance.now(); const answer = await solver(inputs, part, false); const end = performance.now(); const elapsed = (end - start) / 1000; try { const { cancelled, response, dayStats } = await (0, site_1.submitAnswer)(year, day, part, answer, forceSubmit); if (cancelled) { console.log(`Submission cancelled (${elapsed.toFixed(3)}s)`); exports.logger.dump(); } else { console.log(`That's the right answer! ${answer} (${year} day ${day} part ${part}) (new submission) (${elapsed.toFixed(3)}s)`); const timeToFinish = part === 1 ? Date.parse(dayStats.part1Finished) - Date.parse(dayStats.part1Started) : Date.parse(dayStats.part2Finished) - Date.parse(dayStats.part1Finished); console.log(`It took you ${(0, site_1.hms)(timeToFinish)} to finish (started ${new Date(part === 1 ? dayStats.part1Started : dayStats.part1Finished)})`); if (day === 25 || (year >= 2025 && day === 12)) { const $ = cheerio.load(response); const earnedStars = parseInt($('span[class="star-count"]').text() || '0*'); const neededStars = year >= 2025 ? 23 : 49; const lastDay = year >= 2025 ? '12' : '25'; if (earnedStars < neededStars) { console.log(`You don't seem to have enough stars to complete day ${lastDay} (https://adventofcode.com/${year}/day/${lastDay}) so go check your advent calendar (https://adventofcode.com/${year}) for unfinished days!`); } else { console.log(`You have ${earnedStars} stars, now go get that last one (https://adventofcode.com/${year}/day/${lastDay})!`); } } } } catch (error) { if (error?.name === 'Incorrect') { console.log(`Incorrect: ${error.message} (${elapsed.toFixed(3)}s)`); exports.logger.dump(); } else { console.error(error, `(${elapsed.toFixed(3)}s)`); } } } } /** * Automatic runs the provided `solver` against examples and/or inputs * @param yearDay accepts either a string in the format 'xxxYYDD*' where YY is the 2-digit year and DD is the 2-digit day, or an object with year and day properties; intended to be used with the `__filename` parameter for files names like 'aoc2301.ts' * @param solver callback solver function * @param options (optional) run tests or inputs only, or only part 1 or 2 * @param addDb (optional) ad-hoc entry for the example database to override the supplied entry or add support for an as-yet unsupported day * @param addTests (optional) additional test cases * @returns */ async function run(yearDay, solver, options = false, addDb, addTests = []) { if (!preChecksPass()) return; if (typeof options === 'object' && options.testsOnly && options.skipTests) throw new Error('Cannot specify both testsOnly and skipTests'); const runOptions = { testsOnly: typeof options === 'boolean' ? options : (options.testsOnly ?? false), skipTests: typeof options === 'boolean' ? false : !!options.skipTests, runPart1: typeof options === 'boolean' ? true : (options.onlyPart ?? 1) === 1, runPart2: typeof options === 'boolean' ? true : (options.onlyPart ?? 2) === 2, forceSubmit: typeof options === 'boolean' ? false : !!options.forceSubmit, }; const { year, day } = typeof yearDay === 'string' ? getYearDay(yearDay) : yearDay; let puzzle = await (0, site_1.getPuzzle)(year, day); const inputs = await (0, site_1.getInput)(year, day); let $ = cheerio.load(puzzle); const acceptedAnswers = $("p:contains('Your puzzle answer') > code"); const examples = await (0, examples_1.getExamples)(year, day, acceptedAnswers.length == 0, $, addDb, addTests); if (runOptions.testsOnly) { if (runOptions.runPart1) await allPass(year, day, 1, true, examples.filter(e => e.part === 1), solver); if (runOptions.runPart2 && acceptedAnswers.length > 0 && day != 25) await allPass(year, day, 2, true, examples.filter(e => e.part === 2), solver); } else if (acceptedAnswers.length == 0) { if (runOptions.runPart1) await runInput(year, day, 1, solver, examples, inputs, runOptions.skipTests, runOptions.forceSubmit); } else { if (!runOptions.runPart1 || await passes(inputs, year, day, 1, false, solver, acceptedAnswers.first().text())) { if (day === 25) { let stars = parseInt($('span[class="star-count"]').text() || '0*'); if (stars === 49) { puzzle = await (0, site_1.getPuzzle)(year, day, true); $ = cheerio.load(puzzle); stars = parseInt($('span[class="star-count"]').text() || '0*'); } if (stars < 49) { console.log(`You don't seem to have enough stars to complete day 25 (https://adventofcode.com/${year}/day/25) so go check your advent calendar (https://adventofcode.com/${year}) for unfinished days!`); } else if (stars === 49) { console.log(`You have 49 stars, now go get that last one (https://adventofcode.com/${year}/day/25)!`); } else if (stars === 50) { console.log(`Congratulations on completing Advent of Code ${year}! You can go admire your advent calendar (https://adventofcode.com/${year})!`); } } else if (runOptions.runPart2) { if (acceptedAnswers.length == 1) { await runInput(year, day, 2, solver, examples, inputs, runOptions.skipTests, runOptions.forceSubmit); } else if (!(await passes(inputs, year, day, 2, false, solver, acceptedAnswers.last().text()))) { await allPass(year, day, 2, true, examples.filter(e => e.part == 2), solver); } } } else if (runOptions.runPart1) await allPass(year, day, 1, true, examples.filter(e => e.part == 1), solver); } ; } //# sourceMappingURL=runner.js.map