public override string Solve(string input, bool part2) { foreach (var image in GetGroupedLines(input)) { images.Add(new CameraImage(GetLines(image))); } dimension = (int)Math.Floor(Math.Sqrt(images.Count)); Render(false); Console.WriteLine(); List <(int, int, char)> borders = new List <(int, int, char)>(); foreach (CameraImage image in images) { for (int i = 0; i < 2; ++i) { //get all borders for every image. since Right and bottom basically read reversed, we don't need to flip and only do a 180. //since the value for example for a border at the left would be the same for the same border at the top we can skip this rotation step image.RotateRight(); image.RotateRight(); borders.Add((image.ID, image.TopBorderNr, 'U')); borders.Add((image.ID, image.BottomBorderNr, 'D')); borders.Add((image.ID, image.LeftBorderNr, 'L')); borders.Add((image.ID, image.RightBorderNr, 'R')); } } //Group the images according to how they share a border Lookup <int, (int, int, char)> tileGroups = (Lookup <int, (int, int, char)>)borders.ToLookup(k => k.Item2, v => v); //Separate these groups for outer and inner connections //(outer connection being the image border that connects to no other image and is therefore part of the outer border of the final image) var borderConnections = tileGroups.Where(x => x.Count() == 1); var innerConnections = tileGroups.Where(x => x.Count() == 2); //all borders must be pairs. there can't be a border value, that matches more than 2 images. at least i would panic a bit in that case. var undetermined = tileGroups.Where(x => x.Count() > 2); if (undetermined.Count() > 0) { throw new InvalidOperationException("Some borders couldn't be assigned !!"); } //reorganize the inner connections, so that we don't have duplicates (bidirectional) anymore List <(int, int, int, string, int, string)> uniqueInnerConnections = new List <(int, int, int, string, int, string)>(); foreach (var connection in innerConnections) { (int id1, int border1, char side1) = connection.ElementAt(0); (int id2, int border2, char side2) = connection.ElementAt(1); var detected = uniqueInnerConnections.Where(x => (id1 == x.Item1 || id1 == x.Item2) && (id2 == x.Item1 || id2 == x.Item2) ).ToList(); string sideName = side1.ToString() + side2.ToString(); if (detected.Count() == 0) { uniqueInnerConnections.Add((id1, id2, connection.Key, sideName, 0, "")); } else { (int dId1, int dId2, int dBorder1, string dSide1, int dBorder2, string dSide2) = detected[0]; uniqueInnerConnections[uniqueInnerConnections.IndexOf(detected[0])] = (dId1, dId2, dBorder1, dSide1, connection.Key, sideName); } } //reorganize the outer borders, so that all possible border values are assigned to th same image Dictionary <int, List <(int, char)> > uniqueBorderConnections = new Dictionary <int, List <(int, char)> >(); foreach (var connection in borderConnections) { (int id1, int border1, char side) = connection.ElementAt(0); if (!uniqueBorderConnections.ContainsKey(id1)) { uniqueBorderConnections.Add(id1, new List <(int, char)>() { (connection.Key, side) }); } else { List <(int, char)> dBorders = uniqueBorderConnections[id1]; if (dBorders.Count >= 1 && dBorders.Count <= 4) { dBorders.Add((connection.Key, side)); } } } if (part2) { ArrangeImage(uniqueBorderConnections, uniqueInnerConnections); //Create an image from the rendered Map CameraImage map = new CameraImage(GetLines("Tile 2020:\r\n" + Render(true))); Console.WriteLine(); //Create an image from the monster string CameraImage monster = new CameraImage(GetLines("Tile 2020:\r\n..................#.\r\n#....##....##....###\r\n.#..#..#..#..#..#...")); int monsterWidth = monster.TopBorder.Count; int monsterHeight = monster.Image.Count; //Set up trying to find the monsters int currMonsterCnt = 0; int rotationCount = 0; bool flipped = false; Console.SetCursorPosition(0, 0); while (currMonsterCnt == 0) { currMonsterCnt = 0; //Going over every line in the map except the last three, because our monster is 3 rows tall for (int y = 0; y <= map.Image.Count - monsterHeight; ++y) { //Moving along this line leaving enough space for the width of our monster. for (int x = 0; x <= map.Image[0].Count - monsterWidth; ++x) { //go over the lines of the monster bool isMonster = true; for (int monsterY = 0; monsterY < monsterHeight; ++monsterY) { //Get the area of the current line of the map and the monster as number (bitmask) long mappedValue = Bitwise.GetBitMaskLong(map.Image[y + monsterY].GetRange(x, monsterWidth)); long monsterRow = Bitwise.GetBitMaskLong(monster.Image[monsterY]); //if the map contains the monster, the and join should output a perfect replica of the monster if ((mappedValue & monsterRow) != monsterRow) {//otherwise we don't have a complete monster isMonster = false; break; } } if (isMonster) { //When we got a full monster increase the counter and censor the area, the monster is located in //I was to lazy, to work out the pixels, that are part of the monster so we just draw in the area the monster takes up (monsterWidth x monsterHeight) for (int monsterY = 0; monsterY < monsterHeight; ++monsterY) { Console.SetCursorPosition(x, y + monsterY); //MArk the area with a letter to differentiate monsters. especially those that overlap Console.WriteLine("".PadLeft(monsterWidth, (char)(0x41 + currMonsterCnt))); } ++currMonsterCnt; } } } if (currMonsterCnt == 0) {//if we didn't find a single monster, rotate the image by 90 degrees map.RotateRight(); Render(true); if (++rotationCount >= 4) {//we did a full 360. Perhaps we need to flip if (flipped) { if (rotationCount >= 5)//we flipped one more time to be sure. this image couldn't be properly aligned. that shouldn't happen :( { throw new InvalidOperationException("LÖÖP"); } else { continue; } } map.Flip(); rotationCount = 0; flipped = true; } } } Console.CursorTop = map.Image.Count + 1; int roughness = map.ToString().Where(x => x == '#').Count(); roughness -= monster.ToString().Where(x => x == '#').Count() * currMonsterCnt; return("Monsters Found: " + currMonsterCnt + "\r\nRoughness: " + roughness); } else {//part 1 only needs the product of the ids of the corner tiles long product = 1; foreach (var connection in uniqueBorderConnections.Where(x => x.Value.Count == 4)) { product *= connection.Key; } return("Product of corner tile-IDs: " + product); } }