diff --git a/2022/22/solutions.ts b/2022/22/solutions.ts index 9802052..261aba1 100644 --- a/2022/22/solutions.ts +++ b/2022/22/solutions.ts @@ -1,128 +1,152 @@ +import { max } from "../../utils/deno/arrays.ts"; import { Input } from "../../utils/deno/input.ts"; import { mod } from "../../utils/deno/math.ts"; -type Direction = "up" | "down" | "left" | "right"; +type Direction = 0 | 1 | 2 | 3; -type Rotation = "R" | "L"; +type Rotation = -1 | 1; -type Instruction = number | Rotation; +type Instruction = + | { type: "turn"; value: Rotation } + | { type: "move"; value: number }; type Tile = " " | "." | "#"; -function rotate(current: Direction, rotation: Rotation): Direction { - switch (current) { - case "up": - return rotation === "R" ? "right" : "left"; - case "down": - return rotation === "R" ? "left" : "right"; - case "left": - return rotation === "R" ? "up" : "down"; - case "right": - return rotation === "R" ? "down" : "up"; - } +interface Position { + x: number; + y: number; + direction: Direction; +} + +export function rotate(current: Direction, rotation: Rotation): Direction { + return mod(current + rotation, 4) as Direction; } function delta(direction: Direction): { x: number; y: number } { - switch (direction) { - case "up": - return { x: 0, y: -1 }; - case "down": - return { x: 0, y: +1 }; - case "left": - return { x: -1, y: 0 }; - case "right": - return { x: +1, y: 0 }; - } + return [ + { x: 1, y: 0 }, // Right + { x: 0, y: 1 }, // Down + { x: -1, y: 0 }, // Left + { x: 0, y: -1 }, // Up + ][direction]; } export function parseInput(input: Input): [Tile[][], Instruction[]] { const [rawMap, rawPath] = input.raw.trimEnd().split("\n\n"); + const maxWidth = rawMap + .trimEnd() + .split("\n") + .map((x) => x.length) + .reduce(max); const map: Tile[][] = rawMap .trimEnd() .split("\n") - .map((x) => x.split("") as Tile[]); + .map((x) => { + const y = x.split(""); + return new Array(maxWidth).fill(0).map((_, i) => y[i] || " ") as Tile[]; + }); const instructions: Instruction[] = rawPath .trim() .replace(/(R|L)/g, ",$1,") .replace(/^,|,$/g, "") .split(",") - .map((x) => (x === "R" || x === "L" ? x : Number(x))) as Instruction[]; + .map((x) => { + if (x === "R" || x === "L") { + return { type: "turn", value: x === "R" ? 1 : -1 }; + } else { + return { type: "move", value: Number(x) }; + } + }) as Instruction[]; return [map, instructions]; } +function slice(map: Tile[][], position: Position): string { + const height = map.length; + const width = map.map((x) => x.length).reduce(max); + + if (position.direction === 0 || position.direction === 2) { + return new Array(width) + .fill(0) + .map((_, i) => map[position.y][i] || "") + .join(""); + } else { + return new Array(height) + .fill(0) + .map((_, i) => map[i][position.x] || "") + .join(""); + } +} + export function solve( map: Tile[][], - instructions: Instruction[] -): { x: number; y: number; direction: Direction } { + instructions: Instruction[], + start?: Position +): Position { const height = map.length; + const width = map.map((x) => x.length).reduce(max); + + if (!start) { + start = { + x: map[0].findIndex((x) => x === "."), + y: 0, + direction: 0, + }; + } - const visited = [...map.map((x) => [...x])]; - - const current = { - x: map[0].findIndex((x) => x === "."), - y: 0, - direction: "right" as Direction, + const current: Position = { + x: start.x, + y: start.y, + direction: start.direction, }; - // console.log(current.x + 1, current.y + 1, current.direction); - visited[current.y][current.x] = current.direction - .replace("left", "<") - .replace("right", ">") - .replace("up", "^") - .replace("down", "v") as Tile; + for (const instruction of instructions) { - if (typeof instruction === "string") { - const rotation = instruction; + const previous = { ...current }; + if (instruction.type === "turn") { + const rotation = instruction.value; current.direction = rotate(current.direction, rotation); - // console.log(current.x + 1, current.y + 1, current.direction); - visited[current.y][current.x] = current.direction - .replace("left", "<") - .replace("right", ">") - .replace("up", "^") - .replace("down", "v") as Tile; - continue; - } - - const steps = instruction; - - const { x: dx, y: dy } = delta(current.direction); - - // Try to move along the direction and wrap as needed, finding out if the - // destination is valid - let nextX = current.x; - let nextY = current.y; - for (let i = 0, j = 1; i < steps; i++, j++) { - const potentialNextY = mod(current.y + dy * j, height); - const width = map[potentialNextY].length; - const potentialNextX = mod(current.x + dx * j, width); - const tile = map[potentialNextY][potentialNextX]; - if (tile === "#") { - break; - } else if (tile === ".") { - nextX = potentialNextX; - nextY = potentialNextY; - visited[nextY][nextX] = current.direction - .replace("left", "<") - .replace("right", ">") - .replace("up", "^") - .replace("down", "v") as Tile; - } else if (tile === " ") { - // Don't consume a step whilst wrapping - i--; + } else { + const steps = instruction.value; + + const { x: dx, y: dy } = delta(current.direction); + + // Try to move along the direction and wrap as needed, finding out if the + // destination is valid + let nextX = current.x; + let nextY = current.y; + for (let i = 0, j = 1; i < steps; i++, j++) { + const potentialNextY = mod(current.y + dy * j, height); + const potentialNextX = mod(current.x + dx * j, width); + const tile = map[potentialNextY][potentialNextX]; + if (tile === "#") { + break; + } else if (tile === ".") { + nextX = potentialNextX; + nextY = potentialNextY; + } else { + // Don't consume a step whilst wrapping + i--; + } } + + current.x = nextX; + current.y = nextY; } // If the steps led us to a valid location along the path, move - current.x = nextX; - current.y = nextY; + // console.log( + // instruction.type === "turn" + // ? ["l", "", "r"][instruction.value + 1] + // : instruction.value + // ); + // console.log(current.x, current.y, current.direction); + const annotated = [...map.map((x) => [...x])]; + annotated[previous.y][previous.x] = "F" as Tile; + annotated[current.y][current.x] = "T" as Tile; + // console.log("|" + slice(annotated, current) + "|"); + // console.log(); } - // console.log(visited.map((x) => x.join("")).join("\n")); - // console.log( - // current.x + 1, - // current.y + 1, - // ["right", "down", "left", "up"].indexOf(current.direction) - // ); return current; } @@ -131,9 +155,5 @@ export function solvePart1(input: Input): number { const solved = solve(map, instructions); - return ( - 1000 * (solved.y + 1) + - 4 * (solved.x + 1) + - ["right", "down", "left", "up"].indexOf(solved.direction) - ); + return 1000 * (solved.y + 1) + 4 * (solved.x + 1) + solved.direction; } diff --git a/2022/22/test.ts b/2022/22/test.ts index 193c243..10749e8 100644 --- a/2022/22/test.ts +++ b/2022/22/test.ts @@ -1,6 +1,7 @@ import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; import { Input } from "../../utils/deno/input.ts"; -import { parseInput, solve, solvePart1 } from "./solutions.ts"; +import { parseInput, rotate, solve, solvePart1 } from "./solutions.ts"; +import input from "../../utils/deno/input.ts"; Deno.test("part 1 - given test case", () => { const input = ` ...# @@ -27,7 +28,7 @@ Deno.test("part 1 - wrap right", () => { assertEquals(solve(...parseInput(new Input(input))), { x: 1, y: 0, - direction: "right", + direction: 0, }); }); @@ -38,32 +39,38 @@ RR1`; assertEquals(solve(...parseInput(new Input(input))), { x: 2, y: 0, - direction: "left", + direction: 2, }); }); Deno.test("part 1 - wrap down", () => { const input = `... ... + . R2`; assertEquals(solve(...parseInput(new Input(input))), { x: 0, y: 0, - direction: "down", + direction: 1, }); }); Deno.test("part 1 - wrap up", () => { - const input = `... -... - -L1`; - assertEquals(solve(...parseInput(new Input(input))), { - x: 0, - y: 1, - direction: "up", - }); + const input = [" ..", " . ", ".. ", ". ", "", "2"].join("\n"); + + assertEquals( + solve(...parseInput(new Input(input)), { + x: 1, + y: 1, + direction: 3, + }), + { + x: 1, + y: 2, + direction: 3, + } + ); }); Deno.test("part 1 - wrap up wall", () => { @@ -74,7 +81,7 @@ L1`; assertEquals(solve(...parseInput(new Input(input))), { x: 0, y: 0, - direction: "up", + direction: 3, }); }); @@ -85,7 +92,7 @@ RR1`; assertEquals(solve(...parseInput(new Input(input))), { x: 0, y: 0, - direction: "left", + direction: 2, }); }); @@ -99,7 +106,7 @@ R2`; assertEquals(solve(...parseInput(new Input(input))), { x: 0, y: 0, - direction: "down", + direction: 1, }); }); @@ -113,7 +120,7 @@ Deno.test("part 1 - wrap empty x", () => { assertEquals(solve(...parseInput(new Input(input))), { x: 1, y: 2, - direction: "right", + direction: 0, }); }); @@ -124,7 +131,7 @@ Deno.test("part 1 - large move", () => { assertEquals(solve(...parseInput(new Input(input))), { x: 24, y: 0, - direction: "down", + direction: 1, }); }); @@ -135,6 +142,50 @@ Deno.test("part 1 - large move", () => { assertEquals(solve(...parseInput(new Input(input))), { x: 0, y: 0, - direction: "down", + direction: 1, }); }); + +Deno.test("part 1 - large move", () => { + const input = ` ...............#.........${" "} + +50`; + assertEquals( + solve(...parseInput(new Input(input)), { + x: 16, + y: 0, + direction: 2, + }), + { + x: 19, + y: 0, + direction: 2, + } + ); +}); + +Deno.test("part 1 - rotate clockwise", () => { + assertEquals(rotate(0, 1), 1); + assertEquals(rotate(1, 1), 2); + assertEquals(rotate(2, 1), 3); + assertEquals(rotate(3, 1), 0); +}); + +Deno.test("part 1 - rotate counter clockwise", () => { + assertEquals(rotate(0, -1), 3); + assertEquals(rotate(3, -1), 2); + assertEquals(rotate(2, -1), 1); + assertEquals(rotate(1, -1), 0); +}); + +Deno.test("part 1", () => { + const [map] = parseInput(input); + assertEquals( + solve(map, [{ type: "move", value: 21 }], { + x: 52, + y: 3, + direction: 3, + }), + { x: 52, y: 132, direction: 3 } + ); +}); diff --git a/utils/deno/math.test.ts b/utils/deno/math.test.ts index b80a701..bfa026c 100644 --- a/utils/deno/math.test.ts +++ b/utils/deno/math.test.ts @@ -8,4 +8,5 @@ Deno.test("mod", () => { assertEquals(mod(-5, 10), 5); assertEquals(mod(-50, 10), 0); assertEquals(mod(-49, 10), 1); + assertEquals(mod(5, 10), 5); });