From b5b9ef3edc735c67ec4712aefca13f1d358cd4cf Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Tue, 17 Dec 2024 22:24:23 +0800 Subject: [PATCH 1/6] ref: move fxn out of loop --- src/2024/2024-12-05/lib/fixOrderingUpdates.ts | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/2024/2024-12-05/lib/fixOrderingUpdates.ts b/src/2024/2024-12-05/lib/fixOrderingUpdates.ts index 98a6550..b7001eb 100644 --- a/src/2024/2024-12-05/lib/fixOrderingUpdates.ts +++ b/src/2024/2024-12-05/lib/fixOrderingUpdates.ts @@ -10,7 +10,7 @@ type CurrentItem = { } /** - * Finds the next appropriate array index to swap places with the incorrectly placed current item accoring to the "rules" + * Finds the next appropriate array index to swap places with the incorrectly placed current item according to the "rules" * @param rules {Rules} Object containing parsed and formatted rules and updates data * @param restItems {number[]} "updates" array items content * @param currentItem {CurrentItem} Object containing the "current" item data in focus: value and array index @@ -44,30 +44,32 @@ export const fixOrdering = (rules: Rules, unorderedItems: number[]): number[] => throw new Error('Invalid item/s') } - for (let i = 0; i < unorderedItems.length - 1; i += 1) { - let currentItem = unorderedItems[i] as number - const currentItemData = { value: currentItem, index: i } + let currentItem: number = -2 - // Swaps incorrectly placed items with target items in the array - const swapItems = () => { - const indexToSwap = nextHotSwapIndex( - rules, - unorderedItems, - currentItemData - ) + // Swaps incorrectly placed items with target items in the array + const swapItems = (activeItem: CurrentItem, activeIndex: number) => { + const indexToSwap = nextHotSwapIndex( + rules, + unorderedItems, + activeItem + ) - const temp = unorderedItems[indexToSwap] as number - unorderedItems[indexToSwap] = currentItem - unorderedItems[i] = temp - currentItem = temp + const temp = unorderedItems[indexToSwap] as number + unorderedItems[indexToSwap] = activeItem.value + unorderedItems[activeIndex] = temp + currentItem = temp - fixOrdering(rules, unorderedItems) - } + fixOrdering(rules, unorderedItems) + } + + for (let i = 0; i < unorderedItems.length - 1; i += 1) { + currentItem = unorderedItems[i] as number + const currentItemData = { value: currentItem, index: i } // Correct "update" item should have en entry in the "rules" object // Swap places with other items if its incorrect if (rules[currentItem] === undefined) { - swapItems() + swapItems(currentItemData, i) } // Get the rest of items after the current item for comparison @@ -76,7 +78,7 @@ export const fixOrdering = (rules: Rules, unorderedItems: number[]): number[] => // Correct item's "rule" should have the after-item entries // Swap places with other items if its incorrect if (!afterItems.every(item => rules[currentItem]?.includes(item))) { - swapItems() + swapItems(currentItemData, i) } } From 9b6c82ad195e585e6a9af6e68c39c311cb1d517a Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Tue, 17 Dec 2024 22:26:21 +0800 Subject: [PATCH 2/6] chore: update notes --- src/utils/arrays.ts | 12 ++++++++---- src/utils/file.ts | 7 ++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/utils/arrays.ts b/src/utils/arrays.ts index 5748cd4..f8ce6b2 100644 --- a/src/utils/arrays.ts +++ b/src/utils/arrays.ts @@ -20,14 +20,18 @@ export const arrangeArray = (order: ARRAY_ORDERING) => } /** - * Checks if array elements have the same type and has no null or undefined values + * Checks if array elements have the same type using `typeof` and has no null or undefined values * @param items {S[]} array of elements - * @param type {T} type name of the elements inside the `items` array + * @param type {T} primitive type name of the elements inside the array (e.g., number, string, boolean) * @returns {boolean} Flag indicating if all array elements have the same type */ -export const uniformArrayElements = (items: S[], type: T): boolean => { +export const uniformArrayElements = ( + items: S[], + type: T +): boolean => { return ( - items.filter(value => typeof value === type).length === items.length + items.filter(value => typeof value === type) + .length === items.length ) } diff --git a/src/utils/file.ts b/src/utils/file.ts index 247667b..761ad13 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -3,9 +3,10 @@ import { dirname } from 'path' import { fileURLToPath } from 'url' /** - * Get the full file path of the current directory. + * Get the full file path `"__dirname"` of the current directory of a module file + * (scripts running as ESM modules whose package.json has `"type": "module"`) * @param moduleFile {string} File URL of the current module being executed: `"import.meta.url"` - * @returns Full file path to the directory of the calling file/module + * @returns Full file path to the directory of the calling file/module also know as `__dirname` in CommonJS */ export const currentDirectory = (moduleFile: string): string => { const filePath = fileURLToPath(moduleFile) @@ -15,7 +16,7 @@ export const currentDirectory = (moduleFile: string): string => { /** * Reads file from disk * @param pathToFile Full file path to a target file - * @returns {string} String version of the file + * @returns {string} String version of the file contents */ export const readFile = (pathToFile: string): string => { return fs.readFileSync(pathToFile, 'utf-8') From d97e4c105949026189cbcbd7b459decce044fcf6 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Wed, 18 Dec 2024 19:24:56 +0800 Subject: [PATCH 3/6] chore: common aoc file input reader --- src/utils/aocInputFile.ts | 64 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/utils/aocInputFile.ts diff --git a/src/utils/aocInputFile.ts b/src/utils/aocInputFile.ts new file mode 100644 index 0000000..4a0fb5a --- /dev/null +++ b/src/utils/aocInputFile.ts @@ -0,0 +1,64 @@ +// This code contains file input readers for common AoC input types. +import { readFile } from './file.js' + +export enum AOC_OUTPUT_TYPE { + STRING = 'string', + STRING_ARRAY = 'string_array', + STRING_ARRAY_2D = '2d_string_array', + NUMBER_ARRAY = 'number_array', + NUMBER_ARRAY_2D = '2d_number_array' +} + +type FileInput = { + /** @param filePath {string} Full file path to input file */ + filePath: string; + type: AOC_OUTPUT_TYPE; + delimiter?: string; +} + +type Output = string | string[] | string[][] | + number[] | number[][] + +/** + * Reads common AoC input text files. + * @typedef param {FileInput} File input definitions + * @param param.filePath {string} Full file path to an input text file + * @param param.type {AOC_OUTPUT_TYPE} Type to convert the input text file + * @param param.delimiter {string} String delimiter + * @param moduleFile {string} File URL of the current module being executed: `"import.meta.url"` + * @throws {Error} + */ +export const readAOCInputFile = (param: FileInput): Output => { + const file = readFile(param.filePath) + const delimiter = param?.delimiter ?? '' + + if (file === undefined) { + throw new Error('Undefined file') + } + + switch(param.type) { + case AOC_OUTPUT_TYPE.STRING: + return file as string + + case AOC_OUTPUT_TYPE.STRING_ARRAY: + return file.split(delimiter) as string[] || [] + + case AOC_OUTPUT_TYPE.STRING_ARRAY_2D: + return file + .split('\n') + .map(row => row.split(delimiter)) as string[][] || [] + + case AOC_OUTPUT_TYPE.NUMBER_ARRAY: + return file + .split('') + .map(Number) as number[] || [] + + case AOC_OUTPUT_TYPE.NUMBER_ARRAY_2D: + return file + .split('\n') + .map(row => row.split(delimiter).map(Number)) as number[][] || [] + + default: + throw new Error('Unsupported type') + } +} From 854190b617be103e95849623d3219ca65eea9fb9 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Wed, 18 Dec 2024 19:27:01 +0800 Subject: [PATCH 4/6] feat: day 6 guard gallivant 1/2 soln, #11 --- src/2024/2024-12-06/README.md | 6 ++ src/2024/2024-12-06/input.txt | 10 +++ src/2024/2024-12-06/lib/grid.ts | 100 +++++++++++++++++++++ src/2024/2024-12-06/lib/guard.ts | 81 +++++++++++++++++ src/2024/2024-12-06/lib/guardController.ts | 59 ++++++++++++ src/2024/2024-12-06/main.ts | 23 +++++ src/2024/2024-12-06/sample.test.ts | 16 ++++ 7 files changed, 295 insertions(+) create mode 100644 src/2024/2024-12-06/README.md create mode 100644 src/2024/2024-12-06/input.txt create mode 100644 src/2024/2024-12-06/lib/grid.ts create mode 100644 src/2024/2024-12-06/lib/guard.ts create mode 100644 src/2024/2024-12-06/lib/guardController.ts create mode 100644 src/2024/2024-12-06/main.ts create mode 100644 src/2024/2024-12-06/sample.test.ts diff --git a/src/2024/2024-12-06/README.md b/src/2024/2024-12-06/README.md new file mode 100644 index 0000000..e3da586 --- /dev/null +++ b/src/2024/2024-12-06/README.md @@ -0,0 +1,6 @@ +## Day 6: Guard Gallivant + +Visit the Advent of Code website for more information on this puzzle at: + +**Source:** https://adventofcode.com/2024/day/6
+**Status:** On-going ⭐ diff --git a/src/2024/2024-12-06/input.txt b/src/2024/2024-12-06/input.txt new file mode 100644 index 0000000..dc1b519 --- /dev/null +++ b/src/2024/2024-12-06/input.txt @@ -0,0 +1,10 @@ +....#..... +.#.......# +....^..... +..#....... +....#..#.. +.......... +.#........ +....#...#. +#......... +......#... \ No newline at end of file diff --git a/src/2024/2024-12-06/lib/grid.ts b/src/2024/2024-12-06/lib/grid.ts new file mode 100644 index 0000000..b93148c --- /dev/null +++ b/src/2024/2024-12-06/lib/grid.ts @@ -0,0 +1,100 @@ +import { GuardStatus, GuardDirection } from './guard.js' +import type { GuardState } from './guard.js' + +export type GridDimensions = { + length: number; + width: number; +} + +export class Grid { + board: string[][] = [] + length: number = 0 + width: number = 0 + positionCount: number = 0 + + obstruction = '#' + pathSymbol = '.' + + constructor (data: string[][]) { + this.board = data + this.length = data.length + this.width = (data[0])?.length as number + } + + /** + * Finds the initial position and state of the guard in a 2D board array + * @param data {string[][]} 2D string array of the guard board + * @returns {GuardState} Initial position and state of the guard + */ + findGuardStartPosition (): GuardState { + const GuardDirections: string[] = Object.values(GuardDirection) + + const initialState: GuardState = { + direction: null, + xPos: -1, + yPos: -1, + status: GuardStatus.IDLE + } + + for (let y = 0; y < this.board.length; y += 1) { + const row = this.board[y] + + if (row === undefined) { + throw new Error('Undefined row') + } + + for (let i = 0; i < GuardDirections.length; i += 1) { + const indexOfDirection = row.indexOf(GuardDirections[i] as string) + + if (indexOfDirection >= 0) { + initialState.direction = GuardDirections[i] as GuardDirection + initialState.xPos = indexOfDirection + initialState.yPos = y + initialState.status = GuardStatus.ACTIVE + break + } + } + + if (initialState.status === GuardStatus.ACTIVE) break + } + + return initialState + } + + /** + * Checks if an (x,y) coordinate is outside the grid area + * @param {number} x array x coordinate + * @param {number} y array y coordinate + * @returns {boolean} Flag indicating if the guard is outside the grid + */ + isOutOfBounds (x: number, y: number): boolean { + const row = this.board[0] as string[] + + return x < 0 || + x >= row.length || + y < 0 || + y >= this.board.length + } + + /** + * Marks an (x,y) coordinate in the grid + * @param {number} x array x coordinate + * @param {number} y array y coordinate + * @returns {void} + */ + mark (x: number, y: number): void { + const row = this.board[y] + + if (row !== undefined && row[x] !== 'X') { + row[x] = 'X' + this.positionCount += 1 + } + } + + /** + * Displays the guard's distinct positions in the grid + */ + print () { + console.log(this.board.map(row => row.join(' '))) + } +} diff --git a/src/2024/2024-12-06/lib/guard.ts b/src/2024/2024-12-06/lib/guard.ts new file mode 100644 index 0000000..53d06c2 --- /dev/null +++ b/src/2024/2024-12-06/lib/guard.ts @@ -0,0 +1,81 @@ +export enum GuardDirection { + UP = '^', + RIGHT = '>', + DOWN = 'v', + LEFT = '<' +} + +export enum GuardStatus { + IDLE = 'idle', + ACTIVE = 'active', + EXIT = 'exit' +} + +export type GuardState = { + direction: GuardDirection | null; + xPos: number; + yPos: number; + status: GuardStatus; +} + +/** + * @class Guard + * @description Game object that can move and mark its trail in a 2D grid + */ +export class Guard { + direction: GuardDirection | null = null + xPos: number = 0 + yPos: number = 0 + status: GuardStatus = GuardStatus.IDLE + + /** + * @constructor + * @param {GuardState} state - Initial state of the guard + * @param {GridDimensions} [gridDimensions={ length: 5, width: 5 }] - Dimensions of the grid + */ + constructor ( + state: GuardState + ) { + this.direction = state.direction + this.xPos = state.xPos || 0 + this.yPos = state.yPos || 0 + this.status = state.status + } + + /** + * Turn the guard's direction by rotating clockwise once + * @returns {void} + */ + turn (): void { + if (this.direction === null) return + + const directions = Object.values(GuardDirection) + const currentDirectionIndex = directions.indexOf(this.direction) + + const nextIndex = (currentDirectionIndex + 1) < directions.length + ? currentDirectionIndex + 1 + : 0 + + this.direction = directions[nextIndex] as GuardDirection + } + + /** + * Moves the guard one (1) coordinate [x,y] position in the grid and marks the new position + * @param xDirection {number} x coordinate + * @param yDirection {number} y coordinate + * @returns {void} + */ + walk (xDirection: number, yDirection: number): void { + if (xDirection === undefined || yDirection === undefined) return + + this.xPos += xDirection + this.yPos += yDirection + } + + /** + * Set's the guard's status to "exited" + */ + exit (): void { + this.status = GuardStatus.EXIT + } +} diff --git a/src/2024/2024-12-06/lib/guardController.ts b/src/2024/2024-12-06/lib/guardController.ts new file mode 100644 index 0000000..50eea24 --- /dev/null +++ b/src/2024/2024-12-06/lib/guardController.ts @@ -0,0 +1,59 @@ + + +import { Guard, GuardDirection } from './guard.js' +import { Grid } from './grid.js' +import { GuardStatus } from './guard.js' + +/** + * + * @param {string[][]} data 2D string array containing the guards grid paths + * @param {boolean} printTrail Flag to display the distinct guard positions in the grid + * @returns {number} Number of unique guard positions + */ +export const guardController = (data: string[][], printTrail: boolean = false): number => { + const grid = new Grid(data) + const guard = new Guard(grid.findGuardStartPosition()) + + while ( + guard.status !== GuardStatus.EXIT && + !grid.isOutOfBounds(guard.xPos, guard.yPos) + ) { + let yDirection = 0 + let xDirection = 0 + + switch(guard.direction) { + case GuardDirection.UP: + yDirection = -1 + break + case GuardDirection.DOWN: + yDirection = 1 + break + case GuardDirection.LEFT: + xDirection = -1 + break + case GuardDirection.RIGHT: + xDirection = 1 + break + } + + if (grid.board[guard.yPos + yDirection]?.[guard.xPos + xDirection] !== grid.obstruction) { + if ( + guard.yPos === grid.board.length - 1 && + guard.xPos === (grid.board[0] as string[])?.length - 1 + ) { + guard.exit() + } else { + grid.mark(guard.xPos, guard.yPos) + guard.walk(xDirection, yDirection) + } + } else { + guard.turn() + } + } + + if (printTrail) { + grid.print() + } + + return grid.positionCount +} diff --git a/src/2024/2024-12-06/main.ts b/src/2024/2024-12-06/main.ts new file mode 100644 index 0000000..6aeaf60 --- /dev/null +++ b/src/2024/2024-12-06/main.ts @@ -0,0 +1,23 @@ +import path from 'path' +import { readAOCInputFile, AOC_OUTPUT_TYPE } from '@/utils/aocInputFile.js' +import { currentDirectory } from '@/utils/file.js' + +import { guardController } from './lib/guardController.js' + +const file = readAOCInputFile({ + filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + type: AOC_OUTPUT_TYPE.STRING_ARRAY_2D +}) as string [][] + +/** + * Part 1/2 of the 2024-12-06 quiz + * Counts the number of distinct guard positions in a grid + */ +export const quiz20241206_01 = () => { + const positionCount = guardController(file, true) + + console.log('Distinct guard positions', positionCount) + return positionCount +} + +quiz20241206_01() diff --git a/src/2024/2024-12-06/sample.test.ts b/src/2024/2024-12-06/sample.test.ts new file mode 100644 index 0000000..8ed908e --- /dev/null +++ b/src/2024/2024-12-06/sample.test.ts @@ -0,0 +1,16 @@ +import path from 'path' +import { test, expect } from 'vitest' + +import { readAOCInputFile, AOC_OUTPUT_TYPE } from '@/utils/aocInputFile.js' +import { currentDirectory } from '@/utils/file.js' + +import { guardController } from './lib/guardController.js' + +const file = readAOCInputFile({ + filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + type: AOC_OUTPUT_TYPE.STRING_ARRAY_2D +}) as string [][] + +test('Count distinct positions', () => { + expect(guardController(file)).toBe(24) +}) From db1eb28917c14045686f36b175a40be55e32ec02 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 20 Dec 2024 22:14:31 +0800 Subject: [PATCH 5/6] chore: add class notes * ref: use separate type files * chore: create test --- src/2024/2024-12-06/input.txt | 12 ++--- src/2024/2024-12-06/lib/grid.ts | 40 +++++++++++--- src/2024/2024-12-06/lib/grid.types.ts | 10 ++++ src/2024/2024-12-06/lib/guard.ts | 36 ++++--------- src/2024/2024-12-06/lib/guard.types.ts | 62 ++++++++++++++++++++++ src/2024/2024-12-06/lib/guardController.ts | 49 +++++++++-------- src/2024/2024-12-06/main.ts | 6 +-- src/2024/2024-12-06/sample.test.ts | 4 +- 8 files changed, 153 insertions(+), 66 deletions(-) create mode 100644 src/2024/2024-12-06/lib/grid.types.ts create mode 100644 src/2024/2024-12-06/lib/guard.types.ts diff --git a/src/2024/2024-12-06/input.txt b/src/2024/2024-12-06/input.txt index dc1b519..6d7bbf6 100644 --- a/src/2024/2024-12-06/input.txt +++ b/src/2024/2024-12-06/input.txt @@ -1,10 +1,10 @@ ....#..... -.#.......# -....^..... -..#....... -....#..#.. +.........# .......... .#........ -....#...#. -#......... +.......... +.......#.. +.#..^..... +.#......#. +....#..... ......#... \ No newline at end of file diff --git a/src/2024/2024-12-06/lib/grid.ts b/src/2024/2024-12-06/lib/grid.ts index b93148c..38f6019 100644 --- a/src/2024/2024-12-06/lib/grid.ts +++ b/src/2024/2024-12-06/lib/grid.ts @@ -1,24 +1,46 @@ -import { GuardStatus, GuardDirection } from './guard.js' -import type { GuardState } from './guard.js' +import { GuardStatus, GuardDirection } from './guard.types.js' +import type { GuardState } from './guard.types.js' -export type GridDimensions = { - length: number; - width: number; -} +/** + * @class Grid + * @description Object containing a 2D array of strings and other data in which a `Guard` object runs. Each item represents an open path or obstacle. + */ export class Grid { + /** 2D board string array */ board: string[][] = [] + + /** Board length */ length: number = 0 + + /** Board width */ width: number = 0 + + /** Number of distinct positions that a `Guard` can traverse on the board */ positionCount: number = 0 + /** Initial (x,y) coordinate starting position of a `Guard` */ + start: { x: number; y: number; } + + /** Obstruction symbol. Guards turn clockwise if this symbol blocks their next step. */ obstruction = '#' + + /** Path symbol. Guards proceed to the next (x,y) coordinate after on this symbol. */ pathSymbol = '.' - constructor (data: string[][]) { + /** + * @constructor + * @param {string[][]} data 2D string array containing paths `"."`, obstacles `"#"` and the `Guard` object + * @param obstructionSymbol + */ + constructor (data: string[][], obstructionSymbol: string = '#') { this.board = data this.length = data.length this.width = (data[0])?.length as number + this.obstruction = obstructionSymbol + this.start = { x: -1, y: -1 } + + this.findGuardStartPosition() } /** @@ -58,6 +80,9 @@ export class Grid { if (initialState.status === GuardStatus.ACTIVE) break } + this.start.x = initialState.xPos + this.start.y = initialState.yPos + return initialState } @@ -95,6 +120,7 @@ export class Grid { * Displays the guard's distinct positions in the grid */ print () { + console.clear() console.log(this.board.map(row => row.join(' '))) } } diff --git a/src/2024/2024-12-06/lib/grid.types.ts b/src/2024/2024-12-06/lib/grid.types.ts new file mode 100644 index 0000000..4c18219 --- /dev/null +++ b/src/2024/2024-12-06/lib/grid.types.ts @@ -0,0 +1,10 @@ +/** + * `Grid` dimensions properties + * + * @property {number} length - Length of the grid + * @property {number} wwidth - Width of the grid + */ +export type GridDimensions = { + length: number; + width: number; +} diff --git a/src/2024/2024-12-06/lib/guard.ts b/src/2024/2024-12-06/lib/guard.ts index 53d06c2..71e5d2a 100644 --- a/src/2024/2024-12-06/lib/guard.ts +++ b/src/2024/2024-12-06/lib/guard.ts @@ -1,41 +1,23 @@ -export enum GuardDirection { - UP = '^', - RIGHT = '>', - DOWN = 'v', - LEFT = '<' -} - -export enum GuardStatus { - IDLE = 'idle', - ACTIVE = 'active', - EXIT = 'exit' -} - -export type GuardState = { - direction: GuardDirection | null; - xPos: number; - yPos: number; - status: GuardStatus; -} +import type { GuardState } from './guard.types.js' +import { GuardDirection, GuardStatus } from './guard.types.js' /** * @class Guard - * @description Game object that can move and mark its trail in a 2D grid + * @description Game object that can move around in a 2D grid array */ export class Guard { direction: GuardDirection | null = null xPos: number = 0 yPos: number = 0 status: GuardStatus = GuardStatus.IDLE + steps: number = 0 /** + * Creates an instance of the `Guard` class * @constructor * @param {GuardState} state - Initial state of the guard - * @param {GridDimensions} [gridDimensions={ length: 5, width: 5 }] - Dimensions of the grid */ - constructor ( - state: GuardState - ) { + constructor (state: GuardState) { this.direction = state.direction this.xPos = state.xPos || 0 this.yPos = state.yPos || 0 @@ -43,7 +25,8 @@ export class Guard { } /** - * Turn the guard's direction by rotating clockwise once + * Turn the guard's direction clockwise by 90 degrees. + * If the direction is `null`, does nothing. * @returns {void} */ turn (): void { @@ -60,7 +43,7 @@ export class Guard { } /** - * Moves the guard one (1) coordinate [x,y] position in the grid and marks the new position + * Moves the guard by one (1) coordinate [x,y] position in the `Grid` * @param xDirection {number} x coordinate * @param yDirection {number} y coordinate * @returns {void} @@ -70,6 +53,7 @@ export class Guard { this.xPos += xDirection this.yPos += yDirection + this.steps += 1 } /** diff --git a/src/2024/2024-12-06/lib/guard.types.ts b/src/2024/2024-12-06/lib/guard.types.ts new file mode 100644 index 0000000..4106c64 --- /dev/null +++ b/src/2024/2024-12-06/lib/guard.types.ts @@ -0,0 +1,62 @@ +/** + * Directions that a `Guard` can face in a `Grid` + * + * @enum {string} + * @property {string} UP - Upward direction `"^"` + * @property {string} RIGHT - Upward direction `">"` + * @property {string} DOWN - Upward direction `"v"` + * @property {string} LEFT - Upward direction `"<"` + */ +export enum GuardDirection { + UP = '^', + RIGHT = '>', + DOWN = 'v', + LEFT = '<' +} + +/** + * Mapping of `GuardDirections` to their corresponding numeric direction vectors. + * The vector indicates the direction of movement in the `Grid` + * + * @constant + * @type {Record} + * @property {string} [GuardDirection.UP] - Upwards movement direction along the y-axis + * @property {string} [GuardDirection.RIGHT] - Right-ise movement direction along the x-axis + * @property {string} [GuardDirection.DOWN] - Downwards movement direction along the y-axis + * @property {string} [GuardDirection.left] - Left-wise movement direction along the x-axis + */ +export const GuardDirectionVector: Record = { + [GuardDirection.UP]: -1, + [GuardDirection.RIGHT]: 1, + [GuardDirection.DOWN]: 1, + [GuardDirection.LEFT]: -1 +} + +/** + * `Guard` activity status in the `Grid` board + * + * @enum {string} + * @property {string} IDLE - Initial status + * @property {string} ACTIVE - Indicates active placement on the `Grid` + * @property {string} EXIT - Indicates off-Grid placement + */ +export enum GuardStatus { + IDLE = 'idle', + ACTIVE = 'active', + EXIT = 'exit' +} + +/** + * Properties identifying the current state/status of a `Guard` + * + * @property {GuardDirection | null} direction Current direction of a `Guard` in the `Grid` + * @property {number} xPos x-coordinate of the `Guard` in the `Grid` + * @property {number} yPos y-coordinate of the `Guard` in the `Grid` + * @property {GuardStatus} status `Guard` activity status + */ +export type GuardState = { + direction: GuardDirection | null; + xPos: number; + yPos: number; + status: GuardStatus; +} diff --git a/src/2024/2024-12-06/lib/guardController.ts b/src/2024/2024-12-06/lib/guardController.ts index 50eea24..482fb9f 100644 --- a/src/2024/2024-12-06/lib/guardController.ts +++ b/src/2024/2024-12-06/lib/guardController.ts @@ -1,17 +1,15 @@ - - -import { Guard, GuardDirection } from './guard.js' +import { GuardDirection, GuardStatus } from './guard.types.js' +import { Guard } from './guard.js' import { Grid } from './grid.js' -import { GuardStatus } from './guard.js' /** - * + * Runs the `Guard` on the grid, counting distinct positions/steps * @param {string[][]} data 2D string array containing the guards grid paths * @param {boolean} printTrail Flag to display the distinct guard positions in the grid * @returns {number} Number of unique guard positions */ -export const guardController = (data: string[][], printTrail: boolean = false): number => { - const grid = new Grid(data) +export const guardController = (data: string[][], printTrail: boolean = false): Grid => { + const grid = new Grid(structuredClone(data)) const guard = new Guard(grid.findGuardStartPosition()) while ( @@ -21,6 +19,7 @@ export const guardController = (data: string[][], printTrail: boolean = false): let yDirection = 0 let xDirection = 0 + // Find the guard's direction switch(guard.direction) { case GuardDirection.UP: yDirection = -1 @@ -34,26 +33,32 @@ export const guardController = (data: string[][], printTrail: boolean = false): case GuardDirection.RIGHT: xDirection = 1 break + default: + break + } + + if ( + guard.yPos === grid.board.length - 1 && + guard.xPos === (grid.board[0] as string[])?.length - 1 + ) { + // Exit if the coordinates are at the edge of the Grid + guard.exit() } - if (grid.board[guard.yPos + yDirection]?.[guard.xPos + xDirection] !== grid.obstruction) { - if ( - guard.yPos === grid.board.length - 1 && - guard.xPos === (grid.board[0] as string[])?.length - 1 - ) { - guard.exit() - } else { - grid.mark(guard.xPos, guard.yPos) - guard.walk(xDirection, yDirection) - } + const xAhead = guard.xPos + xDirection + const yAhead = guard.yPos + yDirection + const symbol = grid.board[yAhead]?.[xAhead] + + if (symbol !== grid.obstruction) { + // Walk and mark the current coordinates + grid.mark(guard.xPos, guard.yPos) + guard.walk(xDirection, yDirection) } else { + // Turn (rotate clockwise) if the symbol ahead is an obstruction `"#" guard.turn() } } - if (printTrail) { - grid.print() - } - - return grid.positionCount + if (printTrail) grid.print() + return grid } diff --git a/src/2024/2024-12-06/main.ts b/src/2024/2024-12-06/main.ts index 6aeaf60..a5d1e37 100644 --- a/src/2024/2024-12-06/main.ts +++ b/src/2024/2024-12-06/main.ts @@ -14,10 +14,10 @@ const file = readAOCInputFile({ * Counts the number of distinct guard positions in a grid */ export const quiz20241206_01 = () => { - const positionCount = guardController(file, true) + const grid = guardController(file, true) - console.log('Distinct guard positions', positionCount) - return positionCount + console.log('Distinct guard positions:', grid.positionCount) + return grid.positionCount } quiz20241206_01() diff --git a/src/2024/2024-12-06/sample.test.ts b/src/2024/2024-12-06/sample.test.ts index 8ed908e..d0b6e69 100644 --- a/src/2024/2024-12-06/sample.test.ts +++ b/src/2024/2024-12-06/sample.test.ts @@ -11,6 +11,6 @@ const file = readAOCInputFile({ type: AOC_OUTPUT_TYPE.STRING_ARRAY_2D }) as string [][] -test('Count distinct positions', () => { - expect(guardController(file)).toBe(24) +test('Count distinct guard positions', () => { + expect(guardController(file).positionCount).toBe(26) }) From 71999a5c31cee80b1f1d3407de60a6262ae3964e Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 20 Dec 2024 22:34:31 +0800 Subject: [PATCH 6/6] feat: day 6 guard gallivant quiz 2/2 soln, #11 --- src/2024/2024-12-06/README.md | 21 ++- .../2024-12-06/lib/guardControllerLoop.ts | 124 ++++++++++++++++++ src/2024/2024-12-06/main.ts | 14 ++ src/2024/2024-12-06/sample.test.ts | 5 + 4 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 src/2024/2024-12-06/lib/guardControllerLoop.ts diff --git a/src/2024/2024-12-06/README.md b/src/2024/2024-12-06/README.md index e3da586..eb3a023 100644 --- a/src/2024/2024-12-06/README.md +++ b/src/2024/2024-12-06/README.md @@ -3,4 +3,23 @@ Visit the Advent of Code website for more information on this puzzle at: **Source:** https://adventofcode.com/2024/day/6
-**Status:** On-going ⭐ +**Status:** Complete ⭐⭐ + +## Code + +1. **grid.ts** + - `Grid` - class that has a 2D array object containing paths and obstacles in which a `Guard` runs. + - Has functions and methods for managing a `Grid` object + +2. **guard.ts** + - `Guard` - class representing an object that can walk in the `Grid` map. + - Have methods for moving around in the `Grid`. + +3. **guardController.ts** + - `guardController()` - Runs the `Guard` on the `Grid`, counting distinct positions/steps. + +5. **guardControllerLoop.ts** + - `gridHasInfiniteLoop()` + - Runs the `Guard` on a `Grid` with obstacles, checking the placement of paths and obstacles that will make the `Guard` walk in an infinite loop + - `findObstructionPositions()` - Counts the number of positions in the `Grid` in which inserting one (1) obstruction symbol # will cause the `Guard` to walk in an infinite loop. + - > _**WARNING:** This is a very slow, unoptimized program execution that takes about ~3 mins to complete using the actual AoC input text._ diff --git a/src/2024/2024-12-06/lib/guardControllerLoop.ts b/src/2024/2024-12-06/lib/guardControllerLoop.ts new file mode 100644 index 0000000..d25429f --- /dev/null +++ b/src/2024/2024-12-06/lib/guardControllerLoop.ts @@ -0,0 +1,124 @@ +import { GuardDirection, GuardStatus } from './guard.types.js' +import { Guard } from './guard.js' +import { Grid } from './grid.js' +import { guardController } from './guardController.js' + +/** + * Runs the `Guard` on a `Grid` with obstacles, checking the placement of paths and obstacles that will make the `Guard` walk in an infinite loop + * @param {string[][]} data 2D string array containing the original `Grid` inserted with a new obstruction symbol `"#" + * @param {boolean} printTrail Flag to display the distinct guard positions in the grid + * @returns {number} Number of unique guard positions + */ +export const gridHasInfiniteLoop = ( + data: string[][], + printTrail: boolean = false +): boolean => { + const grid = new Grid(data, '#') + const initialPosition = grid.findGuardStartPosition() + + const guard = new Guard({ + ...initialPosition, + yPos: initialPosition.yPos - 1 + }) + + const trail = [] + let isLoop = false + + while ( + guard.status !== GuardStatus.EXIT && + !grid.isOutOfBounds(guard.xPos, guard.yPos) && + !isLoop + ) { + let yDirection = 0 + let xDirection = 0 + + // Find the guard's direction + switch(guard.direction) { + case GuardDirection.UP: + yDirection = -1 + break + case GuardDirection.DOWN: + yDirection = 1 + break + case GuardDirection.LEFT: + xDirection = -1 + break + case GuardDirection.RIGHT: + xDirection = 1 + break + default: + break + } + + if ( + guard.yPos === grid.board.length - 1 && + guard.xPos === (grid.board[0] as string[])?.length - 1 + ) { + // Exit if the coordinates are at the edge of the Grid + guard.exit() + } + + const xAhead = guard.xPos + xDirection + const yAhead = guard.yPos + yDirection + const symbol = grid.board[yAhead]?.[xAhead] + + if (symbol !== grid.obstruction) { + const coord = `${guard.xPos},${guard.yPos},${guard.direction}` + + if (trail.indexOf(coord) >= 0) { + // If the coordinates and direction repeat, + // Guard has already entered an infinite loop + isLoop = true + } else { + // Note the (x,y) coordinates and direction + trail.push(coord) + } + + grid.mark(guard.xPos, guard.yPos) + guard.walk(xDirection, yDirection) + } else { + // Turn (rotate clockwise) if the symbol ahead is an obstruction `"#" + guard.turn() + } + } + + if (printTrail) grid.print() + return isLoop +} + +/** + * Counts the number of positions in the `Grid` in which inserting one (1) + * obstruction symbol # will cause the `Guard` to walk in an infinite loop. + * @param {number} data data 2D string array containing the `Grid` paths + */ +export const findObstructionPositions = (data: string[][], printGrid: boolean = false) => { + let loopCount = 0 + const paths: Grid = guardController(data) + const extras = Array.from({ length: data.length }, () => Array(data.length).fill('.')) + + for (let y = 0; y < paths.board.length; y += 1) { + const row = paths.board[y] as string[] + + for (let x = 0; x < row.length; x += 1) { + if (row[x] !== 'X') continue + if (x === paths.start.x && y === paths.start.y) { + continue + } + + const xData: string[][] = structuredClone(data) + const r = xData[y] as string[] + r[x] = '#' + + const isLoop = gridHasInfiniteLoop(xData) + + if (isLoop) { + const e = extras[y] as string[] + e[x] = 'O' + loopCount += 1 + } + } + } + + if (printGrid) console.log(extras.map(x => x.join(' '))) + return loopCount +} diff --git a/src/2024/2024-12-06/main.ts b/src/2024/2024-12-06/main.ts index a5d1e37..fdd1040 100644 --- a/src/2024/2024-12-06/main.ts +++ b/src/2024/2024-12-06/main.ts @@ -3,6 +3,7 @@ import { readAOCInputFile, AOC_OUTPUT_TYPE } from '@/utils/aocInputFile.js' import { currentDirectory } from '@/utils/file.js' import { guardController } from './lib/guardController.js' +import { findObstructionPositions } from './lib/guardControllerLoop.js' const file = readAOCInputFile({ filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), @@ -20,4 +21,17 @@ export const quiz20241206_01 = () => { return grid.positionCount } +/** + * Part 2/2 of the 2024-12-06 quiz + * Counts the number of positions in the Grid in which placing an + * obstacle will cause the Guard to walk in an infinite loop + */ +export const quiz20241206_02 = () => { + const infinitePositions = findObstructionPositions(file) + + console.log('Obstruction positions for infinite walk:', infinitePositions) + return infinitePositions +} + quiz20241206_01() +quiz20241206_02() diff --git a/src/2024/2024-12-06/sample.test.ts b/src/2024/2024-12-06/sample.test.ts index d0b6e69..b46d07d 100644 --- a/src/2024/2024-12-06/sample.test.ts +++ b/src/2024/2024-12-06/sample.test.ts @@ -5,6 +5,7 @@ import { readAOCInputFile, AOC_OUTPUT_TYPE } from '@/utils/aocInputFile.js' import { currentDirectory } from '@/utils/file.js' import { guardController } from './lib/guardController.js' +import { findObstructionPositions } from './lib/guardControllerLoop.js' const file = readAOCInputFile({ filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), @@ -14,3 +15,7 @@ const file = readAOCInputFile({ test('Count distinct guard positions', () => { expect(guardController(file).positionCount).toBe(26) }) + +test('Count obstackle positions', () => { + expect(findObstructionPositions(file)).toBe(2) +})