From 0c34e27b40686cdb72caa595bcf3d0e2672f64d1 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 10 Jan 2025 02:58:30 +0800 Subject: [PATCH 1/8] chore: add common print array method --- src/2024/2024-12-13/main.ts | 4 ++-- src/aoc/README.md | 1 + src/aoc/grid/utils.ts | 10 ++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) 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/aoc/README.md b/src/aoc/README.md index f649837..d41d794 100644 --- a/src/aoc/README.md +++ b/src/aoc/README.md @@ -30,6 +30,7 @@ A collection of convenience handler functions for AoC 2D input arrays. - **`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/utils.ts b/src/aoc/grid/utils.ts index 7d8cc59..38e6280 100644 --- a/src/aoc/grid/utils.ts +++ b/src/aoc/grid/utils.ts @@ -115,3 +115,13 @@ 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. + * @returns {void} + */ +export const printGrid = (grid: string[][] | number[][]): void => { + if (!grid) return + console.log(grid.map(row => row.join(' '))) +} From cf0901dae5ebcf551614663417e9234cdb230a07 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 10 Jan 2025 03:00:21 +0800 Subject: [PATCH 2/8] feat: day 14 restroom redoubt 1/2 soln, #22 --- src/2024/2024-12-14/README.md | 26 +++ src/2024/2024-12-14/assets/sample.PNG | Bin 0 -> 6633 bytes src/2024/2024-12-14/input.txt | 12 ++ src/2024/2024-12-14/lib/board.ts | 216 ++++++++++++++++++++++++ src/2024/2024-12-14/lib/fileReader.ts | 36 ++++ src/2024/2024-12-14/lib/safetyFactor.ts | 84 +++++++++ src/2024/2024-12-14/lib/types.ts | 42 +++++ src/2024/2024-12-14/main.ts | 21 +++ src/2024/2024-12-14/sample.test.ts | 17 ++ 9 files changed, 454 insertions(+) create mode 100644 src/2024/2024-12-14/README.md create mode 100644 src/2024/2024-12-14/assets/sample.PNG create mode 100644 src/2024/2024-12-14/input.txt create mode 100644 src/2024/2024-12-14/lib/board.ts create mode 100644 src/2024/2024-12-14/lib/fileReader.ts create mode 100644 src/2024/2024-12-14/lib/safetyFactor.ts create mode 100644 src/2024/2024-12-14/lib/types.ts create mode 100644 src/2024/2024-12-14/main.ts create mode 100644 src/2024/2024-12-14/sample.test.ts diff --git a/src/2024/2024-12-14/README.md b/src/2024/2024-12-14/README.md new file mode 100644 index 0000000..98fcc64 --- /dev/null +++ b/src/2024/2024-12-14/README.md @@ -0,0 +1,26 @@ +## 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:** On-going ⭐ + +## Code + +### `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 +- **`getTileValue()`** - Returns the value of the number or character in the `point` coordinate, or `undefined` if the `point` coordinate is out of the grid bounds +- **`setTileValue()`** - Sets the string value in the 2D `this.grid[][]` array +- **`setRobot()`** - Sets a new robot into the `Board`'s robots list and marks its position in `this.grid[][]` array +- **`moveRobot()`** - Moves a robot from a tile and updates the current and new tile's robot count +- **`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. +- **`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 0000000000000000000000000000000000000000..71cb21cca09b7fc390f885f814c50a2d84e337b9 GIT binary patch literal 6633 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 Sxd}u9q<_xnZ1I_^cm4*udR%7! literal 0 HcmV?d00001 diff --git a/src/2024/2024-12-14/input.txt b/src/2024/2024-12-14/input.txt new file mode 100644 index 0000000..6916d15 --- /dev/null +++ b/src/2024/2024-12-14/input.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..3c6653d --- /dev/null +++ b/src/2024/2024-12-14/lib/board.ts @@ -0,0 +1,216 @@ +import type { GridDimensions } from '@/aoc/grid/types.js' +import { isOutOfBounds, printGrid } from '@/aoc/grid/utils.js' +import type { Point } from '@/aoc/point/types.js' +import type { Quadrant, RobotProperty } from './types.js' +import { arrayMiddleIndex } from '@/aoc/array/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[] = [] + + 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() + } + + /** + * Returns the value of the number or character in the `point` coordinate, or + * `undefined` if the `point` coordinate is out of the grid bounds + * @param {Point} point - Object containing (y,x) point coordinate + * @returns {string | undefined} Value of the number or character + */ + getTileValue (point: Point): string | undefined { + if (isOutOfBounds(point, this.settings)) return + return this.grid[point.y]![point.x] + } + + /** + * 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 = this.getTileValue(robotData) + + 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 = this.getTileValue(robotData) + const newPositionTileValue = this.getTileValue(point) + + 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) + } +} 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/safetyFactor.ts b/src/2024/2024-12-14/lib/safetyFactor.ts new file mode 100644 index 0000000..fb586dd --- /dev/null +++ b/src/2024/2024-12-14/lib/safetyFactor.ts @@ -0,0 +1,84 @@ +import type { RobotProperty } from './types.js' + +import { Board } from './board.js' +import { isOutOfBounds, 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) => { + 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 + for (let i = 0; i < params.seconds; i += 1) { + for (let j = 0; j < board.robots.length; j += 1) { + // Robot's current (y,x) position + let { x, y } = board.robots[j] as RobotProperty + + // Increment/decrement the current position by velocity + x += board.robots[j]!.velocity.x + y += board.robots[j]!.velocity.y + + // Correct the new (y,x) position if its out of the grid bounds + if (isOutOfBounds({ x, y }, board.settings)) { + if (x > board.settings.width - 1) { + const overflow = x - board.settings.width + x = overflow + } else if (x < 0) { + x = board.settings.width + x + } + + if (y > board.settings.length - 1) { + const overflow = y - board.settings.length + y = overflow + } else if (y < 0) { + y = board.settings.length + y + } + } + + // Set the robot's new (y,x) position + board.moveRobot(j, { x, y }) + } + } + + 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..cc5d9c7 --- /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..b102a0c --- /dev/null +++ b/src/2024/2024-12-14/main.ts @@ -0,0 +1,21 @@ +import { fileReader } from './lib/fileReader.js' +import { calculateSafetyFactor } from './lib/safetyFactor.js' + +const data = fileReader('../input.txt') + +/** + * Part 1/2 of the 2024-12-14 quiz + * Counts the safety factor from robots after N seconds + */ +const main = () => { + const safetyFactor = calculateSafetyFactor({ + data, + gridMeta: { length: 7, width: 11 }, + seconds: 100, + log: true + }) + + console.log('Safety factor:', safetyFactor) +} + +main() 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..2675bf8 --- /dev/null +++ b/src/2024/2024-12-14/sample.test.ts @@ -0,0 +1,17 @@ +import { test, expect } from 'vitest' + +import { fileReader } from './lib/fileReader.js' +import { calculateSafetyFactor } from './lib/safetyFactor.js' + +const data = fileReader('../input.txt') + +test('Safety factor:', () => { + const safetyFactor = calculateSafetyFactor({ + data, + gridMeta: { length: 7, width: 11 }, + seconds: 100, + log: true + }) + + expect(safetyFactor).toBe(10) +}) From cd208e0d8912f3597543467406d895aaf56c9d92 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 10 Jan 2025 03:02:57 +0800 Subject: [PATCH 3/8] fix: lint error - remove semicolon --- src/2024/2024-12-14/lib/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/2024/2024-12-14/lib/types.ts b/src/2024/2024-12-14/lib/types.ts index cc5d9c7..7fa9f03 100644 --- a/src/2024/2024-12-14/lib/types.ts +++ b/src/2024/2024-12-14/lib/types.ts @@ -1,5 +1,5 @@ import type { Point } from '@/aoc/point/types.js' -import type { GridDimensions } from '@/aoc/grid/types.js'; +import type { GridDimensions } from '@/aoc/grid/types.js' /** * Location and velocity properties of a Robot From 8e32f5abde5ffaa77107e531a1685ad8bd4514d1 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 10 Jan 2025 03:47:49 +0800 Subject: [PATCH 4/8] chore: use templates in the getCoordinateSymbol fnx --- src/2024/2024-12-10/lib/scoresRatings.ts | 4 ++++ src/aoc/README.md | 2 +- src/aoc/grid/types.ts | 9 +++++---- src/aoc/grid/utils.ts | 24 +++++++++++++++--------- 4 files changed, 25 insertions(+), 14 deletions(-) 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/aoc/README.md b/src/aoc/README.md index d41d794..f0bfc83 100644 --- a/src/aoc/README.md +++ b/src/aoc/README.md @@ -24,7 +24,7 @@ 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 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 38e6280..4962e7c 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 ( From 4ff2b2fd5ef8ec2f0161fce042a9c68ce7e4030a Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 10 Jan 2025 03:50:14 +0800 Subject: [PATCH 5/8] chore: get grid symbol using aoc getCoordinateSymbol --- src/2024/2024-12-14/README.md | 1 - src/2024/2024-12-14/lib/board.ts | 21 ++++++--------------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/2024/2024-12-14/README.md b/src/2024/2024-12-14/README.md index 98fcc64..701e501 100644 --- a/src/2024/2024-12-14/README.md +++ b/src/2024/2024-12-14/README.md @@ -16,7 +16,6 @@ Visit the Advent of Code website for more information on this puzzle at: **Board** class - Manages the `Grid`-like 2D string array, methods, and properties in which robots run -- **`getTileValue()`** - Returns the value of the number or character in the `point` coordinate, or `undefined` if the `point` coordinate is out of the grid bounds - **`setTileValue()`** - Sets the string value in the 2D `this.grid[][]` array - **`setRobot()`** - Sets a new robot into the `Board`'s robots list and marks its position in `this.grid[][]` array - **`moveRobot()`** - Moves a robot from a tile and updates the current and new tile's robot count diff --git a/src/2024/2024-12-14/lib/board.ts b/src/2024/2024-12-14/lib/board.ts index 3c6653d..5947314 100644 --- a/src/2024/2024-12-14/lib/board.ts +++ b/src/2024/2024-12-14/lib/board.ts @@ -1,8 +1,9 @@ import type { GridDimensions } from '@/aoc/grid/types.js' -import { isOutOfBounds, printGrid } from '@/aoc/grid/utils.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 @@ -24,6 +25,7 @@ export class Board { /** List (array) of robots coordinate and velocity data */ robots: RobotProperty[] = [] + /** Start and end coordinates of `this.grid` 4 main quadrants */ quadrants: Quadrant[] = [] /** @@ -40,17 +42,6 @@ export class Board { this.quadrants = this.findQuadrants() } - /** - * Returns the value of the number or character in the `point` coordinate, or - * `undefined` if the `point` coordinate is out of the grid bounds - * @param {Point} point - Object containing (y,x) point coordinate - * @returns {string | undefined} Value of the number or character - */ - getTileValue (point: Point): string | undefined { - if (isOutOfBounds(point, this.settings)) return - return this.grid[point.y]![point.x] - } - /** * Sets the string value in the 2D `this.grid[][]` array * @param {Point} point - Object containing (y,x) point coordinate @@ -69,7 +60,7 @@ export class Board { */ setRobot (robotData: RobotProperty): void | undefined { const { x, y } = robotData - const tileValue = this.getTileValue(robotData) + const tileValue = getCoordinateSymbol({ x, y }, this.grid)?.symbol if (!tileValue) return @@ -91,8 +82,8 @@ export class Board { if (robotIndex < 0 || robotIndex > this.robots.length) return const robotData = this.robots[robotIndex] as RobotProperty - const currentTileValue = this.getTileValue(robotData) - const newPositionTileValue = this.getTileValue(point) + const currentTileValue = getCoordinateSymbol(robotData, this.grid)?.symbol + const newPositionTileValue = getCoordinateSymbol(robotData, this.grid)?.symbol if (!currentTileValue || !newPositionTileValue) return From 62eff7fedd7f2665056a3bd31c2046f85155f06b Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 10 Jan 2025 04:34:12 +0800 Subject: [PATCH 6/8] fix: new robots position visual display --- src/2024/2024-12-14/lib/board.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/2024/2024-12-14/lib/board.ts b/src/2024/2024-12-14/lib/board.ts index 5947314..bd3295f 100644 --- a/src/2024/2024-12-14/lib/board.ts +++ b/src/2024/2024-12-14/lib/board.ts @@ -83,7 +83,7 @@ export class Board { const robotData = this.robots[robotIndex] as RobotProperty const currentTileValue = getCoordinateSymbol(robotData, this.grid)?.symbol - const newPositionTileValue = getCoordinateSymbol(robotData, this.grid)?.symbol + const newPositionTileValue = getCoordinateSymbol(point, this.grid)?.symbol if (!currentTileValue || !newPositionTileValue) return From 29b16d8925bbcf536cd0b93fb4a7233af4c17014 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 10 Jan 2025 06:25:31 +0800 Subject: [PATCH 7/8] refactor: put robots walking sim in class --- src/2024/2024-12-14/README.md | 7 +++-- src/2024/2024-12-14/lib/board.ts | 42 +++++++++++++++++++++++++ src/2024/2024-12-14/lib/safetyFactor.ts | 35 ++------------------- src/2024/2024-12-14/main.ts | 13 +++++--- src/aoc/grid/utils.ts | 5 +-- 5 files changed, 60 insertions(+), 42 deletions(-) diff --git a/src/2024/2024-12-14/README.md b/src/2024/2024-12-14/README.md index 701e501..862ffca 100644 --- a/src/2024/2024-12-14/README.md +++ b/src/2024/2024-12-14/README.md @@ -16,10 +16,11 @@ Visit the Advent of Code website for more information on this puzzle at: **Board** class - Manages the `Grid`-like 2D string array, methods, and properties in which robots run -- **`setTileValue()`** - Sets the string value in the 2D `this.grid[][]` array -- **`setRobot()`** - Sets a new robot into the `Board`'s robots list and marks its position in `this.grid[][]` array -- **`moveRobot()`** - Moves a robot from a tile and updates the current and new tile's robot count - **`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/lib/board.ts b/src/2024/2024-12-14/lib/board.ts index bd3295f..c8d2e2b 100644 --- a/src/2024/2024-12-14/lib/board.ts +++ b/src/2024/2024-12-14/lib/board.ts @@ -204,4 +204,46 @@ export class Board { printGrid(grid) } + + /** + * Move (walk) the robots by updating their positions by velocity by `seconds` times. + * @param {number} seconds - Number of seconds to walk 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 + + // Increment/decrement the current position by velocity + x += this.robots[j]!.velocity.x + y += this.robots[j]!.velocity.y + + // 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/safetyFactor.ts b/src/2024/2024-12-14/lib/safetyFactor.ts index fb586dd..581d4d3 100644 --- a/src/2024/2024-12-14/lib/safetyFactor.ts +++ b/src/2024/2024-12-14/lib/safetyFactor.ts @@ -1,7 +1,7 @@ import type { RobotProperty } from './types.js' import { Board } from './board.js' -import { isOutOfBounds, printGrid } from '@/aoc/grid/utils.js' +import { printGrid } from '@/aoc/grid/utils.js' import type { RobotSimulationParams } from './types.js' /** @@ -9,7 +9,7 @@ import type { RobotSimulationParams } from './types.js' * @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) => { +export const calculateSafetyFactor = (params: RobotSimulationParams): number => { const { length, width } = params.gridMeta const board = new Board({ length, width }) @@ -24,36 +24,7 @@ export const calculateSafetyFactor = (params: RobotSimulationParams) => { } // Simulate robots walking - for (let i = 0; i < params.seconds; i += 1) { - for (let j = 0; j < board.robots.length; j += 1) { - // Robot's current (y,x) position - let { x, y } = board.robots[j] as RobotProperty - - // Increment/decrement the current position by velocity - x += board.robots[j]!.velocity.x - y += board.robots[j]!.velocity.y - - // Correct the new (y,x) position if its out of the grid bounds - if (isOutOfBounds({ x, y }, board.settings)) { - if (x > board.settings.width - 1) { - const overflow = x - board.settings.width - x = overflow - } else if (x < 0) { - x = board.settings.width + x - } - - if (y > board.settings.length - 1) { - const overflow = y - board.settings.length - y = overflow - } else if (y < 0) { - y = board.settings.length + y - } - } - - // Set the robot's new (y,x) position - board.moveRobot(j, { x, y }) - } - } + board.simulateRobotsWalk(params.seconds) if (params.log) { console.log(`Robots on grid after ${params.seconds} seconds`) diff --git a/src/2024/2024-12-14/main.ts b/src/2024/2024-12-14/main.ts index b102a0c..4c08afb 100644 --- a/src/2024/2024-12-14/main.ts +++ b/src/2024/2024-12-14/main.ts @@ -1,16 +1,19 @@ import { fileReader } from './lib/fileReader.js' import { calculateSafetyFactor } from './lib/safetyFactor.js' -const data = fileReader('../input.txt') +const dataSample = fileReader('../input_sample.txt') + +// Grid dimensions for the sample input +const gridSample = { length: 7, width: 11 } /** * Part 1/2 of the 2024-12-14 quiz * Counts the safety factor from robots after N seconds */ -const main = () => { +const quiz20241214_01 = () => { const safetyFactor = calculateSafetyFactor({ - data, - gridMeta: { length: 7, width: 11 }, + data: dataSample, + gridMeta: gridSample, seconds: 100, log: true }) @@ -18,4 +21,4 @@ const main = () => { console.log('Safety factor:', safetyFactor) } -main() +quiz20241214_01() diff --git a/src/aoc/grid/utils.ts b/src/aoc/grid/utils.ts index 4962e7c..4f55540 100644 --- a/src/aoc/grid/utils.ts +++ b/src/aoc/grid/utils.ts @@ -125,9 +125,10 @@ export const getCrossNeighbors = (point: Point, data: string[][]): PointSymbol[] /** * 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[][]): void => { +export const printGrid = (grid: string[][] | number[][], delimeter: string = ' '): void => { if (!grid) return - console.log(grid.map(row => row.join(' '))) + console.log(grid.map(row => row.join(delimeter))) } From 6f3919f7b55a0545e884e0ae5e47075f8a768d34 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 10 Jan 2025 08:27:14 +0800 Subject: [PATCH 8/8] feat: day 14 restroom redoubt 2/2 soln, #22 --- README.md | 1 + src/2024/2024-12-14/README.md | 9 +- src/2024/2024-12-14/input.txt | 435 ++++++++++++++++++++++- src/2024/2024-12-14/input_sample.txt | 12 + src/2024/2024-12-14/lib/board.ts | 11 +- src/2024/2024-12-14/lib/findEasterEgg.ts | 66 ++++ src/2024/2024-12-14/main.ts | 22 ++ src/2024/2024-12-14/sample.test.ts | 27 +- 8 files changed, 560 insertions(+), 23 deletions(-) create mode 100644 src/2024/2024-12-14/input_sample.txt create mode 100644 src/2024/2024-12-14/lib/findEasterEgg.ts 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-14/README.md b/src/2024/2024-12-14/README.md index 862ffca..787178f 100644 --- a/src/2024/2024-12-14/README.md +++ b/src/2024/2024-12-14/README.md @@ -3,13 +3,18 @@ Visit the Advent of Code website for more information on this puzzle at: **Source:** https://adventofcode.com/2024/day/14
-**Status:** On-going ⭐ +**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. +- **`calculateSafetyFactor()`** - Counts the safety factor after `params.seconds` of simulating moving the robots. ### `board.ts` diff --git a/src/2024/2024-12-14/input.txt b/src/2024/2024-12-14/input.txt index 6916d15..7138eb1 100644 --- a/src/2024/2024-12-14/input.txt +++ b/src/2024/2024-12-14/input.txt @@ -1,12 +1,423 @@ -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 +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 index c8d2e2b..7c4f2f2 100644 --- a/src/2024/2024-12-14/lib/board.ts +++ b/src/2024/2024-12-14/lib/board.ts @@ -206,8 +206,8 @@ export class Board { } /** - * Move (walk) the robots by updating their positions by velocity by `seconds` times. - * @param {number} seconds - Number of seconds to walk the robots + * 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) { @@ -215,10 +215,13 @@ export class Board { 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 += this.robots[j]!.velocity.x - y += this.robots[j]!.velocity.y + x += velocityX + y += velocityY // Correct the new (y,x) position if its out of the grid bounds if (isOutOfBounds({ x, y }, this.settings)) { 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/main.ts b/src/2024/2024-12-14/main.ts index 4c08afb..7e87a38 100644 --- a/src/2024/2024-12-14/main.ts +++ b/src/2024/2024-12-14/main.ts @@ -1,11 +1,16 @@ 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 @@ -21,4 +26,21 @@ const quiz20241214_01 = () => { 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 index 2675bf8..959ecea 100644 --- a/src/2024/2024-12-14/sample.test.ts +++ b/src/2024/2024-12-14/sample.test.ts @@ -2,16 +2,33 @@ import { test, expect } from 'vitest' import { fileReader } from './lib/fileReader.js' import { calculateSafetyFactor } from './lib/safetyFactor.js' +import { findEasterEgg } from './lib/findEasterEgg.js' -const data = fileReader('../input.txt') +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, - gridMeta: { length: 7, width: 11 }, - seconds: 100, - log: true + 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) +})