Solve 2024-20
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="2024-20" type="BunRunConfiguration">
|
||||||
|
<option name="program" value="2024/20/run.ts" />
|
||||||
|
<option name="workingDirectory" value="$PROJECT_DIR$" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { describe, expect, it } from 'bun:test';
|
||||||
|
import { solveFirst, solveSecond } from './index.ts';
|
||||||
|
|
||||||
|
describe('2024-20', () => {
|
||||||
|
const testInput = `###############
|
||||||
|
#...#...#.....#
|
||||||
|
#.#.#.#.#.###.#
|
||||||
|
#S#...#.#.#...#
|
||||||
|
#######.#.#.###
|
||||||
|
#######.#.#...#
|
||||||
|
#######.#.###.#
|
||||||
|
###..E#...#...#
|
||||||
|
###.#######.###
|
||||||
|
#...###...#...#
|
||||||
|
#.#####.#.###.#
|
||||||
|
#.#...#.#.#...#
|
||||||
|
#.#.#.#.#.#.###
|
||||||
|
#...#...#...###
|
||||||
|
###############`;
|
||||||
|
it('first', () => {
|
||||||
|
expect(solveFirst(testInput, 1)).toBe(14 + 14 + 2 + 4 + 2 + 3 + 5);
|
||||||
|
});
|
||||||
|
it('second', () => {
|
||||||
|
expect(solveSecond(testInput, 50)).toBe(
|
||||||
|
32 + 31 + 29 + 39 + 25 + 23 + 20 + 19 + 12 + 14 + 12 + 22 + 4 + 3
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import { Presets, SingleBar } from 'cli-progress';
|
||||||
|
import { replaceChar } from '../../utils/array.ts';
|
||||||
|
import {
|
||||||
|
type Coordinate,
|
||||||
|
type Coordinates,
|
||||||
|
findCoordsOfDigit,
|
||||||
|
getAdjacent,
|
||||||
|
inBounds,
|
||||||
|
isCoordinateEqual
|
||||||
|
} from '../../utils/coordinates.ts';
|
||||||
|
|
||||||
|
export function solveFirst(input: string, minSave = 100): number {
|
||||||
|
return solve(input, 2, minSave);
|
||||||
|
}
|
||||||
|
export function solveSecond(input: string, minSave = 100): number {
|
||||||
|
return solve(input, 20, minSave);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPath(
|
||||||
|
rows: string[],
|
||||||
|
start: Coordinate,
|
||||||
|
end: Coordinate
|
||||||
|
): Coordinates {
|
||||||
|
let [x, y] = start;
|
||||||
|
const path: Coordinates = [];
|
||||||
|
while (x !== end[0] || y !== end[1]) {
|
||||||
|
const last = path.slice(-1)[0] || [-1, -1];
|
||||||
|
path.push([x, y]);
|
||||||
|
|
||||||
|
const { top, right, down, left } = getAdjacent(rows, x, y);
|
||||||
|
|
||||||
|
if (top === '.' && last[1] !== y - 1) {
|
||||||
|
y--;
|
||||||
|
} else if (right === '.' && last[0] !== x + 1) {
|
||||||
|
x++;
|
||||||
|
} else if (down === '.' && last[1] !== y + 1) {
|
||||||
|
y++;
|
||||||
|
} else if (left === '.' && last[0] !== x - 1) {
|
||||||
|
x--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCheatDirections(maxDistance: number) {
|
||||||
|
const cheatDirections: Coordinates = [];
|
||||||
|
for (let x = -maxDistance; x <= maxDistance; x++) {
|
||||||
|
for (let y = -maxDistance; y <= maxDistance; y++) {
|
||||||
|
const d = Math.abs(x) + Math.abs(y);
|
||||||
|
if (d > 0 && d <= maxDistance) {
|
||||||
|
cheatDirections.push([x, y]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cheatDirections;
|
||||||
|
}
|
||||||
|
|
||||||
|
function solve(input: string, maxDistance: number, minSave: number): number {
|
||||||
|
const rows = input.split('\n');
|
||||||
|
|
||||||
|
const start = findCoordsOfDigit(rows, 'S')[0];
|
||||||
|
const end = findCoordsOfDigit(rows, 'E')[0];
|
||||||
|
|
||||||
|
replaceChar(rows, start[0], start[1], '.');
|
||||||
|
replaceChar(rows, end[0], end[1], '.');
|
||||||
|
|
||||||
|
const path = findPath(rows, start, end);
|
||||||
|
const totalLength = path.length;
|
||||||
|
|
||||||
|
const cheatDirections = getCheatDirections(maxDistance);
|
||||||
|
const isInBounds = inBounds(rows[0].length, rows.length);
|
||||||
|
const bar =
|
||||||
|
process.env.NODE_ENV === 'test'
|
||||||
|
? null
|
||||||
|
: new SingleBar({}, Presets.shades_classic);
|
||||||
|
bar?.start(totalLength, 0);
|
||||||
|
|
||||||
|
const cheats = path.flatMap((cell, i) => {
|
||||||
|
const [x, y] = cell;
|
||||||
|
const downgrade = path.slice(0, i + 3);
|
||||||
|
|
||||||
|
const potentialSpots = cheatDirections
|
||||||
|
.map(([dx, dy]) => [x + dx, y + dy] as Coordinate)
|
||||||
|
.filter(
|
||||||
|
(c) =>
|
||||||
|
isInBounds(c) &&
|
||||||
|
rows[c[1]][c[0]] === '.' &&
|
||||||
|
!downgrade.some((d) => isCoordinateEqual(c, d))
|
||||||
|
);
|
||||||
|
bar?.increment();
|
||||||
|
|
||||||
|
return potentialSpots
|
||||||
|
.map((newStart) => {
|
||||||
|
const end = path.findIndex((p) => isCoordinateEqual(p, newStart));
|
||||||
|
return {
|
||||||
|
newStart,
|
||||||
|
cell,
|
||||||
|
length:
|
||||||
|
(end < 0 ? totalLength : end) -
|
||||||
|
(i +
|
||||||
|
(Math.abs(cell[0] - newStart[0]) +
|
||||||
|
Math.abs(cell[1] - newStart[1])))
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((d) => d.length >= minSave);
|
||||||
|
});
|
||||||
|
|
||||||
|
bar?.start(cheats.length, 0);
|
||||||
|
const existing = new Set<string>();
|
||||||
|
for (let cheat of cheats) {
|
||||||
|
bar?.increment();
|
||||||
|
const key = `${cheat.cell.join(',')}-${cheat.newStart.join(',')}`;
|
||||||
|
if (existing.has(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
existing.add(key);
|
||||||
|
}
|
||||||
|
bar?.stop();
|
||||||
|
return existing.size;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { join } from 'path';
|
||||||
|
import { solveFirst, solveSecond } from './index.ts';
|
||||||
|
|
||||||
|
const input = await Bun.file(join(__dirname, 'input.txt')).text();
|
||||||
|
|
||||||
|
const firstAnswer = solveFirst(input);
|
||||||
|
|
||||||
|
console.log(firstAnswer);
|
||||||
|
|
||||||
|
const secondAnswer = solveSecond(input);
|
||||||
|
|
||||||
|
console.log(secondAnswer);
|
||||||
@@ -12,8 +12,10 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
|
"@types/cli-progress": "^3.11.6",
|
||||||
"@types/javascript-astar": "^0.0.35",
|
"@types/javascript-astar": "^0.0.35",
|
||||||
"@types/prompts": "^2.4.9",
|
"@types/prompts": "^2.4.9",
|
||||||
|
"cli-progress": "^3.12.0",
|
||||||
"p-limit": "^6.1.0",
|
"p-limit": "^6.1.0",
|
||||||
"prettier": "^3.4.0",
|
"prettier": "^3.4.0",
|
||||||
"prettier-plugin-organize-imports": "^4.1.0",
|
"prettier-plugin-organize-imports": "^4.1.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user