UNPKG

expeditaet

Version:
500 lines (463 loc) 12 kB
import { Direction, GridGraph, GridGraphNode, Vec2, type ToString, type Vec2Like, } from '@alexaegis/advent-of-code-lib'; /** * Face connection scenarios * * Imagine a random face marked with `@` * To find the face directly to the top of it could only be present in 6 places * because cubes have 6 faces. * * Folding all of these configurations will leave the tail end at the top * of the face marked with `@` * * Do this with every side (patterns rotated) and you'll get your folded cube. * */ export class CubeMap<T extends ToString> { cubeGraph = new GridGraph<T>(); constructor(private readonly flatCube: (T | undefined)[][]) { for (let y = 0; y < this.flatCube.length; y++) { const row = this.flatCube[y]; if (row !== undefined) { for (let x = 0; x < row.length; x++) { const cell = row[x]; if (cell) { const position = new Vec2(x, y); const node = new GridGraphNode(position, cell); this.cubeGraph.nodes.set(node.coordinate.toString(), node); } } } } } solve(): void { for (const node of this.cubeGraph.nodes.values()) { for (const direction of Direction.cardinalDirections) { for (const facePath of walkCubeFacePaths(direction, node.coordinate)) { if (facePath.every((vertex) => this.cubeGraph.nodes.has(vertex.toString()))) { const neighbouringFacePosition = facePath.last(); const neighbouringNode = this.cubeGraph.nodes.get( neighbouringFacePosition.toString(), ); if (neighbouringNode) { const alreadyFoundNeighbour = node.neighbours.get(direction); if (!alreadyFoundNeighbour) { node.neighbours.set(direction, { from: node, to: neighbouringNode, weight: 0, direction, }); } else if (neighbouringNode !== alreadyFoundNeighbour.to) { throw new Error('Found a different face for an existing neighbour'); } } } } } } } isFolded(): boolean { return [...this.cubeGraph.nodes.values()] .map((node) => node.neighbourNodes.length) .every((length) => length === 4); } } const pathFromDirections = (directions: Direction[]): Vec2[] => { const cursor = Vec2.ORIGIN.clone(); const result: Vec2[] = []; for (const direction of directions) { cursor.addMut(direction); result.push(cursor.clone()); } return result; }; /** * .#. * .@. * ... */ const get1Path = (initialDirection: Direction): Vec2[] => [initialDirection]; /** * #.. * #@. * ... */ const get2PathLeftUp = (initialDirection: Direction): Vec2[] => pathFromDirections([initialDirection.left(), initialDirection]); /** * ..# * .@# * ... */ const get2PathRightUp = (initialDirection: Direction): Vec2[] => pathFromDirections([initialDirection.right(), initialDirection]); /** * ..... * ...@. * .###. * ..... */ const get3PathDownLeft = (initialDirection: Direction): Vec2[] => [ initialDirection.reverse(), initialDirection.reverse().add(initialDirection.left()), initialDirection.reverse().add(initialDirection.left()).add(initialDirection.left()), ]; /** * ....... * ...@... * ...###. * ....... */ const get3PathDownRight = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.reverse(), initialDirection.right(), initialDirection.right(), ]); /** * .#... * .##@. * ..... */ const get3PathLeftUp = (initialDirection: Direction): Vec2[] => pathFromDirections([initialDirection.left(), initialDirection.left(), initialDirection]); /** * .....#. * ...@##. * ....... */ const get3PathRightUp = (initialDirection: Direction): Vec2[] => pathFromDirections([initialDirection.right(), initialDirection.right(), initialDirection]); /** * ... * .@. * .#. * .#. * .#. * ... */ const get3PathBack = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.reverse(), initialDirection.reverse(), initialDirection.reverse(), ]); /** * ..... * ..#@. * ..#.. * ..#.. * ..#.. */ const get4PathLeftBack = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.left(), initialDirection.reverse(), initialDirection.reverse(), initialDirection.reverse(), ]); /** * ..... * ..@#. * ...#. * ...#. * ...#. */ const get4PathRightBack = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.right(), initialDirection.reverse(), initialDirection.reverse(), initialDirection.reverse(), ]); /** * #.... * ###@. * ..... */ const get4PathLeftUp = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.left(), initialDirection.left(), initialDirection.left(), initialDirection, ]); /** * ....# * .@### * ..... */ const get4PathRightUp = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.right(), initialDirection.right(), initialDirection.right(), initialDirection, ]); /** * ..... * ..#@. * ###.. * ..... */ const get4PathLeftBackLeft = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.left(), initialDirection.reverse(), initialDirection.left(), initialDirection.left(), ]); /** * ........ * ...@#... * ....###. * ........ */ const get4PathRightDownRight = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.right(), initialDirection.reverse(), initialDirection.right(), initialDirection.right(), ]); /** * .... * ..@. * .##. * ##.. * .... */ const get4PathDownLeftDownLeft = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.reverse(), initialDirection.left(), initialDirection.reverse(), initialDirection.left(), ]); /** * ..... * .@... * .##.. * ..##. * ..... */ const get4PathDownRightDownRight = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.reverse(), initialDirection.right(), initialDirection.reverse(), initialDirection.right(), ]); /** * ... * .@. * .#. * ##. * #.. * ... */ const get4PathDownLeftDown = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.reverse(), initialDirection.reverse(), initialDirection.left(), initialDirection.reverse(), ]); /** * ... * .@. * .#. * .## * ..# * ... */ const get4PathDownRightDown = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.reverse(), initialDirection.reverse(), initialDirection.right(), initialDirection.reverse(), ]); /** * ...... * ..##@. * ###... * ...... */ const get5PathLeftDownLeft = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.left(), initialDirection.left(), initialDirection.reverse(), initialDirection.left(), initialDirection.left(), ]); /** * ....... * .@##... * ...###. * ....... */ const get5PathRightDownRight = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.right(), initialDirection.right(), initialDirection.reverse(), initialDirection.right(), initialDirection.right(), ]); /** * ..... * ..#@. * .##.. * ##... * ..... * @param direction * @returns */ const get5PathLeftDownLeftDownLeft = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.left(), initialDirection.reverse(), initialDirection.left(), initialDirection.reverse(), initialDirection.left(), ]); /** * ...... * .@#... * ..##.. * ...##. * ...... * @param direction * @returns */ const get5PathRightDownRightDownRight = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.right(), initialDirection.reverse(), initialDirection.right(), initialDirection.reverse(), initialDirection.right(), ]); /** * .... * .#@. * .#.. * ##.. * #... * .... * @param direction * @returns */ const get5PathLeftDownLeftDown = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.left(), initialDirection.reverse(), initialDirection.reverse(), initialDirection.left(), initialDirection.reverse(), ]); /** * ...... * ..@#.. * ...#.. * ...##. * ....#. * ...... * @param direction * @returns */ const get5PathRightDownRightDown = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.right(), initialDirection.reverse(), initialDirection.reverse(), initialDirection.right(), initialDirection.reverse(), ]); /** * .... * ..@. * .##. * .#.. * ##.. * .... * @param direction * @returns */ const get5PathDownLeftDownLeft = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.reverse(), initialDirection.left(), initialDirection.reverse(), initialDirection.reverse(), initialDirection.left(), ]); /** * ...... * ..@... * ..##.. * ...#.. * ...##. * ...... * @param initialDirection * @returns */ const get5PathDownRightDownRight = (initialDirection: Direction): Vec2[] => pathFromDirections([ initialDirection.reverse(), initialDirection.right(), initialDirection.reverse(), initialDirection.reverse(), initialDirection.right(), ]); /** * In this collection of paths, every path should either be fully matching, * (for every coordinate there is a node in the flat cubemap) or none at all, * the last node in the path will be connected to the source node in the * direction of initialDirection */ export const everyCubeFacePath = (initialDirection: Direction, shiftBy: Vec2Like): Vec2[][] => [ ...walkCubeFacePaths(initialDirection, shiftBy), ]; export function* walkCubeFacePaths( initialDirection: Direction, shiftBy: Vec2Like, ): Generator<Vec2[]> { yield get1Path(initialDirection).map((position) => position.add(shiftBy)); yield get2PathLeftUp(initialDirection).map((position) => position.add(shiftBy)); yield get2PathRightUp(initialDirection).map((position) => position.add(shiftBy)); yield get3PathLeftUp(initialDirection).map((position) => position.add(shiftBy)); yield get3PathRightUp(initialDirection).map((position) => position.add(shiftBy)); yield get3PathDownLeft(initialDirection).map((position) => position.add(shiftBy)); yield get3PathDownRight(initialDirection).map((position) => position.add(shiftBy)); yield get3PathBack(initialDirection).map((position) => position.add(shiftBy)); yield get4PathLeftUp(initialDirection).map((position) => position.add(shiftBy)); yield get4PathRightUp(initialDirection).map((position) => position.add(shiftBy)); yield get4PathLeftBack(initialDirection).map((position) => position.add(shiftBy)); yield get4PathRightBack(initialDirection).map((position) => position.add(shiftBy)); yield get4PathDownLeftDownLeft(initialDirection).map((position) => position.add(shiftBy)); yield get4PathDownRightDownRight(initialDirection).map((position) => position.add(shiftBy)); yield get4PathRightDownRight(initialDirection).map((position) => position.add(shiftBy)); yield get4PathLeftBackLeft(initialDirection).map((position) => position.add(shiftBy)); yield get4PathDownRightDown(initialDirection).map((position) => position.add(shiftBy)); yield get4PathDownLeftDown(initialDirection).map((position) => position.add(shiftBy)); yield get5PathLeftDownLeft(initialDirection).map((position) => position.add(shiftBy)); yield get5PathRightDownRight(initialDirection).map((position) => position.add(shiftBy)); yield get5PathDownLeftDownLeft(initialDirection).map((position) => position.add(shiftBy)); yield get5PathDownRightDownRight(initialDirection).map((position) => position.add(shiftBy)); yield get5PathLeftDownLeftDown(initialDirection).map((position) => position.add(shiftBy)); yield get5PathRightDownRightDown(initialDirection).map((position) => position.add(shiftBy)); yield get5PathLeftDownLeftDownLeft(initialDirection).map((position) => position.add(shiftBy)); yield get5PathRightDownRightDownRight(initialDirection).map((position) => position.add(shiftBy), ); }