diff --git a/2024/15/2024-15.run.xml b/2024/15/2024-15.run.xml
new file mode 100644
index 0000000..bf25c84
--- /dev/null
+++ b/2024/15/2024-15.run.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/2024/15/index.test.ts b/2024/15/index.test.ts
new file mode 100644
index 0000000..24f4689
--- /dev/null
+++ b/2024/15/index.test.ts
@@ -0,0 +1,55 @@
+import { describe, expect, it } from 'bun:test';
+import { solveFirst, solveSecond } from './index.ts';
+
+describe('2024-15', () => {
+ const testInput = `##########
+#..O..O.O#
+#......O.#
+#.OO..O.O#
+#..O@..O.#
+#O#..O...#
+#O..O..O.#
+#.OO.O.OO#
+#....O...#
+##########
+
+^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^
+vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv<
+<>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
+^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^><
+^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
+<><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
+^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><
+v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^`;
+ it('first', () => {
+ expect(
+ solveFirst(`########
+#..O.O.#
+##@.O..#
+#...O..#
+#.#.O..#
+#...O..#
+#......#
+########
+
+<^^>>>vv>v<<`)
+ ).toBe(2028);
+ expect(solveFirst(testInput)).toBe(10092);
+ });
+ it('second', () => {
+ expect(
+ solveSecond(`#######
+#...#.#
+#.....#
+#..OO@#
+#..O..#
+#.....#
+#######
+
+') {
+ if (right === '#') {
+ continue;
+ }
+ if (right === '.') {
+ x++;
+ continue;
+ }
+ if (right === 'O') {
+ let firstFree = 2;
+ while (firstFree + x < width) {
+ if (map[y][firstFree + x] === '#') {
+ continue outer;
+ }
+ if (map[y][firstFree + x] === '.') {
+ break;
+ }
+ firstFree++;
+ }
+ if (firstFree + x >= width) {
+ continue;
+ }
+ replaceChar(map, x + 1, y, '.');
+ replaceChar(map, x + firstFree, y, 'O');
+
+ x++;
+ continue;
+ }
+ } else if (instruction === 'v') {
+ if (down === '#') {
+ continue;
+ }
+ if (down === '.') {
+ y++;
+ continue;
+ }
+
+ if (down === 'O') {
+ let firstFree = 2;
+ while (firstFree + y < height) {
+ if (map[firstFree + y][x] === '#') {
+ continue outer;
+ }
+ if (map[firstFree + y][x] === '.') {
+ break;
+ }
+ firstFree++;
+ }
+ if (firstFree + y >= height) {
+ continue;
+ }
+ replaceChar(map, x, y + 1, '.');
+ replaceChar(map, x, y + firstFree, 'O');
+
+ y++;
+ continue;
+ }
+ } else if (instruction === '<') {
+ if (left === '#') {
+ continue;
+ }
+ if (left === '.') {
+ x--;
+ continue;
+ }
+ if (left === 'O') {
+ let firstFree = -2;
+ while (firstFree + x > 0) {
+ if (map[y][firstFree + x] === '#') {
+ continue outer;
+ }
+ if (map[y][firstFree + x] === '.') {
+ break;
+ }
+ firstFree--;
+ }
+ if (firstFree + x <= 0) {
+ continue;
+ }
+ replaceChar(map, x - 1, y, '.');
+ replaceChar(map, x + firstFree, y, 'O');
+
+ x--;
+ continue;
+ }
+ } else if (instruction === '^') {
+ if (top === '#') {
+ continue;
+ }
+ if (top === '.') {
+ y--;
+ continue;
+ }
+
+ if (top === 'O') {
+ let firstFree = -2;
+ while (firstFree + y > 0) {
+ if (map[firstFree + y][x] === '#') {
+ continue outer;
+ }
+ if (map[firstFree + y][x] === '.') {
+ break;
+ }
+ firstFree--;
+ }
+ if (firstFree + y <= 0) {
+ continue;
+ }
+ replaceChar(map, x, y - 1, '.');
+ replaceChar(map, x, y + firstFree, 'O');
+
+ y--;
+ continue;
+ }
+ }
+ console.log('ERROR!');
+ break;
+ }
+
+ // console.log(map.join('\n'));
+
+ const boxes = findCoordsOfDigit(map, 'O');
+
+ return sumArray(boxes.map(([x, y]) => 100 * y + x));
+}
+
+export function solveSecond(input: string): number {
+ const [rawMap, rawInstructions] = input.split('\n\n');
+ const map = rawMap.split('\n').map((row) =>
+ row
+ .split('')
+ .map((d) => (d === 'O' ? '[]' : d === '@' ? '@.' : d + d))
+ .join('')
+ );
+ const instructions = rawInstructions.split('\n').join('');
+
+ // console.log(map);
+ // console.log(instructions);
+
+ const width = map[0].length;
+
+ let [[x, y]] = findCoordsOfDigit(map, '@');
+
+ replaceChar(map, x, y, '.');
+
+ outer: for (const instruction of instructions) {
+ if (map.some((r) => r.includes('[[') || r.includes(']]'))) {
+ console.log('ERROR!');
+ replaceChar(map, x, y, '@');
+ break;
+ }
+
+ // console.log(map.join('\n'));
+ // console.log(instruction, x, y);
+ const { left, right, down, top } = getAdjacent(map, x, y);
+
+ if (instruction === '>') {
+ if (right === '#') {
+ continue;
+ }
+ if (right === '.') {
+ x++;
+ continue;
+ }
+
+ if (right === '[') {
+ let firstFree = 3;
+ while (firstFree + x < width) {
+ if (map[y][firstFree + x] === '#') {
+ continue outer;
+ }
+ if (map[y][firstFree + x] === '.') {
+ break;
+ }
+ firstFree++;
+ }
+ if (firstFree + x >= width) {
+ continue;
+ }
+ const boxes = map[y].substring(x + 1, x + firstFree);
+ replaceChar(map, x + 2, y, boxes);
+ replaceChar(map, x + 1, y, '.');
+
+ x++;
+ continue;
+ }
+ } else if (instruction === 'v') {
+ if (down === '#') {
+ continue;
+ }
+ if (down === '.') {
+ y++;
+ continue;
+ }
+ const toMove: Coordinates = [];
+ const toMoveDigit: string[] = [];
+ if (down === ']') {
+ toMove.push([x, y + 1], [x - 1, y + 1]);
+ toMoveDigit.push(']', '[');
+ } else if (down === '[') {
+ toMove.push([x, y + 1], [x + 1, y + 1]);
+ toMoveDigit.push('[', ']');
+ }
+ for (let i = 0; i < toMove.length; i++) {
+ const [mX, mY] = toMove[i];
+ const { down: mDown } = getAdjacent(map, mX, mY);
+ if (!mDown) {
+ continue;
+ }
+ if (mDown === '[') {
+ toMove.push([mX, mY + 1], [mX + 1, mY + 1]);
+ toMoveDigit.push('[', ']');
+ } else if (mDown === ']') {
+ toMove.push([mX - 1, mY + 1], [mX, mY + 1]);
+ toMoveDigit.push('[', ']');
+ }
+ }
+ // TODO: filter unique??
+ // console.log(toMove);
+
+ if (toMove.some(([mX, mY]) => map[mY + 1][mX] === '#')) {
+ continue;
+ }
+ // console.log('move', 1);
+ toMove.forEach(([mX, mY], i) =>
+ replaceChar(map, mX, mY + 1, toMoveDigit[i])
+ );
+ toMove.forEach(
+ ([mX, mY]) =>
+ !toMove.some((c) => isCoordinateEqual(c, [mX, mY - 1])) &&
+ replaceChar(map, mX, mY, '.')
+ );
+ y++;
+ continue;
+ } else if (instruction === '<') {
+ if (left === '#') {
+ continue;
+ }
+ if (left === '.') {
+ x--;
+ continue;
+ }
+ if (left === ']') {
+ let firstFree = -3;
+ while (firstFree + x > 0) {
+ if (map[y][firstFree + x] === '#') {
+ continue outer;
+ }
+ if (map[y][firstFree + x] === '.') {
+ break;
+ }
+ firstFree--;
+ }
+ if (firstFree + x <= 0) {
+ continue;
+ }
+ const boxes = map[y].substring(x + firstFree + 1, x);
+ replaceChar(map, x + firstFree, y, boxes);
+ replaceChar(map, x - 1, y, '.');
+
+ x--;
+ continue;
+ }
+ } else if (instruction === '^') {
+ if (top === '#') {
+ continue;
+ }
+ if (top === '.') {
+ y--;
+ continue;
+ }
+ const toMove: Coordinates = [];
+ const toMoveDigit: string[] = [];
+ if (top === ']') {
+ toMove.push([x, y - 1], [x - 1, y - 1]);
+ toMoveDigit.push(']', '[');
+ } else if (top === '[') {
+ toMove.push([x, y - 1], [x + 1, y - 1]);
+ toMoveDigit.push('[', ']');
+ }
+
+ for (let i = 0; i < toMove.length; i++) {
+ const [mX, mY] = toMove[i];
+ const { top: mTop } = getAdjacent(map, mX, mY);
+ if (!mTop) {
+ continue;
+ }
+ if (mTop === '[') {
+ toMove.push([mX, mY - 1], [mX + 1, mY - 1]);
+ toMoveDigit.push('[', ']');
+ } else if (mTop === ']') {
+ toMove.push([mX - 1, mY - 1], [mX, mY - 1]);
+ toMoveDigit.push('[', ']');
+ }
+ }
+ // TODO: filter unique??
+ // console.log(toMove);
+
+ if (toMove.some(([mX, mY]) => map[mY - 1][mX] === '#')) {
+ continue;
+ }
+ // console.log('move', 1);
+ toMove.forEach(([mX, mY], i) =>
+ replaceChar(map, mX, mY - 1, toMoveDigit[i])
+ );
+ toMove.forEach(
+ ([mX, mY]) =>
+ !toMove.some((c) => isCoordinateEqual(c, [mX, mY + 1])) &&
+ replaceChar(map, mX, mY, '.')
+ );
+ y--;
+ continue;
+ }
+ console.log('ERROR!');
+ break;
+ }
+
+ // console.log(map.join('\n'));
+
+ const boxes = findCoordsOfDigit(map, /\[/g);
+
+ return sumArray(boxes.map(([x, y]) => 100 * y + x));
+}
diff --git a/2024/15/run.ts b/2024/15/run.ts
new file mode 100644
index 0000000..88783cd
--- /dev/null
+++ b/2024/15/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/utils/array.ts b/utils/array.ts
index cdcc2b8..93f0fd5 100644
--- a/utils/array.ts
+++ b/utils/array.ts
@@ -135,5 +135,5 @@ export function unique(
* @param char Char
*/
export function replaceChar(arr: string[], x: number, y: number, char: string) {
- arr[y] = arr[y].substring(0, x) + char + arr[y].substring(x + 1);
+ arr[y] = arr[y].substring(0, x) + char + arr[y].substring(x + char.length);
}