
Introduction
Here is my latest diversion, a little game I wrote for amusement and to help
sharpen my programming skills. I hope you will enjoy playing it and perhaps even
pick up a programming tip or trick in the process. The goal of the game is to
draw a path through the invisible maze using the numbers along the side and top
as an indication of how many squares in that row or column have a line going
through them. Sometimes there will be more than one solution which satisfies the
row and column numbers, but you must draw the one solution that the computer is
"thinking of" to win!
Background
I first saw this type of game in one of my wife's puzzle magazines and
decided it looked like fun. Once I decided to write it as a computer game I
realized I didn't know a lot about how to generate a maze, so With Google's help
I found a description of an algorithm called depth-first search which is easy
enough to program and will generate a perfect maze every time. I started by
building a class to represent one square on the board, with methods and
properties to allow drawing line shapes, setting and removing walls, processing
mouse and keyboard input and so on.
I then went on to write the main form code which adds an array of these
pieces to itself to create the board layout as well as manage the game play,
scoring etc. The main form uses a nice custom control written by Mick Doherty
called TabControlEx[^], and I
hacked together a control based on StatusBar, so changing the Background color
on the Options tab changes all the controls (the exceptions being the spin
buttons of the NumericUpDown controls and the dropdown arrows on the ComboBox
controls).
Inside the code
If you want to see how you can persist application settings for your program
check out the ApplicationSettings class (in Settings.vb). In
particular look at the Players property, and the assignment of
mySerializer in the SaveAppSettings() method which
uses an overload specifying an array of additional object types to serialize:
mySerializer = New XmlSerializer( _
GetType(ApplicationSettings), New Type() {GetType(PlayerInfoCollection)})Note
also in method
frmMathMaze_Load that after the call to
LoadAppSettings() the
players variable does not merely
assign to
appsettings.Players. If we did this then any change to
players would show up in
appsettings.Players. But the
ApplicationSettings class looks to see if any of the properties
have changed to decide whether to save a new copy by checking the
appSettingsChanged variable. Look at the setter in the
Players property:
Set(ByVal Value As PlayerInfoCollection)
If Not Value.Equals(_players) Then
_players = Value
appSettingsChanged = True
End If
End SetClass
PlayerInfoCollection uses an overloaded
Equals function (as well as an overloaded
Contains) to
see if the new Value passed is equal to the current collection. So we need to
make a deep copy of
appsettings.Players when assigning to
players in the main form. The line:
players = New PlayerInfoCollection(.Players)
does
just that by invoking:
Public Sub New(ByVal source As PlayerInfoCollection)
For Each pi As PlayerInfo In source
Me.Add(New PlayerInfo(pi.PlayerName, pi.Wins, pi.Losses))
Next
End SubThis facet of reference type objects is often overlooked by
beginners and can be the cause of many headaches! When we do
A =
anObject and then
B = A, sometimes we want changes in
A to be reflected in
B, but when we don't we need to
resort to making a deep copy of
A.
Take a look over the PlayerInfoCollection class which inherits
CollectionBase to see how to create your own strongly typed
collection based on a class that you write (in this case it's the
PlayerInfo class). Also the Piece class may be of
interest with its custom painting code and overridden ProcessCmdKey
method to handle the tab and arrow keys.
The data binding of the players collection to two ComboBox
controls proved interesting; I kept getting strange errors when using the
'Remove' button on the Options tab until I realized the problem. I had to resort
to using a different BindingContext for each to avoid errors when updating
players even though I was releasing and refreshing the
DataSource of each ComboBox to update each. Otherwise I would see:
Specified argument was out of the range of valid
values.
Points of Interest
While writing Marh Maze I included some testing code so I could actually see
that the methods for generating the maze and then finding the solution path were
working correctly. I left this code in, see if you can find where on the main
form to click to activate it when playing the game!
References
Here are a couple of sites I found valuable info on while writing Math Maze.
MazeWorks - How
to Build a Maze[^]
Think Labyrinth: Maze
Algorithms[^]
History
29 Mar 2006 Initial release
1 Apr
2006 Fixed sizing issue
2 Apr
2006 Added 'Hint' button