Never being one to pass up $5.00, I thought I'd give it a try in summer 2007....
And here is the solution (white lines demark piece boundaries):
As a 40-something physicist who grew up programming in procedural languages such as FORTRAN, I was one of those "self-taught" programmers who, together with collaborators in large experiments, generated towering monsters of code that would teeter just at the edge of non-maintainability and inflexibility. Then came along object-oriented practices and languages like C++, and things changed quickly. Despite the initial protests of us old-timers, the improvements have been immense and the art of programming has gained a new elegance and power.
My younger colleagues live and breathe this new world, and smile indulgently at my less graceful moves. I'm sure this discussion will give them more cause to smile-- I can already see some degree of clunkiness in my implementation-- but still I had such fun solving this puzzle, and believe it instructive in some aspects of OO coding, that I thought it was worth writing up.
This document is in no way any kind of complete discussion, and if you don't know OO coding or C++ you might get little out of sections 3 and 4. (And if you know a lot about those things, you might get little more out of it than a smile.) But it has a nice movie at the end, so keep reading.
The idea is to solve the puzzle more or less the way a human would, though keeping strictly systematic, and of course with no intuitive leaps which make "by-hand" puzzle-solving so fun.
As with any C++ code, most of the focus is on the classes of objects. We have four classes:
As often with C++ codes, most of the work goes into the classes themselves, and the "procedural" part
is pretty small. It is sometimes amazing that the process of trying something, moving forward, backing up,
reconfiguring, undoing, etc, in a
Here is the relevant snip from SolvePuzzle.c.
vectorTheMoves; Move* FirstMove = new Move(AllPieces, TheBoard.NextAvailableBlackSpace().Column(), TheBoard.NextAvailableBlackSpace().Row()); TheMoves.push_back(FirstMove); int NumConfigsLookedAt=0; int NumSuccessMoves=0; while (TheMoves.size() < AllPieces.size()+1){ NumConfigsLookedAt++; cout << "Number of moves made is " << TheMoves.size() << endl; Move* CurrentMove = TheMoves[TheMoves.size()-1]; if (CurrentMove->Attempt(&TheBoard) == 0){ // 0 return value means successful attempt NumSuccessMoves++; cout << " Move worked -- "; if (TheMoves.size()==12){ cout << " and that's it!!\n"; break; } else { cout << " make the next move " << endl; } Move* NextMove = new Move(AllPieces, TheBoard.NextAvailableBlackSpace().Column(), TheBoard.NextAvailableBlackSpace().Row()); TheMoves.push_back(NextMove); } else{ cout << " Move failed -- delete it " << endl; delete CurrentMove; TheMoves.pop_back(); } assert(TheMoves.size()); // essentially demanding that at least ONE move must be working !!! } // done !!
For reference, here are the 12 pieces. Refer to this page for more about visualization and the Pieces.
Note that Pieces 4 and 12 are identical to each other, as per the puzzle specification.
![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() |
What we see below are the first Moves in the solution procedure. Note that only "successful" Moves are shown. (I.e. not ones which immediately produce orphans, or overlap Pieces or stick off Board's edge.) As you see, we work from lower-left corner of Board.
![]() 1: Move 1 instantiated P. 1 is placed in lower left space. This is the only rotational orientation which works for P. 1. | ![]() 2: Move 2 instantiated P. 2 (in the only orientation which works) takes the next open spot. | ![]() 3: Move 3 instantiated P. 6 next (note that pieces 3,4,5 couldn't fit). | ![]() 4: Move 4 instantiated P. 4 (P. 3 didn't work) | ![]() 5: Move 4 modified. Hey!! Isn't this just a repeat of the previous frame? No!- see note. | ![]() 6: Move 4 modified. P. 4 is discarded, and pieces 5, 7, 8, 9 didn't work. So P. 10 is next. | ![]() 7: Move 4 modified. All attemtps to continue fail. So P. 10 is discarded. P. 11 doesn't work, so go with P. 12. (See note) |
![]() 8: Move 4 modified. P. 12, but rotated. (See note) | ![]() 9: All possibilities exhausted, so Move 4 deleted, and Move 3 modiified: P. 7 replaces P. 6. (See note) | ![]() 10: Move 4 instantiated P. 4 | ![]() 11: Move 5 instantiated P. 10. | ![]() 12: Move 5 deleted, and Move 4 modiified: P. 4 rotated (See note) | ![]() 13: See if you can fill in the narrative from here...! | ![]() 14: |
![]() 15: | ![]() 16: | ![]() 17: | ![]() 18: | ![]() 19: | ![]() 20: | ![]() 21: |
OK, well it turns out that it takes 7550 Frames to tell the whole story, and that's best done in a movie, so click here to see that. You might want to hit "reload" when you get there, to start the movie (just an animated gif) at the beginning.