You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
107 lines
3.0 KiB
107 lines
3.0 KiB
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}`);
|
|
|