Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

Adapted WinMine Source for Teaching Win32 API Programming

4.82/5 (19 votes)
21 Apr 2011CPOL5 min read 35.9K   1.2K  
An ideal source package for introducing students to the basics of Win32 programming

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: 

C++
void InitBoard(); // Compute the members of "board" that do not change from game to game
void LoadBoard(); // Load preferences from the registry
void SaveBoard(); // Save preferences to the registry
void DestroyBoard();// Release memory DCs and bitmaps associated with the board
void CheckLevel(); // Check whether the level parameters are sane
void NewBoard(); 	// Compute the members of "board" that may change from game to game
void NewGame(); 	// Call NewBoard and refresh the window
void TranslateMouseMsg(UINT* puMsg, WPARAM wParam);
void ProcessMouseMsg(UINT uMsg, LPARAM lParam);
void OnCommand(WPARAM wParam, LPARAM lParam);
void SetDifficulty(DIFFICULTY Difficulty); // Set new difficulty for the game
void DrawBackground(HDC hDC); 	// Draw the margins and borders of the rect's
void DrawMinesLeft(HDC hdc); 	// Draw the number of remaining mines in LED digits
void DrawFace(HDC hdc); 		// Draw the face button
void DrawTime(HDC hdc); 	// Draw the number of seconds elapsed since the game begins
void DrawGrid(HDC hdc); 	// Draw the grid of boxes
void DisplayFace(FACE_STATE faceState); // Set new state of the face and show it
void SetAndDispBoxState(int r, int c, BOX_STATE state); // Set new state of the box 
						// and show it 
void DecMinesLeft(); // Decrement the number of remaining mines and show it
void IncMinesLeft(); // Increment the number of remaining mines and show it
void ZeroMinesLeft(); // Set the number of remaining mines to zero and show it
void IncTime(); // Increment the number of seconds elapsed and show it
void LayMines(int row, int col); 	// Lay the mines, this occurs after the 
				// first WM_LBUTTON message on a box
void Pt2RowCol(POINT pt, int *prow, int *pcol); // Calculate the current 
					//mouse point is in what box (row,col)
void GameWon(); // Called when the game is won
void GameLost(); // Called when the game is lost
void PressBox(int row, int col); 	// Do the pressing of the specified box
void UnPressBox(); 	// Do the unpressing of the currently pressed box
UINT CountMines(int row, int col); 	// Count how many mines are there in the 
				// surrounding boxes
BOOL StepBox(int row, int col); 	// Steps on a box, a recursive function

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. 

  1. 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
  2. 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! 
  3. 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.
  4. 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:
    1. There can be no or only one box that is pressed at any time.
    2. When we unpress a box, it must be the box currently pressed. 
    3. 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:

C++
// steps on this box, return value indicates if it is safe
BOOL StepBox(int row, int col)
{
    UINT cMinesSurround;

    if (board.Box[row][col].State != BS_INITIAL && 
        board.Box[row][col].State != BS_DICEY)
    {
        // previously stepped, so safe, no need to step second time, 
        // or already flagged as a mine
        return TRUE;
    }

    if (board.Box[row][col].fMine)
    {
        // stepped on a mine!
        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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)