2024-12-06 22:13:09 +00:00
|
|
|
import fs from "node:fs";
|
|
|
|
import { printTime, now } from "../util";
|
|
|
|
|
2024-12-07 04:10:44 +00:00
|
|
|
type tile = {
|
|
|
|
guard: boolean;
|
|
|
|
blocked: boolean;
|
|
|
|
visited: boolean;
|
|
|
|
hit: { north: number; east: number; south: number; west: number };
|
|
|
|
};
|
2024-12-06 22:13:09 +00:00
|
|
|
|
2024-12-07 04:10:44 +00:00
|
|
|
const test = false;
|
2024-12-06 22:13:09 +00:00
|
|
|
|
|
|
|
const data = fs.readFileSync(
|
|
|
|
test ? "./inputs/testinput" : "./inputs/input",
|
|
|
|
"utf8"
|
|
|
|
);
|
|
|
|
|
2024-12-07 04:10:44 +00:00
|
|
|
const generateFloor = (data: string): Record<string, tile> => {
|
|
|
|
return data
|
|
|
|
.split("\n")
|
|
|
|
.slice(0, -1)
|
|
|
|
.reduce((floor, row, i) => {
|
|
|
|
row.split("").forEach((v, col) => {
|
|
|
|
floor[`${i}|${col}`] = {
|
|
|
|
guard: v === "^",
|
|
|
|
blocked: v === "#",
|
|
|
|
visited: false,
|
|
|
|
hit: {
|
|
|
|
north: 0,
|
|
|
|
south: 0,
|
|
|
|
east: 0,
|
|
|
|
west: 0,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
});
|
|
|
|
return floor;
|
|
|
|
}, {} as Record<string, tile>);
|
|
|
|
};
|
|
|
|
|
|
|
|
const keyToCoord = (string): [number, number] => {
|
|
|
|
return string.split("|").map((v) => parseInt(v, 10));
|
|
|
|
};
|
|
|
|
|
2024-12-06 22:13:09 +00:00
|
|
|
let timer = now.instant();
|
|
|
|
console.log(
|
|
|
|
"part one:",
|
|
|
|
(() => {
|
2024-12-07 04:10:44 +00:00
|
|
|
let floor = generateFloor(data);
|
2024-12-06 22:13:09 +00:00
|
|
|
let direction = "north";
|
|
|
|
let [guard] = Object.entries(floor).filter((tile) => tile[1].guard)[0];
|
|
|
|
while (true) {
|
2024-12-07 04:10:44 +00:00
|
|
|
let [x, y] = keyToCoord(guard);
|
2024-12-06 22:13:09 +00:00
|
|
|
floor[guard].visited = true;
|
|
|
|
let next = "";
|
|
|
|
switch (direction) {
|
|
|
|
case "north":
|
|
|
|
next = `${x - 1}|${y}`;
|
|
|
|
break;
|
|
|
|
case "south":
|
|
|
|
next = `${x + 1}|${y}`;
|
|
|
|
break;
|
|
|
|
case "east":
|
|
|
|
next = `${x}|${y + 1}`;
|
|
|
|
break;
|
|
|
|
case "west":
|
|
|
|
next = `${x}|${y - 1}`;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (floor[next] === undefined) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!floor[next].blocked) {
|
|
|
|
floor[next].guard = true;
|
|
|
|
floor[guard].guard = false;
|
|
|
|
guard = next;
|
|
|
|
} else {
|
|
|
|
if (direction === "north") {
|
|
|
|
direction = "east";
|
|
|
|
} else if (direction === "east") {
|
|
|
|
direction = "south";
|
|
|
|
} else if (direction === "south") {
|
|
|
|
direction = "west";
|
|
|
|
} else if (direction === "west") {
|
|
|
|
direction = "north";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Object.entries(floor).reduce((total, tile) => {
|
|
|
|
return total + (tile[1].visited ? 1 : 0);
|
|
|
|
}, 0);
|
|
|
|
})(),
|
|
|
|
printTime(now.instant().since(timer))
|
|
|
|
);
|
2024-12-07 04:10:44 +00:00
|
|
|
|
|
|
|
timer = now.instant();
|
|
|
|
console.log(
|
|
|
|
"part two:",
|
|
|
|
(() => {
|
|
|
|
const floor = generateFloor(data);
|
|
|
|
|
|
|
|
let direction = "north";
|
|
|
|
let guard = Object.entries(floor).filter((tile) => tile[1].guard)[0][0];
|
|
|
|
const initialGuardPosition = guard;
|
|
|
|
while (true) {
|
|
|
|
let [x, y] = keyToCoord(guard);
|
|
|
|
floor[guard].visited = true;
|
|
|
|
let next = "";
|
|
|
|
switch (direction) {
|
|
|
|
case "north":
|
|
|
|
next = `${x - 1}|${y}`;
|
|
|
|
break;
|
|
|
|
case "south":
|
|
|
|
next = `${x + 1}|${y}`;
|
|
|
|
break;
|
|
|
|
case "east":
|
|
|
|
next = `${x}|${y + 1}`;
|
|
|
|
break;
|
|
|
|
case "west":
|
|
|
|
next = `${x}|${y - 1}`;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (floor[next] === undefined) {
|
|
|
|
// guard left the floor, exit
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!floor[next].blocked) {
|
|
|
|
// move the guard
|
|
|
|
floor[next].guard = true;
|
|
|
|
floor[guard].guard = false;
|
|
|
|
guard = next;
|
|
|
|
} else {
|
|
|
|
// guard is blocked, rotate
|
|
|
|
if (direction === "north") {
|
|
|
|
direction = "east";
|
|
|
|
} else if (direction === "east") {
|
|
|
|
direction = "south";
|
|
|
|
} else if (direction === "south") {
|
|
|
|
direction = "west";
|
|
|
|
} else if (direction === "west") {
|
|
|
|
direction = "north";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let newObstructions = Object.entries(floor).reduce((total, tile) => {
|
|
|
|
if (tile[1].visited && tile[0] !== initialGuardPosition) {
|
|
|
|
return [...total, tile[0]];
|
|
|
|
}
|
|
|
|
return total;
|
|
|
|
}, [] as string[]);
|
|
|
|
return newObstructions.reduce((total, curr) => {
|
|
|
|
const floor = generateFloor(data);
|
|
|
|
floor[curr].blocked = true;
|
|
|
|
|
|
|
|
// iterate as above, but this time we tract how many times the guard hits any obstruction from the same direction.
|
|
|
|
// if the guard ever hits one from the same direction twice, we know they're in a loop.
|
|
|
|
let direction = "north";
|
|
|
|
let guard = Object.entries(floor).filter((tile) => tile[1].guard)[0][0];
|
|
|
|
while (true) {
|
|
|
|
let [x, y] = keyToCoord(guard);
|
|
|
|
let next = "";
|
|
|
|
switch (direction) {
|
|
|
|
case "north":
|
|
|
|
next = `${x - 1}|${y}`;
|
|
|
|
break;
|
|
|
|
case "south":
|
|
|
|
next = `${x + 1}|${y}`;
|
|
|
|
break;
|
|
|
|
case "east":
|
|
|
|
next = `${x}|${y + 1}`;
|
|
|
|
break;
|
|
|
|
case "west":
|
|
|
|
next = `${x}|${y - 1}`;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (floor[next] === undefined) {
|
|
|
|
// guard left the floor, exit
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!floor[next].blocked) {
|
|
|
|
// move the guard
|
|
|
|
floor[next].guard = true;
|
|
|
|
floor[guard].guard = false;
|
|
|
|
guard = next;
|
|
|
|
} else {
|
|
|
|
// guard is blocked, log impact and rotate
|
|
|
|
if (direction === "north") {
|
|
|
|
floor[next].hit.north++;
|
|
|
|
if (floor[next].hit.north === 2) {
|
|
|
|
return total + 1;
|
|
|
|
}
|
|
|
|
direction = "east";
|
|
|
|
} else if (direction === "east") {
|
|
|
|
floor[next].hit.east++;
|
|
|
|
if (floor[next].hit.east === 2) {
|
|
|
|
return total + 1;
|
|
|
|
}
|
|
|
|
direction = "south";
|
|
|
|
} else if (direction === "south") {
|
|
|
|
floor[next].hit.south++;
|
|
|
|
if (floor[next].hit.south === 2) {
|
|
|
|
return total + 1;
|
|
|
|
}
|
|
|
|
direction = "west";
|
|
|
|
} else if (direction === "west") {
|
|
|
|
floor[next].hit.west++;
|
|
|
|
if (floor[next].hit.west === 2) {
|
|
|
|
return total + 1;
|
|
|
|
}
|
|
|
|
direction = "north";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return total;
|
|
|
|
}, 0);
|
|
|
|
})(),
|
|
|
|
printTime(now.instant().since(timer))
|
|
|
|
);
|