namespace aoc2020; /// /// Day 20: /// public sealed class Day20 : Day { private readonly List _allPermutations; private readonly List _topLefts; public Day20() : base(20, "Jurassic Jigsaw") { var lines = new List(); var tiles = new List(); var currentTileId = 0; foreach (var line in Input) if (line.StartsWith("Tile ")) { currentTileId = int.Parse(line.Split(' ', ':')[1]); } else if (line == "") { tiles.Add(new(currentTileId, lines.Select(l => l.ToCharArray()).ToArray())); lines.Clear(); } else { lines.Add(line); } if (lines.Any()) tiles.Add(new(currentTileId, lines.Select(l => l.ToCharArray()).ToArray())); _allPermutations = tiles.SelectMany(t => t.AllPermutations()).ToList(); _topLefts = _allPermutations .Where(t => !_allPermutations.Any(t2 => t.TileId != t2.TileId && t.LeftId == t2.RightId) && !_allPermutations.Any(t2 => t.TileId != t2.TileId && t.TopId == t2.BottomId)) .ToList(); } private int Roughness(Tile arg) { var seaMonster = new[] { " # ", "# ## ## ###", " # # # # # # " }.Select(s => s.ToArray()).ToArray(); const int seaMonsterWidth = 20; const int seaMonsterHeight = 3; const int seaMonsterTiles = 15; var placedTiles = new Dictionary<(int x, int y), Tile>(); bool NotPlaced(Tile tile) => placedTiles!.Values.All(t => t.TileId != tile.TileId); char Grid(int i, int j) => placedTiles![(i / 8, j / 8)].Pixels[j % 8 + 1][i % 8 + 1]; bool HasSeaMonster((int x, int y) location) { for (var j = 0; j < seaMonsterHeight; j++) for (var i = 0; i < seaMonsterWidth; i++) { if (seaMonster![j][i] == ' ') continue; if (Grid(location.x + i, location.y + j) != '#') return false; } return true; } placedTiles[(0, 0)] = arg; int x = 1, y = 0; while (true) { placedTiles.TryGetValue((x - 1, y), out var left); placedTiles.TryGetValue((x, y - 1), out var top); if (left == null && top == null) break; var firstMatch = _allPermutations .Where(t => (left is null || t.LeftId == left.RightId) && (top is null || t.TopId == top.BottomId)) .Where(NotPlaced) .FirstOrDefault(); if (firstMatch is not null) { placedTiles[(x, y)] = firstMatch; x++; } else { x = 0; y++; } } var gridWidth = placedTiles.Keys.Max(t => t.x) + 1; var gridHeight = placedTiles.Keys.Max(t => t.y) + 1; var seaMonsterCount = Enumerable.Range(0, gridWidth * 8 - seaMonsterWidth) .SelectMany(_ => Enumerable.Range(0, gridHeight * 8 - seaMonsterHeight), (i, j) => (i, j)) .Count(HasSeaMonster); if (seaMonsterCount == 0) return 0; var roughness = 0; for (var j = 0; j < gridHeight; j++) for (var i = 0; i < gridWidth; i++) if (Grid(x, y) == '#') roughness++; return roughness - seaMonsterCount * seaMonsterTiles; } public override string Part1() { return $"{_topLefts.Select(t => t.TileId).Distinct().Aggregate(1L, (acc, next) => acc * next)}"; } public override string Part2() { return $"{_topLefts.Select(Roughness).First(r => r > 0)}"; } private record Tile(int TileId, char[][] Pixels) { private const int Size = 10; internal int TopId => GetId(z => (z, 0)); internal int BottomId => GetId(z => (z, Size - 1)); internal int LeftId => GetId(z => (0, z)); internal int RightId => GetId(z => (Size - 1, z)); private int GetId(Func selector) { return Enumerable.Range(0, Size) .Select(selector) .Select((c, i) => (Pixels[c.x][c.y] == '#' ? 1 : 0) << i) .Aggregate(0, (acc, next) => acc | next); } private Tile RotateClockwise() { return Transform((x, y, newPixels) => newPixels[x][Size - 1 - y] = Pixels[y][x]); } private Tile Flip() { return Transform((x, y, newPixels) => newPixels[y][Size - 1 - x] = Pixels[y][x]); } private Tile Transform(Action transformFunc) { var newPixels = Enumerable.Repeat(false, Size).Select(_ => new char[Size]).ToArray(); for (var y = 0; y < Size; y++) for (var x = 0; x < Size; x++) transformFunc(x, y, newPixels); return new(TileId, newPixels); } internal IEnumerable AllPermutations() { var tile = this; for (var i = 1; i <= 8; i++) { yield return tile; if (i == 4) tile = Flip(); else if (i == 8) yield break; tile = tile.RotateClockwise(); } } public string Format() { return $"Tile {TileId}:\n{string.Join("\n", Pixels.Select(p => new string(p)))}"; } } }