diff --git a/src/AdventOfCode/Day16.cs b/src/AdventOfCode/Day16.cs
index 501082c..5f96ca0 100644
--- a/src/AdventOfCode/Day16.cs
+++ b/src/AdventOfCode/Day16.cs
@@ -1,4 +1,3 @@
-using System;
using System.Collections.Generic;
using System.Linq;
using AdventOfCode.Utilities;
@@ -10,142 +9,183 @@ namespace AdventOfCode
///
public class Day16
{
- public int Part1(string[] input)
- {
- char[,] grid = input.ToGrid();
+ public int Part1(string[] input) => BeamGrid.Parse(input).EnergisedTiles((0, 0), Bearing.East);
- return Simulate(grid, (0,0), Bearing.East);
- }
+ public int Part2(string[] input) => BeamGrid.Parse(input).MaxEnergisedTiles();
- public int Part2(string[] input)
+ ///
+ /// A grid of reflectors and splitters for light beams
+ ///
+ /// Grid
+ /// Grid width
+ /// Grid height
+ private record BeamGrid(char[,] Grid, int Width, int Height)
{
- char[,] grid = input.ToGrid();
-
- int max = int.MinValue;
-
- foreach (int x in Enumerable.Range(0, input[0].Length))
+ private readonly Queue<(Point2D Point, Bearing Bearing)> queue = new();
+ private readonly HashSet<(Point2D Point, Bearing Bearing)> seen = new();
+
+ ///
+ /// Parse the beam grid
+ ///
+ /// Input
+ /// Beam grid
+ public static BeamGrid Parse(IReadOnlyList input)
{
- int result = Simulate(grid, (x, 0), Bearing.South);
- max = Math.Max(max, result);
+ var grid = input.ToGrid();
+ int width = input[0].Length;
+ int height = input.Count;
- result = Simulate(grid, (x, input.Length - 1), Bearing.North);
- max = Math.Max(max, result);
+ return new BeamGrid(grid, width, height);
}
- foreach (int y in Enumerable.Range(0, input.Length))
+ ///
+ /// Maximum energised tiles that can be achieved from all starting edge points
+ ///
+ /// Max energised tiles
+ public int MaxEnergisedTiles() => this.WalkEdges().Max(edge => this.EnergisedTiles(edge.Point, edge.Bearing));
+
+ ///
+ /// Calculate how many tiles would be energised by the light beam starting at the given point on the given bearing
+ ///
+ /// Start point
+ /// Start bearing
+ /// Number of energised tiles
+ public int EnergisedTiles(Point2D startPoint, Bearing startBearing)
{
- int result = Simulate(grid, (0, y), Bearing.East);
- max = Math.Max(max, result);
-
- result = Simulate(grid, (input[0].Length - 1, y), Bearing.West);
- max = Math.Max(max, result);
- }
-
- return max;
- }
-
- private static int Simulate(char[,] grid, Point2D start, Bearing startBearing)
- {
- Queue<(Point2D Point, Bearing Bearing)> queue = new();
- queue.Enqueue((start, startBearing));
-
- HashSet<(Point2D Point, Bearing Bearing)> seen = new();
+ this.queue.Enqueue((startPoint, startBearing));
- while (queue.Count > 0)
- {
- (Point2D point, Bearing bearing) = queue.Dequeue();
+ while (this.queue.Count > 0)
+ {
+ (Point2D point, Bearing bearing) = this.queue.Dequeue();
- seen.Add((point, bearing));
+ this.seen.Add((point, bearing));
- char current = grid[point.Y, point.X];
+ char current = this.Grid[point.Y, point.X];
- foreach ((Point2D Point, Bearing Bearing) next in Next(current, point, bearing))
- {
- if (next.Point.X < 0 || next.Point.X >= grid.GetLength(1) || next.Point.Y < 0 || next.Point.Y >= grid.GetLength(0))
+ foreach (Bearing move in NextMoves(current, bearing))
{
- // fell off the edge
- continue;
- }
+ (Point2D Point, Bearing Bearing) next = (point.Move(move), move);
- if (!seen.Contains(next))
- {
- queue.Enqueue(next);
+ if (this.InBounds(next.Point) && !this.seen.Contains(next))
+ {
+ this.queue.Enqueue(next);
+ }
}
}
- }
- return seen.Select(pair => pair.Point).Distinct().Count();
- }
+ int energised = this.seen.Select(pair => pair.Point).Distinct().Count();
- private static IEnumerable<(Point2D Point, Bearing Bearing)> Next(char current, Point2D point, Bearing bearing)
- {
- switch (current)
- {
- case '.':
- yield return (point.Move(bearing), bearing);
- break;
- case '-':
- switch (bearing)
- {
- case Bearing.East or Bearing.West:
- yield return (point.Move(bearing), bearing);
- break;
- case Bearing.North or Bearing.South:
- yield return (point.Move(Bearing.East), Bearing.East);
- yield return (point.Move(Bearing.West), Bearing.West);
- break;
- }
+ // TODO: Retain the seen list between calls so we can memoize future calls without re-simulating
+ this.seen.Clear();
- break;
- case '|':
- switch (bearing)
- {
- case Bearing.East or Bearing.West:
- yield return (point.Move(Bearing.North), Bearing.North);
- yield return (point.Move(Bearing.South), Bearing.South);
- break;
- case Bearing.North or Bearing.South:
- yield return (point.Move(bearing), bearing);
- break;
- }
+ return energised;
+ }
- break;
- case '\\':
- switch (bearing)
- {
- case Bearing.North:
- yield return (point.Move(Bearing.West), Bearing.West);
- break;
- case Bearing.South:
- yield return (point.Move(Bearing.East), Bearing.East);
- break;
- case Bearing.East:
- yield return (point.Move(Bearing.South), Bearing.South);
- break;
- case Bearing.West:
- yield return (point.Move(Bearing.North), Bearing.North);
- break;
- }
+ ///
+ /// Calculate where the light beam will move next after hitting the current tile on the given bearing
+ ///
+ /// The beam can split if it hits a splitter, hence why more than one result can be returned
+ ///
+ /// Current tile
+ /// Current bearing
+ /// Bearing(s) of the light beam continuation(s)
+ private static IEnumerable NextMoves(char current, Bearing bearing)
+ {
+ switch (current)
+ {
+ case '.':
+ yield return bearing;
+ break;
+ case '-':
+ switch (bearing)
+ {
+ case Bearing.East or Bearing.West:
+ yield return bearing;
+ break;
+ case Bearing.North or Bearing.South:
+ yield return Bearing.East;
+ yield return Bearing.West;
+ break;
+ }
+
+ break;
+ case '|':
+ switch (bearing)
+ {
+ case Bearing.East or Bearing.West:
+ yield return Bearing.North;
+ yield return Bearing.South;
+ break;
+ case Bearing.North or Bearing.South:
+ yield return bearing;
+ break;
+ }
+
+ break;
+ case '\\':
+ switch (bearing)
+ {
+ case Bearing.North:
+ yield return Bearing.West;
+ break;
+ case Bearing.South:
+ yield return Bearing.East;
+ break;
+ case Bearing.East:
+ yield return Bearing.South;
+ break;
+ case Bearing.West:
+ yield return Bearing.North;
+ break;
+ }
+
+ break;
+ case '/':
+ switch (bearing)
+ {
+ case Bearing.North:
+ yield return Bearing.East;
+ break;
+ case Bearing.South:
+ yield return Bearing.West;
+ break;
+ case Bearing.East:
+ yield return Bearing.North;
+ break;
+ case Bearing.West:
+ yield return Bearing.South;
+ break;
+ }
+
+ break;
+ }
+ }
- break;
- case '/':
- switch (bearing)
- {
- case Bearing.North:
- yield return (point.Move(Bearing.East), Bearing.East);
- break;
- case Bearing.South:
- yield return (point.Move(Bearing.West), Bearing.West);
- break;
- case Bearing.East:
- yield return (point.Move(Bearing.North), Bearing.North);
- break;
- case Bearing.West:
- yield return (point.Move(Bearing.South), Bearing.South);
- break;
- }
+ ///
+ /// Check if the given point is in bounds
+ ///
+ /// Point
+ /// Point is in bounds
+ private bool InBounds(Point2D point) => point.X >= 0 && point.X < this.Width
+ && point.Y >= 0 && point.Y < this.Height;
+
+ ///
+ /// Get all the edge locations along with their inward pointing bearing
+ ///
+ /// Edges
+ private IEnumerable<(Point2D Point, Bearing Bearing)> WalkEdges()
+ {
+ foreach (int x in Enumerable.Range(0, this.Width))
+ {
+ yield return ((x, 0), Bearing.South);
+ yield return ((x, this.Height - 1), Bearing.North);
+ }
- break;
+ foreach (int y in Enumerable.Range(0, this.Height))
+ {
+ yield return ((0, y), Bearing.East);
+ yield return ((this.Width - 1, y), Bearing.West);
+ }
}
}
}
diff --git a/src/AdventOfCode/Utilities/GridUtilities.cs b/src/AdventOfCode/Utilities/GridUtilities.cs
index c50db3e..a195cba 100644
--- a/src/AdventOfCode/Utilities/GridUtilities.cs
+++ b/src/AdventOfCode/Utilities/GridUtilities.cs
@@ -23,12 +23,12 @@ private static readonly (int x, int y)[] Deltas =
///
/// Input
/// Char grid
- public static char[,] ToGrid(this string[] input)
+ public static char[,] ToGrid(this IReadOnlyList input)
{
// y,x remember, not x,y
- char[,] grid = new char[input.Length, input[0].Length];
+ char[,] grid = new char[input.Count, input[0].Length];
- for (int y = 0; y < input.Length; y++)
+ for (int y = 0; y < input.Count; y++)
{
for (int x = 0; x < input[y].Length; x++)
{