aoc-copilot
Version:
Advent of Code automatic runner for examples and inputs
268 lines (267 loc) • 12.2 kB
JavaScript
;
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