import { readFileSync } from 'node:fs'; const input = readFileSync('input', 'utf-8'); // Parse grid const grid = input .split('\n') .filter(line => line) .map(line => line .split('') .filter(c => c) .map(height => parseInt(height)) ); const w = grid[0]?.length || 0; const h = grid.length; // Confirm grid is complete for (const row of grid) { if (row.length !== w) { throw new Error('Invalid grid row'); } } // Return an array with the remaining integers after start and up to end function rest(start, end) { const steps = Math.abs(start - end); const mult = start < end ? 1 : -1; return new Array(steps) .fill() .map((_, i) => start + (i + 1) * mult) } // Calculate the maximum value returned by getHeight when called with a range // of values from start (non-inclusive) to end function maxHeight(start, end, getHeight) { return rest(start, end).reduce( (max, pos) => Math.max(max, getHeight(pos)), 0 ); } // Create a map where each item represents the smallest of the next tallest // tree in all four directions const maxMap = grid.map((row, y) => row.map((height, x) => { const u = maxHeight(y, 0, (pos) => grid[pos][x]); const d = maxHeight(y, h - 1, (pos) => grid[pos][x]); const l = maxHeight(x, 0, (pos) => grid[y][pos]); const r = maxHeight(x, w - 1, (pos) => grid[y][pos]); // Return the smallest tree of the tallest trees in each direction return Math.min(u, r, d, l); })); // Create a map of visible trees const visibleTrees = grid.map((row, y) => row.map((height, x) => { // Edge case if (x === 0 || y === 0 || x === w - 1 || y === h - 1) { return true; } const obscuredByHeight = maxMap[y][x]; return height > obscuredByHeight; })); // Sum visible trees const totalVisibleTrees = visibleTrees .flat() .reduce((total, visible) => total + visible, 0); console.log(`The total number of trees visible is: ${totalVisibleTrees}`); // Calculate how many trees are visible based on heights returned by getHeight // for a range of values from start (non-inclusive) to end function countVisibleTrees(start, end, maxHeight, getHeight) { let count = 0; for (const pos of rest(start, end)) { count++; if (getHeight(pos) >= maxHeight) { break; } } return count; } // Calculate the scenic score for a tree function scenicScore(x, y) { const maxHeight = grid[y][x]; const u = countVisibleTrees(y, 0, maxHeight, (pos) => grid[pos][x]); const d = countVisibleTrees(y, h - 1, maxHeight, (pos) => grid[pos][x]); const l = countVisibleTrees(x, 0, maxHeight, (pos) => grid[y][pos]); const r = countVisibleTrees(x, w - 1, maxHeight, (pos) => grid[y][pos]); // Return the smallest tree of the tallest trees in each direction return u * r * d * l; } // Create a map of scenic scores for all trees const scenicMap = grid.map((row, y) => row.map((height, x) => scenicScore(x, y) )); const highestScenicScore = Math.max(...scenicMap.flat()); console.log(`The highest possible scenic score is: ${highestScenicScore}`);