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))) +}