diff --git a/README.md b/README.md index b3c8d61..4bf1dbf 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ This repository contains solutions and a local development environment for the [ - 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) +- Day 15: Warehouse Woes [[link]](/src/2024/2024-12-15/README.md) diff --git a/src/2024/2024-12-06/lib/guard.types.ts b/src/2024/2024-12-06/lib/guard.types.ts index 4106c64..d2efd31 100644 --- a/src/2024/2024-12-06/lib/guard.types.ts +++ b/src/2024/2024-12-06/lib/guard.types.ts @@ -3,9 +3,9 @@ * * @enum {string} * @property {string} UP - Upward direction `"^"` - * @property {string} RIGHT - Upward direction `">"` - * @property {string} DOWN - Upward direction `"v"` - * @property {string} LEFT - Upward direction `"<"` + * @property {string} RIGHT - Right direction `">"` + * @property {string} DOWN - Downward direction `"v"` + * @property {string} LEFT - Left direction `"<"` */ export enum GuardDirection { UP = '^', diff --git a/src/2024/2024-12-15/README.md b/src/2024/2024-12-15/README.md new file mode 100644 index 0000000..d2522b1 --- /dev/null +++ b/src/2024/2024-12-15/README.md @@ -0,0 +1,95 @@ +## Day 15: Warehouse Woes + +Visit the Advent of Code website for more information on this puzzle at: + +**Source:** https://adventofcode.com/2024/day/15
+**Status:** Complete ⭐⭐ + +![part2 animation from AoC online sample](assets/part2.gif)
+(Part2 animation from AoC online sample) + +## Code + +### `robot.ts` + +**Robot** class + +- Manages the `Robot` object that runs across the grid and moves boxes +- **`findInitialPosition()`** - Finds the robot's initial `Point` position in the 2D grid and stores them in the `this.pos` object +- **`readInstruction()`** - Reads the next instruction and sets the (y,x) direction +- **`walk()`** - Increments the robot's (y,x) coordinate by direction +- **`next()`** - Finds the **next** (y,x) coordinate of the robot or a given `Point` parameter using the robot's current direction. +- **`prev()`** - Finds the robot's **previous** (y,x) coordinate or a given `Point` parameter using the robot's current direction. + +### `calculateGPS.ts` + +- **`moveBoxes()`** - Moves the robot and boxes across the grid +- **`calculateGPS()`** - Calculates the GPS sum of all boxes in the grid + +### `calculateExpandedGPS.ts` + +- **`moveExpandedBoxes()`** - Moves the robot and the expanded (2x size) boxes across the grid. +- **`calculateExpandedGPS()`** - Calculates the GPS sum of all expanded boxes in the grid. + +### `fileReader.ts` + +- **`fileReader()`** - Reads and formats the day 15 quiz input file. + +### `fileReaderExpanded.ts` + +- **`fileReader()`** - Reads and formats the day 15 - part 2 quiz input file. Expands the tile symbols by 2. + +## Notes + +### Main Objects + +1. GRID + - 2d board array + - contains + - blank space: `.` + - walls: `#` + - box: `O` + - amok robot: = `@` + +2. WALLS + - symbol `#` + - surrounds grid edges + - found inside grid + - impassable by: `O`, `@` + - passable: GPS + +3. BOX + - symbol `O` + - impassable: `@`, other `O` + - passable: GPS + - can be moved up/down/left/right by `@` + +4. ROBOT + - symbol `@` + - has move instructions + - can move up/down/left/rigt + - can push `O` + - cannot move if blocked by unpushable `O` + - cannot move if blocked by `#` + +### Algo + +#### Phase 1 - Move Boxes + +- initialize all pos in grid +- read robot move instruction +- turn + move according to instruction +- if symbol ahead is `O` + - chain pushing preceeding `O`s + - if `O` unmovable, stop +- if symbol ahead is `.` + - move +- if symbol ahead is `#` + - stop +- repeat until end of move instructions + +#### Phase 2 - Calculate Box GPS + +- for each box, + - GPS = 100 * distance from top + distance from left +- sum all box GPS diff --git a/src/2024/2024-12-15/assets/part2.gif b/src/2024/2024-12-15/assets/part2.gif new file mode 100644 index 0000000..b9ce532 Binary files /dev/null and b/src/2024/2024-12-15/assets/part2.gif differ diff --git a/src/2024/2024-12-15/input.txt b/src/2024/2024-12-15/input.txt new file mode 100644 index 0000000..8d35d95 --- /dev/null +++ b/src/2024/2024-12-15/input.txt @@ -0,0 +1,10 @@ +######## +#..O.O.# +##@.O..# +#...O..# +#.#.O..# +#...O..# +#......# +######## + +<^>>v>>^v \ No newline at end of file diff --git a/src/2024/2024-12-15/input2.txt b/src/2024/2024-12-15/input2.txt new file mode 100644 index 0000000..621c19c --- /dev/null +++ b/src/2024/2024-12-15/input2.txt @@ -0,0 +1,15 @@ +########## +#..O..O.O# +#......O.# +#.OO..O.O# +#..O@..O.# +#O#..O...# +#O..O..O.# +#.OO.O.OO# +#....O...# +########## + +^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^ +vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv< +<>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ \ No newline at end of file diff --git a/src/2024/2024-12-15/lib/calculateExpandedGPS.ts b/src/2024/2024-12-15/lib/calculateExpandedGPS.ts new file mode 100644 index 0000000..06f0066 --- /dev/null +++ b/src/2024/2024-12-15/lib/calculateExpandedGPS.ts @@ -0,0 +1,156 @@ +import type { Point, PointSymbol } from '@/aoc/point/types.js' + +import { getCoordinateSymbol, getGridDimensions, isOutOfBounds } from '@/aoc/grid/utils.js' + +import { + getBoxStartCoordinates, + getReverseSymbol, + getSymbolDirection, + isExpandedBoxSymbol +} from './utils.js' + +import { Robot } from './robot.js' + +/** + * Moves the robot and the expanded (2x size) boxes across the grid + * @param {string[][]} grid - 2D string array containing walls, boxes and space data + * @param {string[]} instructions - String array containing the robot's pushBox instructions + * @returns {void} + */ +export const moveExpandedBoxes = (grid: string[][], instructions: string[]): void => { + const dimensions = getGridDimensions(grid) + const robot = new Robot(grid, instructions) + + while (robot.instructions.length > 0) { + robot.readInstruction() + + let nextPos = robot.next() + let nextSymbol = getCoordinateSymbol(nextPos, grid)?.symbol as string + + // Move robot to the next blank space + if (nextSymbol === '.') { + grid[robot.pos.y]![robot.pos.x] = '.' + grid[nextPos.y]![nextPos.x] = robot.symbol + robot.walk() + } + + if (isExpandedBoxSymbol(nextSymbol)) { + // Move boxes along the horizontal x-axis + if (robot.direction.x !== 0) { + const boxes: Point[] = [nextPos] + + while (!isOutOfBounds(nextPos, dimensions) && isExpandedBoxSymbol(nextSymbol)) { + nextPos = robot.next(nextPos) + nextSymbol = getCoordinateSymbol(nextPos, grid)?.symbol as string + + if (isExpandedBoxSymbol(nextSymbol)) { + boxes.push(nextPos) + } + } + + // Move groups of boxes if there's a space at their end + if (nextSymbol === '.' && boxes.length > 0) { + for (let i = boxes.length - 1; i >= 0; i -= 1) { + const next = robot.next(boxes[i]) + const temp = grid[boxes[i]!.y]![boxes[i]!.x] as string + + grid[boxes[i]!.y]![boxes[i]!.x] = '.' + grid[next.y]![next.x] = temp + } + + // Move the robot + grid[robot.pos.y]![robot.pos.x] = '.' + robot.walk() + grid[robot.pos.y]![robot.pos.x] = robot.symbol + } + } + + // Move boxes along the vertical y-axis + if (robot.direction.y !== 0) { + // 1st box after the robot + const { side1, side2 } = getBoxStartCoordinates(nextPos, grid) + let start = { ...side1 } + + const visited: PointSymbol[] = [] + const retrace: PointSymbol[] = [side2] + let canMove = true + + while (start && isExpandedBoxSymbol(start.symbol)) { + const next = robot.next(start) + const nextSym = getCoordinateSymbol(next, grid)!.symbol + + // Store the visited box coordinates + visited.push(start) + + if (['.', '#'].includes(nextSym as string)) { + // End of line reached (space or wall): Retrace unvisited connected coordinates + start = retrace.pop() as PointSymbol + + // Connected boxes will not move at anytime a wall `#` is encountered + if (nextSym === '#') { + canMove = false + } + } else if (nextSym === start.symbol) { + // Found stack of boxes: + // Move to the next (up/down) coordinate if its symbol is similar to the current symbol + start = { ...next } as PointSymbol + start.symbol = getCoordinateSymbol(start, grid)!.symbol + } else if (nextSym === getReverseSymbol(start.symbol)) { + // Found half-stacked boxes: + // Stack the next coordinate if it's symbol is different from the current symbol + retrace.push({ ...next, symbol: nextSym }) + + // Move to the next diagonally-aligned half-box symbol coordinate + const xDirection = getSymbolDirection(nextSym) + + if (xDirection) { + start = { ...next, x: next.x + xDirection } as PointSymbol + start.symbol = getCoordinateSymbol(start, grid)!.symbol + } + } + } + + // Move the boxes + if (canMove) { + visited + .sort((a, b) => { + if (robot.direction.y < 0) { + return (a.y < b.y ? -1 : 1) + } else { + return (a.y > b.y ? -1 : 1) + } + }) + .forEach(item => { + const next = robot.next(item) + grid[item.y]![item.x] = '.' + grid[next.y]![next.x] = item.symbol + }) + + grid[robot.pos.y]![robot.pos.x] = '.' + grid[nextPos.y]![nextPos.x] = robot.symbol + robot.walk() + } + } + } + } +} + +/** + * Calculates the GPS sum of all expanded boxes in the grid + * @param {string[][]} grid - 2D string array containing walls, boxes and space data + * @returns {number} GPS sum of all boxes in the grid + */ +export const calculateExpandedGPS = (grid: string[][]): number => { + const dimensions = getGridDimensions(grid) + let sumGPS = 0 + + for (let y = 0; y < dimensions.length; y += 1) { + for (let x = 0; x < dimensions.width; x += 1) { + if (grid[y]![x] === '[') { + sumGPS += y * 100 + x + } + } + } + + return sumGPS +} diff --git a/src/2024/2024-12-15/lib/calculateGPS.ts b/src/2024/2024-12-15/lib/calculateGPS.ts new file mode 100644 index 0000000..ba7fd5d --- /dev/null +++ b/src/2024/2024-12-15/lib/calculateGPS.ts @@ -0,0 +1,79 @@ +import type { Point } from '@/aoc/point/types.js' + +import { getCoordinateSymbol, isOutOfBounds } from '@/aoc/grid/utils.js' +import { Robot } from './robot.js' + +/** + * Moves the robot and boxes across the grid + * @param {string[][]} grid - 2D string array containing walls, boxes and space data + * @param {string[]} instructions - String array containing the robot's move instructions + * @returns {void} + */ +export const moveBoxes = (grid: string[][], instructions: string[]): void => { + const dimensions = { length: grid.length, width: grid[0]!.length } + const robot = new Robot(grid, instructions) + + while (robot.instructions.length > 0) { + robot.readInstruction() + + let nextPos = robot.next() + let nextSymbol = getCoordinateSymbol(nextPos, grid)?.symbol + + // Move robot to the next blank space + if (nextSymbol === '.') { + grid[robot.pos.y]![robot.pos.x] = '.' + grid[nextPos.y]![nextPos.x] = robot.symbol + robot.walk() + } + + // Find connected boxes until a space symbol + if (nextSymbol === 'O') { + const boxes: Point[] = [nextPos] + + while (!isOutOfBounds(nextPos, dimensions) && nextSymbol === 'O') { + nextPos = robot.next(nextPos) + nextSymbol = getCoordinateSymbol(nextPos, grid)?.symbol + + if (nextSymbol === 'O') { + boxes.push(nextPos) + } + } + + // Move groups of boxes if there's a space at their end + if (nextSymbol === '.' && boxes.length > 0) { + for (let i = boxes.length - 1; i >= 0; i -= 1) { + const next = robot.next(boxes[i]) + + grid[boxes[i]!.y]![boxes[i]!.x] = '.' + grid[next.y]![next.x] = 'O' + } + + // Move the robot + grid[robot.pos.y]![robot.pos.x] = '.' + robot.walk() + + grid[robot.pos.y]![robot.pos.x] = robot.symbol + } + } + } +} + +/** + * Calculates the GPS sum of all boxes in the grid + * @param {string[][]} grid - 2D string array containing walls, boxes and space data + * @returns {number} GPS sum of all boxes in the grid + */ +export const calculateGPS = (grid: string[][]): number => { + const dimensions = { length: grid.length, width: grid[0]!.length } + let sumGPS = 0 + + for (let y = 0; y < dimensions.length; y += 1) { + for (let x = 0; x < dimensions.width; x += 1) { + if (grid[y]![x] === 'O') { + sumGPS += y * 100 + x + } + } + } + + return sumGPS +} diff --git a/src/2024/2024-12-15/lib/fileReader.ts b/src/2024/2024-12-15/lib/fileReader.ts new file mode 100644 index 0000000..68099c6 --- /dev/null +++ b/src/2024/2024-12-15/lib/fileReader.ts @@ -0,0 +1,39 @@ +import type { RobotWarehouseData } from './types.js' + +import { AOCOutputType, readAOCInputFile } from '@/aoc/file/aocfile.js' +import { file } from '@/aoc/file/utils.js' + +/** + * Reads and formats the day 15 quiz input file. + * @param {string} fileName - Input text filename path relative to this script + * @returns {RobotWarehouseData} Quiz input data + */ +export const fileReader = (fileName: string): RobotWarehouseData => { + const input = readAOCInputFile({ + filePath: file(import.meta.url, fileName), + type: AOCOutputType.STRING + }) as string + + return input + .split('\n\n') + .reduce((data, item, index) => { + // Grid + if (index === 0) { + return { + ...data, + grid: item + .split('\n') + .map(line => line.split('')) + } + } + + // Instructions + return { + ...data, + instructions: item + .split('') + .reverse() + .filter(direction => direction !== '\n') + } + }, {}) as RobotWarehouseData +} diff --git a/src/2024/2024-12-15/lib/fileReaderExpanded.ts b/src/2024/2024-12-15/lib/fileReaderExpanded.ts new file mode 100644 index 0000000..09eda51 --- /dev/null +++ b/src/2024/2024-12-15/lib/fileReaderExpanded.ts @@ -0,0 +1,54 @@ +import type { RobotWarehouseData } from './types.js' + +import { AOCOutputType, readAOCInputFile } from '@/aoc/file/aocfile.js' +import { file } from '@/aoc/file/utils.js' + +/** + * Reads and formats the day 15 - part 2 quiz input file. Expands the tile symbols by 2. + * @param {string} fileName - Input text filename path relative to this script + * @returns {RobotWarehouseData} Quiz input data + */ +export const fileReaderExpand = (fileName: string): RobotWarehouseData => { + const input = readAOCInputFile({ + filePath: file(import.meta.url, fileName), + type: AOCOutputType.STRING + }) as string + + return input + .split('\n\n') + .reduce((data, item, index) => { + // Grid + if (index === 0) { + return { + ...data, + grid: item + .split('\n') + .map(line => line.split('')) + .reduce((list: string[][], tiles) => { + const processed = tiles.reduce((subList: string[], elem) => { + if (['.', '#'].includes(elem)) { + subList.push(elem, elem) + } else if (elem === 'O') { + subList.push('[', ']') + } else if (elem === '@') { + subList.push('@', '.') + } + + return subList + }, []) + + return [...list, processed] + }, []) + } + } + + // Instructions + return { + ...data, + instructions: item + .split('') + .reverse() + .filter(direction => direction !== '\n') + } + }, {}) as RobotWarehouseData +} diff --git a/src/2024/2024-12-15/lib/robot.ts b/src/2024/2024-12-15/lib/robot.ts new file mode 100644 index 0000000..e4cb898 --- /dev/null +++ b/src/2024/2024-12-15/lib/robot.ts @@ -0,0 +1,106 @@ +import { GuardDirection } from '@/2024/2024-12-06/lib/guard.types.js' +import { getGridCoordinate } from '@/aoc/grid/utils.js' +import type { Point } from '@/aoc/point/types.js' + +/** + * @class Robot + * @description A set of methods, objects and variables for managing a `Robot` + */ +export class Robot { + /** Symbol representation in the grid */ + symbol: string = '@' + + /** Current (y,x) grid coordinate */ + pos: Point = { x: -1, y: -1 } + + /** Direction indicator up/down/left/right */ + direction: Point = { x: 0, y: 0 } + + /** Current active move instruction symbol */ + instruction: GuardDirection | '-' = '-' + + /** Moves instructions set */ + instructions: string[] + + /** + * @constructor + * @param {string[][]} grid - 2D string array grid + */ + constructor (grid: string[][], instructions: string[]) { + this.findInitialPosition(grid) + this.instructions = [...instructions] + } + + /** + * Finds the robot's initial `Point` position in the 2D grid, storing them in the `this.pos` object + * @param {string[][]} grid - 2D string array grid + */ + findInitialPosition (grid: string[][]): void { + const start = getGridCoordinate(grid, this.symbol) + + this.pos.x = start.x + this.pos.y = start.y + } + + /** Reads the next instruction and sets the (y,x) direction */ + readInstruction (): void { + this.instruction = this.instructions.pop() as GuardDirection || '-' + + switch(this.instruction) { + case GuardDirection.UP: + this.direction = { x: 0, y: -1 } + break + case GuardDirection.DOWN: + this.direction = { x: 0, y: 1 } + break + case GuardDirection.LEFT: + this.direction = { x: -1, y: 0 } + break + case GuardDirection.RIGHT: + this.direction = { x: 1, y: 0 } + break + default: + break + } + } + + /** Increments the robot's (y,x) coordinate by direction */ + walk (): void { + this.pos.x += this.direction.x + this.pos.y += this.direction.y + } + + /** + * Finds the next (y,x) coordinate of the robot or a given `Point` parameter using the robot's current direction. + * @param {Point} [point] - (Optional) (y,x) coordinate to find the next step coorndinate from + * @returns {Point} Next (y,x) coordinate after 1 step + */ + next (point?: Point): Point { + const x = point?.x || this.pos.x + const y = point?.y || this.pos.y + + const nextX = x + this.direction.x + const nextY = y + this.direction.y + + return { + x: nextX, y: nextY + } + } + + /** + * Finds the robot's previous (y,x) coordinate or a given `Point` parameter using the robot's current direction. + * @param {Point} [point] - (Optional) (y,x) coordinate to find the previous step coorndinate from + * @returns {Point} Next (y,x) coordinate before the provided coordinate + */ + prev (point?: Point): Point { + const x = point?.x || this.pos.x + const y = point?.y || this.pos.y + + const nextX = x - this.direction.x + const nextY = y - this.direction.y + + return { + x: nextX, y: nextY + } + } +} diff --git a/src/2024/2024-12-15/lib/types.ts b/src/2024/2024-12-15/lib/types.ts new file mode 100644 index 0000000..c873f25 --- /dev/null +++ b/src/2024/2024-12-15/lib/types.ts @@ -0,0 +1,9 @@ +/** + * @type {Object} RobotWarehouseData + * @property {string[][]} grid - 2D string array containng walls, boxes, spaces and robot symbols + * @property {string[]} instructions - String array contaning the robot's move instructions + */ +export type RobotWarehouseData = { + grid: string[][]; + instructions: string[]; +} diff --git a/src/2024/2024-12-15/lib/utils.ts b/src/2024/2024-12-15/lib/utils.ts new file mode 100644 index 0000000..48cad4e --- /dev/null +++ b/src/2024/2024-12-15/lib/utils.ts @@ -0,0 +1,66 @@ +import type { Point, PointSymbol } from '@/aoc/point/types.js' +import { getCoordinateSymbol } from '@/aoc/grid/utils.js' + +/** + * Checks if the `symbol` parameter is an expanded box symbol + * @param {string} symbol - String character representing an expanded box symbol + * @returns {boolean} Flag indicating if the `symbol` character is a box symbol + */ +export const isExpandedBoxSymbol = (symbol: string) => ['[', ']'].includes(symbol as string) + +/** + * Retrieves the 2 half-box (y,x) coordinates of a full expanded box - left and right `PointSymbol` + * @param {Point} point - (x,y) coordinate of one side of the box + */ +export const getBoxStartCoordinates = (point: Point, grid: string[][]): +{ side1: PointSymbol, side2: PointSymbol } => { + + const rightCoord = { ...point, x: point.x + 1 } + const leftCoord = { ...point, x: point.x - 1 } + let side2: PointSymbol = { x: -1, y: -1, symbol: '-' } + + const symbol = getCoordinateSymbol(point, grid)?.symbol as string + const right = getCoordinateSymbol(rightCoord, grid)?.symbol as string + const left = getCoordinateSymbol(leftCoord, grid)?.symbol as string + + if (`${symbol}${right}` === '[]') { + side2 = { ...side2, ...rightCoord, symbol: right } + } + + if (`${left}${symbol}` === '[]') { + side2 = { ...side2, ...leftCoord, symbol: left } + } + + const side1 = { ...point, symbol } + + return { + side1, + side2 + } +} + +/** + * Retrieves the reverse matching symbol of an expanded box symbol + * @param {string} symbol - String character representing an expanded box symbol + * @returns {string | undefined} Reverse matching symbol of an expanded box symbol or `undefined` + */ +export const getReverseSymbol = (symbol: string): string | undefined => { + if (!['[', ']'].includes(symbol)) return + return symbol === '[' ? ']' : '[' +} + +/** + * Retrieves the `x` direction associated with an expanded box symbol + * @param symbol - String character representing an expanded box symbol + * @returns {number | undefined} + */ +export const getSymbolDirection = (symbol: string): number | undefined => { + if (!['[', ']'].includes(symbol)) return + + let xDirection = 0 + + if (symbol === '[') xDirection = 1 + if (symbol === ']') xDirection = -1 + + return xDirection +} diff --git a/src/2024/2024-12-15/main.ts b/src/2024/2024-12-15/main.ts new file mode 100644 index 0000000..c461b28 --- /dev/null +++ b/src/2024/2024-12-15/main.ts @@ -0,0 +1,34 @@ +import { fileReader } from './lib/fileReader.js' +import { fileReaderExpand } from './lib/fileReaderExpanded.js' + +import { moveBoxes, calculateGPS } from './lib/calculateGPS.js' +import { moveExpandedBoxes, calculateExpandedGPS } from './lib/calculateExpandedGPS.js' + +/** + * Part 1/2 of the 2024-12-15 quiz + * Moves the robot, boxes and calculates all boxes' GPS + */ +const quiz20241215_01 = () => { + const { grid, instructions } = fileReader('../input.txt') + + moveBoxes(grid, instructions) + const gps = calculateGPS(grid) + + console.log('Part 1: Regular boxes GPS:', gps) +} + +/** + * Part 2/2 of the 2024-12-15 quiz + * Moves the robot, expanded boxes and calculates all expanded boxes' GPS + */ +const quiz20241215_02 = () => { + const { grid, instructions } = fileReaderExpand('../input.txt') + + moveExpandedBoxes(grid, instructions) + const gps = calculateExpandedGPS(grid) + + console.log('Part 2: Expanded boxes GPS:', gps) +} + +quiz20241215_01() +quiz20241215_02() diff --git a/src/2024/2024-12-15/sample.test.ts b/src/2024/2024-12-15/sample.test.ts new file mode 100644 index 0000000..a8495d5 --- /dev/null +++ b/src/2024/2024-12-15/sample.test.ts @@ -0,0 +1,29 @@ +import { test, expect } from 'vitest' + +import { fileReader } from './lib/fileReader.js' +import { fileReaderExpand } from './lib/fileReaderExpanded.js' + +import { moveBoxes, calculateGPS } from './lib/calculateGPS.js' +import { moveExpandedBoxes, calculateExpandedGPS } from './lib/calculateExpandedGPS.js' + +test('Part 1: Move boxes and calculate GPS', () => { + const data = fileReader('../input.txt') + const data2 = fileReader('../input2.txt') + + moveBoxes(data.grid, data.instructions) + expect(calculateGPS(data.grid)).toBe(2027) + + moveBoxes(data2.grid, data2.instructions) + expect(calculateGPS(data2.grid)).toBe(9897) +}) + +test('Part 2: Move expanded boxes and calculate GPS', () => { + const data = fileReaderExpand('../input.txt') + const data2 = fileReaderExpand('../input2.txt') + + moveExpandedBoxes(data.grid, data.instructions) + expect(calculateExpandedGPS(data.grid)).toBe(1950) + + moveExpandedBoxes(data2.grid, data2.instructions) + expect(calculateExpandedGPS(data2.grid)).toBe(9125) +}) diff --git a/src/aoc/README.md b/src/aoc/README.md index f0bfc83..bc29099 100644 --- a/src/aoc/README.md +++ b/src/aoc/README.md @@ -25,6 +25,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 `string` or `number` array +- **`getGridCoordinate()`** - Retrieves the first `Point` (y,x) coordinate of a `symbol` that appears in a 2D 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/utils.ts b/src/aoc/grid/utils.ts index 4f55540..c9a4841 100644 --- a/src/aoc/grid/utils.ts +++ b/src/aoc/grid/utils.ts @@ -10,7 +10,7 @@ import { findNeighbors } from '../point/utils.js' /** * 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. + * @template T Extends `string` or `number`, representing the type of elements in the 2D `data` array. * @param {Point} point - (y,x) coordinatate in the 2D array * @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 @@ -27,9 +27,34 @@ export const getCoordinateSymbol = } } +/** + * Retrieves the first `Point` (y,x) coordinate of a `symbol` that appears in a 2D array. + * @param {T[][]} grid - 2D `string` or `number` array + * @param {string} symbol - Character or number to look for in the `grid` + * @returns {Point} Object containing the (y,x) coordinate of the `symbol` + */ +export const getGridCoordinate = (grid: T[][], symbol: T): Point => { + const point: Point = { x: -1, y: -1 } + + for (let y = 0; y < grid.length; y += 1) { + for (let x = 0; x < grid[0]!.length; x += 1) { + if (grid[y]![x] === symbol) { + point.x = x + point.y = y + break + } + + if (point.x !== -1) break + } + } + + return point +} + /** * Retrieves the length and width of a generic 2D array - * @param {T[][] | T[][]} data - 2D array, usually `string[][]`, `number[][]` or others. + * @template T Type of elements in the 2D `data` array. + * @param {T[][]} data - 2D array, usually `string[][]`, `number[][]` or others. * @returns {GridDimensions} Object containig the length and width of the 2D array */ export const getGridDimensions = (data: T[][]): GridDimensions => {