cleaning up the cube

This commit is contained in:
eleith 2023-02-25 22:38:25 -08:00
parent 767070d3e7
commit fb753ef7bd
1 changed files with 166 additions and 250 deletions

View File

@ -29,12 +29,6 @@ type TrailInstructionTurns = typeof InstructionTurn[keyof typeof InstructionTurn
type TrailSpace = {
type: TrailSpaceTypes
point: TrailPoint
neighbors: {
[TrailDirection.LEFT]?: TrailSpace
[TrailDirection.RIGHT]?: TrailSpace
[TrailDirection.UP]?: TrailSpace
[TrailDirection.DOWN]?: TrailSpace
}
}
type TrailPoint = {
@ -48,16 +42,15 @@ type TrailPosition = {
}
type Trail = {
start: TrailSpace
at: TrailPosition
start: TrailPosition
spaces: { [coordinates: string]: TrailSpace }
height: number
width: number
}
type TrailCube = {
size: number
indexes: [number, number][]
cube: {
size: number
indexes: [number, number][]
edges: TrailSpace[]
}
}
type TrailInstruction = {
@ -75,86 +68,90 @@ type TrailCubeFace = {
}
const TrailCompass = [
TrailDirection.UP,
TrailDirection.RIGHT,
TrailDirection.DOWN,
TrailDirection.LEFT,
TrailDirection.UP,
]
const matchTrailInstruction = /\d+(R|L)?/g
function parseTrailAsCube(lines: string[]): {
trail: Trail
cube: TrailCube
} {
function getNeighbors(trail: Trail, { x, y }: TrailPoint) {
const up = y < 0 ? trail.height - 1 : y - 1
const down = y > trail.height - 1 ? 0 : y + 1
const left = x < 0 ? trail.width - 1 : x - 1
const right = x > trail.width - 1 ? 0 : x + 1
return {
[TrailDirection.DOWN]: { point: { x, y: down }, dir: TrailDirection.DOWN },
[TrailDirection.UP]: { point: { x, y: up }, dir: TrailDirection.UP },
[TrailDirection.LEFT]: { point: { x: left, y }, dir: TrailDirection.LEFT },
[TrailDirection.RIGHT]: { point: { x: right, y }, dir: TrailDirection.RIGHT },
}
}
function parseTrail(lines: string[]): Trail {
const start = { point: { x: 0, y: 0 }, type: TrailSpaceType.EMPTY, neighbors: {} }
const columns: TrailSpace[][] = Array(Math.max(...lines.map((line) => line.length)))
.fill('')
.map(() => [])
const trail: Trail = {
at: {
start: {
space: start,
facing: TrailDirection.RIGHT,
},
start,
spaces: {},
height: lines.length,
width: columns.length,
width: lines.reduce((width, line) => Math.max(width, line.length), 0),
cube: {
size: lines.length,
indexes: [],
edges: [],
},
}
let minWidth = lines.length
let minHeight = columns.length
const column = { min: trail.height, max: 0 }
lines.forEach((line, y) => {
const spaces = line.split('') as TrailSpaceTypes[]
const row: TrailSpace[] = []
const row = { min: trail.width, max: 0 }
spaces.forEach((space, x) => {
if (TrailSpaceType.EMPTY !== space) {
const trailSpace: TrailSpace = {
point: { x, y },
type: space,
neighbors: {},
}
row.push(trailSpace)
columns[x].push(trailSpace)
if (trail.start.space.type === TrailSpaceType.EMPTY) {
trail.start.space = trailSpace
}
setSpace(trail, trailSpace)
row.min = Math.min(row.min, x)
row.max = Math.max(row.max, x)
column.min = Math.min(column.min, y)
column.max = Math.max(column.max, y)
}
})
minWidth = Math.min(minWidth, row.length)
row.forEach((space, index) => {
if (index === 0 && y === 0) {
trail.start = space
trail.at.space = space
}
if (index !== 0 || index !== row.length - 1) {
space.neighbors[TrailDirection.LEFT] = row[index - 1]
space.neighbors[TrailDirection.RIGHT] = row[index + 1]
}
})
trail.cube.size = Math.min(trail.cube.size, row.max - row.min + 1)
})
columns.forEach((column) => {
minHeight = Math.min(minHeight, column.length)
column.forEach((space, index) => {
if (index !== 0 || index !== column.length - 1) {
space.neighbors[TrailDirection.UP] = column[index - 1]
space.neighbors[TrailDirection.DOWN] = column[index + 1]
}
setSpace(trail, space)
})
})
if (minHeight === minWidth) {
return { trail, cube: foldTrailAsCube(trail, minHeight) }
if (trail.cube.size !== Math.min(trail.cube.size, column.max - column.min + 1)) {
throw new Error('Not a cube')
}
throw new Error('Trail is not a cube')
trail.cube.edges = Object.values(trail.spaces).filter((space) =>
Object.values(getNeighbors(trail, space.point)).some(
(neighbor) => !getSpace(trail, neighbor.point)
)
)
trail.cube.indexes = getFaceIndexes(trail.cube.edges, trail.cube.size)
return trail
}
const arrayEquals = <T>(index: T[], index2: T[]): boolean =>
index.length === index2.length && index.every((value, i) => value === index2[i])
const arrayEquals = <T>(one: T[], two: T[]): boolean =>
one.length === two.length && one.every((value, i) => value === two[i])
function getTurnsBeforeRotation(dirs: TrailDirections[]) {
return dirs.reduce((foundIndex, _, index) => {
@ -166,29 +163,27 @@ function getTurnsBeforeRotation(dirs: TrailDirections[]) {
}
function getRotation(dirs: TrailDirections[]) {
const isCounterClockwise = dirs.some((_, index) => {
const first = TrailCompass.indexOf(dirs[index])
const second = TrailCompass.indexOf(dirs[index + 1])
if (first === -1 || second === -1) {
return false
return dirs.reduce((turn, _, index) => {
if (index + 1 < dirs.length) {
const first = TrailCompass.indexOf(dirs[index])
const second = TrailCompass.indexOf(dirs[index + 1])
const isCounterClockwise = first - second === 1 || second - first === 3
const isClockwise = second - first === 1 || first - second === 3
if (isClockwise && isCounterClockwise) {
return turn
} else if (turn === InstructionTurn.COUNTER_CLOCKWISE && isClockwise) {
return null
} else if (turn === InstructionTurn.CLOCKWISE && isCounterClockwise) {
return null
} else if (isCounterClockwise) {
return InstructionTurn.COUNTER_CLOCKWISE
} else if (isClockwise) {
return InstructionTurn.CLOCKWISE
}
}
return first - second === 1 || (first === 0 && first - second === -3)
})
const isClockwise = dirs.some((_, index) => {
const first = TrailCompass.indexOf(dirs[index])
const second = TrailCompass.indexOf(dirs[index + 1])
if (first === -1 || second === -1) {
return false
}
return second - first === 1 || (first === 3 && second - first === 3)
})
if (isClockwise === isCounterClockwise) {
return null
}
return isClockwise ? InstructionTurn.CLOCKWISE : InstructionTurn.COUNTER_CLOCKWISE
return turn
}, null as TrailInstructionTurns | null)
}
function isFlipped(dirs: TrailDirections[]) {
@ -198,9 +193,9 @@ function isFlipped(dirs: TrailDirections[]) {
})
}
function findFaceFor(
function findNextFaceFor(
trail: Trail,
index: [number, number],
allIndexes: [number, number][],
dir: TrailDirections
): TrailCubeFace {
const queue: TrailCubeFace[] = [{ index, folds: [] }]
@ -212,7 +207,11 @@ function findFaceFor(
const turnDir = getRotation(faceMap.folds)
const turnTimes = getTurnsBeforeRotation(faceMap.folds) + 1
if (lastFold === dir || (isFlipped(faceMap.folds) && getOppositeDirection(lastFold) === dir)) {
if (
lastFold === dir ||
(isFlipped(faceMap.folds) && getOppositeDirection(lastFold) === dir) ||
visited.length === 5
) {
if (turnDir) {
faceMap.rotation = {
turn: turnDir,
@ -224,19 +223,18 @@ function findFaceFor(
const x = faceMap.index[0]
const y = faceMap.index[1]
const neighbors = [
[x, y + 1, TrailDirection.DOWN],
[x, y - 1, TrailDirection.UP],
[x - 1, y, TrailDirection.LEFT],
[x + 1, y, TrailDirection.RIGHT],
] as [number, number, TrailDirections][]
neighbors
.map(([x, y, fold]) => {
const turnFold = turnDir ? turnDirectionTimes(fold, turnDir, turnTimes) : fold
return { index: [x, y], folds: [...faceMap.folds, turnFold] } as TrailCubeFace
Object.values(getNeighbors(trail, { x, y }))
.map((neighbor) => {
const turnFold = turnDir
? turnDirectionTimes(neighbor.dir, turnDir, turnTimes)
: neighbor.dir
return {
index: [neighbor.point.x, neighbor.point.y],
folds: [...faceMap.folds, turnFold],
} as TrailCubeFace
})
.filter((neighbor) => allIndexes.find((index) => arrayEquals(index, neighbor.index)))
.filter((neighbor) => trail.cube.indexes.find((index) => arrayEquals(index, neighbor.index)))
.filter((neighbor) => !visited.find((visit) => arrayEquals(visit, neighbor.index)))
.forEach((neighbor) => {
queue.push(neighbor)
@ -272,117 +270,43 @@ function getFaceIndexes(edges: TrailSpace[], size: number): [number, number][] {
}, [] as [number, number][])
}
function foldTrailAsCube(trail: Trail, size: number): TrailCube {
const edges = Object.values(trail.spaces).filter((space) =>
Object.values(space.neighbors).some((neighbor) => neighbor === undefined)
)
const cube: TrailCube = {
size,
indexes: getFaceIndexes(edges, size),
function findFoldedEdgeFor(
trail: Trail,
fromEdge: TrailSpace,
fromDirection: TrailDirections
): TrailSpace {
const fromIndex = getFaceIndex(fromEdge, trail.cube.size)
const toFaceMap = findNextFaceFor(trail, fromIndex, fromDirection)
if (!toFaceMap) {
throw new Error(`Could not find edge for ${fromEdge}`)
}
edges.forEach((fromEdge) => {
const fromIndex = getFaceIndex(fromEdge, cube.size)
TrailCompass.filter((fromDirection) => fromEdge.neighbors[fromDirection] === undefined).forEach(
(fromDirection) => {
const toFaceMap = findFaceFor(fromIndex, cube.indexes, fromDirection)
if (!toFaceMap) {
throw new Error(`Could not find edge for ${fromEdge}`)
}
const fromMod = getMod(fromEdge, fromDirection, cube.size)
const oppositeDir = getOppositeDirection(fromDirection)
const oppositeTurn = getOppositeTurn(toFaceMap.rotation?.turn || InstructionTurn.CLOCKWISE)
const toDirection = toFaceMap.rotation?.turn
? turnDirectionTimes(oppositeDir, oppositeTurn, toFaceMap.rotation.times)
: fromDirection
const toEdge = edges
.filter((edge) => arrayEquals(getFaceIndex(edge, cube.size), toFaceMap.index))
.filter((edge) => edge.neighbors[toDirection] === undefined)
.find((edge) => {
return (
fromMod ===
getMod(
edge,
toDirection,
cube.size,
toFaceMap.rotation?.turn,
toFaceMap.rotation?.times
)
)
})
if (!toEdge) {
throw new Error(`Could not connect edge for ${JSON.stringify(fromEdge.point)}`)
}
fromEdge.neighbors[fromDirection] = toEdge
toEdge.neighbors[toDirection] = fromEdge
}
)
})
return cube
}
function parseTrail(lines: string[]): Trail {
const start = {
point: { x: 0, y: 0 },
type: TrailSpaceType.EMPTY,
neighbors: {},
edge: false,
}
const columns: TrailSpace[][] = Array(Math.max(...lines.map((line) => line.length)))
.fill('')
.map(() => [])
const trail: Trail = {
at: {
space: start,
facing: TrailDirection.RIGHT,
},
start,
spaces: {},
height: lines.length,
width: columns.length,
}
lines.forEach((line, y) => {
const spaces = line.split('') as TrailSpaceTypes[]
const row: TrailSpace[] = []
spaces.forEach((space, x) => {
if (TrailSpaceType.EMPTY !== space) {
const trailSpace: TrailSpace = {
point: { x, y },
type: space,
neighbors: {},
}
row.push(trailSpace)
columns[x].push(trailSpace)
}
const oppositeDir = getOppositeDirection(fromDirection)
const oppositeTurn = getOppositeTurn(toFaceMap.rotation?.turn || InstructionTurn.CLOCKWISE)
const toDirection = toFaceMap.rotation?.turn
? turnDirectionTimes(oppositeDir, oppositeTurn, toFaceMap.rotation.times)
: fromDirection
const fromMod = getMod(fromEdge, fromDirection, trail.cube.size)
const toEdge = trail.cube.edges
.filter((edge) => arrayEquals(getFaceIndex(edge, trail.cube.size), toFaceMap.index))
.filter((edge) => !getSpace(trail, getNeighbors(trail, edge.point)[toDirection].point))
.find((edge) => {
return (
fromMod ===
getMod(
edge,
toDirection,
trail.cube.size,
toFaceMap.rotation?.turn,
toFaceMap.rotation?.times
)
)
})
row.forEach((space, index) => {
if (index === 0 && y === 0) {
trail.start = space
trail.at.space = space
}
space.neighbors[TrailDirection.LEFT] = row[index === 0 ? row.length - 1 : index - 1]
space.neighbors[TrailDirection.RIGHT] = row[index === row.length - 1 ? 0 : index + 1]
})
})
if (!toEdge) {
throw new Error(`Could not connect edge for ${JSON.stringify(fromEdge.point)}`)
}
columns.forEach((column) => {
column.forEach((space, index) => {
space.neighbors[TrailDirection.UP] = column[index === 0 ? column.length - 1 : index - 1]
space.neighbors[TrailDirection.DOWN] = column[index === column.length - 1 ? 0 : index + 1]
setSpace(trail, space)
})
})
return trail
return toEdge
}
function parseInstructions(line: string): TrailInstruction[] {
@ -412,27 +336,21 @@ function turnDirectionTimes(
}
function turnDirection(facing: TrailDirections, dir: TrailInstructionTurns): TrailDirections {
const compass = [
TrailDirection.UP,
TrailDirection.RIGHT,
TrailDirection.DOWN,
TrailDirection.LEFT,
]
const compass = TrailCompass
const index = compass.indexOf(facing)
const oppositeIndex = InstructionTurn.CLOCKWISE === dir ? index + 1 : index - 1
if (oppositeIndex < 0) {
return compass[compass.length - 1]
} else if (oppositeIndex > compass.length - 1) {
return compass[oppositeIndex % compass.length]
}
return compass[oppositeIndex]
return compass[oppositeIndex % compass.length]
}
function positionMove(
trail: Trail,
position: TrailPosition,
instruction: TrailInstruction,
cube?: TrailCube
asCube = false
): TrailPosition {
let dir = position.facing
let at = position.space
@ -440,18 +358,31 @@ function positionMove(
for (let i = 0; i < instruction.distance; i++) {
at.type = TrailSpaceType[dir]
next = at.neighbors[dir]
let neighbor = getNeighbors(trail, at.point)[dir]
next = getSpace(trail, neighbor.point)
if (!next) {
if (asCube) {
next = findFoldedEdgeFor(trail, at, dir)
} else {
while (!next) {
neighbor = getNeighbors(trail, neighbor.point)[dir]
next = getSpace(trail, neighbor.point)
}
}
}
if (!next) {
throw new Error(`No next space for ${at.point.y},${at.point.y} in direction ${dir}`)
}
if (next.type !== TrailSpaceType.WALL) {
if (cube) {
const atIndex = getFaceIndex(at, cube.size)
const nextIndex = getFaceIndex(next, cube.size)
if (asCube) {
const atIndex = getFaceIndex(at, trail.cube.size)
const nextIndex = getFaceIndex(next, trail.cube.size)
if (!arrayEquals(atIndex, nextIndex)) {
const face = findFaceFor(atIndex, cube.indexes, dir)
const face = findNextFaceFor(trail, atIndex, dir)
if (face.rotation) {
dir = turnDirectionTimes(dir, getOppositeTurn(face.rotation.turn), face.rotation.times)
}
@ -494,8 +425,8 @@ function getMod(
case TrailDirection.DOWN:
if (
rotations === 0 ||
(turn && turn === InstructionTurn.CLOCKWISE && rotations === 4) ||
(turn && turn === InstructionTurn.CLOCKWISE && rotations === 1)
(turn && turn === InstructionTurn.CLOCKWISE && (rotations === 4 || rotations === 1)) ||
(turn && turn === InstructionTurn.COUNTER_CLOCKWISE && (rotations === 3 || rotations === 4))
) {
return space.point.x % size
} else {
@ -505,8 +436,8 @@ function getMod(
case TrailDirection.RIGHT:
if (
rotations === 0 ||
(turn && turn === InstructionTurn.CLOCKWISE && rotations === 4) ||
(turn && turn === InstructionTurn.COUNTER_CLOCKWISE && rotations === 1)
(turn && turn === InstructionTurn.CLOCKWISE && (rotations === 4 || rotations === 3)) ||
(turn && turn === InstructionTurn.COUNTER_CLOCKWISE && (rotations === 1 || rotations === 4))
) {
return space.point.y % size
} else {
@ -525,7 +456,10 @@ function printTrail(trail: Trail): void {
}
}
function parseInput(lines: string[]): { trail: Trail; instructions: TrailInstruction[] } {
function parseInput(lines: string[]): {
trail: Trail
instructions: TrailInstruction[]
} {
if (!lines.length) {
throw new Error('could not parse input')
}
@ -537,62 +471,44 @@ function parseInput(lines: string[]): { trail: Trail; instructions: TrailInstruc
return { trail, instructions }
}
function parseInputAsCube(lines: string[]): {
trail: Trail
instructions: TrailInstruction[]
cube: TrailCube
} {
if (!lines.length) {
throw new Error('could not parse input')
}
const instructionLine = lines.slice(-1)[0]
const instructions = parseInstructions(instructionLine)
const { trail, cube } = parseTrailAsCube(lines.slice(0, -2))
return { trail, instructions, cube }
}
function password(destination: TrailPosition): number {
const facings = [
TrailDirection.RIGHT,
TrailDirection.LEFT,
TrailDirection.DOWN,
TrailDirection.UP,
]
const row = 1000 * (destination.space.point.y + 1)
const column = 4 * (destination.space.point.x + 1)
const face = facings.indexOf(destination.facing)
const face = TrailCompass.indexOf(destination.facing)
return row + column + face
}
function solvePart1(lines: string[], debug = false): number {
const { trail, instructions } = parseInput(lines)
const positions: TrailPosition[] = [trail.start]
instructions.forEach((instruction) => {
trail.at = positionMove(trail.at, instruction)
const position = positionMove(trail, positions.slice(-1)[0], instruction)
positions.push(position)
})
if (debug) {
printTrail(trail)
}
return password(trail.at)
return password(positions.slice(-1)[0])
}
function solvePart2(lines: string[], debug = false): number {
const { trail, instructions, cube } = parseInputAsCube(lines)
const { trail, instructions } = parseInput(lines)
const positions: TrailPosition[] = [trail.start]
instructions.forEach((instruction) => {
trail.at = positionMove(trail.at, instruction, cube)
const position = positionMove(trail, positions.slice(-1)[0], instruction, true)
positions.push(position)
})
if (debug) {
printTrail(trail)
}
return password(trail.at)
return password(positions.slice(-1)[0])
}
function run() {