Advent of Code - Day 8

Introduction

Day 8, I slept in a bit, so I didn't have time to write an article. I'll do that later.

Spoilers ahead of the challenges of Day 8

If you plan on doing the challenges, I would advise you to not read any further. The solutions are posted below, and I will explain how I solved them. After doing the challenges, you can come back and read this article to see how I solved them.

Code snippet

I will post the code snippet for the challenges of Day 8. I will explain how I solved them later.

"Time is an illusion. Santa is a time traveler. He's been to the future, and he knows how it ends. He's trying to prevent it from happening. That's why he's bringing you presents."
— Artificial Intelligence Santa

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;

class TreeGridSolver extends Command
{
  protected $signature = 'day:8-1';

  protected $description = 'Tree Grid Solver';

  private int $gridWidth = 0;

  private int $gridHeight = 0;

  /** @var array<int, array<int, int> $grid */
  private array $grid = [];

  public function handle()
  {
    $this->grid = collect(explode("\n", Storage::get('input-8.txt')))
      ->filter()
      ->map(function ($row) {
        $treesInRow = str_split($row);

        if ($this->gridWidth < count($treesInRow)) {
          $this->gridWidth = count($treesInRow);
        }

        return collect($treesInRow)->map(fn($tree) => (int)$tree)->toArray();
      });

    $this->gridHeight = $this->grid->count();

    $visibilityGrid = [];
    for ($x = 0; $x < $this->gridWidth; $x++) {
      for ($y = 0; $y < $this->gridHeight; $y++) {
        $visibilityGrid[$y][$x] = $this->getVisibility($x, $y);
      }
    }

    $totalVisibleTrees = collect($visibilityGrid)->sum(fn($row) => collect($row)->sum());
    $this->line('The total amount of visible trees is: ' . $totalVisibleTrees);

    $scenicScoreGrid = [];
    for ($x = 0; $x < $this->gridWidth; $x++) {
      for ($y = 0; $y < $this->gridHeight; $y++) {
        $scenicScoreGrid[$y . '-' . $x] = $this->getScenicScore($x, $y);
      }
    }

    $this->line('The highest scenic score is: ' . collect($scenicScoreGrid)->max());
  }

  private function getVisibility(int $x, int $y): int
  {
    if ($x === 0 || $y === 0 || $x === $this->gridWidth - 1 || $y === $this->gridHeight - 1) {
      return 1;
    }

    $highestTreeToTheNorth = $this->getHighestTreeInDirection('north', $x, $y);
    $highestTreeToTheSouth = $this->getHighestTreeInDirection('south', $x, $y);
    $highestTreeToTheWest = $this->getHighestTreeInDirection('west', $x, $y);
    $highestTreeToTheEast = $this->getHighestTreeInDirection('east', $x, $y);

    $treeHeight = $this->grid[$y][$x];

    if (
      $treeHeight > $highestTreeToTheNorth ||
      $treeHeight > $highestTreeToTheSouth ||
      $treeHeight > $highestTreeToTheWest ||
      $treeHeight > $highestTreeToTheEast
    ) {
      return 1;
    }

    return 0;
  }

  private function getHighestTreeInDirection(string $direction, int $x, int $y): int
  {
    switch ($direction) {
      case 'north':
        return $this->findHighestTree([$x], range(0, $y - 1));
      case 'south':
        return $this->findHighestTree([$x], range($y + 1, $this->gridHeight - 1));
      case 'west':
        return $this->findHighestTree(range(0, $x - 1), [$y]);
      case 'east':
        return $this->findHighestTree(range($x + 1, $this->gridWidth - 1), [$y]);
    }
  }

  private function findHighestTree(array $xRange, array $yRange)
  {
    $highestTree = 0;

    foreach ($xRange as $x) {
      foreach ($yRange as $y) {
        if ($this->grid[$y][$x] > $highestTree) {
          $highestTree = $this->grid[$y][$x];
        }
      }
    }

    return $highestTree;
  }

  private function getScenicScore(int $x, int $y)
  {
    $northScore = $this->getScenicScoreInDirection('north', $x, $y);
    $southScore = $this->getScenicScoreInDirection('south', $x, $y);
    $westScore = $this->getScenicScoreInDirection('west', $x, $y);
    $eastScore = $this->getScenicScoreInDirection('east', $x, $y);

    return $northScore * $southScore * $westScore * $eastScore;
  }

  private function getScenicScoreInDirection(string $direction, int $x, int $y)
  {
    switch ($direction) {
      case 'north':
        if ($y === 0) {
          return 0;
        }

        $treesToCheck = $this->getTreesInDirection('north', $x, $y);
        break;
      case 'south':
        if ($y === $this->gridHeight - 1) {
          return 0;
        }

        $treesToCheck = $this->getTreesInDirection('south', $x, $y);
        break;
      case 'west':
        if ($x === 0) {
          return 0;
        }

        $treesToCheck = $this->getTreesInDirection('west', $x, $y);
        break;
      case 'east':
        if ($x === $this->gridWidth - 1) {
          return 0;
        }

        $treesToCheck = $this->getTreesInDirection('east', $x, $y);
        break;
    }

    $visibleTrees = 0;
    $currentTreeHeight = $this->grid[$y][$x];

    foreach ($treesToCheck as $tree) {
      $visibleTrees++;

      if ($tree >= $currentTreeHeight) {
        break;
      }
    }

    return $visibleTrees;
  }

  private function getTreesInDirection(string $direction, int $x, int $y)
  {
    switch ($direction) {
      case 'north':
        return collect(range($y - 1, 0))->map(fn($y) => $this->grid[$y][$x])->toArray();
      case 'south':
        return collect(range($y + 1, $this->gridHeight - 1))->map(fn($y) => $this->grid[$y][$x])->toArray();
      case 'west':
        return collect(range($x - 1, 0))->map(fn($x) => $this->grid[$y][$x])->toArray();
      case 'east':
        return collect(range($x + 1, $this->gridWidth - 1))->map(fn($x) => $this->grid[$y][$x])->toArray();
    }
  }
}