As a university teacher, I am teaching the "Windows programming" course this semester. I have chosen Charles Petzold's book "Programming Windows" as our textbook. However, since we have only 32 course hours, we are unable to cover so many sample programs in the book, so I decided to use two to three samples with a fair degree of sophistication, and to present each of them in several steps. While the first step is only a skeleton, each new step will introduce new features based on the previous step, until it grows into a complete application in the final step.
The ideal candidate for the first sample program, I think, is the WinMine (or MineSweeper) game. It illustrates the use of many fundamental Windows API functions, such as
SetWindowPos, etc., and the processing of many commonly used window messages, such as the
WM_PAINT message and the mouse messages. It is sophisticated enough to allow it to be a real application, while it is also simple enough not to overwhelm the student with a lot of new concepts to grasp. Moreover, as a well known game, it also makes understanding its source code much easier than other programs.
There are already many open source implementations of
WinMine, among them I chose the
WineMine project written by Joshua Thielen of the ReactOS team; it is very concise and clean. The package I finally delivered to my students, is the
WinMine4Edu project attached to this article.
WinMine4Edu is based on
WineMine but I have made many modifications so that it looks more like Microsoft
WinMine and I have rewritten the mouse message processing part, because I failed to understand this part of
WineMine and I feel my implementation might be a little neater.
Considerations When Writing the Code
I have dual purposes when I was adapting the code. I wish to make it a close clone of the original Microsoft
WinMine, but I must always bear in mind that the audience are students who have little prior knowledge in Windows programming. As a result of this consideration, I decided not to implement middle mouse button processing, as it is not strictly necessary for a complete game.
To simplify things a bit further. We also adopted a slightly different rule from Microsoft
WinMine when dealing with the
WM_RBUTTONUP message. Microsoft
WinMine allows flagging a mine when the game is in the
WAITING state, but we do not allow this; Microsoft
WinMine also allows negative number of remaining mines, and we do not allow that. Such an approach, I believe, completely makes sense, and it saves us the labor of writing code to show negative numbers in LED digits.
Architecture of the Code
There are three source C files to
WinMine4Edu: WinMine.c, MinePaint.c, and MineGame.c. WinMine.c contains
WinMain and the main window procedure, which includes command processing and other miscellaneous tasks. MinePaint.c contains the graphic portion, and MineGame.c implements mouse message processing.
As inherited from
Winemine, there is a global
struct variable "
board", which contains all preferences, settings, and current states of the game.
The main routines are the following:
void InitBoard(); void LoadBoard(); void SaveBoard(); void DestroyBoard();void CheckLevel(); void NewBoard(); void NewGame(); void TranslateMouseMsg(UINT* puMsg, WPARAM wParam);
void ProcessMouseMsg(UINT uMsg, LPARAM lParam);
void OnCommand(WPARAM wParam, LPARAM lParam);
void SetDifficulty(DIFFICULTY Difficulty); void DrawBackground(HDC hDC); void DrawMinesLeft(HDC hdc); void DrawFace(HDC hdc); void DrawTime(HDC hdc); void DrawGrid(HDC hdc); void DisplayFace(FACE_STATE faceState); void SetAndDispBoxState(int r, int c, BOX_STATE state); void DecMinesLeft(); void IncMinesLeft(); void ZeroMinesLeft(); void IncTime(); void LayMines(int row, int col); void Pt2RowCol(POINT pt, int *prow, int *pcol); void GameWon(); void GameLost(); void PressBox(int row, int col); void UnPressBox(); UINT CountMines(int row, int col); BOOL StepBox(int row, int col);
Points to Note
Let us begin with a little terminology here. The rectangle displaying the number of remaining mines is called "
CounterRect"; the rectangle displaying the current time is called "
TimerRect"; the button showing the current status of the game, is called "
Face"; and we have square "boxes" which may or may not have a mine; the rectangular array of boxes is called "
Grid" of boxes.
I have included a bunch of features which may seem nice to you.
- When any element of the client area (the rect's, face, or boxes) needs to be redrawn, we do not send or post a
WM_PAINT message to the window function, instead we draw it directly using
hDC obtained by
- Each box only has two information items, one specifies whether it has a mine, and the other indicates the current state of the box. The visual appearance of the box depends only on this state (the state uniquely determines the offset of the bitmap to be shown on the box), so this is why you find my
DrawBox function so simple with only one statement!
- Like in
WineMine, we have
PressBox when the left mouse button is pressed on the box and
UnPressBox when the mouse leaves or the left button is released, or any other mouse event other than
WM_LBUTTONDOWN is occurring. Our implementation is that
PressBox changes the state of the box only when the box is in an "unstepped" and "non-final" state:
BS_DICEY. When a box with state
BS_INITIAL is pressed, it is temporarily in the
BS_DOWN state, When a box with state
BS_DICEY is pressed, it is temporarily in the
BS_DICEY_DOWN state. Unpressing a box returns the box's state to its original state,
- When the left mouse button is released on a box, this box is said to be "stepped". The "stepped" states are
BS_DOWN is both a temporary state when pressed and a permanent state after stepping, the context will determine which is the case. This is achieved by obeying the following rules when writing the code:
- There can be no or only one box that is pressed at any time.
- When we unpress a box, it must be the box currently pressed.
- When we change any box's state in response to a mouse event (other than pressing/unpressing), we do unpressing first. This ensures that at that time, no box is pressed. So we can guarantee if we encounter a
BS_DOWN state at that time, it must be a stepped permanent state, not a result of pressing.
StepBox implements the stepping of a box, note that it is recursive, which means that it may call itself when the stepped box has no mine surrounding it. Please see the following snippet:
BOOL StepBox(int row, int col)
if (board.Box[row][col].State != BS_INITIAL &&
board.Box[row][col].State != BS_DICEY)
SetAndDispBoxState(row, col, BS_BLAST);
cMinesSurround = CountMines(row, col);
SetAndDispBoxState(row, col, BS_DOWN - cMinesSurround);
if (cMinesSurround == 0)
int r, c;
for (r = row-1; r <= row+1; r++)
for (c = col-1; c <= col+1; c++)
if (WITHIN_GRID(r, c) && (r != row || c != col))
Our end result is a fairly complete
WinMine program with a controlled complexity, making it very ideal for presenting the details of Win32 API programming. In my teaching practice, I presented this program in five steps. The students' knowledge in Win32 programming accumulated from one step to the next, and their confidence and interest in Windows programming increased as well.