Compare commits

...

6 Commits

Author SHA1 Message Date
Eric Wagoner
514227fefc Day twelve both parts 2025-12-13 18:13:39 -05:00
Eric Wagoner
cb5a3599fd day eleven both parts 2025-12-13 17:30:16 -05:00
Eric Wagoner
c7392f0453 day ten both parts 2025-12-10 18:29:24 -05:00
Eric Wagoner
f06e5b5f64 day nine both parts 2025-12-09 16:29:28 -05:00
Eric Wagoner
7bbedb47a5 day eight both parts 2025-12-09 00:30:06 -05:00
Eric Wagoner
42b2af4452 day seven both parts 2025-12-07 01:38:11 -05:00
7 changed files with 869 additions and 1 deletions

2
.gitignore vendored
View File

@@ -1,6 +1,6 @@
# Advent of Code input files # Advent of Code input files
input.txt input.txt
sample.txt sample*.txt
# ---> Node # ---> Node
# Logs # Logs

103
07/code.js Normal file
View File

@@ -0,0 +1,103 @@
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");
let newInput = JSON.parse(JSON.stringify(usedArray));
for (let i = 0; i < newInput.length; i++) {
newInput[i] = newInput[i].split("");
}
for (let i = 1; i < newInput.length; i++) {
for (let j = 0; j < newInput[i].length; j++) {
if ((newInput[i - 1][j] === '|' || newInput[i - 1][j] === 'S') && newInput[i][j] === '.') {
newInput[i][j] = '|';
}
if (newInput[i - 1][j] === '|' && newInput[i][j] === '^') {
if (j > 0 && newInput[i][j - 1] === '.') {
newInput[i][j - 1] = '|';
}
}
if (newInput[i - 1][j] === '|' && newInput[i][j] === '^') {
if (j < newInput[i].length - 1 && newInput[i][j + 1] === '.') {
newInput[i][j + 1] = '|';
}
}
}
}
let splits = 0;
for (let i = 1; i < newInput.length; i++) {
for (let j = 0; j < newInput[i].length; j++) {
if (newInput[i][j] === '^' && newInput[i - 1][j] === '|') {
splits++;
}
}
}
console.timeEnd("part1");
console.log(splits);
// Part Two
console.time("part2");
newInput = JSON.parse(JSON.stringify(usedArray));
for (let i = 0; i < newInput.length; i++) {
newInput[i] = newInput[i].split("");
}
for (let i = 1; i < newInput.length; i++) {
for (let j = 0; j < newInput[i].length; j++) {
if (newInput[i - 1][j] === 'S') {
newInput[i][j] = 1;
}
if (Number.parseInt(newInput[i - 1][j]) > 0 && Number.parseInt(newInput[i][j]) > 0) {
newInput[i][j] = newInput[i][j] + newInput[i - 1][j];
}
if ((Number.parseInt(newInput[i - 1][j]) > 0) && newInput[i][j] === '.') {
newInput[i][j] = newInput[i - 1][j];
}
if (Number.parseInt(newInput[i - 1][j]) > 0 && newInput[i][j] === '^') {
if (j > 0 && newInput[i][j - 1] === '.') {
newInput[i][j - 1] = newInput[i - 1][j];
} else if (j > 0 && Number.parseInt(newInput[i][j - 1]) > 0) {
newInput[i][j - 1] = Number.parseInt(newInput[i][j - 1]) + newInput[i - 1][j];
}
if (j < newInput[i].length - 1 && newInput[i][j + 1] === '.') {
newInput[i][j + 1] = newInput[i - 1][j];
} else if (j < newInput[i].length - 1 && Number.parseInt(newInput[i][j + 1]) > 0) {
newInput[i][j + 1] = Number.parseInt(newInput[i][j + 1]) + newInput[i - 1][j];
}
}
}
}
let timelines = 0;
const bottomRow = newInput[newInput.length - 1];
for (let i = 0; i < bottomRow.length; i++) {
if (Number.parseInt(bottomRow[i]) > 0) {
timelines += Number.parseInt(bottomRow[i]);
}
}
console.timeEnd("part2");
console.log(timelines);
// functions

120
08/code.js Normal file
View File

@@ -0,0 +1,120 @@
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");
let coordinatesArray = [];
for (const element of usedArray) {
const coordinates = element.split(",");
coordinatesArray.push({ x: Number.parseInt(coordinates[0]), y: Number.parseInt(coordinates[1]), z: Number.parseInt(coordinates[2]) });
}
let segments = [];
let parent = {};
for (let i = 0; i < coordinatesArray.length; i++) {
for (let j = i + 1; j < coordinatesArray.length; j++) {
const x2 = Math.pow(coordinatesArray[i].x - coordinatesArray[j].x, 2);
const y2 = Math.pow(coordinatesArray[i].y - coordinatesArray[j].y, 2);
const z2 = Math.pow(coordinatesArray[i].z - coordinatesArray[j].z, 2);
const distance = Math.sqrt(x2 + y2 + z2);
segments.push({ from: i, to: j, distance });
}
}
segments.sort((a, b) => a.distance - b.distance);
const shortest = segments.slice(0, sampleMode ? 10 : 1000);
for (const seg of shortest) {
union(seg.from, seg.to);
}
const circuits = {};
for (const seg of shortest) {
const root = find(seg.from);
circuits[root] = circuits[root] || new Set();
circuits[root].add(seg.from);
circuits[root].add(seg.to);
}
const circuitList = Object.values(circuits);
const sizes = circuitList.map(nodes => nodes.size).sort((a, b) => b - a);
const result = sizes[0] * sizes[1] * sizes[2];
console.timeEnd("part1");
console.log(result);
// Part Two
console.time("part2");
coordinatesArray = [];
for (const element of usedArray) {
const coordinates = element.split(",");
coordinatesArray.push({ x: Number.parseInt(coordinates[0]), y: Number.parseInt(coordinates[1]), z: Number.parseInt(coordinates[2]) });
}
segments = [];
parent = {};
for (let i = 0; i < coordinatesArray.length; i++) {
for (let j = i + 1; j < coordinatesArray.length; j++) {
const x2 = Math.pow(coordinatesArray[i].x - coordinatesArray[j].x, 2);
const y2 = Math.pow(coordinatesArray[i].y - coordinatesArray[j].y, 2);
const z2 = Math.pow(coordinatesArray[i].z - coordinatesArray[j].z, 2);
const distance = Math.sqrt(x2 + y2 + z2);
segments.push({ from: i, to: j, distance });
}
}
segments.sort((a, b) => a.distance - b.distance);
let circuitCount = coordinatesArray.length;
let lastSegment;
for (const seg of segments) {
const rootFrom = find(seg.from);
const rootTo = find(seg.to);
if (rootFrom !== rootTo) {
union(seg.from, seg.to);
circuitCount--;
lastSegment = seg;
if (circuitCount === 1) break;
}
}
const result2 = coordinatesArray[lastSegment.from].x * coordinatesArray[lastSegment.to].x;
console.timeEnd("part2");
console.log(result2);
// functions
function find(x) {
if (parent[x] === undefined) parent[x] = x;
if (parent[x] !== x) parent[x] = find(parent[x]);
return parent[x];
}
function union(a, b) {
const rootA = find(a);
const rootB = find(b);
if (rootA !== rootB) {
parent[rootA] = rootB;
}
}

151
09/code.js Normal file
View File

@@ -0,0 +1,151 @@
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");
let coordinatesArray = [];
for (const element of usedArray) {
const coordinates = element.split(",");
coordinatesArray.push({ x: Number.parseInt(coordinates[0]), y: Number.parseInt(coordinates[1]) });
}
let segments = [];
for (let i = 0; i < coordinatesArray.length; i++) {
for (let j = i + 1; j < coordinatesArray.length; j++) {
const dx = Math.abs(coordinatesArray[i].x - coordinatesArray[j].x) + 1;
const dy = Math.abs(coordinatesArray[i].y - coordinatesArray[j].y) + 1;
const area = dx * dy;
segments.push({ dx: dx, dy: dy, area: area });
}
}
segments.sort((a, b) => a.area > b.area ? -1 : 1);
console.timeEnd("part1");
console.log(segments[0].area);
// Part Two
console.time("part2");
// Only collect the RED tile coordinates (endpoints)
const xSet = new Set();
const ySet = new Set();
for (const tile of coordinatesArray) {
xSet.add(tile.x);
ySet.add(tile.y);
}
const xCoords = [...xSet].sort((a, b) => a - b);
const yCoords = [...ySet].sort((a, b) => a - b);
// Store horizontal and vertical line segments for boundary checking
const hLines = []; // {y, x1, x2}
const vLines = []; // {x, y1, y2}
for (let i = 0; i < coordinatesArray.length; i++) {
const curr = coordinatesArray[i];
const next = coordinatesArray[(i + 1) % coordinatesArray.length];
if (curr.x === next.x) {
vLines.push({ x: curr.x, y1: Math.min(curr.y, next.y), y2: Math.max(curr.y, next.y) });
} else {
hLines.push({ y: curr.y, x1: Math.min(curr.x, next.x), x2: Math.max(curr.x, next.x) });
}
}
// Check if a point is on the boundary
function isOnBoundary(x, y) {
for (const line of hLines) {
if (y === line.y && x >= line.x1 && x <= line.x2) return true;
}
for (const line of vLines) {
if (x === line.x && y >= line.y1 && y <= line.y2) return true;
}
return false;
}
// Check if a point is inside using ray casting (count vertical line crossings to the left)
function isInside(x, y) {
let crossings = 0;
for (const line of vLines) {
if (line.x < x && y >= line.y1 && y < line.y2) {
crossings++;
}
}
return crossings % 2 === 1;
}
// Check if a point is valid (on boundary or inside)
function isValid(x, y) {
return isOnBoundary(x, y) || isInside(x, y);
}
// Check if entire rectangle is valid
function isRectValid(x1, y1, x2, y2) {
// Check all corner and edge points at compressed coordinates within the rectangle
for (const y of yCoords) {
if (y < y1 || y > y2) continue;
for (const x of xCoords) {
if (x < x1 || x > x2) continue;
if (!isValid(x, y)) return false;
}
}
// Also check if any boundary line crosses through the rectangle's interior
// creating an "outside" region
for (const line of vLines) {
if (line.x > x1 && line.x < x2) {
// Vertical line inside rectangle - check if it creates a gap
if (line.y1 > y1 || line.y2 < y2) {
// Line doesn't span full height - might create invalid region
if (line.y1 > y1 && !isValid(line.x, y1)) return false;
if (line.y2 < y2 && !isValid(line.x, y2)) return false;
}
}
}
return true;
}
// Find largest valid rectangle with red corners
let maxArea = 0;
for (let i = 0; i < coordinatesArray.length; i++) {
for (let j = i + 1; j < coordinatesArray.length; j++) {
const r1 = coordinatesArray[i];
const r2 = coordinatesArray[j];
const x1 = Math.min(r1.x, r2.x);
const x2 = Math.max(r1.x, r2.x);
const y1 = Math.min(r1.y, r2.y);
const y2 = Math.max(r1.y, r2.y);
if (isRectValid(x1, y1, x2, y2)) {
const area = (x2 - x1 + 1) * (y2 - y1 + 1);
if (area > maxArea) maxArea = area;
}
}
}
console.timeEnd("part2");
console.log(maxArea);
// functions

208
10/code.js Normal file
View File

@@ -0,0 +1,208 @@
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");
let totalPresses = 0;
for (const element of usedArray) {
const lights = element.match(/\[([.#]+)\]/)[1];
const buttonMatches = [...element.matchAll(/\(([0-9,]+)\)/g)];
const numLights = lights.length;
// Target state
const target = [...lights].map(c => c === '#' ? 1 : 0);
// Parse buttons
const buttons = buttonMatches.map(match => {
const indices = match[1].split(',').map(Number);
const effect = new Array(numLights).fill(0);
for (const idx of indices) effect[idx] = 1;
return effect;
});
const numButtons = buttons.length;
let minPresses = Infinity;
for (let mask = 0; mask < (1 << numButtons); mask++) {
const state = new Array(numLights).fill(0);
let presses = 0;
for (let b = 0; b < numButtons; b++) {
if (mask & (1 << b)) {
presses++;
for (let i = 0; i < numLights; i++) state[i] ^= buttons[b][i];
}
}
if (state.every((v, i) => v === target[i]) && presses < minPresses) {
minPresses = presses;
}
}
totalPresses += minPresses;
}
console.timeEnd("part1");
console.log(totalPresses);
// Part Two
console.time("part2");
let totalPresses2 = 0;
for (const element of usedArray) {
const buttonMatches = [...element.matchAll(/\(([0-9,]+)\)/g)];
const joltageMatch = element.match(/\{([0-9,]+)\}/);
const targets = joltageMatch[1].split(',').map(Number);
const numCounters = targets.length;
const buttons = buttonMatches.map(match => {
const indices = match[1].split(',').map(Number);
const effect = new Array(numCounters).fill(0);
for (const idx of indices) {
if (idx < numCounters) effect[idx] = 1;
}
return effect;
});
const numButtons = buttons.length;
// Build augmented matrix [A | b] where A is transpose of buttons matrix
// Rows = counters, Cols = buttons + 1 (for target)
const matrix = [];
for (let c = 0; c < numCounters; c++) {
const row = [];
for (let b = 0; b < numButtons; b++) {
row.push(buttons[b][c]);
}
row.push(targets[c]);
matrix.push(row);
}
// Gaussian elimination (over rationals, tracking which columns are pivots)
const pivotCols = [];
let pivotRow = 0;
for (let col = 0; col < numButtons && pivotRow < numCounters; col++) {
// Find pivot
let maxRow = pivotRow;
for (let row = pivotRow + 1; row < numCounters; row++) {
if (Math.abs(matrix[row][col]) > Math.abs(matrix[maxRow][col])) {
maxRow = row;
}
}
if (matrix[maxRow][col] === 0) continue;
// Swap rows
[matrix[pivotRow], matrix[maxRow]] = [matrix[maxRow], matrix[pivotRow]];
// Eliminate
for (let row = 0; row < numCounters; row++) {
if (row !== pivotRow && matrix[row][col] !== 0) {
const factor = matrix[row][col] / matrix[pivotRow][col];
for (let c = col; c <= numButtons; c++) {
matrix[row][c] -= factor * matrix[pivotRow][c];
}
}
}
pivotCols.push(col);
pivotRow++;
}
// Free variables are buttons not in pivotCols
const freeVars = [];
for (let b = 0; b < numButtons; b++) {
if (!pivotCols.includes(b)) freeVars.push(b);
}
// Search over free variables with bounded range
const maxFreeVal = Math.max(...targets);
let bestPresses = Infinity;
function searchFree(freeIdx, freeVals) {
if (freeIdx === freeVars.length) {
// Compute pivot variables from free variables
const x = new Array(numButtons).fill(0);
for (let i = 0; i < freeVars.length; i++) {
x[freeVars[i]] = freeVals[i];
}
// Back-substitute to find pivot variables
for (let i = pivotCols.length - 1; i >= 0; i--) {
const col = pivotCols[i];
let val = matrix[i][numButtons]; // RHS
for (let c = col + 1; c < numButtons; c++) {
val -= matrix[i][c] * x[c];
}
val /= matrix[i][col];
x[col] = val;
}
// Check all non-negative integers
let valid = true;
let totalPresses = 0;
for (let b = 0; b < numButtons; b++) {
if (x[b] < -0.0001 || Math.abs(x[b] - Math.round(x[b])) > 0.0001) {
valid = false;
break;
}
x[b] = Math.round(x[b]);
totalPresses += x[b];
}
if (valid && totalPresses < bestPresses) {
// Verify solution
let correct = true;
for (let c = 0; c < numCounters; c++) {
let sum = 0;
for (let b = 0; b < numButtons; b++) {
sum += buttons[b][c] * x[b];
}
if (sum !== targets[c]) {
correct = false;
break;
}
}
if (correct) bestPresses = totalPresses;
}
return;
}
// Prune based on current sum
const currentSum = freeVals.reduce((a, b) => a + b, 0);
if (currentSum >= bestPresses) return;
const maxVal = Math.min(maxFreeVal, bestPresses - currentSum);
for (let v = 0; v <= maxVal; v++) {
searchFree(freeIdx + 1, [...freeVals, v]);
}
}
searchFree(0, []);
totalPresses2 += bestPresses;
}
console.timeEnd("part2");
console.log(totalPresses2);
// functions

89
11/code.js Normal file
View File

@@ -0,0 +1,89 @@
import { readFileSync } from 'node:fs';
let sampleMode = true;
const sampleArray1 = readFileSync('sample.txt').toString().split("\n");
const sampleArray2 = readFileSync('sample2.txt').toString().split("\n");
const inputArray = readFileSync('input.txt').toString().split("\n");
const usedArray1 = sampleMode ? sampleArray1 : inputArray;
const usedArray2 = sampleMode ? sampleArray2 : inputArray;
// Part One
console.time("part1");
// Parse the graph
const graph = new Map();
for (const element of usedArray1) {
const [node, targets] = element.split(': ');
graph.set(node, targets.split(' '));
}
// Count paths from 'you' to 'out' using memoization
const memo = new Map();
function countPaths(node) {
if (node === 'out') return 1n;
if (memo.has(node)) return memo.get(node);
if (!graph.has(node)) return 0n;
let total = 0n;
for (const next of graph.get(node)) {
total += countPaths(next);
}
memo.set(node, total);
return total;
}
const part1 = countPaths('you');
console.timeEnd("part1");
console.log(part1.toString());
// Part Two
console.time("part2");
// Re-parse graph for part 2 (may use different sample file)
const graph2 = new Map();
for (const element of usedArray2) {
const [node, targets] = element.split(': ');
graph2.set(node, targets.split(' '));
}
// Count paths between any two nodes using memoization
const memo2 = new Map();
function countPathsBetween(from, to) {
const key = `${from}->${to}`;
if (memo2.has(key)) return memo2.get(key);
if (from === to) return 1n;
if (!graph2.has(from)) return 0n;
let total = 0n;
for (const next of graph2.get(from)) {
total += countPathsBetween(next, to);
}
memo2.set(key, total);
return total;
}
// Paths from svr to out visiting both dac and fft:
// Either: svr -> dac -> fft -> out
// Or: svr -> fft -> dac -> out
const path1 = countPathsBetween('svr', 'dac') * countPathsBetween('dac', 'fft') * countPathsBetween('fft', 'out');
const path2 = countPathsBetween('svr', 'fft') * countPathsBetween('fft', 'dac') * countPathsBetween('dac', 'out');
const part2 = path1 + path2;
console.timeEnd("part2");
console.log(part2.toString());
// functions

197
12/code.js Normal file
View File

@@ -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