You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

311 lines
8.4 KiB
JavaScript

/**
* TODO: optimize for reduced complexity
* TODO: optimize for cleaner code
*/
const fs = require("fs");
function main() {
const input = fs.readFileSync("input", "utf-8").trim();
const tiles = input.split("\n\n").map((tile) => new Tile(tile));
const tilesMap = Object.fromEntries(tiles.map((tile) => [tile.id, tile]));
const tileSet = new Set(tiles);
// console.log(tiles);
// console.log(tileSet);
const borderMap = {};
for (const tile of tiles) {
for (const border of Object.values(tile.borders)) {
const key = canonical(border);
if (typeof borderMap[key] === "undefined") {
borderMap[key] = [];
}
borderMap[key].push(tile.id);
if (borderMap[key].length > 2) {
console.warn(
"Assumption does not hold: One edge is present in more than two tiles"
);
}
}
}
// console.log(borderMap);
const edgeTiles = new Set();
const cornerTiles = new Set();
const neighbors = {};
for (const ids of Object.values(borderMap)) {
if (ids.length === 1) {
const id = ids[0];
if (edgeTiles.has(id)) {
edgeTiles.delete(id);
cornerTiles.add(id);
} else {
edgeTiles.add(id);
}
} else {
const [tileA, tileB] = ids;
if (typeof neighbors[tileA] === "undefined") neighbors[tileA] = [];
if (typeof neighbors[tileB] === "undefined") neighbors[tileB] = [];
neighbors[tileA].push(tileB);
neighbors[tileB].push(tileA);
}
}
console.log(neighbors);
let topLeft = tilesMap[Array.from(cornerTiles)[0]];
console.log("TopLeft:", topLeft.id);
const isEdge = (border) => borderMap[canonical(border)].length === 1;
console.log(topLeft.pprint());
const reprs = [];
for (const t of allOrientations(topLeft)) {
reprs.push(t.pprint().split("\n"));
}
for (let i = 0; i < reprs[0].length; i++) {
for (let j = 0; j < reprs.length; j++) {
process.stdout.write(reprs[j][i]);
process.stdout.write(" ");
}
process.stdout.write("\n");
}
while (isEdge(topLeft.borders.e) || isEdge(topLeft.borders.s)) {
console.log("rotating corner tile");
topLeft.rot90();
}
// console.log(topLeft)
tileSet.delete(topLeft);
let curRow = [topLeft];
const orderedTiles = [];
const tileBuffer = Array.from(tileSet);
let lastTile = topLeft;
let loopDetection = 0;
while (tileBuffer.length > 0) {
// console.log("Tiles in buffer:", tileBuffer.length);
let cur = tileBuffer.shift();
if (isEdge(lastTile.borders.e)) {
// console.log("Looking for southener of", topLeft.id, cur.id);
for (cur of allOrientations(cur)) {
if (reverse(topLeft.borders.s) === cur.borders.n) {
orderedTiles.push(curRow);
curRow = [];
lastTile = cur;
topLeft = cur;
curRow.push(cur);
break;
}
}
} else {
// console.log("Looking for eastern of", lastTile.id, cur.id);
for (cur of allOrientations(cur)) {
if (lastTile.borders.e === reverse(cur.borders.w)) {
lastTile = cur;
curRow.push(cur);
break;
}
}
}
if (cur !== lastTile) {
tileBuffer.push(cur);
} else {
// console.log("Tile found! Its", cur.id);
loopDetection = 0;
}
// sleep(50);
if (loopDetection > tileBuffer.length) {
console.log("loop detected");
break;
}
loopDetection++;
}
orderedTiles.push(curRow);
console.log("Found map arrangement");
console.log(orderedTiles);
let map = "";
for (const row of orderedTiles) {
for (let i = 0; i < row[0].content.length; i++) {
for (let j = 0; j < row.length; j++) {
map += row[j].content[i].join("");
// map += " ";
}
map += "\n";
}
// map += "\n";
}
const hashCount = map.match(/#/g).length;
console.log("hashCount", hashCount);
// console.log(map);
map = map.split("\n").map((row) => row.split(""));
let sumSeamonsters;
for (let i = 0; i < 8; i++) {
if (i === 4) {
console.log("flip map");
map = flipH(map);
} else {
console.log("rotate map");
map = rotSquare(map);
}
sumSeamonsters = findSeamonster(map);
console.log("Seamonsters found:", sumSeamonsters);
if (sumSeamonsters) {
break;
}
}
const seamonsterHashes = 15 * sumSeamonsters;
console.log(hashCount - seamonsterHashes);
}
const reverse = (str) => str.split("").reverse().join("");
const isCanonical = (edge) => edge < reverse(edge);
const canonical = (edge) => (isCanonical(edge) ? edge : reverse(edge));
const trimEnds = (arr) => arr.slice(1, arr.length - 1);
function rotSquare(mat) {
const result = [];
const length = mat.length;
for (let i = 0; i < length; i++) {
const row = [];
for (let j = 0; j < length; j++) {
row.push(mat[length - 1 - j][i]);
}
result.push(row);
}
return result;
}
const flipH = (mat) => mat.map((row) => row.reverse());
const flipV = (mat) => mat.reverse();
function* allOrientations(tile) {
tile.rot90();
yield tile;
tile.rot90();
yield tile;
tile.rot90();
yield tile;
tile.rot90();
yield tile;
tile.flipH();
yield tile;
tile.rot90();
yield tile;
tile.rot90();
yield tile;
tile.rot90();
yield tile;
}
function findSeamonster(map) {
map = map.map((row) => row.join(""));
const sm1 = " # ";
const sm2 = "# ## ## ###";
const sm3 = " # # # # # # ";
const pattern1 = new RegExp(`(?=${sm1.replaceAll(" ", ".")})`, "g");
const pattern2 = new RegExp(`(?=${sm2.replaceAll(" ", ".")})`, "g");
const pattern3 = new RegExp(`(?=${sm3.replaceAll(" ", ".")})`, "g");
let sum = 0;
for (let lineIdx = 0; lineIdx < map.length - 3; lineIdx++) {
const [line1, line2, line3] = map.slice(lineIdx, lineIdx + 3);
let solutions1 = new Set();
let solutions2 = new Set();
let solutions3 = new Set();
// console.log("Processing line", lineIdx, line1);
for (const match of line1.matchAll(pattern1)) {
solutions1.add(match.index);
}
// console.log(solutions1);
for (const match of line2.matchAll(pattern2)) {
if (solutions1.has(match.index)) solutions2.add(match.index);
}
// console.log(solutions2);
for (const match of line3.matchAll(pattern3)) {
if (solutions2.has(match.index)) solutions3.add(match.index);
}
// console.log(solutions3);
if (solutions3.size) {
for (const idx of solutions3) {
console.log(`Seamonster found at [${lineIdx}, ${idx}]`);
}
}
sum += solutions3.size;
}
return sum;
}
const rot90Map = { n: "w", w: "s", s: "e", e: "n" };
const flipHMap = { w: "e", e: "w" };
const flipVMap = { n: "s", s: "n" };
function applyMap(map, obj, doReverse = false) {
const result = {};
for (const [target, source] of Object.entries(map)) {
if (obj[source] === undefined) {
console.log(source, obj);
throw Error("aa");
}
result[target] = doReverse ? reverse(obj[source]) : obj[source];
}
return { ...obj, ...result };
}
class Tile {
constructor(rawInput) {
const lines = rawInput.split("\n");
this.id = lines.shift().match("[0-9]+")[0];
this.borders = {
n: lines[0],
e: lines.map((line) => line[line.length - 1]).join(""),
s: reverse(lines[lines.length - 1]),
w: reverse(lines.map((line) => line[0]).join("")),
};
this.content = trimEnds(lines)
.map(trimEnds)
.map((line) => line.split(""));
}
rot90(times = 1) {
while (times--) {
this.borders = applyMap(rot90Map, this.borders);
this.content = rotSquare(this.content);
}
}
flipH() {
this.borders = applyMap(flipHMap, this.borders, true);
this.borders.n = reverse(this.borders.n);
this.borders.s = reverse(this.borders.s);
this.content.map((row) => row.reverse());
}
flipV() {
this.borders = applyMap(flipVMap, this.borders, true);
this.borders.w = reverse(this.borders.w);
this.borders.e = reverse(this.borders.e);
this.content.reverse();
}
pprint() {
let str = "";
str += " " + this.borders.n + " \n";
for (let i = 0; i < this.borders.w.length; i++) {
str += reverse(this.borders.w)[i];
if (i === 0 || i === this.borders.w.length - 1) {
str += " ".repeat(this.borders.w.length);
} else {
str += " " + this.content[i - 1].join("") + " ";
}
str += this.borders.e[i];
str += "\n";
}
str += " " + reverse(this.borders.s) + " ";
return str;
}
}
main();