CPPSweeper
I've rewritten the game model again - in C++. In a sense it's less fun because when translating to C you have to rewrite object-oriented code into procedural code like the older compilers do, but when translating to C++ it's mostly a syntax transformation. Anyway, I had a little trouble with reference vs pointers - I wanted to always use references for the sleeker style, but I couldn't get it compiled if I declare everything as reference...

Valgrind doesn't seem to like STL vector very much.
CSweeper
I've spent two days translating the game model code (package clustermines.core) to C. The C code tried to emulate Java as much as possible, with all structs stored on the heap and passed by reference, etc.

The game compiles and runs correctly on OS X. On Linux, it throws a segfault when trying to free memory at the end. Valgrind gives an error (it says two but the second is sorta explaining the first) which I don't really understand. The code it points to was copied from comp.lang.c FAQs, so I guess I'll just blame the Usenet...

==29046==
==29046== ERROR SUMMARY: 2 errors from 1 contexts (suppressed: 12 from 1)
==29046==
==29046== 2 errors in context 1 of 1:
==29046== Invalid write of size 4
==29046== at 0x80489A7: malloc2dArray (Sweeper.c:84)
==29046== by 0x804884C: newSweeper (Sweeper.c:58)
==29046== by 0x80484EE: main (main.c:39)
==29046== Address 0x1B90A0B0 is 0 bytes after a block of size 32 alloc'd
==29046== at 0x1B904A80: malloc (vg_replace_malloc.c:131)
==29046== by 0x804894D: malloc2dArray (Sweeper.c:80)
==29046== by 0x804884C: newSweeper (Sweeper.c:58)
==29046== by 0x80484EE: main (main.c:39)
--29046--
--29046-- supp: 12 Ugly strchr error in /lib/ld-2.3.3.so
==29046==
==29046== IN SUMMARY: 2 errors from 1 contexts (suppressed: 12 from 1)
==29046==
==29046== malloc/free: in use at exit: 0 bytes in 0 blocks.
==29046== malloc/free: 245 allocs, 245 frees, 6492 bytes allocated.
==29046==
==29046== No malloc'd blocks -- no leaks are possible.

Source is on svn: http://clustermines.svn.sourceforge.net/viewvc/clustermines/CSweeper/
0.7.3 - Solver
I've fixed solver for the multi-mines mode. It works as before now, doing only one-square analysis. The rest might be a little hard, though.
0.7.0 - Multi-mines mode
It's done! Try it out!

Like I thought before, it wasn't too difficult to finish up the coding. Making all those icons probably took more time with my über clumsy graphic design skillz. Anyway, this means the only primary goal left is a working solver. I wonder if I could write one that can solve a multi-mine game too.
Applet
I've got the applet version of the game up and running now. Some changes are made so that you can't play large and huge boards in applet mode, since it'll break the layout of web page. The page has been tested on Firefox, IE 6 and Safari.

I wonder why the necessary code for correctly displaying an applet is still such a mess after so many years...
0.6.1 - SweeperHistory bug fixed
Suppose we are playing this board. All squares are hidden, except for square a being revealed, and square F flagged. Squares a-j do not contain mines.

+---------------+
| * f g h * * * |
| * i b c d * * |
| * j e a F * * |
+---------------+

When we chord on a, chord() generates a queue like this:

b c d e

because b is an empty square, reveal() appends all its neighbours to the queue - after e. This gives a revealing sequence of

b c d e f g h i j

When we are replaying, we are revealing squares in this order. The problem is that we'll have a chain reaction on b before revealing c, which gives a totally different revealing sequence:

b f g h i c j e d

This difference is significant because SweeperHistory tries to replay a game by imitating recorded moves from the original game on a new Sweeper instance. There are two ways of recording moves, one is to call SweeperHistory whenever a method in Sweeper is called. The other is to let SweeperHistory to listen to Sweeper, so it will catch all the Sweeper's changes on its own. The second one is obviously more elegant, except for one problem: SweeperListener doesn't tell if a square is the target of a reveal() call, or if it's revealed by chain action, or by a chording on a neighbour.

Suppose we reveal an empty square, and its eight neighbours by chain reaction, we'd have recorded 9 changes. We could naively replay all 9 steps, no problem, the program won't crash. But we'd have to click on the replay button for 9 times to get what was done with one click in the original game. A smart program ought to do better.

How do we tell if some square is revealed by a chain reaction, in order to skip it in step()? A minesweeper game is deterministic after the board is generated. Revealing the same square on two different Sweeper instances that have the same states should generate the same revealing sequence. So, SweeperHistory could check the events sent by Sweeper after step() is called. The sequence of these events should be identical to those that generated the history in the first place, provided that SweeperHistory has perfectly emulated the recorded game so far. If an event SweeperHistory received matches the next move in its record, it knows that this event was caused by the last move it made, and that the event should be skipped.

The original Sweeper used a non-recursive breadth-first searching utilising a queue. There were two reveal() methods in Sweeper. Reveal(Square) was an interface which used reveal(LinkedList) for the real job. Reveal(Square) was used by both SweeperHistory and the game GUI, SweeperPanel, hence it could be emulated by SweeperHistory correctly. Another method, chord(), is used only by SweeperPanel on the other hand. Chord() used reveal(LinkedList) directly and would give a revealing sequence that would not be emulated with reveal(Square), in the way illustrated at the beginning. When SweeperHistory saw a different move than the one in the record, it thinks it's a new move which would have been made by the player to interrupt replaying, and it complied.

In essence, the information of the difference between chord() and reveal(Square) was lost the way record was kept in SweeperHistory.

One way to fix this is to let chord() use reveal(Square) instead, so there won't be any difference between these two methods. The other is to tell SweeperHistory that Sweeper is doing a chord(), which requires a change to SweeperListener. Again, the first one is more elegant, and is chosen.