After learning Python, I decided to expand my programming languages skillset, and moved to master Java. With some prior programming experience, there wasn't much of a learning curve. I chose this first project, because it implemented many of the fundamental Java principles, as well as giving me some experience with classes. Not to mention, my frustration when trying to do a puzzle would always end up with a half-done puzzle after I gave up. Now, I can solve any puzzle, no matter the size, within seconds. 😁
In standard jigsaw puzzles, each piece has four sides, and each side is either a tab (indenting outwards), blank (indenting inwards) or a straight edge. To make puzzles fit in only one way, each tab can only interlock with one other blank. One way to represent this in a program is to use a class where each side is represented by an integer. Tabs can be represented by positive numbers, edges by negative numbers, and sides by zero. If a tab and knot fit, they should have the same value (but different sign).
Each piece has the following attributes: name, which is a string that uniquely identifies each piece and side1, side2, side3, side4, all of which are integers representing the four sides of the jigsaw puzzle as integers. (Assume the orientation of each piece has side 1 on the left, side 2 on the top, side 3 on the right, side 4 on the bottom.)
In the case that a corresponding puzzle piece is not found, the program will continue until there are no more suitable pieces to complete the puzzle, and terminate itself.
One of the biggest challenges I faced while working on my jigsaw puzzle solver was figuring out a general formula that could be used for any puzzle size. I wanted to refrain from brute-forcing it, and instead attempt to develop a systematic approach. The general idea was to solve row by row, from left to right. To identify the top-left piece, side 1 (left) and side 2 (top) would have to be straight edges. After this piece, I focused on finding the piece to its right, which would need side 1 (left) to match side 3 (right) of the top-left piece. I continued this process until I reached the right edge of the puzzle, where the last piece in the row would have a straight edge on side 3 (right).
My next struggle was how to figure out the remaining rows and when to stop. I wasn't sure how to transition to the next row. I realised I could solve this by adding all the ordered pieces to a list as I placed them. After I finished the first row, I would know how many pieces each row has, and I could determine the number of rows by dividing the total number of pieces by the number of pieces in a row. Since the top-left piece would be at the first index in the ordered list, I would have to find a corresponding side 2 (top) of a piece to side 4 (bottom) of the piece above it, which I would know since it would be ordered in my ordered list.
Since I knew how many rows the puzzle has, I implemented a while loop to continue until there would be no more rows to solve. Another solution I had, to know when the puzzle was completed, was to check if the piece at the end of the row had a straight edge on side 3 and side 4. this would indicate that it is the bottom right piece, and it is the last piece of the puzzle.
Through this project, I learned the importance of breaking down complex problems into manageable, logical steps. Initially, I struggled with conceptualizing a general method to solve puzzles of varying sizes without brute-forcing each piece together. By focusing on specific patterns, like identifying the top-left corner and building row by row, I developed a structured approach that eliminated redundancy and increased the clarity of my solution. This process reinforced the value of clear, methodical problem-solving in programming.
I also learned about leveraging data structures to simplify complex tasks. By maintaining an ordered list of puzzle pieces, I could track the arrangement and use it to calculate row lengths and determine relationships between adjacent pieces. This experience emphasized how thoughtful design choices in code, such as using meaningful data organization and loops, can guide logic and make the program adaptable to different scenarios.
To showcase my learning, you can view my implementation of a jigsaw puzzle solver and find more information about this project on my Github.