Windows Systems Programming: Spring 2002

[ Home | Syllabus | Course Notes | Assignments | Search]


Handling the Mouse (chapter 3)

Let's examine the TICTAC Application. TICTAC is a simple two player game based on a 3 by 3 grid. players alternate marking the grid (one player uses 'X's and the other 'O's), until one player marks three spaces in a line or all spaces are taken. This program will enforce the rules of the game letting player one mark a space with the left mouse and the other with the right mouse. It will detect when the game is over. Additionally, double clicking on a black line will reset the game.

Important points:

Source Code here

#define EX 1
#define OH 2

class CMyApp : public CWinApp
{
public:
    virtual BOOL InitInstance ();
};

class CMainWindow : public CWnd 
{
protected:
    static const CRect m_rcSquares[9];	// Grid coordinates
    int m_nGameGrid[9];			// Grid contents
    int m_nNextChar;			// Next character (EX or OH)

    int GetRectID (CPoint point);
    void DrawBoard (CDC* pDC);
    void DrawX (CDC* pDC, int nPos);
    void DrawO (CDC* pDC, int nPos);
    void ResetGame ();
    void CheckForGameOver ();
    int IsWinner ();
    BOOL IsDraw ();

public:
    CMainWindow ();

protected:
    virtual void PostNcDestroy ();

    afx_msg void OnPaint ();
    afx_msg void OnLButtonDown (UINT nFlags, CPoint point);
    afx_msg void OnLButtonDblClk (UINT nFlags, CPoint point);
    afx_msg void OnRButtonDown (UINT nFlags, CPoint point);

    DECLARE_MESSAGE_MAP ()
};

The message map identifies all messages that this program handles

BEGIN_MESSAGE_MAP (CMainWindow, CWnd)
    ON_WM_PAINT ()// always needed
    ON_WM_LBUTTONDOWN () // player 1 marks 'X'
    ON_WM_LBUTTONDBLCLK () // reset game
    ON_WM_RBUTTONDOWN () // player 2 marks 'O'
END_MESSAGE_MAP ()

WM_PAINT handler

//// onPaint calls DrawBoard which contains
    CPen pen (PS_SOLID, 16, RGB (0, 0, 0)); // solid black wide pen
    CPen* pOldPen = pDC->SelectObject (&pen); // remember current pen to restore

    pDC->MoveTo (120, 16); // position pen
    pDC->LineTo (120, 336); // draw one line of grid

    pDC->MoveTo (232, 16);
    pDC->LineTo (232, 336);

    pDC->MoveTo (16, 120);
    pDC->LineTo (336, 120);

    pDC->MoveTo (16, 232);
    pDC->LineTo (336, 232);

	//
	// Draw the Xs and Os.
	//
    for (int i=0; i<9; i++) {
        if (m_nGameGrid[i] == EX)
            DrawX (pDC, i);
        else if (m_nGameGrid[i] == OH)
            DrawO (pDC, i);
    }
    pDC->SelectObject (pOldPen); // restore previous pen
//// look at DrawX
    CPen pen (PS_SOLID, 16, RGB (255, 0, 0)); // solid wide red pen
    CPen* pOldPen = pDC->SelectObject (&pen);

    CRect rect = m_rcSquares[nPos];
    rect.DeflateRect (16, 16); // don't draw right to the edge
    pDC->MoveTo (rect.left, rect.top); // draw diagonals for an 'X'
    pDC->LineTo (rect.right, rect.bottom);
    pDC->MoveTo (rect.left, rect.bottom);
    pDC->LineTo (rect.right, rect.top);

    pDC->SelectObject (pOldPen);
//// and DrawO
    CPen pen (PS_SOLID, 16, RGB (0, 0, 255)); // blue pen
    CPen* pOldPen = pDC->SelectObject (&pen);
    pDC->SelectStockObject (NULL_BRUSH); // don't brush new interior

    CRect rect = m_rcSquares[nPos];
    rect.DeflateRect (16, 16);
    pDC->Ellipse (rect); // put circle inside deflated rectangle

    pDC->SelectObject (pOldPen);
//// experiments
// 1) remove deflateRect
// 2) change color of pen
// 3) change brush

WM_LBUTTONDOWN handler

    if (m_nNextChar != EX) // if not this player's turn - ignore
        return;

    // "point" is point where mouse click occurred
    int nPos = GetRectID (point); // which space selected
    // then check if inside a space and space empty
    if ((nPos == -1) || (m_nGameGrid[nPos] != 0))
        return;

	//
	// Add an X to the game grid and toggle m_nNextChar.
	//
    m_nGameGrid[nPos] = EX;
    m_nNextChar = OH;

	//
	// Draw an X on the screen and see if either player has won.
	//
    CClientDC dc (this);
    DrawX (&dc, nPos); // what if this was missing?
    CheckForGameOver ();	
//// GetRectID
int CMainWindow::GetRectID (CPoint point)
{
    for (int i=0; i<9; i++) {
        if (m_rcSquares[i].PtInRect (point)) 
            return i;
    }
    return -1;
}

Handling a double click

void CMainWindow::OnLButtonDblClk (UINT nFlags, CPoint point)
{
    CClientDC dc (this);
    if (dc.GetPixel (point) == RGB (0, 0, 0)) // if clicked on black line
        ResetGame ();
}
void CMainWindow::ResetGame ()
{
    m_nNextChar = EX;
    ::ZeroMemory (m_nGameGrid, 9 * sizeof (int));
    Invalidate ();
}
// how does the above "erase" the 'X's and 'O's ?

Message Box - easy output - useful for debugging also

if (nWinner = IsWinner ()) {
        CString string = (nWinner == EX) ?
            _T ("X wins!") : _T ("O wins!");
        MessageBox (string, _T ("Game Over"), MB_ICONEXCLAMATION | MB_OK);
        ResetGame ();
    }

//// Interesting discussion on message handling p. 127
//// this code would bypass client area mouse handling by mapping all mouse
//// hits to the CAPTION message handlers.
UINT CMainWindow::OnNcHitTest(CPoint point){
	UINT nHitTest = CWnd::OnNcHitTest(point);
	if(nHitTest == HTCLIENT)
		nHitTest = HTCAPTION;
	return nHitTest;
}

 

 


Copyright chris wild 1999-2002.
For problems or questions regarding this web contact [Dr. Wild].
Last updated: January 28, 2002.