diff --git a/2024/18/2024-18.run.xml b/2024/18/2024-18.run.xml
new file mode 100644
index 0000000..c8a06bb
--- /dev/null
+++ b/2024/18/2024-18.run.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/2024/18/index.test.ts b/2024/18/index.test.ts
new file mode 100644
index 0000000..9d033ce
--- /dev/null
+++ b/2024/18/index.test.ts
@@ -0,0 +1,36 @@
+import { describe, expect, it } from 'bun:test';
+import { solveFirst, solveSecond } from './index.ts';
+
+describe('2024-18', () => {
+ const testInput = `5,4
+4,2
+4,5
+3,0
+2,1
+6,3
+2,4
+1,5
+0,6
+3,3
+2,6
+5,1
+1,2
+5,5
+2,5
+6,5
+1,4
+0,4
+6,4
+1,1
+6,1
+1,0
+0,5
+1,6
+2,0`;
+ it('first', () => {
+ expect(solveFirst(testInput, 7, 7, 12)).toBe(22);
+ });
+ it('second', () => {
+ expect(solveSecond(testInput, 7, 7, 12)).toBe('6,1');
+ });
+});
diff --git a/2024/18/index.ts b/2024/18/index.ts
new file mode 100644
index 0000000..daeee86
--- /dev/null
+++ b/2024/18/index.ts
@@ -0,0 +1,167 @@
+// @ts-ignore
+import { astar, Graph } from 'javascript-astar';
+import { replaceChar } from '../../utils/array.ts';
+import {
+ type Coordinate,
+ inBounds,
+ isCoordinateEqual
+} from '../../utils/coordinates.ts';
+import { MultiMap } from '../../utils/multi-map.ts';
+import { PriorityQueue } from '../../utils/queue.ts';
+
+function getGraph(
+ allBytes: [number, number][],
+ byteCount: number,
+ height: number,
+ width: number
+): Graph {
+ const bytes = allBytes.slice(0, byteCount);
+
+ const map = new Array(height)
+ .fill(null)
+ .map((_, y) =>
+ new Array(width)
+ .fill(0)
+ .map((_, x) =>
+ bytes.findIndex((c) => isCoordinateEqual(c, [x, y])) >= 0 ? 0 : 1
+ )
+ );
+
+ return new Graph(map);
+}
+
+export function solveFirst(
+ input: string,
+ width = 71,
+ height = 71,
+ byteCount = 1024
+): number {
+ const match = input.matchAll(/^(\d*),(\d*)/gm);
+
+ const allBytes = Array.from(
+ match,
+ (m) => m.slice(1, 3).map(Number) as Coordinate
+ );
+ const graph = getGraph(allBytes, byteCount, height, width);
+
+ const start = graph.grid[0][0];
+ const end = graph.grid[height - 1][width - 1];
+
+ const result = astar.search(graph, start, end);
+ return result.length;
+}
+
+export function solveSecond(
+ input: string,
+ width = 71,
+ height = 71,
+ byteCount = 1024
+): string {
+ const match = input.matchAll(/^(\d*),(\d*)/gm);
+
+ const allBytes = Array.from(
+ match,
+ (m) => m.slice(1, 3).map(Number) as Coordinate
+ );
+
+ // let lastPath: Coordinates = [];
+ for (let i = byteCount; i < allBytes.length; i++) {
+ const nextByte = allBytes[i];
+ // if (
+ // lastPath.length > 0 &&
+ // !lastPath.some((p) => isCoordinateEqual(p, nextByte))
+ // ) {
+ // console.log(lastPath);
+ // continue;
+ // }
+
+ const graph = getGraph(allBytes, i + 1, height, width);
+ const start = graph.grid[0][0];
+
+ const end = graph.grid[height - 1][width - 1];
+ const result = astar.search(graph, start, end);
+ if (result.length <= 0) {
+ return nextByte.join(',');
+ }
+ // lastPath = result.map((n: GridNode) => [n.x, n.y] as Coordinate);
+ }
+ return '';
+}
+
+export function solveFirstOld(
+ input: string,
+ width = 71,
+ height = 71,
+ byteCount = 1024
+): number {
+ const match = input.matchAll(/^(\d*),(\d*)/gm);
+
+ const bytes = Array.from(
+ match,
+ (m) => m.slice(1, 3).map(Number) as Coordinate
+ ).slice(0, byteCount);
+
+ const map = new Array(height)
+ .fill(null)
+ .map(() => new Array(width).fill('.').join('') as string);
+
+ bytes.forEach(([x, y]) => replaceChar(map, x, y, '#'));
+ replaceChar(map, width - 1, height - 1, 'E');
+
+ console.log(map.join('\n'));
+ const start = { x: 0, y: 0, score: 0 };
+ const possibleOptions = new PriorityQueue();
+
+ possibleOptions.enqueue(start, 0);
+ const visited = new Set<`${number}:${number}`>();
+ const scores = new MultiMap();
+ visited.add(`${start.x}:${start.y}`);
+
+ let i = 0;
+ while (possibleOptions.size() > 0) {
+ const el = possibleOptions.dequeue()!!;
+
+ scores.multiSet(el.node.score, el.node.y, el.node.x);
+ visited.add(`${el.node.x}:${el.node.y}`);
+ if (map[el.node.y]?.[el.node.x] === 'E') {
+ // console.log(visited);
+ return el.node.score;
+ }
+ if (possibleOptions.size() % 10_000 === 0) {
+ console.log(visited.size, possibleOptions.size());
+ }
+ for (let [x, y] of [
+ [0, 1],
+ [0, -1],
+ [1, 0],
+ [-1, 0]
+ ]) {
+ const nx = el.node.x + x;
+ const ny = el.node.y + y;
+ const score = el.node.score + 1;
+ if (!inBounds(width, height)([nx, ny]) || map[ny]?.[nx] === '#') {
+ continue;
+ }
+ if (visited.has(`${nx}:${ny}`)) {
+ continue;
+ }
+ const existingScore = scores.multiGet(ny, nx) || Number.MAX_SAFE_INTEGER;
+ if (score < existingScore) {
+ possibleOptions.enqueue({ x: nx, y: ny, score }, score);
+ }
+ }
+ // console.log('a', el.node, visited.size, possibleOptions.size());
+ // return 0;
+ i++;
+
+ if (i >= 10_000_000) {
+ visited.forEach((v) => {
+ const [x, y] = v.split(':').map(Number);
+ replaceChar(map, x, y, 'O');
+ });
+ console.log(map.join('\n'));
+ return 0;
+ }
+ }
+ return 0;
+}
diff --git a/2024/18/run.ts b/2024/18/run.ts
new file mode 100644
index 0000000..88783cd
--- /dev/null
+++ b/2024/18/run.ts
@@ -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);
diff --git a/bun.lockb b/bun.lockb
index a847f08..24b152f 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index d7bb010..4c76cca 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
},
"devDependencies": {
"@types/bun": "latest",
+ "@types/javascript-astar": "^0.0.35",
"@types/prompts": "^2.4.9",
"p-limit": "^6.1.0",
"prettier": "^3.4.0",
@@ -21,5 +22,8 @@
},
"peerDependencies": {
"typescript": "^5.7.2"
+ },
+ "dependencies": {
+ "javascript-astar": "^0.4.1"
}
}