public static bool EdgePatternMatches(PieceData a, PieceData b, DIRECTION d) { switch (d) { case DIRECTION.NORTH: return(EdgePatternMatches(a.NorthPattern, b.SouthPattern)); case DIRECTION.SOUTH: return(EdgePatternMatches(a.SouthPattern, b.NorthPattern)); case DIRECTION.EAST: return(EdgePatternMatches(a.EastPattern, b.WestPattern)); case DIRECTION.WEST: return(EdgePatternMatches(a.WestPattern, b.EastPattern)); default: return(false); } }
// return all (sorted) similarity measures for a slot and a list of given pieces private List <Tuple <PieceData, long> > GetSimilarityMeasures(int x, int y, List <PieceData> pieces) { List <Tuple <PieceData, PieceData.DIRECTION> > neighbors = GetNeighbors(x, y); // check every single direction (if neighbor is present) List <Tuple <PieceData, long> > guestimationsNorth = (y > 0) ? PieceData.GuestimateMatchingPiece(this.solvedPuzzle[x, y - 1], pieces, PieceData.DIRECTION.SOUTH) : new List <Tuple <PieceData, long> >(); List <Tuple <PieceData, long> > guestimationsSouth = (y < PUZZLE_HEIGHT - 1) ? PieceData.GuestimateMatchingPiece(this.solvedPuzzle[x, y + 1], pieces, PieceData.DIRECTION.NORTH) : new List <Tuple <PieceData, long> >(); List <Tuple <PieceData, long> > guestimationsEast = (x < PUZZLE_WIDTH - 1) ? PieceData.GuestimateMatchingPiece(this.solvedPuzzle[x + 1, y], pieces, PieceData.DIRECTION.WEST) : new List <Tuple <PieceData, long> >(); List <Tuple <PieceData, long> > guestimationsWest = (x > 0) ? PieceData.GuestimateMatchingPiece(this.solvedPuzzle[x - 1, y], pieces, PieceData.DIRECTION.EAST) : new List <Tuple <PieceData, long> >(); List <Tuple <PieceData, long> > guestimatedAverage = new List <Tuple <PieceData, long> >(); foreach (PieceData piece in pieces) { int guestimateCount = 0; long guestimateSum = 0; if (guestimationsNorth.Count > 0) { guestimateCount++; guestimateSum += guestimationsNorth.FirstOrDefault(guess => guess.Item1.Equals(piece)).Item2; } if (guestimationsSouth.Count > 0) { guestimateCount++; guestimateSum += guestimationsSouth.FirstOrDefault(guess => guess.Item1.Equals(piece)).Item2; } if (guestimationsEast.Count > 0) { guestimateCount++; guestimateSum += guestimationsEast.FirstOrDefault(guess => guess.Item1.Equals(piece)).Item2; } if (guestimationsWest.Count > 0) { guestimateCount++; guestimateSum += guestimationsWest.FirstOrDefault(guess => guess.Item1.Equals(piece)).Item2; } guestimatedAverage.Add(new Tuple <PieceData, long>(piece, guestimateSum / guestimateCount)); } guestimatedAverage.Sort((a, b) => a.Item2.CompareTo(b.Item2)); return(guestimatedAverage); }
public static List <Tuple <PieceData, long> > GuestimateMatchingPiece(PieceData given, List <PieceData> possible, DIRECTION dir) { // no elements were passed if ((given == null) || (possible.Count == 0)) { Console.WriteLine("Problem while finding pieces!"); return(new List <Tuple <PieceData, long> >()); } // exactly one element left if (possible.Count == 1) { return(new List <Tuple <PieceData, long> >(new Tuple <PieceData, long>[] { new Tuple <PieceData, long>(possible[0], 0) })); } // more than one element left List <Tuple <PieceData, long> > differences = new List <Tuple <PieceData, long> >(); foreach (PieceData piece in possible) { long diff = 0; switch (dir) { case DIRECTION.NORTH: for (int x = 2; x < DATA_WIDTH - 2; x++) { for (int givenY = 0; givenY <= 4; givenY++) { if (given.GetImagePixel(x, givenY).Sum != 765) { for (int pieceY = 0; pieceY <= 4; pieceY++) { if (piece.GetImagePixel(x, DATA_HEIGHT - pieceY - 1).Sum != 765) { diff += ColorTupleRGB.GetQuadraticDifference(given.GetImagePixel(x, givenY), piece.GetImagePixel(x, DATA_HEIGHT - pieceY - 1)); break; } } break; } } } break; case DIRECTION.SOUTH: for (int x = 2; x < DATA_WIDTH - 2; x++) { for (int givenY = 0; givenY <= 4; givenY++) { if (given.GetImagePixel(x, DATA_HEIGHT - givenY - 1).Sum != 765) { for (int pieceY = 0; pieceY <= 4; pieceY++) { if (piece.GetImagePixel(x, pieceY).Sum != 765) { diff += ColorTupleRGB.GetQuadraticDifference(given.GetImagePixel(x, DATA_HEIGHT - givenY - 1), piece.GetImagePixel(x, pieceY)); break; } } break; } } } break; case DIRECTION.EAST: for (int y = 2; y < DATA_HEIGHT - 2; y++) { for (int givenX = 0; givenX <= 4; givenX++) { if (given.GetImagePixel(DATA_WIDTH - givenX - 1, y).Sum != 765) { for (int pieceX = 0; pieceX <= 4; pieceX++) { if (piece.GetImagePixel(pieceX, y).Sum != 765) { diff += ColorTupleRGB.GetQuadraticDifference(given.GetImagePixel(DATA_HEIGHT - givenX - 1, y), piece.GetImagePixel(pieceX, y)); break; } } break; } } } break; case DIRECTION.WEST: for (int y = 2; y < DATA_HEIGHT - 2; y++) { for (int givenX = 0; givenX <= 4; givenX++) { if (given.GetImagePixel(givenX, y).Sum != 765) { for (int pieceX = 0; pieceX <= 4; pieceX++) { if (piece.GetImagePixel(DATA_WIDTH - pieceX - 1, y).Sum != 765) { diff += ColorTupleRGB.GetQuadraticDifference(given.GetImagePixel(givenX, y), piece.GetImagePixel(DATA_HEIGHT - pieceX - 1, y)); break; } } break; } } } break; } differences.Add(new Tuple <PieceData, long>(piece, diff)); } Console.WriteLine("Guestimate from " + possible.Count + " items: " + string.Join(" , ", differences.Select(x => x.Item2 + ":" + x.Item1.Filename))); return(differences); }
// return all empty slots that have at least one neighbor and all the (unused) pieces that physically fit into that slot private List <Tuple <int, int, List <PieceData> > > GetNextPossibleStepsAndChoiceCount() { List <Tuple <int, int, List <PieceData> > > retVal = new List <Tuple <int, int, List <PieceData> > >(); for (int y = 0; y < PUZZLE_HEIGHT; y++) { for (int x = 0; x < PUZZLE_WIDTH; x++) { if (this.solvedPuzzle[x, y] == null) { List <Tuple <PieceData, PieceData.DIRECTION> > neighbors = GetNeighbors(x, y); if (neighbors.Count > 0) { IEnumerable <PieceData> unusedPieces = this.pieceData.Where(piece => !piece.AlreadyUsed); IEnumerable <PieceData> unusedPiecesMatchingN = unusedPieces.Where(piece => !neighbors.Any(nei => nei.Item2 == PieceData.DIRECTION.NORTH) || PieceData.EdgePatternMatches(this.solvedPuzzle[x, y - 1], piece, PieceData.DIRECTION.SOUTH)); IEnumerable <PieceData> unusedPiecesMatchingNS = unusedPiecesMatchingN.Where(piece => !neighbors.Any(nei => nei.Item2 == PieceData.DIRECTION.SOUTH) || PieceData.EdgePatternMatches(this.solvedPuzzle[x, y + 1], piece, PieceData.DIRECTION.NORTH)); IEnumerable <PieceData> unusedPiecesMatchingNSE = unusedPiecesMatchingNS.Where(piece => !neighbors.Any(nei => nei.Item2 == PieceData.DIRECTION.EAST) || PieceData.EdgePatternMatches(this.solvedPuzzle[x + 1, y], piece, PieceData.DIRECTION.WEST)); IEnumerable <PieceData> unusedPiecesMatchingNSEW = unusedPiecesMatchingNSE.Where(piece => !neighbors.Any(nei => nei.Item2 == PieceData.DIRECTION.WEST) || PieceData.EdgePatternMatches(this.solvedPuzzle[x - 1, y], piece, PieceData.DIRECTION.EAST)); retVal.Add(new Tuple <int, int, List <PieceData> >(x, y, unusedPiecesMatchingNSEW.ToList())); } } } } return(retVal); }
private void Form1_Load(object sender, EventArgs e) { // try to find xml meta data file if (File.Exists(DATA_FILE)) { using (TextReader reader = new StreamReader(DATA_FILE)) { this.pieceData = this.xmlSerializer.Deserialize(reader) as HashSet <PieceData>; } } // if it does not exist, create it if (this.pieceData == null) { this.pieceData = new HashSet <PieceData>(); foreach (string fileLocation in Directory.GetFiles(PIECE_DIRECTORY)) { this.pieceData.Add(new PieceData(fileLocation, new Bitmap(fileLocation))); } using (TextWriter writer = new StreamWriter(DATA_FILE)) { this.xmlSerializer.Serialize(writer, this.pieceData); } } return; // put the corners in place this.solvedPuzzle[0, 0] = this.pieceData.FirstOrDefault(x => x.IsBorderNorth && x.IsBorderWest); this.solvedPuzzle[0, 0].AlreadyUsed = true; this.solvedPuzzle[PUZZLE_WIDTH - 1, 0] = this.pieceData.FirstOrDefault(x => x.IsBorderNorth && x.IsBorderEast); this.solvedPuzzle[PUZZLE_WIDTH - 1, 0].AlreadyUsed = true; this.solvedPuzzle[0, PUZZLE_HEIGHT - 1] = this.pieceData.FirstOrDefault(x => x.IsBorderSouth && x.IsBorderWest); this.solvedPuzzle[0, PUZZLE_HEIGHT - 1].AlreadyUsed = true; this.solvedPuzzle[PUZZLE_WIDTH - 1, PUZZLE_HEIGHT - 1] = this.pieceData.FirstOrDefault(x => x.IsBorderSouth && x.IsBorderEast); this.solvedPuzzle[PUZZLE_WIDTH - 1, PUZZLE_HEIGHT - 1].AlreadyUsed = true; // check that we found all corners if ((this.solvedPuzzle[0, 0] == null) || (this.solvedPuzzle[PUZZLE_WIDTH - 1, 0] == null) || (this.solvedPuzzle[0, PUZZLE_HEIGHT - 1] == null) || (this.solvedPuzzle[PUZZLE_WIDTH - 1, PUZZLE_HEIGHT - 1] == null)) { txtBoxLog.AppendText("Could not find all corners." + Environment.NewLine); return; } else { txtBoxLog.AppendText("Found all corners." + Environment.NewLine); } // try building northern border (-1 because of index, -1 for both corners) for (int borderX = 0; borderX <= PUZZLE_WIDTH - 3; borderX++) { IEnumerable <PieceData> northPieces = this.pieceData.Where(x => x.IsBorderNorth); IEnumerable <PieceData> northAndUnusedPieces = northPieces.Where(x => !x.AlreadyUsed); IEnumerable <PieceData> northAndUnusedAndMatchingPieces = northAndUnusedPieces.Where(x => PieceData.EdgePatternMatches(this.solvedPuzzle[borderX, 0], x, PieceData.DIRECTION.EAST)); Console.WriteLine("North:" + northPieces.Count() + " NorthUnused:" + northAndUnusedPieces.Count() + " NorthUnusedMatching:" + northAndUnusedAndMatchingPieces.Count()); if (northAndUnusedAndMatchingPieces.Count() == 0) { txtBoxLog.AppendText("Could not finish northern border at position #" + borderX + "." + Environment.NewLine); break; } List <Tuple <PieceData, long> > guestimatedSimilarities = PieceData.GuestimateMatchingPiece(this.solvedPuzzle[borderX, 0], northAndUnusedAndMatchingPieces.ToList(), PieceData.DIRECTION.EAST); if (guestimatedSimilarities.Count > 0) { guestimatedSimilarities.Sort((x, y) => x.Item2.CompareTo(y.Item2)); this.solvedPuzzle[borderX + 1, 0] = guestimatedSimilarities[0].Item1; this.solvedPuzzle[borderX + 1, 0].AlreadyUsed = true; Console.WriteLine("Found piece for " + (borderX + 1) + ":0"); } else { txtBoxLog.AppendText("Could NOT find a matching piece for " + (borderX + 1) + ":0" + Environment.NewLine); } } // try building southern border (-1 because of index, -1 for both corners) for (int borderX = 0; borderX <= PUZZLE_WIDTH - 3; borderX++) { IEnumerable <PieceData> southPieces = this.pieceData.Where(x => x.IsBorderSouth); IEnumerable <PieceData> southAndUnusedPieces = southPieces.Where(x => !x.AlreadyUsed); IEnumerable <PieceData> southAndUnusedAndMatchingPieces = southAndUnusedPieces.Where(x => PieceData.EdgePatternMatches(this.solvedPuzzle[borderX, PUZZLE_HEIGHT - 1], x, PieceData.DIRECTION.EAST)); Console.WriteLine("South:" + southPieces.Count() + " SouthUnused:" + southAndUnusedPieces.Count() + " SouthUnusedMatching:" + southAndUnusedAndMatchingPieces.Count()); if (southAndUnusedAndMatchingPieces.Count() == 0) { txtBoxLog.AppendText("Could not finish southern border at position #" + borderX + "." + Environment.NewLine); break; } List <Tuple <PieceData, long> > guestimatedSimilarities = PieceData.GuestimateMatchingPiece(this.solvedPuzzle[borderX, PUZZLE_HEIGHT - 1], southAndUnusedAndMatchingPieces.ToList(), PieceData.DIRECTION.EAST); if (guestimatedSimilarities.Count > 0) { guestimatedSimilarities.Sort((x, y) => x.Item2.CompareTo(y.Item2)); this.solvedPuzzle[borderX + 1, PUZZLE_HEIGHT - 1] = guestimatedSimilarities[0].Item1; this.solvedPuzzle[borderX + 1, PUZZLE_HEIGHT - 1].AlreadyUsed = true; Console.WriteLine("Found piece for " + (borderX + 1) + ":" + (PUZZLE_HEIGHT - 1)); } else { txtBoxLog.AppendText("Could NOT find a matching piece for " + (borderX + 1) + ":" + (PUZZLE_HEIGHT - 1) + Environment.NewLine); } } // try building eastern border (-1 because of index, -1 for both corners) for (int borderY = 0; borderY <= PUZZLE_HEIGHT - 3; borderY++) { IEnumerable <PieceData> easternPieces = this.pieceData.Where(x => x.IsBorderEast); IEnumerable <PieceData> easternAndUnusedPieces = easternPieces.Where(x => !x.AlreadyUsed); IEnumerable <PieceData> easternAndUnusedAndMatchingPieces = easternAndUnusedPieces.Where(x => PieceData.EdgePatternMatches(this.solvedPuzzle[PUZZLE_WIDTH - 1, borderY], x, PieceData.DIRECTION.SOUTH)); Console.WriteLine("Eastern:" + easternPieces.Count() + " EasternUnused:" + easternAndUnusedPieces.Count() + " EasternUnusedMatching:" + easternAndUnusedAndMatchingPieces.Count()); if (easternAndUnusedAndMatchingPieces.Count() == 0) { txtBoxLog.AppendText("Could not finish eastern border at position #" + borderY + "." + Environment.NewLine); break; } List <Tuple <PieceData, long> > guestimatedSimilarities = PieceData.GuestimateMatchingPiece(this.solvedPuzzle[PUZZLE_WIDTH - 1, borderY], easternAndUnusedAndMatchingPieces.ToList(), PieceData.DIRECTION.SOUTH); if (guestimatedSimilarities.Count > 0) { guestimatedSimilarities.Sort((x, y) => x.Item2.CompareTo(y.Item2)); this.solvedPuzzle[PUZZLE_WIDTH - 1, borderY + 1] = guestimatedSimilarities[0].Item1; this.solvedPuzzle[PUZZLE_WIDTH - 1, borderY + 1].AlreadyUsed = true; Console.WriteLine("Found piece for " + (PUZZLE_WIDTH - 1) + ":" + (borderY + 1)); } else { txtBoxLog.AppendText("Could NOT find a matching piece for " + (PUZZLE_WIDTH - 1) + ":" + (borderY + 1) + Environment.NewLine); } } // test List <Tuple <int, int, List <PieceData> > > nextPossibleSteps = GetNextPossibleStepsAndChoiceCount(); while (nextPossibleSteps.Count > 0) { Console.WriteLine("Pieces left: " + this.pieceData.Count(x => !x.AlreadyUsed)); if (nextPossibleSteps.Any(x => x.Item3.Count == 0)) { txtBoxLog.AppendText("f****d up" + Environment.NewLine); break; } if (nextPossibleSteps.Any(x => x.Item3.Count == 1)) { Tuple <int, int, List <PieceData> > uniqueSolution = nextPossibleSteps.FirstOrDefault(x => x.Item3.Count == 1); this.solvedPuzzle[uniqueSolution.Item1, uniqueSolution.Item2] = uniqueSolution.Item3[0]; this.solvedPuzzle[uniqueSolution.Item1, uniqueSolution.Item2].AlreadyUsed = true; txtBoxLog.AppendText("Found a unique piece! :-D" + Environment.NewLine); nextPossibleSteps = GetNextPossibleStepsAndChoiceCount(); continue; } txtBoxLog.AppendText("Found " + nextPossibleSteps.Count() + " next possible steps." + Environment.NewLine); nextPossibleSteps.Sort((x, y) => x.Item3.Count.CompareTo(y.Item3.Count)); txtBoxLog.AppendText("In order of choice count: " + string.Join(" , ", nextPossibleSteps.Select(x => x.Item1 + ":" + x.Item2 + "#" + x.Item3.Count)) + Environment.NewLine); // measure all promising possibilities (<10 choices) List <Tuple <int, int, List <Tuple <PieceData, long> > > > measuredPossibilities = new List <Tuple <int, int, List <Tuple <PieceData, long> > > >(); foreach (Tuple <int, int, List <PieceData> > possibility in nextPossibleSteps.Where(x => x.Item3.Count < 10)) { txtBoxLog.AppendText(possibility.Item1 + ":" + possibility.Item2 + Environment.NewLine); measuredPossibilities.Add(new Tuple <int, int, List <Tuple <PieceData, long> > >(possibility.Item1, possibility.Item2, GetSimilarityMeasures(possibility.Item1, possibility.Item2, possibility.Item3))); foreach (Tuple <PieceData, long> item in measuredPossibilities.Last().Item3) { txtBoxLog.AppendText(item.Item1.Filename + " ~" + item.Item2 + Environment.NewLine); } } txtBoxLog.AppendText("Sorted:" + Environment.NewLine); // use the possibility with the biggest relative difference between measurement #1 and #2 measuredPossibilities.Sort((x, y) => ((double)y.Item3[1].Item2 / y.Item3[0].Item2).CompareTo((double)x.Item3[1].Item2 / x.Item3[0].Item2)); foreach (Tuple <int, int, List <Tuple <PieceData, long> > > item in measuredPossibilities) { txtBoxLog.AppendText(item.Item1 + ":" + item.Item2 + Environment.NewLine); } this.solvedPuzzle[measuredPossibilities[0].Item1, measuredPossibilities[0].Item2] = measuredPossibilities[0].Item3[0].Item1; this.solvedPuzzle[measuredPossibilities[0].Item1, measuredPossibilities[0].Item2].AlreadyUsed = true; nextPossibleSteps = GetNextPossibleStepsAndChoiceCount(); } //// build image row by row (there should be less conflicts due to the two edges that have to match up) //bool problem = false; //for (int pieceY = 1; pieceY < PUZZLE_HEIGHT; pieceY++) { // for (int pieceX = PUZZLE_WIDTH - 2; pieceX >= 0; pieceX--) { // IEnumerable<PieceData> unusedPieces = this.pieceData.Where(x => !x.AlreadyUsed); // IEnumerable<PieceData> unusedPiecesMatchingNorth = unusedPieces.Where(x => PieceData.EdgePatternMatches(this.solvedPuzzle[ pieceX, pieceY - 1 ], x, PieceData.DIRECTION.SOUTH)); // IEnumerable<PieceData> unusedPiecesMatchingNorthAndEast = unusedPiecesMatchingNorth.Where(x => PieceData.EdgePatternMatches(this.solvedPuzzle[ pieceX + 1, pieceY ], x, PieceData.DIRECTION.WEST)); // Console.WriteLine("Unused:" + unusedPieces.Count() + " UnusedMatchingNorth:" + unusedPiecesMatchingNorth.Count() + " UnusedMatchingNorthAndEast:" + unusedPiecesMatchingNorthAndEast.Count()); // if (unusedPiecesMatchingNorthAndEast.Count() == 0) { // Console.WriteLine("Could not finish row #" + pieceY + " at position #" + pieceX + "."); // problem = true; // break; // } // List<Tuple<PieceData, long>> guestimatedSimilaritiesWest = PieceData.GuestimateMatchingPiece(this.solvedPuzzle[ pieceX + 1, pieceY ], unusedPiecesMatchingNorthAndEast.ToList(), PieceData.DIRECTION.WEST); // List<Tuple<PieceData, long>> guestimatedSimilaritiesSouth = PieceData.GuestimateMatchingPiece(this.solvedPuzzle[ pieceX, pieceY - 1 ], unusedPiecesMatchingNorthAndEast.ToList(), PieceData.DIRECTION.SOUTH); // List<Tuple<PieceData, long>> guestimatedAverage = guestimatedSimilaritiesWest.Select(x => new Tuple<PieceData, long>(x.Item1, (x.Item2 + guestimatedSimilaritiesSouth.FirstOrDefault(y => y.Item1.Filename.Equals(x.Item1.Filename)).Item2) / 2)).ToList(); // if (guestimatedAverage.Count > 0) { // guestimatedAverage.Sort((x, y) => x.Item2.CompareTo(y.Item2)); // this.solvedPuzzle[ pieceX, pieceY ] = guestimatedAverage[ 0 ].Item1; // this.solvedPuzzle[ pieceX, pieceY ].AlreadyUsed = true; // Console.WriteLine("Found piece for " + pieceX + ":" + pieceY); // } // else { // txtBoxLog.AppendText("Could NOT find a matching piece for " + pieceX + ":" + pieceY + Environment.NewLine); // } // } // if (problem) { // break; // } //} // draw image and show Bitmap output = new Bitmap(2 + PUZZLE_WIDTH * (PieceData.DATA_WIDTH - 4) + 2, 2 + PUZZLE_HEIGHT * (PieceData.DATA_HEIGHT - 4) + 2); for (int solvedY = 0; solvedY < PUZZLE_HEIGHT; solvedY++) { for (int solvedX = 0; solvedX < PUZZLE_WIDTH; solvedX++) { if (this.solvedPuzzle[solvedX, solvedY] == null) { continue; } for (int dataY = 0; dataY < PieceData.DATA_HEIGHT; dataY++) { for (int dataX = 0; dataX < PieceData.DATA_WIDTH; dataX++) { PieceData.ColorTupleRGB foo = this.solvedPuzzle[solvedX, solvedY].GetImagePixel(dataX, dataY); if ((foo.R != 255) && (foo.G != 255) && (foo.B != 255)) { output.SetPixel((solvedX * (PieceData.DATA_WIDTH - 4) + 2) + (dataX - 2), (solvedY * (PieceData.DATA_HEIGHT - 4) + 2) + (dataY - 2), Color.FromArgb(foo.R, foo.G, foo.B)); } } } } } pictureBox1.Image = output; }