-
-
Save AlaskanShade/5c46b96a4b1f08cdb6568c15b6e86341 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public override string Solve(string input, bool part2 = false, bool isTest = false) | |
{ | |
// Parse the tiles | |
var tiles = Helpers.GetLineGroups(input).Select(g => new Tile(g)).ToArray(); | |
// The image is a square, so calculate how many tiles wide/high | |
var gridSize = (int)Math.Sqrt(tiles.Length); | |
// Find the corners | |
var corners = tiles.Where(t => t.Matches(tiles) == 2).ToList(); | |
// We can also find the edges and middle, but it isn't needed | |
//var edges = tiles.Where(t => t.Matches(tiles) == 3); | |
//var middle = tiles.Where(t => t.Matches(tiles) == 4); | |
// For part 1, multiply the corner IDs | |
if (!part2) | |
return corners.Aggregate(1L, (i, v) => i * v.ID).ToString(); | |
// Generate a lookup by side values so we don't have to regenerate them every time | |
var sides = new Dictionary<int, List<Tile>>( | |
tiles.SelectMany(t => t.Sides.Select(s => new { s, t })) | |
.GroupBy(s => s.s) | |
.Where(s => s.Count() > 1) | |
.Select(s => new KeyValuePair<int, List<Tile>>(s.Key, s.Select(i => i.t).ToList())) | |
); | |
// Assemble the pieces starting with the top left corner | |
var assembled = new Tile[gridSize, gridSize]; | |
//Console.WriteLine("{0} {1}", topLeft.GetSide(Tile.SideName.Left), topLeft.GetSide(Tile.SideName.Right)); | |
for (int c = 0; c < gridSize; c++) | |
for (int r = 0; r < gridSize; r++) | |
{ | |
Tile nextTile; | |
if (c == 0 && r == 0) // Find the corner and orient | |
{ | |
nextTile = corners.First(); | |
while (!sides.ContainsKey(nextTile.GetSide(Tile.SideName.Right)) || !sides.ContainsKey(nextTile.GetSide(Tile.SideName.Bottom))) | |
nextTile.Rotate(); | |
} | |
else if (r == 0) // top row: find the tile that fits to the right of the last one | |
{ | |
var lastRight = assembled[r, c - 1].GetSide(Tile.SideName.Right); | |
nextTile = sides[lastRight].Single(e => !e.IsPlaced); | |
// Rotate until the right side is on the left | |
while (nextTile.GetSide(Tile.SideName.Left) != lastRight && nextTile.GetSide(Tile.SideName.LeftReverse) != lastRight) | |
nextTile.Rotate(); | |
// Flip if necessary | |
if (nextTile.GetSide(Tile.SideName.Left) != lastRight) | |
nextTile.FlipVertically(); | |
} | |
else // find the tile that fits the one above | |
{ | |
var lastBottom = assembled[r - 1, c].GetSide(Tile.SideName.Bottom); | |
nextTile = sides[lastBottom].Single(e => !e.IsPlaced); | |
while (nextTile.GetSide(Tile.SideName.Top) != lastBottom && nextTile.GetSide(Tile.SideName.TopReverse) != lastBottom) | |
nextTile.Rotate(); | |
if (nextTile.GetSide(Tile.SideName.Top) != lastBottom) | |
nextTile.FlipHorizontally(); | |
} | |
assembled[r, c] = nextTile; | |
nextTile.IsPlaced = true; | |
} | |
// Extract the final image | |
var grid = new char[96, 96]; | |
for (int c = 0; c < gridSize; c++) | |
for (int i = 1; i < 9; i++) | |
for (int r = 0; r < gridSize; r++) | |
for (int j = 1; j < 9; j++) | |
grid[c * 8 + i - 1, r * 8 + j - 1] = assembled[r, c].Data[j, i]; | |
// Count the total water, including potential monsters | |
var waterCount = grid.Cast<char>().Count(c => c == '#'); | |
// Go through each orientation and see if we find monsters | |
for (int i = 0; i < 8; i++) | |
{ | |
var count = CountMonsters(grid); | |
// Spotted! Subtract 15 for each monster from the total | |
if (count > 0) return (waterCount - count * 15).ToString(); | |
// After the first 4 rotations, do a flip | |
if (i == 4) grid = Helpers.FlipH(grid); | |
else grid = Helpers.RotateCW(grid); | |
} | |
throw new NotFoundException(); | |
} | |
private int CountMonsters(char[,] input) | |
{ | |
var cnt = 0; | |
for (int c = 0; c < input.GetLength(1) - 20; c++) | |
for (int r = 0; r < input.GetLength(0) - 3; r++) | |
if (HasMonster(input, r, c)) cnt++; | |
return cnt; | |
} | |
private bool HasMonster(char[,] grid, int r, int c) | |
{ | |
var monster = Helpers.GetCharArray(@"..................#. | |
#....##....##....### | |
.#..#..#..#..#..#..."); | |
for (int c2 = 0; c2 < 20; c2++) | |
for (int r2 = 0; r2 < 3; r2++) | |
if (monster[c2, r2] == '#' && grid[c + c2, r + r2] != '#') return false; | |
return true; | |
} | |
[DebuggerDisplay("{ID}")] | |
private class Tile | |
{ | |
public enum SideName { Top, TopReverse, Left, LeftReverse, Bottom, BottomReverse, Right, RightReverse } | |
public int ID { get; private set; } | |
public char[,] Data { get; private set; } | |
public bool IsPlaced { get; set; } | |
public IEnumerable<int> Sides => Enum.GetValues(typeof(SideName)).Cast<SideName>().Select(n => GetSide(n)); | |
public Tile(string[] lines) | |
{ | |
ID = int.Parse(lines[0].Substring(5).TrimEnd(':')); | |
Data = Helpers.GetCharArray(String.Join(Environment.NewLine, lines.Skip(1).ToArray())); | |
} | |
public int GetSide(SideName name) | |
{ | |
switch (name) | |
{ | |
case SideName.Top: | |
return ParseNumber(new String(Enumerable.Range(0, 10).Select(r => Data[0, r]).ToArray())); | |
case SideName.TopReverse: | |
return ParseNumber(new String(Enumerable.Range(0, 10).Select(r => Data[0, r]).Reverse().ToArray())); | |
case SideName.Left: | |
return ParseNumber(new String(Enumerable.Range(0, 10).Select(r => Data[r, 0]).ToArray())); | |
case SideName.LeftReverse: | |
return ParseNumber(new String(Enumerable.Range(0, 10).Select(r => Data[r, 0]).Reverse().ToArray())); | |
case SideName.Bottom: | |
return ParseNumber(new String(Enumerable.Range(0, 10).Select(r => Data[9, r]).ToArray())); | |
case SideName.BottomReverse: | |
return ParseNumber(new String(Enumerable.Range(0, 10).Select(r => Data[9, r]).Reverse().ToArray())); | |
case SideName.Right: | |
return ParseNumber(new String(Enumerable.Range(0, 10).Select(r => Data[r, 9]).ToArray())); | |
case SideName.RightReverse: | |
return ParseNumber(new String(Enumerable.Range(0, 10).Select(r => Data[r, 9]).Reverse().ToArray())); | |
} | |
return -1; | |
} | |
public void Rotate() | |
{ | |
Data = Helpers.RotateCW(Data); | |
} | |
public void FlipHorizontally() | |
{ | |
Data = Helpers.FlipH(Data); | |
} | |
public void FlipVertically() | |
{ | |
Data = Helpers.FlipV(Data); | |
} | |
public int Matches(IEnumerable<Tile> tiles) | |
{ | |
return tiles.Count(t => t.ID != ID && t.Sides.Any(s => Sides.Contains(s))); | |
} | |
private int ParseNumber(string text) | |
{ | |
return Convert.ToInt32(text.Replace('#', '1').Replace('.', '0'), 2); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment