Developing Minesweeper like it's 1999 (or fun with VB6)
Minesweeper was one of the first games I played on a computer (after Solitaire ). I wouldn’t say that I’m very good at it, but that doesn’t discourage me from wanting to implement it. For some added nostalgia, let’s make it with tooling from more than 20 years ago!
Minesweeper for dummies
In Minesweeper, the mines are scattered throughout a minefield, which is divided into squares, which may or may not contain a mine. According to Wikipedia article on Minesweeper calls them board and cells. The player left-clicks a square to uncover it.
If it contained a mine, the game is over. If not, the cell will now contain a number with a number of adjacent mines (on the sides and diagonally) or it will stay blank, if there are no adjacent mines. Also, if the cell turned out blank, all neighboring tiles will be automatically uncovered.
The player is supposed to flag mines with right click (displayed as X in my version), deducing whether the square will contain a mine from the neighboring numbers.
I’ve chosen Visual Basic 6 on Windows XP. I’ve never done a game in VB6, but I did work on a Visual Basic .NET project for about a year and C# + Windows Forms for about eight years before that, so I hoped it would feel familiar.
VB.NET with Windows Forms was a replacement stack for the VB6 developers, and it was fun transitioning in the opposite direction, where some things work exactly as they do now, and others need a clever workaround.
On to programming
Separating the concerns a little bit, I’ve structured the project around visual controls and modules.
However, there’s a liberal mixing of responsibilities, so the Field and Square handles some bits of logic, as I tried to avoid having callbacks to re-trigger visual stuff if the core logic changed state.
VB6 crash course
Dim i As Integer declares a variable called
If blocks are terminated with
For blocks are terminated with
Next and the bounds are inclusive.
For i = 0 to 3 MsgBox(i) End For
The code block above will show 4 message boxes when ran, not 3, as in most other languages
Arrays are indexed with parentheses, such as
Arrays can be resized with
ReDim, if used as
ReDim Preserve, it will copy the contents of the old array to the resized one.
Procedures can be invoked with the
Call keyword, then the argument list needs to be enclosed in parentheses, such as
Call MineLookup.Add(key, Str(key)). If there are no arguments, the list can be omitted, for example
However, the procedures can also be invoked without the
Call keyword, such as
MineLookup.Add key, Str(key).
Procedures can be exited early by the
Exit Sub statement
Returning value 9 from a function MyFunction is done with a statement
MyFunction = 9.
Functions can be exited early using the
Exit Function statement, provided you set the return value.
Public MineLookup As New Collection creates a public Collection
that can be used as a look up table with a string key.
Colors can be expressed with a hexadecimal constants in the format of
&HRRGGBB&, for example
Overall, I didn’t fell terribly limited by Visual Basic, it was reasonably productive to use for a GUI-based app. ❤️
Generating the minefield
Internally the field is represented by an array of Integers, which states the presence of a mine (-1), or the amount of adjacent mines (neighbors).
The minefield array gets configured at the game start in terms of its size (rows x columns) and the amount of mines contained. Populating it happens in a simple
Do While .. Loop loop that picks a random coordinate and attempts. After the mines are placed, the adjacent mine count gets calculated, as we’ll refer to those numbers later.
UserControlField generates individual
UserControlSquare controls into a Control Array, which is a VB6 way to handle multiple controls of the same type under the same identifier.
Note: After multiple vain attempts I figured out how to remove controls from the item array (after resizing the game board): First the control needs to be set to invisible first, then remove it with
It was also an opportunity to use VB6
With scoping statement on the newly created control:
Load Squares(idx) With Squares(idx) .Move col * 405, Row * 405, 405, 405 If Minefield.HasMine(Row, col) Then .HasMine = True End If .MineNeighborCount = Minefield.MinesWithCount(Minefield.GetIndex(Row, col)) ... Call .ShowState End With
Given a 4x4 minefield with two mines at (2,2) and (2,3)
0111 02X2 02X2 0111
This was the early output, when the square controls just displayed the amount of neighbors / mine presence by default.
It was impractical to work with two-dimensional arrays or maps, so I’m using one-dimensional data structures everywhere and generate the index with a simple function:
Public Function GetIndex(Row As Integer, Column As Integer) As Integer GetIndex = Row * Configuration.columns + Column End Function
Handling input and output
All of the user interaction is done by clicking some kind of buttons or capturing mouse click events on the labels / control surface.
This is done by creating an event handler:
Sub Btn_MouseUp(Button As Integer, Shift As Integer, x As Single, y As Single)
and that either marks it as a potential mine or uncovers the square, maybe exploding it in the process!
We change the text on the controls (mine square, score board or the timer) by changing the text via the
Caption property, there’s no data binding of any kind going on.
Compared to modern frameworks this is a bit tedious, but you as the programmer know exactly when the UI is going to get updated.
As described earlier, designing a fixed-size window is a breeze with the VB6 Form designer, as you just need to drag and drop controls from a “palette” to the working area. If you prefer separation of concerns, you can design your own controls and reuse them just like the prebuilt ones.
Note: I had to close the control designer instance for ControlXYZ to make it available in another form/control designer instance.
Visual Basic form designer with some code
Adding menu and menu handlers
To edit the window menu (File->…), we can invoke the standalone menu editor with Ctrl+E, then edit the menu visually, and add separators where necessary. After the menu items are in place, they appear in the designer view,
There’s the usual array of debugging tools, such as breakpoints, watches and the Immediate Window, where we can evaluate expressions and execute statements, just like in in newer Visual Studios:
? MineLookup.Count 3 ? TypeName (MineLookup) Collection
Revealing adjacent blank squares with a flood fill
When the player clicks a tile that doesn’t have a bomb and has 0 adjacent mines, all neighboring tiles should be automatically uncovered. This is implemented by a textbook flood fill algorithm.
I have to admit that this bit took me much more time than I expected. I’ve read the Flood fill
Wikipedia page and re-wrote the algorithm in VB6. It took a while to figure out the boundaries of the function, as it turned out to be better called from the
UserControlField control, which can reach both the individual
Square controls and the
ModuleMinefield non-GUI module.
The 8-way flood fill pseudocode is:
# implicitly stack-based, recursive, eight-way flood-fill implementation goes as follows: Flood-fill (node): 1. If node is already visited, return. 2. Reveal the node 3-10. Perform Flood-fill one step to the neighbors of node (all 8 directions) 11. Return.
My VB6 implementation
has an added extra simplification, that the
Visited array could be persisted across flood fills in the same game, as it made no sense to uncover already uncovered fields.
Things I wished I knew about Visual Basic 6.0 before
Renaming a control doesn’t rename all references in the code, which is nasty compared to more modern IDEs.
I could not figure out a way to trigger flood fill from the child control, the syntax is actually:
Definitely set “Save changes” option in VB6 settings->Environment->When program starts, as VB6 IDE doesn’t save your code changes by default, and you’re bound to lose some work if you manage to cause a crash.
Using variant data type in collections is tricky, as collection.Item(1) also returned the first item in the collection, not only the one added with the key
File->Make tries to compile the code to native code and also stops and notifies the developer on a compile errror, so this is useful for discovering syntax errors before your runtime code encounters them.
This was a fun exercise, I will do something similar again soon!
Repository is available on GitHub: https://github.com/jborza/mines
And this time, you can download the binary from the GitHub release page .