From 514227fefc8a0e44d874e1d626626a3e866d2284 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Sat, 13 Dec 2025 18:13:39 -0500 Subject: [PATCH] Day twelve both parts --- 12/code.js | 197 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 12/code.js diff --git a/12/code.js b/12/code.js new file mode 100644 index 0000000..4f64108 --- /dev/null +++ b/12/code.js @@ -0,0 +1,197 @@ +import { readFileSync } from 'node:fs'; + +let sampleMode = false; +let usedArray = []; + +const sampleArray = readFileSync('sample.txt').toString().split("\n"); +const inputArray = readFileSync('input.txt').toString().split("\n"); + +if (sampleMode) { + usedArray = sampleArray; +} else { + usedArray = inputArray; +} + +// Part One + +console.time("part1"); + +// Parse input +const shapes = []; +const regions = []; + +let idx = 0; +// Parse shapes - continue until we hit a region line (NxN: ...) +while (idx < usedArray.length) { + const regionMatch = usedArray[idx].match(/^(\d+)x(\d+): (.+)$/); + if (regionMatch) break; // Start of regions section + + const shapeMatch = usedArray[idx].match(/^(\d+):$/); + if (shapeMatch) { + const shapeLines = []; + idx++; + while (idx < usedArray.length && usedArray[idx] !== '' && !usedArray[idx].match(/^\d+:/) && !usedArray[idx].match(/^\d+x\d+:/)) { + shapeLines.push(usedArray[idx]); + idx++; + } + // Convert shape to list of coordinates + const coords = []; + for (let y = 0; y < shapeLines.length; y++) { + for (let x = 0; x < shapeLines[y].length; x++) { + if (shapeLines[y][x] === '#') { + coords.push([x, y]); + } + } + } + shapes.push(coords); + } else { + idx++; + } +} + +// Parse regions +while (idx < usedArray.length) { + if (usedArray[idx] === '') { idx++; continue; } + const match = usedArray[idx].match(/^(\d+)x(\d+): (.+)$/); + if (match) { + const width = Number(match[1]); + const height = Number(match[2]); + const counts = match[3].split(' ').map(Number); + regions.push({ width, height, counts }); + } + idx++; +} + +// Generate all rotations and flips of a shape +function getAllOrientations(coords) { + const orientations = new Set(); + let current = coords; + + for (let flip = 0; flip < 2; flip++) { + for (let rot = 0; rot < 4; rot++) { + // Normalize: translate to origin (min x = 0, min y = 0) + const minX = Math.min(...current.map(c => c[0])); + const minY = Math.min(...current.map(c => c[1])); + const normalized = current.map(c => [c[0] - minX, c[1] - minY]); + normalized.sort((a, b) => a[1] - b[1] || a[0] - b[0]); + orientations.add(JSON.stringify(normalized)); + + // Rotate 90 degrees: (x, y) -> (-y, x) + current = current.map(c => [-c[1], c[0]]); + } + // Flip horizontally: (x, y) -> (-x, y) + current = coords.map(c => [-c[0], c[1]]); + } + + return [...orientations].map(s => JSON.parse(s)); +} + +// Precompute all orientations for all shapes +const shapeOrientations = shapes.map(getAllOrientations); + +// Check if a region can fit all required presents +function canFit(width, height, counts) { + const grid = Array(height).fill(null).map(() => Array(width).fill(false)); + + // Build list of pieces to place + const pieces = []; + for (let shapeIdx = 0; shapeIdx < counts.length; shapeIdx++) { + for (let j = 0; j < counts[shapeIdx]; j++) { + pieces.push(shapeIdx); + } + } + + // Calculate total cells needed - must not exceed region size + const totalCells = pieces.reduce((sum, shapeIdx) => sum + shapes[shapeIdx].length, 0); + if (totalCells > width * height) return false; // Can't exceed region size + + if (pieces.length === 0) return true; + + function canPlace(orientedShape, startX, startY) { + for (const [dx, dy] of orientedShape) { + const x = startX + dx; + const y = startY + dy; + if (x < 0 || x >= width || y < 0 || y >= height || grid[y][x]) { + return false; + } + } + return true; + } + + function place(orientedShape, startX, startY) { + for (const [dx, dy] of orientedShape) { + grid[startY + dy][startX + dx] = true; + } + } + + function unplace(orientedShape, startX, startY) { + for (const [dx, dy] of orientedShape) { + grid[startY + dy][startX + dx] = false; + } + } + + // Track placement positions for each shape type to avoid duplicates + const lastPlacement = new Map(); // shapeIdx -> [x, y] of last placement + + function solve(pieceIdx) { + if (pieceIdx >= pieces.length) { + return true; // All pieces placed successfully + } + + const shapeIdx = pieces[pieceIdx]; + const orientations = shapeOrientations[shapeIdx]; + + // Get minimum position for this shape type (to avoid duplicate placements of same shape) + const minPos = lastPlacement.get(shapeIdx); + + for (const oriented of orientations) { + // Try all valid placements in the grid + for (let startY = 0; startY < height; startY++) { + for (let startX = 0; startX < width; startX++) { + // Skip if we've already placed this shape type at a later position + // (enforces canonical ordering for identical pieces) + if (minPos && (startY < minPos[1] || (startY === minPos[1] && startX <= minPos[0]))) { + continue; + } + + if (canPlace(oriented, startX, startY)) { + place(oriented, startX, startY); + const prevPos = lastPlacement.get(shapeIdx); + lastPlacement.set(shapeIdx, [startX, startY]); + if (solve(pieceIdx + 1)) return true; + if (prevPos) { + lastPlacement.set(shapeIdx, prevPos); + } else { + lastPlacement.delete(shapeIdx); + } + unplace(oriented, startX, startY); + } + } + } + } + + return false; + } + + return solve(0); +} + +let fitCount = 0; +for (const region of regions) { + if (canFit(region.width, region.height, region.counts)) { + fitCount++; + } +} + +console.timeEnd("part1"); +console.log(fitCount); + + +// Part Two + +console.time("part2"); + +console.timeEnd("part2"); +console.log("Free star!"); + +// functions