Introduction
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 CreateWindow
, 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 GetDC
. - 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_INITIAL
or 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, BS_INITIAL
or BS_DICEY
. - When the left mouse button is released on a box, this box is said to be "stepped". The "stepped" states are
BS_NUM1
to BS_NUM8
, BS_DOWN
, and BS_BLAST
. So 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.
The function 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)
{
UINT cMinesSurround;
if (board.Box[row][col].State != BS_INITIAL &&
board.Box[row][col].State != BS_DICEY)
{
return TRUE;
}
if (board.Box[row][col].fMine)
{
SetAndDispBoxState(row, col, BS_BLAST);
return FALSE;
}
board.uSteps++;
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))
{
StepBox(r, c);
}
}
}
return TRUE;
}
Conclusion
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.