import { Inspect } from "../common.ts"; /* Directions */ const Directions = { NORTH: { X: 0, Y: -1, }, SOUTH: { X: 0, Y: 1, }, EAST: { X: 1, Y: 0, }, WEST: { X: -1, Y: 0, }, NORTH_EAST: { X: 1, Y: -1, }, NORTH_WEST: { X: -1, Y: -1, }, SOUTH_EAST: { X: 1, Y: 1, }, SOUTH_WEST: { X: -1, Y: 1, }, } /** * Parse the schematics to find all possible Engine Parts * @param {string[][]} schematicsMap A 2D grid of characters making up a schematic * @returns {EnginePart[]} An array of potential Engine Parts */ export function ParseSchematics(schematicsMap: string[][]): EnginePart[] { const engineParts: EnginePart[] = []; /** The current pointer within the schematics' 2D array */ const schematicsPointer: Vector2 = { X: 0, Y: 0, }; for(; schematicsPointer.Y < schematicsMap.length; schematicsPointer.Y++) { // Reset the X pointer each line schematicsPointer.X = 0; /** A line from the total schematics */ const schematicsLine = schematicsMap[schematicsPointer.Y]; // Create a reusable EnginePart store let part: EnginePart|null = null; for(; schematicsPointer.X < schematicsMap[schematicsPointer.Y].length; schematicsPointer.X++) { const char = schematicsMap[schematicsPointer.Y][schematicsPointer.X]; // Check if the current character is a number if (/\d/.test(char)) { // Check if our EnginePart store is initialized if (part == null) { part = { PartNumber: 0, Coordinates: [] }; } if (part.PartNumber) { // Concatenate the number to the existing part of the Part Number part.PartNumber = Number("" + part.PartNumber + char); } else { // Start the new Part Number part.PartNumber = Number(char); } // Push the coordinates part.Coordinates.push({ X: schematicsPointer.X, Y: schematicsPointer.Y, }); } // If it's not else { // Check if the EnginePart store has a value if(part != null) { // If it does, store it and empty it engineParts.push(part); part = null; } } } if(part !== null) { engineParts.push(part); } } return engineParts; } /** * Validates all previously parsed Engine Parts * * An Engine Part is only valid if it's connected, even diagonally, to a * symbol other than `.`. * * @param {EnginePart[]} potentialParts All potential Engine Parts * @param {string[][]} schematicsMap A 2D grid of characters making up a schematic * @returns {EnginePart[]} A filtered list of all valid Engine Parts */ export function ValidateEngineParts(potentialParts: EnginePart[], schematicsMap: string[][]): EnginePart[] { const validParts: EnginePart[] = []; for (const part of potentialParts) { let symbolFound = false; for (const point of part.Coordinates) { if (BordersASymbol(point, schematicsMap)) { symbolFound = true; validParts.push(part); break; } } } return validParts; } /** * Check if a point on the schematics map borders a symbol * * A symbol is any character other than a digit or a period. * * @param point A point on the schematics map * @param schematicsMap A 2D grid of characters making up a schematic * @returns {boolean} Whether the point borders a symbol or not */ export function BordersASymbol(point: Vector2, schematicsMap: string[][]): boolean { // Initialize all border chars as NOPs let northernBorder = "."; let easternBorder = "."; let southernBorder = "."; let westernBorder = "."; let northEastBorder = "."; let northWestBorder = "."; let southEastBorder = "."; let southWestBorder = "."; // Check what borders exist const northExists = !!schematicsMap[point.Y + Directions.NORTH.Y]; const southExists = !!schematicsMap[point.Y + Directions.SOUTH.Y]; const eastExists = !!schematicsMap[point.Y][point.X + Directions.EAST.X]; const westExists = !!schematicsMap[point.Y][point.X + Directions.WEST.X]; // Find the border chars if(northExists) { northernBorder = schematicsMap[point.Y + Directions.NORTH.Y][point.X + Directions.NORTH.X]; if(eastExists) { northEastBorder = schematicsMap[point.Y + Directions.NORTH_EAST.Y][point.X + Directions.NORTH_EAST.X]; } if(westExists) { northWestBorder = schematicsMap[point.Y + Directions.NORTH_WEST.Y][point.X + Directions.NORTH_WEST.X]; } } if(southExists) { southernBorder = schematicsMap[point.Y + Directions.SOUTH.Y][point.X + Directions.SOUTH.X]; if(eastExists) { southEastBorder = schematicsMap[point.Y + Directions.SOUTH_EAST.Y][point.X + Directions.SOUTH_EAST.X]; } if(westExists) { southWestBorder = schematicsMap[point.Y + Directions.SOUTH_WEST.Y][point.X + Directions.SOUTH_WEST.X]; } } if (eastExists) { easternBorder = schematicsMap[point.Y + Directions.EAST.Y][point.X + Directions.EAST.X]; } if (westExists) { westernBorder = schematicsMap[point.Y + Directions.WEST.Y][point.X + Directions.WEST.X]; } // Combine all bordering characters into one long string const allBorders = northernBorder + easternBorder + southernBorder + westernBorder + northEastBorder + northWestBorder + southEastBorder + southWestBorder; return /[^0-9\.]/.test(allBorders); } /** * Split the schematics into a 2D grid of characters * * @param {string[]} schematics The array of lines from the schematics * @returns {string[][]} A 2D grid of characters */ export function BuildSchematicsMap(schematics: string[]): string[][] { return schematics.map((schematicsLine) => schematicsLine.split('')); } /** * An engine part */ export type EnginePart = { PartNumber: number, Coordinates: Vector2[], }; /** * A gear on the schematics map * * A gear is any `*` symbol from the schematics map that borders * exactly two engine part numbers. */ export type SchematicsGear = { Symbol: string, Coordinates: Vector2, GearRatio: number }; /** * A position within a 2D grid */ export type Vector2 = { X: number, Y: number, };