I’ve been thinking lately of making my Battleships program more robust by having a complete autosolver and a random puzzle creator. The first step was programming a step that would do the simple stuff:
- If a row has n remaining water cells and n remaining empty cells, all the remaining cells have to be water. Ditto for columns, ditto for ships.
- If a cell has a ship segment, then the neighboring four corners have to be water.
- Certain neighboring cells can be filled in based on clued ship segments.
These generally solve the easiest “Seaman” Battleships puzzles, and go a long way to solving the harder ones. The harder ones require an additional bit of programming through trial-and-error placement, which I’m still mulling around in my mind. The simplest bits, though, are rote enough that I added a command to the program to process it.
Since I was tooling around in the program, I decided to make a few other tweaks as well. One was adding an undo. It occurred to me that, because the data was of a different sort, undo and save weren’t interrelated in the same way as with Canfield. With Canfield, undo required recording the entire state of the card deck, including which piles everything was located on, so saving was a variation on that theme.
However, with Battleships, undo only requires recording the state of the grid; so long as I didn’t need to undo across loading a puzzle, the clues themselves don’t need recording. Save would require recording the state of the grid, the clues, and the source of the puzzle (so that if the saved file is later loaded and finished, that fact can be recorded). Since undo was simpler, I decided to allow for multiple undo steps.
First I created a list of arrays:
List<int[,]> alSnapshots = new List<int[,]>();</int[,]></int[,]>
I had intended to simply assign a new list to the current grid status before making any changes to the grid:
alSnapshots.Add(iCellStatus);
One thing I was reminded of quickly was that equating arrays results in a reference assignment, not a value assignment. That is, the code above results in a list populated with pointers to iCellStatus, so when it changes, each member of the list changes as well. That obviously wasn’t what I desired, so I had to get a bit more explicit:
private void TakeSnapshot() { int[,] iSnapShot = new int[GridSize, GridSize]; for (int x = 0; x < GridSize; x++) { for (int y = 0; y < GridSize; y++) { iSnapShot[x, y] = iCellStatus[x, y]; } } alSnapshots.Add(iSnapShot); if (alSnapshots.Count > 20) // Only save 20 steps { alSnapshots.RemoveAt(0); } }
I arbitrarily chose 20 as the max number of undoes; each snapshot doesn’t take up that much memory.
This illustrates how to create and use a List consisting of arrays (in this case, a two-dimensional integer array). I have an additional method to clear the array, when a new puzzle is loaded:
private void ClearSnapshots() { alSnapshots.Clear(); }
Allowing for redo would require a small modification to TakeSnapshot.
I also added a toolbar, using some free icons; the revised program is available on my website (edit 7/28/21: not anymore!).