Article: LP_MineSweeper

Home Page


Consultancy

  • Service Vouchers
  • Escrow Service

Shop



Programming
  • Articles
  • Tools
  • Links

Search

 

Contact

 

Chess Puzzles




DWHS

Valid XHTML 1.0 Transitional
Valid CSS!
Mobile-friendly!

My take on the popular game.

category 'game', language C#, created 12-Feb-2022, version 1.0, by Luc Pattyn


License: The author hereby grants you a worldwide, non-exclusive license to use and redistribute the files and the source code in the article in any way you see fit, provided you keep the copyright notice in place; when code modifications are applied, the notice must reflect that. The author retains copyright to the article, you may not republish or otherwise make available the article, in whole or in part, without the prior written consent of the author.

Disclaimer: This work is provided “as is”, without any express or implied warranties or conditions or guarantees. You, the user, assume all risk in its use. In no event will the author be liable to you on any legal theory for any special, incidental, consequential, punitive or exemplary damages arising out of this license or the use of the work or otherwise.


LPMineSweeper_example1.gif

 

Everyone knows the MineSweeper game that has been part of Windows since the early days. Implementing such a game in C# isn't hard at all, and this article describes my take on it.

LP_MineSweeper has been implemented in WinForms (i.e. using the System.Windows.Forms namespace), using Visual Studio 2022 and .NET Framework 4.5

The program is available in executable and in source form.

 

Design

My design is based on three mayor classes:

  • a GameForm class (inheriting from Form) representing the main form where the game board gets displayed and the user interaction occurs;
  • a Game class that sets up a new game with random mines, tracks user progress, performs a floodfill when new safe squares are revealed, etc.
  • a Cell class (inheriting from Button) representing a single square of the board. It reacts to mouse clicks and changes its outlook accordingly. A cell is unaware of its neighbors, so when clicked it also tells the current game to calculate and update statistics, and to execute a floodfill as required.

How these classes interact

When a game is on, there is one GameForm instance, one Game instance, and as many Cells as are required to describe the game (the player can choose any of three built-in game sizes).

The GameForm has a menu structure, a "Start" button, and a container that will be used to display the game board. Interaction is handled by the typical "click handlers". When requested to do so, the GameForm will create a Game instance and then can interact with it, e.g. abort it when a player gives up.

A Game instance basically creates a two-dimensional array of Cell instances, and a Timer. The game does not know the form, it can not directly interact with the player. All it has is a few delegates that it should invoke when it wants to signal something to the player; examples include ShowStatus, ShowTime and PlaySound. In this way one could reuse the Game class unmodified with a very different front-end.

As it inherits from Button, each Cell has a button_Click handler to deal with the player clicking a square. The handler first discerns left and right mouse clicks, then adjusts the cell state (mostly properties IsRevealed and IsFlagged), modifies its appearance (by changing button properties such as BackColor and Text), and finally calls Game.UpdateGame to signal the player did something to a square. That is where the game performs a floodfill when required, recalculates the game state, and informs the outside world as appropriate.

The floodfill

When the player clicks a square that has no neighboring mines (a "safe" cell), the game is supposed to automatically reveal the adjacent cells, and to keep doing that for all safe cells it can reach from the current position. That is typically called a floodfill. Where there exist highly optimized (and complex) floodfill algorithms, we have chosen a very simple one: two nested loops scan the board from top to bottom, and from left to right, and just propagate the information they discover. In order to also support upwards and leftwards information flow, the double loop has been nested inside a while loop, which runs as long as new information is discovered.

It is important to note that the floodfill does not simulate a player really clicking a cell, as that would cause a chain of cell clicks and floodfills that would be nesting inside one another, causing a waste of memory and time, and possibly also avalanche problems. Instead of all that, the flooffill is launched at most once per actual cell click.

Miscellaneous points of interest

  • seven-segment displays: the GameForm uses two seven-segment displays that were provided by Dmitry Brant's article on CodeProject.
  • TableLayoutPanel: the container used to hold all the Cells on the GameForm is an instance of TableLayoutPanel; it works fine, except it is a bit hard to set it up for all cells having exactly the same size (see GameForm.setupTablePanel). This should have been made a lot easier!
    The TableLayoutPanel provides an optional ContextMenu, but that hides right-click events to the cells. The remedy is simple: my Cell class uses the MouseDown event instead of the MouseClick event!
  • form sizing and centering: the GameForm size depends on the chosen game size; GameForm.resizeForm holds the code to resize the form and to center it, taking into account the working area (i.e. the screen size excluding the task bar). On a multi-monitor system, this code should work as well, and it should keep the form on the same screen while doing so. This has not been tested lacking such hardware.
  • button appearance: buttons seem rather flat, there isn't much of a 3D effect anymore as Windows evolves. Cell.button_Paint has been added to give unrevealed cells a true 3D effect; note: the base painting is still handled by the original Button, only the 3D stuff has been added.
  • embedded resources: two PNG images and one WAV sound are stored as an embedded resource, and retrieved at run-time, reducing the number of files in the executable folder; the sound is played using the System.Media.SoundPlayer class.
  • user settings: the player's preferences are persisted with the Properties.Settings class (ref); the data is stored in an XML file in the C:\Users\username\AppData\Local\.... path.
  • logging:as usual I incorporated a logging mechanism, consisting of a log method that outputs messages to a ListBox. The visibility of the ListBox is controlled through the Options/Debug menu item.

A note on C# syntax

The C# language has been extended over the years. Some of the additions are just "syntactic sugar", meaning they don't offer new functionality, but just an easier way to code what is intended. I have used some of these, making my code need Visual Studio 2015 at least. Two examples:

  • the null_conditional operator hides a null test, i.e. game?.Stop(); is equivalent to if (game!=null) game.Stop();
  • anonymous function and lambda expression: game.ShowTime = (time) => ssTime.Value = time; is the single-line equivalent to game.ShowTime = setTime; assuming the following function were also present:
    void setTime(string time) {
    	ssTime.Value = time;
    }

Conclusion

This has been fun.

History

  • Version 1.0 (12-FEB-2022): Original version


Perceler

Copyright © 2012, Luc Pattyn

Last Modified 02-Sep-2013