108 lines
3.0 KiB
JavaScript
108 lines
3.0 KiB
JavaScript
|
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}`);
|