Last active
December 9, 2021 05:23
-
-
Save bluepichu/9542fee5ae58f506e7952f2d25b3b108 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { readFile, writeFile } from "fs/promises"; | |
import { join as pathJoin } from "path"; | |
import { List } from "immutable"; | |
import fetch from "node-fetch"; | |
import * as readline from "readline"; | |
const readlineInterface = readline.createInterface({ input: process.stdin, output: process.stdout }); | |
const session = (await readFile(pathJoin(process.env.HOME!, ".advent_cookie"))).toString().trim(); | |
export const int = (x: string): number => parseInt(x, 10); | |
export const num = (x: string): number => parseFloat(x); | |
export const str = (x: any): string => x.toString(); | |
export const bool = (x: any): boolean => !!x; | |
export const arr = <T>(n: number, fn: (i: number) => T) => Array.from(new Array(n), (_, i) => fn(i)); | |
export const print = console.log; | |
export interface InputOptions { | |
day: number; | |
year?: number; | |
strip?: boolean; | |
} | |
export class Input { | |
constructor( | |
private content: string | |
) {} | |
public static async get(options: InputOptions): Promise<Input> { | |
const day = options.day; | |
const year = options.year ?? 2021; | |
const useSample = process.env.ADVENT_SAMPLE === "1"; | |
const file = useSample ? "sample.txt" : "input.txt"; | |
const url = useSample ? undefined : `https://adventofcode.com/${options.year}/day/${options.day}/input`; | |
const strip = options.strip ?? true; | |
const preprocess = (data: string) => strip ? data.trimRight() : data; | |
try { | |
const buffer = await readFile(file); | |
print(`Using input from ${file}`); | |
return new Input(preprocess(buffer.toString())); | |
} catch (error) { | |
if ( | |
error instanceof Error | |
&& "code" in error | |
&& (error as any).code === "ENOENT" | |
&& url !== undefined | |
) { | |
print("Downloading input..."); | |
const response = await fetch(`https://adventofcode.com/${year}/day/${day}/input`, { | |
headers: { | |
cookie: `session=${session}`, | |
}, | |
}); | |
if (response.status !== 200) { | |
print(await response.text()); | |
throw new Error(`Failed to fetch input: ${response.status}`); | |
} | |
const content = await response.text(); | |
await writeFile(file, content); | |
return new Input(preprocess(content)); | |
} | |
throw error; | |
} | |
} | |
public all(): string { | |
return this.content; | |
} | |
public lines(): string[] { | |
return this.content.split("\n"); | |
} | |
public forLines<T>(fn: (input: Input) => T): T[] { | |
return this.lines().map((line) => fn(new Input(line))); | |
} | |
public tokens(separator: RegExp = /[\s\n]+/g): string[] { | |
return this.content.split(separator); | |
} | |
public lineTokens(lineSeparator: RegExp = /\n/g, tokenSeparator: RegExp = /[\s]+/g): string[][] { | |
return this.content.split(lineSeparator).map((line) => line.split(tokenSeparator)); | |
} | |
public ints(): number[] { | |
return this.tokens().map(int); | |
} | |
public nums(): number[] { | |
return this.tokens().map(num); | |
} | |
} | |
export type Point = [number, number]; | |
export type Point3D = [number, number, number]; | |
export const GridDirections = [ | |
[-1, -1], | |
[-1, 0], | |
[-1, 1], | |
[0, -1], | |
[0, 1], | |
[1, -1], | |
[1, 0], | |
[1, 1], | |
]; | |
export const GridDirectionsWithoutDiagonals = [ | |
[-1, 0], | |
[0, -1], | |
[1, 0], | |
[0, 1], | |
]; | |
export function* neighbors(point: Point, includeDiagonal: boolean = true): Generator<Point> { | |
const directions = includeDiagonal ? GridDirections : GridDirectionsWithoutDiagonals; | |
for (let [di, dj] of directions) { | |
yield [point[0] + di, point[1] + dj]; | |
} | |
} | |
export async function Advent(options: InputOptions) { | |
const input = await Input.get(options); | |
const submit = async (answer: number | string, level: 1 | 2 = 1) => { | |
await new Promise<void>((resolve) => { | |
readlineInterface.question(`Submit answer \x1b[36m${answer}\x1b[0m for \x1b[35mpart ${level}\x1b[0m? \x1b[90m(Y to submit)\x1b[0m `, (response) => { | |
if (response.toLowerCase() === "y") { | |
readlineInterface.close(); | |
resolve(); | |
} else { | |
print("\x1b[31mRefusing to submit\x1b[0m"); | |
process.exit(1); | |
} | |
}); | |
}); | |
print("\x1b[34mSubmitting...\x1b[0m"); | |
const response = await fetch(`https://adventofcode.com/${options.year ?? 2021}/day/${options.day}/answer`, { | |
method: "POST", | |
headers: { | |
cookie: `session=${session}`, | |
"Content-Type": "application/x-www-form-urlencoded", | |
}, | |
body: `level=${level}&answer=${encodeURIComponent(answer)}`, | |
}); | |
const text = await response.text(); | |
if (response.status !== 200) { | |
print(await response.text()); | |
throw new Error(`Failed to submit answer: ${response.status}`); | |
} | |
if (text.includes("That's not the right answer")) { | |
print(text); | |
print("\x1b[31mAnswer incorrect!\x1b[0m"); | |
process.exit(1); | |
} else if (text.includes("You gave an answer too recently")) { | |
print(`\x1b[33mWait for the timeout (${/You have (.*) left to wait/.exec(text)![1]})\x1b[0m`); | |
process.exit(1); | |
} else if (text.includes("That's the right answer!")) { | |
print("\x1b[32mAnswer correct!\x1b[0m"); | |
process.exit(0); | |
} else if (text.includes("You don't seem to be solving the right level.")) { | |
print("\x1b[31mWrong level! (Already solved?)\x1b[0m"); | |
process.exit(1); | |
} else { | |
print(text); | |
print(`\x1b[33mUnknown outcome!\x1b[0m`); | |
process.exit(1); | |
} | |
}; | |
return { | |
input, | |
submit, | |
compute: async (fn: (input: Input) => Promise<number | string>, level: 1 | 2 = 1) => { | |
const answer = await fn(input); | |
await submit(answer, level); | |
}, | |
computeCheck: async (fn: (input: Input) => AsyncGenerator<number | string>, level: 1 | 2 = 1) => { | |
let answer: number | string | undefined; | |
let ok = true; | |
for await (const a of fn(input)) { | |
if (answer === undefined) { | |
answer = a; | |
} else { | |
if (answer !== a) { | |
print(`\x1b[31mGot mismatched answer: ${answer} ≠ ${a}\x1b[0m`); | |
ok = false; | |
} | |
} | |
} | |
if (answer === undefined) { | |
print("\x1b[31mDidn't get an answer\x1b[0m"); | |
process.exit(1); | |
} else if (!ok) { | |
print("\x1b[31mRefusing to submit due to mismatched answers\x1b[0m"); | |
process.exit(1); | |
} else { | |
await submit(answer, level); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment