diff --git a/.gitignore b/.gitignore
index 2930746..0448c6a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -176,4 +176,5 @@ dist
input.txt
+input.js
!template/input.txt
\ No newline at end of file
diff --git a/2024/14/2024-14.run.xml b/2024/14/2024-14.run.xml
new file mode 100644
index 0000000..bedc182
--- /dev/null
+++ b/2024/14/2024-14.run.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/2024/14/example-input.js b/2024/14/example-input.js
new file mode 100644
index 0000000..42a9b66
--- /dev/null
+++ b/2024/14/example-input.js
@@ -0,0 +1,15 @@
+const xMax = 11;
+const yMax = 7;
+
+const input = `p=0,4 v=3,-3
+p=6,3 v=-1,-3
+p=10,3 v=-1,2
+p=2,0 v=2,-1
+p=0,0 v=1,3
+p=3,0 v=-2,-2
+p=7,6 v=-1,-3
+p=3,0 v=-1,-2
+p=9,3 v=2,3
+p=7,3 v=-1,2
+p=2,4 v=2,-3
+p=9,5 v=-3,-3`;
diff --git a/2024/14/index.test.ts b/2024/14/index.test.ts
new file mode 100644
index 0000000..e741fb0
--- /dev/null
+++ b/2024/14/index.test.ts
@@ -0,0 +1,21 @@
+import { describe, expect, it } from 'bun:test';
+import { solveFirst } from './index.ts';
+
+describe('2024-14', () => {
+ const testInput = `p=0,4 v=3,-3
+p=6,3 v=-1,-3
+p=10,3 v=-1,2
+p=2,0 v=2,-1
+p=0,0 v=1,3
+p=3,0 v=-2,-2
+p=7,6 v=-1,-3
+p=3,0 v=-1,-2
+p=9,3 v=2,3
+p=7,3 v=-1,2
+p=2,4 v=2,-3
+p=9,5 v=-3,-3`;
+ it('first', () => {
+ expect(solveFirst(`p=2,4 v=2,-3`, 11, 7, 0)).toBe(0);
+ expect(solveFirst(testInput, 11, 7)).toBe(12);
+ });
+});
diff --git a/2024/14/index.ts b/2024/14/index.ts
new file mode 100644
index 0000000..85769c8
--- /dev/null
+++ b/2024/14/index.ts
@@ -0,0 +1,109 @@
+import { multArray } from '../../utils/array.ts';
+import {
+ calculateVariance,
+ type Coordinate,
+ CoordinateIndices,
+ type Coordinates,
+ type CoordinateSides,
+ isCoordinateEqual
+} from '../../utils/coordinates.ts';
+
+function mod(x: number, m: number) {
+ return ((x % m) + m) % m;
+}
+
+export function solveFirst(
+ input: string,
+ xMax = 101,
+ yMax = 103,
+ iterations = 100
+): number {
+ const match = input.matchAll(/^p=(-?\d*),(-?\d*) v=(-?\d*),(-?\d*)$/gm);
+
+ const matches = Array.from(match, (m) => ({
+ p: m.slice(1, 3).map(Number) as Coordinate,
+ v: m.slice(3, 5).map(Number) as Coordinate
+ }));
+
+ const finalPositions = matches.map(
+ (p) =>
+ [
+ mod(p.p[0] + p.v[0] * iterations, xMax),
+ mod(p.p[1] + p.v[1] * iterations, yMax)
+ ] as Coordinate
+ );
+
+ const centerX = Math.floor(xMax / 2);
+ const centerY = Math.floor(yMax / 2);
+
+ const sumQuadrants = finalPositions.reduce(
+ (quads, position) => {
+ const [x, y] = position;
+ if (x < centerX) {
+ if (y < centerY) {
+ quads[0]++;
+ } else if (y > centerY) {
+ quads[3]++;
+ }
+ } else if (x > centerX) {
+ if (y < centerY) {
+ quads[1]++;
+ } else if (y > centerY) {
+ quads[2]++;
+ }
+ }
+ return quads;
+ },
+ [0, 0, 0, 0]
+ );
+
+ return multArray(sumQuadrants);
+}
+
+export function solveSecond(input: string, xMax = 101, yMax = 103): number {
+ const match = input.matchAll(/^p=(-?\d*),(-?\d*) v=(-?\d*),(-?\d*)$/gm);
+
+ const matches = Array.from(match, (m) => ({
+ p: m.slice(1, 3).map(Number) as Coordinate,
+ v: m.slice(3, 5).map(Number) as Coordinate
+ }));
+
+ for (let i = 0; i < xMax * yMax; i++) {
+ const finalPositions = matches.map(
+ (p) =>
+ [
+ mod(p.p[0] + p.v[0] * i, xMax),
+ mod(p.p[1] + p.v[1] * i, yMax)
+ ] as Coordinate
+ );
+ const oX = calculateVariance(finalPositions, CoordinateIndices.x);
+
+ const oY = calculateVariance(finalPositions, CoordinateIndices.y);
+ calculateVariance([[0, 1, 2]] as CoordinateSides, CoordinateIndices.side);
+
+ // hard coded, ups
+ // would be better to find the lowest oX = lX in 0-maxX and lowest oY = lY in 0-maxY
+ // then solve yMax * n + lY = xMax * (n+1) + lX, for n
+ // IDK why x+1 tho
+ if (oX < 600 && oY < 600) {
+ if (process.env.NODE_ENV === 'production') {
+ console.log(i, oX.toFixed(0), oY.toFixed(0));
+ renderRobots(finalPositions, xMax, yMax);
+ }
+ return i;
+ }
+ }
+
+ return 0;
+}
+
+function renderRobots(coords: Coordinates, xMax: number, yMax: number) {
+ for (let y = 0; y < yMax; y++) {
+ const row = new Array(xMax)
+ .fill(' ')
+ .map((_, x) =>
+ coords.some((c) => isCoordinateEqual(c, [x, y])) ? '*' : ' '
+ );
+ console.log(row.join(''));
+ }
+}
diff --git a/2024/14/run.ts b/2024/14/run.ts
new file mode 100644
index 0000000..88783cd
--- /dev/null
+++ b/2024/14/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/2024/14/solveSecond.html b/2024/14/solveSecond.html
new file mode 100644
index 0000000..1fe4f98
--- /dev/null
+++ b/2024/14/solveSecond.html
@@ -0,0 +1,99 @@
+
+
+
+
+ AOC 2024-14 Part 2
+
+
+
+
+
+
+
+
+
+
+
+
+