diff --git a/README.md b/README.md
index 94396aa..b3c8d61 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,7 @@ This repository contains solutions and a local development environment for the [
- Day 11: Plutonian Pebbles [[link]](/src/2024/2024-12-11/README.md)
- Day 12: Garden Groups [[link]](/src/2024/2024-12-12/README.md)
- Day 13: Claw Contraption [[link]](/src/2024/2024-12-13/README.md)
+- Day 14: Restroom Redoubt [[link]](/src/2024/2024-12-14/README.md)
diff --git a/src/2024/2024-12-10/lib/scoresRatings.ts b/src/2024/2024-12-10/lib/scoresRatings.ts
index 63d2e6d..5f0518d 100644
--- a/src/2024/2024-12-10/lib/scoresRatings.ts
+++ b/src/2024/2024-12-10/lib/scoresRatings.ts
@@ -28,6 +28,8 @@ const findPaths = (pointVector: PointDirection, data: number[][], isRating: bool
if (step === undefined) continue
const pt = getCoordinateSymbol(step, data)
+ if (pt === undefined) continue
+
if (pt.symbol === 9) {
if (isRating) {
// Rating: count all trails ending in 9's
@@ -76,6 +78,8 @@ export const countTrailScores = (data: number[][], params?: InputOptions): Trail
}
const pt = getCoordinateSymbol(starts[i] as Point, data)
+ if (!pt) continue
+
activeZeroIndex = pt.coordinate
scores[activeZeroIndex] = []
diff --git a/src/2024/2024-12-13/main.ts b/src/2024/2024-12-13/main.ts
index 880f955..2d1faaf 100644
--- a/src/2024/2024-12-13/main.ts
+++ b/src/2024/2024-12-13/main.ts
@@ -6,7 +6,7 @@ const data = fileReader('../input.txt')
const data2 = fileReader('../input2.txt')
/**
- * Part 1/2 of the 2024-12-14 quiz
+ * Part 1/2 of the 2024-12-13 quiz
* Counts the number of tokens needed to win all possible prizes.
*/
const quiz20241213_01 = () => {
@@ -17,7 +17,7 @@ const quiz20241213_01 = () => {
}
/**
- * Part 2/2 of the 2024-12-14 quiz
+ * Part 2/2 of the 2024-12-13 quiz
* Counts the number of tokens needed to win all possible prizes,
* adjusted to the additional `10000000000000` of the prize coordinates
*/
diff --git a/src/2024/2024-12-14/README.md b/src/2024/2024-12-14/README.md
new file mode 100644
index 0000000..787178f
--- /dev/null
+++ b/src/2024/2024-12-14/README.md
@@ -0,0 +1,31 @@
+## Day 14: Restroom Redout
+
+Visit the Advent of Code website for more information on this puzzle at:
+
+**Source:** https://adventofcode.com/2024/day/14
+**Status:** Complete ⭐⭐
+
+## Code
+
+### `findEasterEgg.ts`
+
+- **`findEasterEgg()`** - Counts the no. of seconds before robots display (form) the Christmas Tree easter egg.
+ > **NOTE:** I got tips and hints from about the figure of the Christmas tree [here](https://elixirforum.com/t/advent-of-code-2024-day-14/68091/3) after trying to observe for a 🎄 pattern in grid renders per second iteration.
+
+### `safetyFactor.ts`
+
+- **`calculateSafetyFactor()`** - Counts the safety factor after `params.seconds` of simulating moving the robots.
+
+### `board.ts`
+
+**Board** class
+
+- Manages the `Grid`-like 2D string array, methods, and properties in which robots run
+- **`create()`** - Creates a blank `this.length` x `this.width` board, clearing
+- **`findQuadrants()`** - Finds the four (4) main quadrants of `this.grid`, each containing a set of inclusive `start` and `end` coordinates.
+- **`getQuadrant()`** - Finds the quadrant `ID` of a `Point` within the `this.grid` 2D array.
+- **`moveRobot()`** - Moves a robot from a tile and updates the current and new tile's robot count
+- **`setRobot()`** - Sets a new robot into the `Board`'s robots list and marks its position in `this.grid[][]` array
+- **`setTileValue()`** - Sets the string value in the 2D `this.grid[][]` array
+- **`simulateRobotsWalk()`** - Move (walk) the robots by updating their positions by velocity by `seconds` times.
+- **`viewQuadrants()`** - Draws categorized symbols per quadrant on a temporary 2D array for visualization.
diff --git a/src/2024/2024-12-14/assets/sample.PNG b/src/2024/2024-12-14/assets/sample.PNG
new file mode 100644
index 0000000..71cb21c
Binary files /dev/null and b/src/2024/2024-12-14/assets/sample.PNG differ
diff --git a/src/2024/2024-12-14/input.txt b/src/2024/2024-12-14/input.txt
new file mode 100644
index 0000000..7138eb1
--- /dev/null
+++ b/src/2024/2024-12-14/input.txt
@@ -0,0 +1,423 @@
+p=8,47 v=0,0
+p=20,47 v=0,0
+p=21,47 v=0,0
+p=22,47 v=0,0
+p=23,47 v=0,0
+p=24,47 v=0,0
+p=25,47 v=0,0
+p=26,47 v=0,0
+p=27,47 v=0,0
+p=28,47 v=0,0
+p=29,47 v=0,0
+p=30,47 v=0,0
+p=31,47 v=0,0
+p=32,47 v=0,0
+p=33,47 v=0,0
+p=34,47 v=0,0
+p=35,47 v=0,0
+p=36,47 v=0,0
+p=37,47 v=0,0
+p=38,47 v=0,0
+p=39,47 v=0,0
+p=40,47 v=0,0
+p=41,47 v=0,0
+p=42,47 v=0,0
+p=43,47 v=0,0
+p=44,47 v=0,0
+p=45,47 v=0,0
+p=46,47 v=0,0
+p=47,47 v=0,0
+p=48,47 v=0,0
+p=49,47 v=0,0
+p=50,47 v=0,0
+p=20,48 v=0,0
+p=50,48 v=0,0
+p=76,48 v=0,0
+p=20,49 v=0,0
+p=50,49 v=0,0
+p=70,49 v=0,0
+p=86,49 v=0,0
+p=20,50 v=0,0
+p=50,50 v=0,0
+p=99,50 v=0,0
+p=20,51 v=0,0
+p=50,51 v=0,0
+p=69,51 v=0,0
+p=94,51 v=0,0
+p=20,52 v=0,0
+p=35,52 v=0,0
+p=50,52 v=0,0
+p=73,52 v=0,0
+p=20,53 v=0,0
+p=34,53 v=0,0
+p=35,53 v=0,0
+p=36,53 v=0,0
+p=50,53 v=0,0
+p=84,53 v=0,0
+p=20,54 v=0,0
+p=33,54 v=0,0
+p=34,54 v=0,0
+p=35,54 v=0,0
+p=36,54 v=0,0
+p=37,54 v=0,0
+p=50,54 v=0,0
+p=20,55 v=0,0
+p=32,55 v=0,0
+p=33,55 v=0,0
+p=34,55 v=0,0
+p=35,55 v=0,0
+p=36,55 v=0,0
+p=37,55 v=0,0
+p=38,55 v=0,0
+p=50,55 v=0,0
+p=20,56 v=0,0
+p=31,56 v=0,0
+p=32,56 v=0,0
+p=33,56 v=0,0
+p=34,56 v=0,0
+p=35,56 v=0,0
+p=36,56 v=0,0
+p=37,56 v=0,0
+p=38,56 v=0,0
+p=39,56 v=0,0
+p=50,56 v=0,0
+p=90,56 v=0,0
+p=20,57 v=0,0
+p=33,57 v=0,0
+p=34,57 v=0,0
+p=35,57 v=0,0
+p=36,57 v=0,0
+p=37,57 v=0,0
+p=50,57 v=0,0
+p=20,58 v=0,0
+p=32,58 v=0,0
+p=33,58 v=0,0
+p=34,58 v=0,0
+p=35,58 v=0,0
+p=36,58 v=0,0
+p=37,58 v=0,0
+p=38,58 v=0,0
+p=50,58 v=0,0
+p=20,59 v=0,0
+p=31,59 v=0,0
+p=32,59 v=0,0
+p=33,59 v=0,0
+p=34,59 v=0,0
+p=35,59 v=0,0
+p=36,59 v=0,0
+p=37,59 v=0,0
+p=38,59 v=0,0
+p=39,59 v=0,0
+p=50,59 v=0,0
+p=54,59 v=0,0
+p=79,59 v=0,0
+p=20,60 v=0,0
+p=30,60 v=0,0
+p=31,60 v=0,0
+p=32,60 v=0,0
+p=33,60 v=0,0
+p=34,60 v=0,0
+p=35,60 v=0,0
+p=36,60 v=0,0
+p=37,60 v=0,0
+p=38,60 v=0,0
+p=39,60 v=0,0
+p=40,60 v=0,0
+p=50,60 v=0,0
+p=100,60 v=0,0
+p=12,61 v=0,0
+p=20,61 v=0,0
+p=29,61 v=0,0
+p=30,61 v=0,0
+p=31,61 v=0,0
+p=32,61 v=0,0
+p=33,61 v=0,0
+p=34,61 v=0,0
+p=35,61 v=0,0
+p=36,61 v=0,0
+p=37,61 v=0,0
+p=38,61 v=0,0
+p=39,61 v=0,0
+p=40,61 v=0,0
+p=41,61 v=0,0
+p=50,61 v=0,0
+p=81,61 v=0,0
+p=92,61 v=0,0
+p=14,62 v=0,0
+p=20,62 v=0,0
+p=31,62 v=0,0
+p=32,62 v=0,0
+p=33,62 v=0,0
+p=34,62 v=0,0
+p=35,62 v=0,0
+p=36,62 v=0,0
+p=37,62 v=0,0
+p=38,62 v=0,0
+p=39,62 v=0,0
+p=50,62 v=0,0
+p=81,62 v=0,0
+p=20,63 v=0,0
+p=30,63 v=0,0
+p=31,63 v=0,0
+p=32,63 v=0,0
+p=33,63 v=0,0
+p=34,63 v=0,0
+p=35,63 v=0,0
+p=36,63 v=0,0
+p=37,63 v=0,0
+p=38,63 v=0,0
+p=39,63 v=0,0
+p=40,63 v=0,0
+p=50,63 v=0,0
+p=70,63 v=0,0
+p=20,64 v=0,0
+p=29,64 v=0,0
+p=30,64 v=0,0
+p=31,64 v=0,0
+p=32,64 v=0,0
+p=33,64 v=0,0
+p=34,64 v=0,0
+p=35,64 v=0,0
+p=36,64 v=0,0
+p=37,64 v=0,0
+p=38,64 v=0,0
+p=39,64 v=0,0
+p=40,64 v=0,0
+p=41,64 v=0,0
+p=50,64 v=0,0
+p=68,64 v=0,0
+p=10,65 v=0,0
+p=20,65 v=0,0
+p=28,65 v=0,0
+p=29,65 v=0,0
+p=30,65 v=0,0
+p=31,65 v=0,0
+p=32,65 v=0,0
+p=33,65 v=0,0
+p=34,65 v=0,0
+p=35,65 v=0,0
+p=36,65 v=0,0
+p=37,65 v=0,0
+p=38,65 v=0,0
+p=39,65 v=0,0
+p=40,65 v=0,0
+p=41,65 v=0,0
+p=42,65 v=0,0
+p=50,65 v=0,0
+p=3,66 v=0,0
+p=20,66 v=0,0
+p=27,66 v=0,0
+p=28,66 v=0,0
+p=29,66 v=0,0
+p=30,66 v=0,0
+p=31,66 v=0,0
+p=32,66 v=0,0
+p=33,66 v=0,0
+p=34,66 v=0,0
+p=35,66 v=0,0
+p=36,66 v=0,0
+p=37,66 v=0,0
+p=38,66 v=0,0
+p=39,66 v=0,0
+p=40,66 v=0,0
+p=41,66 v=0,0
+p=42,66 v=0,0
+p=43,66 v=0,0
+p=50,66 v=0,0
+p=20,67 v=0,0
+p=29,67 v=0,0
+p=30,67 v=0,0
+p=31,67 v=0,0
+p=32,67 v=0,0
+p=33,67 v=0,0
+p=34,67 v=0,0
+p=35,67 v=0,0
+p=36,67 v=0,0
+p=37,67 v=0,0
+p=38,67 v=0,0
+p=39,67 v=0,0
+p=40,67 v=0,0
+p=41,67 v=0,0
+p=50,67 v=0,0
+p=2,68 v=0,0
+p=20,68 v=0,0
+p=28,68 v=0,0
+p=29,68 v=0,0
+p=30,68 v=0,0
+p=31,68 v=0,0
+p=32,68 v=0,0
+p=33,68 v=0,0
+p=34,68 v=0,0
+p=35,68 v=0,0
+p=36,68 v=0,0
+p=37,68 v=0,0
+p=38,68 v=0,0
+p=39,68 v=0,0
+p=40,68 v=0,0
+p=41,68 v=0,0
+p=42,68 v=0,0
+p=50,68 v=0,0
+p=67,68 v=0,0
+p=20,69 v=0,0
+p=27,69 v=0,0
+p=28,69 v=0,0
+p=29,69 v=0,0
+p=30,69 v=0,0
+p=31,69 v=0,0
+p=32,69 v=0,0
+p=33,69 v=0,0
+p=34,69 v=0,0
+p=35,69 v=0,0
+p=36,69 v=0,0
+p=37,69 v=0,0
+p=38,69 v=0,0
+p=39,69 v=0,0
+p=40,69 v=0,0
+p=41,69 v=0,0
+p=42,69 v=0,0
+p=43,69 v=0,0
+p=50,69 v=0,0
+p=84,69 v=0,0
+p=4,70 v=0,0
+p=20,70 v=0,0
+p=26,70 v=0,0
+p=27,70 v=0,0
+p=28,70 v=0,0
+p=29,70 v=0,0
+p=30,70 v=0,0
+p=31,70 v=0,0
+p=32,70 v=0,0
+p=33,70 v=0,0
+p=34,70 v=0,0
+p=35,70 v=0,0
+p=36,70 v=0,0
+p=37,70 v=0,0
+p=38,70 v=0,0
+p=39,70 v=0,0
+p=40,70 v=0,0
+p=41,70 v=0,0
+p=42,70 v=0,0
+p=43,70 v=0,0
+p=44,70 v=0,0
+p=50,70 v=0,0
+p=67,70 v=0,0
+p=3,71 v=0,0
+p=20,71 v=0,0
+p=25,71 v=0,0
+p=26,71 v=0,0
+p=27,71 v=0,0
+p=28,71 v=0,0
+p=29,71 v=0,0
+p=30,71 v=0,0
+p=31,71 v=0,0
+p=32,71 v=0,0
+p=33,71 v=0,0
+p=34,71 v=0,0
+p=35,71 v=0,0
+p=36,71 v=0,0
+p=37,71 v=0,0
+p=38,71 v=0,0
+p=39,71 v=0,0
+p=40,71 v=0,0
+p=41,71 v=0,0
+p=42,71 v=0,0
+p=43,71 v=0,0
+p=44,71 v=0,0
+p=45,71 v=0,0
+p=50,71 v=0,0
+p=91,71 v=0,0
+p=20,72 v=0,0
+p=34,72 v=0,0
+p=35,72 v=0,0
+p=36,72 v=0,0
+p=50,72 v=0,0
+p=20,73 v=0,0
+p=34,73 v=0,0
+p=35,73 v=0,0
+p=36,73 v=0,0
+p=50,73 v=0,0
+p=72,73 v=0,0
+p=79,73 v=0,0
+p=16,74 v=0,0
+p=20,74 v=0,0
+p=34,74 v=0,0
+p=35,74 v=0,0
+p=36,74 v=0,0
+p=50,74 v=0,0
+p=82,74 v=0,0
+p=20,75 v=0,0
+p=50,75 v=0,0
+p=76,75 v=0,0
+p=20,76 v=0,0
+p=50,76 v=0,0
+p=20,77 v=0,0
+p=50,77 v=0,0
+p=64,77 v=0,0
+p=20,78 v=0,0
+p=50,78 v=0,0
+p=7,79 v=0,0
+p=13,79 v=0,0
+p=20,79 v=0,0
+p=21,79 v=0,0
+p=22,79 v=0,0
+p=23,79 v=0,0
+p=24,79 v=0,0
+p=25,79 v=0,0
+p=26,79 v=0,0
+p=27,79 v=0,0
+p=28,79 v=0,0
+p=29,79 v=0,0
+p=30,79 v=0,0
+p=31,79 v=0,0
+p=32,79 v=0,0
+p=33,79 v=0,0
+p=34,79 v=0,0
+p=35,79 v=0,0
+p=36,79 v=0,0
+p=37,79 v=0,0
+p=38,79 v=0,0
+p=39,79 v=0,0
+p=40,79 v=0,0
+p=41,79 v=0,0
+p=42,79 v=0,0
+p=43,79 v=0,0
+p=44,79 v=0,0
+p=45,79 v=0,0
+p=46,79 v=0,0
+p=47,79 v=0,0
+p=48,79 v=0,0
+p=49,79 v=0,0
+p=50,79 v=0,0
+p=100,80 v=0,0
+p=4,81 v=0,0
+p=35,81 v=0,0
+p=88,81 v=0,0
+p=97,81 v=0,0
+p=14,82 v=0,0
+p=39,82 v=0,0
+p=82,83 v=0,0
+p=79,84 v=0,0
+p=13,85 v=0,0
+p=14,85 v=0,0
+p=51,85 v=0,0
+p=9,86 v=0,0
+p=75,86 v=0,0
+p=25,87 v=0,0
+p=2,88 v=0,0
+p=6,88 v=0,0
+p=69,89 v=0,0
+p=95,89 v=0,0
+p=14,90 v=0,0
+p=20,90 v=0,0
+p=62,92 v=0,0
+p=63,92 v=0,0
+p=6,93 v=0,0
+p=1,95 v=0,0
+p=75,95 v=0,0
+p=76,95 v=0,0
+p=90,95 v=0,0
+p=5,96 v=0,0
+p=13,96 v=0,0
+p=22,97 v=0,0
+p=0,98 v=0,0
+p=20,102 v=0,0
\ No newline at end of file
diff --git a/src/2024/2024-12-14/input_sample.txt b/src/2024/2024-12-14/input_sample.txt
new file mode 100644
index 0000000..6916d15
--- /dev/null
+++ b/src/2024/2024-12-14/input_sample.txt
@@ -0,0 +1,12 @@
+p=1,5 v=3,-3
+p=5,2 v=-1,-3
+p=11,4 v=-1,2
+p=3,1 v=2,-1
+p=0,0 v=1,3
+p=4,1 v=-2,-2
+p=6,5 v=-1,-3
+p=4,2 v=-1,-2
+p=8,2 v=2,3
+p=8,4 v=-1,2
+p=1,3 v=2,-3
+p=7,2 v=-3,-3
\ No newline at end of file
diff --git a/src/2024/2024-12-14/lib/board.ts b/src/2024/2024-12-14/lib/board.ts
new file mode 100644
index 0000000..7c4f2f2
--- /dev/null
+++ b/src/2024/2024-12-14/lib/board.ts
@@ -0,0 +1,252 @@
+import type { GridDimensions } from '@/aoc/grid/types.js'
+import type { Point } from '@/aoc/point/types.js'
+import type { Quadrant, RobotProperty } from './types.js'
+
+import { arrayMiddleIndex } from '@/aoc/array/utils.js'
+import { getCoordinateSymbol, isOutOfBounds, printGrid } from '@/aoc/grid/utils.js'
+
+/**
+ * @class Board
+ * @description Manages the `Grid`-like 2D string array, methods, and properties in which robots run
+ */
+export class Board {
+ /** Length and width settings of the 2D array */
+ settings: GridDimensions = {
+ length: 0,
+ width: 0
+ }
+
+ /** Default character value in the 2D array without robots */
+ tileSymbol: string = '.'
+
+ /** 2D string array */
+ grid: string[][] = []
+
+ /** List (array) of robots coordinate and velocity data */
+ robots: RobotProperty[] = []
+
+ /** Start and end coordinates of `this.grid` 4 main quadrants */
+ quadrants: Quadrant[] = []
+
+ /**
+ * @constructor
+ * @param {GridDimensions} grid - Object containing the length and width of the board (a 2D `string[][]` array)
+ */
+ constructor (grid: GridDimensions) {
+ this.settings = {
+ length: grid.length,
+ width: grid.width
+ }
+
+ this.create()
+ this.quadrants = this.findQuadrants()
+ }
+
+ /**
+ * Sets the string value in the 2D `this.grid[][]` array
+ * @param {Point} point - Object containing (y,x) point coordinate
+ * @param {string} symbol - String character to render on the grid tile (usually a number character)
+ * @returns {void | undefined }
+ */
+ setTileValue (point: Point, symbol: string): void | undefined {
+ if (isOutOfBounds(point, this.settings)) return
+ this.grid[point.y]![point.x] = symbol
+ }
+
+ /**
+ * Sets a new robot into the `Board`'s robots list and marks its position in `this.grid[][]` array
+ * @param {RobotProperty} robotData - Object containing one (1) robot coordinate and velocity data
+ * @returns {void | undefined}
+ */
+ setRobot (robotData: RobotProperty): void | undefined {
+ const { x, y } = robotData
+ const tileValue = getCoordinateSymbol({ x, y }, this.grid)?.symbol
+
+ if (!tileValue) return
+
+ const newPositionTileValue = tileValue === this.tileSymbol
+ ? '1'
+ : `${Number(tileValue) + 1}`
+
+ this.setTileValue({ x, y }, newPositionTileValue)
+ this.robots.push(robotData)
+ }
+
+ /**
+ * Moves a robot from a tile and updates the current and new tile's robot count
+ * @param {number} robotIndex - Array index number of a robot in the `this.robots[]` array
+ * @param {Point} point - Object containing new (y,x) coordinates for the robot at `robotIndex`
+ * @returns {void | undefined}
+ */
+ moveRobot (robotIndex: number, point: Point): void {
+ if (robotIndex < 0 || robotIndex > this.robots.length) return
+
+ const robotData = this.robots[robotIndex] as RobotProperty
+ const currentTileValue = getCoordinateSymbol(robotData, this.grid)?.symbol
+ const newPositionTileValue = getCoordinateSymbol(point, this.grid)?.symbol
+
+ if (!currentTileValue || !newPositionTileValue) return
+
+ // Clean old grid value
+ const currentTileSymbol = currentTileValue === '1'
+ ? this.tileSymbol
+ : `${Number(currentTileValue) - 1}`
+
+ // Set new grid value
+ const newPositionTileSymbol = newPositionTileValue === this.tileSymbol
+ ? '1'
+ : `${Number(newPositionTileValue) + 1}`
+
+ this.setTileValue(robotData, currentTileSymbol)
+ this.setTileValue(point, newPositionTileSymbol)
+
+ // Set the robot's new (y,x) coordinate
+ this.robots[robotIndex]!.x = point.x
+ this.robots[robotIndex]!.y = point.y
+ }
+
+ /**
+ * Creates a blank `this.length` x `this.width` board, clearing
+ * the current board contents
+ */
+ create (): void {
+ const { length, width } = this.settings
+
+ this.grid = Array.from({ length },
+ () => Array(width).fill(this.tileSymbol)
+ )
+ }
+
+ /**
+ * Finds the four (4) main quadrants of `this.grid`, each containing a set of inclusive `start` and `end` coordinates.
+ * @returns {Quadrant[]} An array of `Quadrant` objects containing the start and end coordinates of the board's 4 main quadrants.
+ */
+ findQuadrants (): Quadrant[] {
+ const xMid = arrayMiddleIndex(this.grid[0] as string[]) - 1
+ const yMid = arrayMiddleIndex(this.grid) - 1
+
+ const q1: Quadrant = {
+ id: 1,
+ start: { x: 0, y: 0 },
+ end: { x: xMid - 1, y: yMid - 1 }
+ }
+
+ const q2: Quadrant = {
+ id: 2,
+ start: { x: xMid + 1, y: 0 },
+ end: { x: this.settings.width - 1, y: yMid - 1 }
+ }
+
+ const q3: Quadrant = {
+ id: 3,
+ start: { x: 0, y: yMid + 1 },
+ end: { x: xMid - 1, y: this.settings.length - 1 }
+ }
+
+ const q4: Quadrant = {
+ id: 4,
+ start: { x: xMid + 1, y: yMid + 1 },
+ end: { x: this.settings.width - 1, y: this.settings.length - 1 }
+ }
+
+ return [q1, q2, q3, q4]
+ }
+
+ /**
+ * Finds the quadrant `ID` of a `Point` within the `this.grid` 2D array.
+ * @param {Point} point - Object containing the (y,x) coordinates of a robot
+ * @returns {number} `Quadrant.id` ID number of a `Point`
+ */
+ getQuadrant (point: Point): number {
+ const { x, y } = point
+
+ const middleX = this.quadrants[0]!.end.x + 1
+ const middleY = this.quadrants[0]!.end.y + 1
+
+ let quadrantID = -1 // gutter
+
+ if (x < middleX && y < middleY) quadrantID = 1
+ if (x > middleX && y < middleY) quadrantID = 2
+ if (x < middleX && y > middleY) quadrantID = 3
+ if (x > middleX && y > middleY) quadrantID = 4
+
+ return quadrantID
+ }
+
+ /**
+ * Draws categorized symbols per quadrant on a temporary 2D array for visualization.
+ */
+ viewQuadrants (): void {
+ const symbols = ['*', '#', '@', '%']
+
+ const { start, end } = this.quadrants[0] as Quadrant
+ const length = end.y - start.y
+ const width = end.x - start.x
+
+ const grid = Array.from({ length: this.settings.length },
+ () => Array(this.settings.width).fill(this.tileSymbol)
+ )
+
+ this.quadrants.forEach((quadrant: Quadrant, index: number) => {
+ let { x, y } = quadrant.start
+
+ for (let row = 0; row <= length; row += 1) {
+ x = quadrant.start.x
+
+ for (let col = 0; col <= width; col += 1) {
+ grid[y]![x] = symbols[index] as string
+ x += 1
+ }
+
+ y += 1
+ }
+ })
+
+ printGrid(grid)
+ }
+
+ /**
+ * Move (walk) the robots by updating their positions by velocity for N (`seconds`) times.
+ * @param {number} seconds - Number of seconds to iterate walking the robots
+ * @param {Function} callback - An external callback function that gets executed after every second iteration.
+ */
+ simulateRobotsWalk (seconds: number, callback?: (elapsedSeconds: number) => void) {
+ for (let i = 0; i < seconds; i += 1) {
+ for (let j = 0; j < this.robots.length; j += 1) {
+ // Robot's current (y,x) position
+ let { x, y } = this.robots[j] as RobotProperty
+ const { x: velocityX, y: velocityY } = this.robots[j]!.velocity
+
+ if (velocityX === 0 && velocityY === 0) continue
+
+ // Increment/decrement the current position by velocity
+ x += velocityX
+ y += velocityY
+
+ // Correct the new (y,x) position if its out of the grid bounds
+ if (isOutOfBounds({ x, y }, this.settings)) {
+ if (x > this.settings.width - 1) {
+ const overflow = x - this.settings.width
+ x = overflow
+ } else if (x < 0) {
+ x = this.settings.width + x
+ }
+
+ if (y > this.settings.length - 1) {
+ const overflow = y - this.settings.length
+ y = overflow
+ } else if (y < 0) {
+ y = this.settings.length + y
+ }
+ }
+
+ // Set the robot's new (y,x) position
+ this.moveRobot(j, { x, y })
+ }
+
+ if (callback) {
+ callback(i + 1)
+ }
+ }
+ }
+}
diff --git a/src/2024/2024-12-14/lib/fileReader.ts b/src/2024/2024-12-14/lib/fileReader.ts
new file mode 100644
index 0000000..e02b89a
--- /dev/null
+++ b/src/2024/2024-12-14/lib/fileReader.ts
@@ -0,0 +1,36 @@
+import type { RobotProperty } from './types.js'
+
+import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/aoc/file/aocfile.js'
+import { file } from '@/aoc/file/utils.js'
+
+/**
+ * Reads and formats current positions and velocities data of robots.
+ * @param {string} fileName - Input file name or relative path
+ * @returns {RobotProperty[]} Array of `RobotProperty[]` data
+ */
+export const fileReader = (fileName: string): RobotProperty[] => {
+ const input = readAOCInputFile({
+ filePath: file(import.meta.url, fileName),
+ type: AOC_OUTPUT_TYPE.STRING
+ }) as string
+
+ return input
+ .split('\n')
+ .reduce((list: RobotProperty[], line) => {
+ const indexPoint = line.indexOf('p=')
+ const indexVelocity = line.indexOf('v=')
+
+ const point = line.substring(indexPoint + 2, line.indexOf(' '))
+ const velocity = line.substring(indexVelocity + 2, line.length)
+
+ const [x, y] = point.split(',').map(Number)
+ const [vx, vy] = velocity.split(',').map(Number)
+
+ const robot = {
+ x, y,
+ velocity: { x: vx, y: vy }
+ } as RobotProperty
+
+ return [...list, robot]
+ }, [])
+}
diff --git a/src/2024/2024-12-14/lib/findEasterEgg.ts b/src/2024/2024-12-14/lib/findEasterEgg.ts
new file mode 100644
index 0000000..d2dbb48
--- /dev/null
+++ b/src/2024/2024-12-14/lib/findEasterEgg.ts
@@ -0,0 +1,66 @@
+import type { RobotProperty } from './types.js'
+
+import { Board } from './board.js'
+import { getCoordinateSymbol, printGrid } from '@/aoc/grid/utils.js'
+import type { RobotSimulationParams } from './types.js'
+
+/**
+ * Counts the no. of seconds before robots display (form) the easter egg.
+ * @typedef {RobotSimulationParams} params - Object input parameters. See the `RobotSimulationParams` interface for more information.
+ * @returns {number} Number of seconds elapsed before robots display the easter egg.
+ */
+export const findEasterEgg = (params: RobotSimulationParams): number => {
+ const { length, width } = params.gridMeta
+ const board = new Board({ length, width })
+
+ /**
+ * Count of horizontally adjacent robots without breaks (spaces) between.
+ * Hints: Count >= 30 marks the rendering start of the xmas tree's top border
+ */
+ let countAdjacentStreak = 0
+ let seconds = 0
+
+ const MAX_ADJACENT_STREAK = 30
+
+ // Set robots initial data
+ for (let i = 0; i < params.data.length; i += 1) {
+ board.setRobot(params.data[i] as RobotProperty)
+ }
+
+ board.simulateRobotsWalk(params.seconds, (elapsedSeconds: number) => {
+ if (countAdjacentStreak >= MAX_ADJACENT_STREAK) return
+
+ // Watch for adjacent symbols streak
+ for (let y = 0; y < board.settings.length; y += 1) {
+ for (let x = 0; x < board.settings.width - 1; x += 1) {
+ const symbol = getCoordinateSymbol({ x, y }, board.grid)?.symbol
+ const nextSymbol = getCoordinateSymbol({ x: x + 1, y }, board.grid)?.symbol
+
+ if (symbol === board.tileSymbol || nextSymbol === board.tileSymbol) {
+ countAdjacentStreak = 0
+ continue
+ }
+
+ if (symbol === nextSymbol) {
+ countAdjacentStreak += 1
+ } else {
+ countAdjacentStreak = 0
+ }
+
+ if (countAdjacentStreak >= MAX_ADJACENT_STREAK) {
+ if (params.log) {
+ printGrid(board.grid, '')
+ console.log('Printing grid at', x, y)
+ }
+
+ seconds = elapsedSeconds
+ break
+ }
+ }
+
+ if (countAdjacentStreak >= MAX_ADJACENT_STREAK) break
+ }
+ })
+
+ return seconds
+}
diff --git a/src/2024/2024-12-14/lib/safetyFactor.ts b/src/2024/2024-12-14/lib/safetyFactor.ts
new file mode 100644
index 0000000..581d4d3
--- /dev/null
+++ b/src/2024/2024-12-14/lib/safetyFactor.ts
@@ -0,0 +1,55 @@
+import type { RobotProperty } from './types.js'
+
+import { Board } from './board.js'
+import { printGrid } from '@/aoc/grid/utils.js'
+import type { RobotSimulationParams } from './types.js'
+
+/**
+ * Counts the safety factor after `params.seconds` of simulating moving the robots.
+ * @typedef {RobotSimulationParams} params - Object input parameters. See the `RobotSimulationParams` interface for more information.
+ * @returns {number} Safety factor after `params.seconds` have elapsed.
+ */
+export const calculateSafetyFactor = (params: RobotSimulationParams): number => {
+ const { length, width } = params.gridMeta
+ const board = new Board({ length, width })
+
+ // Set robots initial data
+ for (let i = 0; i < params.data.length; i += 1) {
+ board.setRobot(params.data[i] as RobotProperty)
+ }
+
+ if (params.log) {
+ console.log('Initialize grid')
+ printGrid(board.grid)
+ }
+
+ // Simulate robots walking
+ board.simulateRobotsWalk(params.seconds)
+
+ if (params.log) {
+ console.log(`Robots on grid after ${params.seconds} seconds`)
+ printGrid(board.grid)
+ }
+
+ // Count robots per quadrant
+ const robotsByQuadrant: Record = {}
+
+ for (let i = 0; i < board.robots.length; i += 1) {
+ const { x, y } = board.robots[i] as RobotProperty
+
+ const quadrant = board.getQuadrant({ x, y })
+ if (quadrant === -1) continue
+
+ if (robotsByQuadrant[quadrant] === undefined) {
+ robotsByQuadrant[quadrant] = 1
+ } else {
+ robotsByQuadrant[quadrant] += 1
+ }
+ }
+
+ const safetyFactor = Object.values(robotsByQuadrant).reduce((total, number) => {
+ return total *= number
+ }, 1)
+
+ return safetyFactor
+}
diff --git a/src/2024/2024-12-14/lib/types.ts b/src/2024/2024-12-14/lib/types.ts
new file mode 100644
index 0000000..7fa9f03
--- /dev/null
+++ b/src/2024/2024-12-14/lib/types.ts
@@ -0,0 +1,42 @@
+import type { Point } from '@/aoc/point/types.js'
+import type { GridDimensions } from '@/aoc/grid/types.js'
+
+/**
+ * Location and velocity properties of a Robot
+ * This interface extends {@link Point}, inheriting the `x` and `y` properties that represent
+ * the object's position in a 2D array.
+ * @interface RobotProperty
+ * @extends {Point}
+ * @property {Point} velocity - Object containing the `x` and `y` number velocities
+ */
+export interface RobotProperty extends Point {
+ velocity: Point;
+}
+
+/**
+ * Represents the quadrant of a 2D array
+ * @type {Object} Quadrant
+ * @property {number} id - Quadrant number ID
+ * @property {Point} start - (y,x) coordinates of the quadrant's starting `Point` (upper-left coordinates)
+ * @property {Point} end - (y,x) coordinates of the quadrant's ending `Point` (lower-right coordinates)
+ */
+export type Quadrant = {
+ id: number;
+ start: Point;
+ end: Point;
+}
+
+/**
+ * Object input parameter for calculating the robots safety factor
+ * @interface RobotSimulationParams
+ * @property {RobotProperty[]} data - Array of `RobotProperty` containing the initial locations and velocities of robots
+ * @property {GridDimensions} gridMeta - Object containing the length and width of the 2D string array
+ * @property {number} seconds - Number of seconds for moving the robots
+ * @property {boolean} [log] - Flag to print the 2D array on screen
+ */
+export interface RobotSimulationParams {
+ data: RobotProperty[],
+ gridMeta: GridDimensions,
+ seconds: number,
+ log?: boolean
+}
diff --git a/src/2024/2024-12-14/main.ts b/src/2024/2024-12-14/main.ts
new file mode 100644
index 0000000..7e87a38
--- /dev/null
+++ b/src/2024/2024-12-14/main.ts
@@ -0,0 +1,46 @@
+import { fileReader } from './lib/fileReader.js'
+import { calculateSafetyFactor } from './lib/safetyFactor.js'
+import { findEasterEgg } from './lib/findEasterEgg.js'
+
+const dataSample = fileReader('../input_sample.txt')
+const dataQuiz = fileReader('../input.txt')
+
+// Grid dimensions for the sample input
+const gridSample = { length: 7, width: 11 }
+
+// Grid dimensions for the randomized quiz input
+const gridQuiz = { length: 103, width: 101 }
+
+/**
+ * Part 1/2 of the 2024-12-14 quiz
+ * Counts the safety factor from robots after N seconds
+ */
+const quiz20241214_01 = () => {
+ const safetyFactor = calculateSafetyFactor({
+ data: dataSample,
+ gridMeta: gridSample,
+ seconds: 100,
+ log: true
+ })
+
+ console.log('Safety factor:', safetyFactor)
+}
+
+/**
+ * Part 2/2 of the 2024-12-14 quiz
+ * Counts the no. of seconds before robots display the easter egg
+ */
+const quiz20241214_02 = () => {
+ const seconds = findEasterEgg({
+ data: dataQuiz,
+ gridMeta: gridQuiz,
+ seconds: 10000
+ // Uncomment to print the xmas tree
+ // log: true
+ })
+
+ console.log('Easter egg iteration no. (seconds):', seconds)
+}
+
+quiz20241214_01()
+quiz20241214_02()
diff --git a/src/2024/2024-12-14/sample.test.ts b/src/2024/2024-12-14/sample.test.ts
new file mode 100644
index 0000000..959ecea
--- /dev/null
+++ b/src/2024/2024-12-14/sample.test.ts
@@ -0,0 +1,34 @@
+import { test, expect } from 'vitest'
+
+import { fileReader } from './lib/fileReader.js'
+import { calculateSafetyFactor } from './lib/safetyFactor.js'
+import { findEasterEgg } from './lib/findEasterEgg.js'
+
+const dataSample = fileReader('../input_sample.txt')
+const dataQuiz = fileReader('../input.txt')
+
+// Grid dimensions for the sample input
+const gridSample = { length: 7, width: 11 }
+
+// Grid dimensions for the randomized quiz input
+const gridQuiz = { length: 103, width: 101 }
+
+test('Safety factor:', () => {
+ const safetyFactor = calculateSafetyFactor({
+ data: dataSample,
+ gridMeta: gridSample,
+ seconds: 100
+ })
+
+ expect(safetyFactor).toBe(10)
+})
+
+test('Easter egg iteration no. (seconds):', () => {
+ const seconds = findEasterEgg({
+ data: dataQuiz,
+ gridMeta: gridQuiz,
+ seconds: 10000
+ })
+
+ expect(seconds).toBe(1)
+})
diff --git a/src/aoc/README.md b/src/aoc/README.md
index f649837..f0bfc83 100644
--- a/src/aoc/README.md
+++ b/src/aoc/README.md
@@ -24,12 +24,13 @@ A collection handler functions for manipulating regular arrays.
A collection of convenience handler functions for AoC 2D input arrays.
-- **`getCoordinateSymbol()`** - Converts a 2D `Point` point object to a string and returns its value from the 2D array
+- **`getCoordinateSymbol()`** - Converts a 2D `Point` point object to a string and returns its value from the 2D `string` or `number` array
- **`getGridDimensions()`** - Retrieves the length and width of a generic 2D array
- **`isIllegalCoordinate()`** - Checks if a point at coordinate (y,x) in a grid is illegal: if it's out of bounds of the grid area or if its symbol differs from the symbol parameter.
- **`isOutOfBounds()`** - Checks if a (y,x) coordinate is out of the grid area
- **`getDiagonalNeighbors()`** - Retrieves the four (4) diagonally aligned (y,x) coordinates and the symbol character from a `Point` in the grid. Substitutes a `"*"` symbol character in the `PointSymbol.symbol`if the `point` is out of the grid bounds.
- **`getCrossNeighbors()`** - Retrieves the four (4) horizontal/vertical aligned (y,x) coordinates and the symbol character from a `Point` in the grid. Substitutes a `"*"` symbol character in the `PointSymbol.symbol`if the `point` is out of the grid bounds.
+- **`printGrid()`** - Prints the contents of a 2D `string` or `number` array to screen.
### 📂 `number`
diff --git a/src/aoc/grid/types.ts b/src/aoc/grid/types.ts
index 81d1d08..44ca9f0 100644
--- a/src/aoc/grid/types.ts
+++ b/src/aoc/grid/types.ts
@@ -2,12 +2,13 @@ import type { Point } from '../point/types.js'
/**
* Represents a "(y,x)" coordinate in string and its value from a 2D array.
- * @param {string} coordinate - String version of an "(y,x)" coordinate
- * @param {string | number} symbol - Number or character in a 2D arary denoted by the (y,x) coordinate
+ * @template T Extends `string` or `number`, representing the type of the symbol value.
+ * @property {string} coordinate - String version of a "(y,x)" coordinate
+ * @property {T} symbol - Number or character in a 2D array denoted by the (y,x) coordinate
*/
-export type GridCoordinateSymbol = {
+export type GridCoordinateSymbol = {
coordinate: string;
- symbol: string | number;
+ symbol: T
}
/**
diff --git a/src/aoc/grid/utils.ts b/src/aoc/grid/utils.ts
index 7d8cc59..4f55540 100644
--- a/src/aoc/grid/utils.ts
+++ b/src/aoc/grid/utils.ts
@@ -9,17 +9,23 @@ import type { Point, PointSymbol } from '../point/types.js'
import { findNeighbors } from '../point/utils.js'
/**
- * Converts a 2D `Point` point object to a string and returns its value from the 2D array
+ * Converts a 2D `Point` point object to a string and returns its value from the 2D `string` or `number` array
+ * @template T Extends `string` or `number`, representing the type of elements in the 2D array.
* @param {Point} point - (y,x) coordinatate in the 2D array
- * @param {number[][]} data - 2D number array containing hiking trail data
- * @returns {GridCoordinateSymbol} Returns the `GridCoordinateSymbol` (x,y) coordinate expressed in string and its value
+ * @param {T[][]} data - 2D array containing `number` or `string` elements
+ * @returns {GridCoordinateSymbol | undefined} Returns the `GridCoordinateSymbol` (x,y) coordinate expressed in string and its value
+ * or `undefined` if the `point` coordinate is out of the grid bounds
*/
-export const getCoordinateSymbol = (point: Point, data: number[][] | string[][]): GridCoordinateSymbol => {
- return {
- coordinate: `${point!.x},${point!.y}`,
- symbol: data[point!.y]![point!.x] as number
+export const getCoordinateSymbol =
+ (point: Point, data: T[][]): GridCoordinateSymbol | undefined => {
+ const gridMeta = { length: data.length, width: data[0]!.length }
+ if (isOutOfBounds(point, gridMeta)) return
+
+ return {
+ coordinate: `${point!.x},${point!.y}`,
+ symbol: data[point!.y]![point!.x] as T
+ }
}
-}
/**
* Retrieves the length and width of a generic 2D array
@@ -56,7 +62,7 @@ export const isIllegalCoordinate = (params: IllegalCoordinateParams): boolean =>
* Checks if a (y,x) coordinate is out of the grid area
* @param {Point} point - (y,x) coordinate
* @param {GridDimensions} gridMeta - Length and width definitions of a 2D array (grid)
- * @returns {boolean} Flag if a coordinate is out of the grid area
+ * @returns {boolean} Flag indicating if a coordinate is out of the grid area
*/
export const isOutOfBounds = (point: Point, gridMeta: GridDimensions): boolean => {
return (
@@ -115,3 +121,14 @@ export const getCrossNeighbors = (point: Point, data: string[][]): PointSymbol[]
return [...list, item]
}, [])
}
+
+/**
+ * Prints the contents of a 2D `string` or `number` array to screen.
+ * @param {string[][] | number[][]} grid - 2D string or number array to print on screen.
+ * @param {string} delimeter - String character to put between the array elements. Default value is a space `" "`.
+ * @returns {void}
+ */
+export const printGrid = (grid: string[][] | number[][], delimeter: string = ' '): void => {
+ if (!grid) return
+ console.log(grid.map(row => row.join(delimeter)))
+}