namespace Day21; public class Map { private readonly int _width; private readonly int _height; private readonly Tile[,] _map; private readonly Tile? _start; public Map(int width, int height, IEnumerable tiles) { _width = width; _height = height; _map = new Tile[width, height]; var i = 0; foreach (var t in tiles) { if (t.IsStart) { _start = t; } t.Position = new Vec(i % width, i / width); _map[t.Position.X, t.Position.Y] = t; i++; } if (_start == null) { throw new Exception("No start tile found"); } } public void Print(ISet? mark = null) { for (int y = 0; y < _height; y++) { for (int x = 0; x < _width; x++) { if (mark != null && mark.Contains(new Vec(x, y))) { Console.Write('O'); } else { Console.Write(_map[x, y]); } } Console.WriteLine(); } Console.WriteLine(); } public long ReachableTiles(long steps, Vec? start = null, bool print = false) { start ??= _start!.Position; var reachable = new HashSet(); reachable.Add(start.Value); for (int i = 0; i < steps; i++) { var next = new HashSet(); foreach (var p in reachable) { foreach (var neighbor in Surrounding(p).Where(n => At(n)?.IsWalkable ?? false)) { next.Add(neighbor); } } reachable = next; } if (print) { Print(reachable); } return reachable.Count; } public long ReachableLarge(long steps) { var fullMap = PassableTileCount(); Console.WriteLine($"Width: {_width} | Height: {_height}"); Console.WriteLine($"Odd Reachable: {fullMap.Odd} | Even Reachable: {fullMap.Even}"); var even = steps % 2 == 0; Console.WriteLine($"Step count is even: {even}"); var count = 0L; var fullChunks = ((steps - (_width / 2)) / _width) - 1; Console.WriteLine($"Full chunks after center: {fullChunks}"); // round UP to the nearest multiple of 2 count += Square(fullChunks / 2 * 2 + 1) * fullMap.Odd; // round DOWN to the nearest multiple of 2 count += Square((fullChunks + 1) / 2 * 2) * fullMap.Even; var majorSteps = (long)(_width + _width / 2 - 1); var minorSteps = (long)(_width / 2 - 1); var cornerT = ReachableTiles(_height - 1, new Vec(_width / 2, _height - 1)); count += cornerT; var cornerB = ReachableTiles(_height - 1, new Vec(_width / 2, 0)); count += cornerB; var cornerR = ReachableTiles(_width - 1, new Vec(_width - 1, _height / 2)); count += cornerR; var cornerL = ReachableTiles(_width - 1, new Vec(0, _height / 2)); count += cornerL; Console.WriteLine($"Corners: {cornerT} {cornerR} {cornerB} {cornerL}"); var largeBL = ReachableTiles(majorSteps, new Vec(0, _height - 1)); count += fullChunks * largeBL; var smallBL = ReachableTiles(minorSteps, new Vec(0, _height - 1)); count += (fullChunks + 1) * smallBL; var largeTL = ReachableTiles(majorSteps, new Vec(0, 0)); count += fullChunks * largeTL; var smallTL = ReachableTiles(minorSteps, new Vec(0, 0)); count += (fullChunks + 1) * smallTL; var largeTR = ReachableTiles(majorSteps, new Vec(_width - 1, 0)); count += fullChunks * largeTR; var smallTR = ReachableTiles(minorSteps, new Vec(_width - 1, 0)); count += (fullChunks + 1) * smallTR; var largeBR = ReachableTiles(majorSteps, new Vec(_width - 1, _height - 1)); count += fullChunks * largeBR; var smallBR = ReachableTiles(minorSteps, new Vec(_width - 1, _height - 1)); count += (fullChunks + 1) * smallBR; Console.WriteLine($"Small Diagonals: {smallTL} {smallTR} {smallBL} {smallBR}"); Console.WriteLine($"Large Diagonals: {largeTL} {largeTR} {largeBL} {largeBR}"); return count; } private IEnumerable Surrounding(Vec middle) { yield return middle.Add(ScreenSpace.Up); yield return middle.Add(ScreenSpace.Right); yield return middle.Add(ScreenSpace.Down); yield return middle.Add(ScreenSpace.Left); } private Tile? At(Vec p) { if (p.X < 0 || p.X >= _width || p.Y < 0 || p.Y >= _height) { return null; } return _map[p.X, p.Y]; } private long Square(long num) { return num * num; } private (long Even, long Odd) PassableTileCount() { // Note: there is ONE single unreachable tile that is not a rock, but surrounded by rocks, so we have to // "fill" to find the correct numbers return (ReachableTiles(_width + 1), ReachableTiles(_width)); } }