[ Home | Syllabus | Course Notes | Assignments | Search]
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:
Handling Mouse Events
Drawing on Window
Message Box
Built on CWnd instead of CFrameWnd
#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 () };
F1: Derived from CWnd since don't need all the power of CFrameWnd - but we will have to do more (closer to SDK style programming)
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 ()
//// 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
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; }
F2: PtInRect returns true if point is in the rectangle object.
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 ?
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; }