MFC Tic-Tac-Toe Example

This Tic-Tac-Toe game is the simpliest example of an MFC application. It contains one source file and implements one window class, the most basic one - CWnd. Compare it to the .NET Tic-Tac-Toe example and WinAPI Tic-Tac-Toe example. Download the source code: MFCTICTACTOE.ZIP

#include "TicTac.h"

CMyApp myApp;

BOOL CMyApp::InitInstance()
{
	m_pMainWnd = new CMainWindow;
	m_pMainWnd->ShowWindow(m_nCmdShow);
	m_pMainWnd->UpdateWindow();
	return TRUE;
}

// THERE MACROS ARE VERY IMPORTANT TO MFC - DO NOT REMOVE
BEGIN_MESSAGE_MAP(CMainWindow, CWnd)
	ON_WM_PAINT()
	ON_WM_LBUTTONDOWN()
	ON_WM_MBUTTONDOWN()
	ON_WM_RBUTTONDOWN()
END_MESSAGE_MAP()

const CRect CMainWindow::m_rcSquares[9] = {
	CRect( 16, 16,112,112),
	CRect(128, 16,224,112),
	CRect(240, 16,336,112),
	CRect( 16,128,112,224),
	CRect(128,128,224,224),
	CRect(240,128,336,224),
	CRect( 16,240,112,336),
	CRect(128,240,224,336),
	CRect(240,240,336,336)
	};

CMainWindow::CMainWindow()
{
	m_nNextChar = EX;

	::ZeroMemory(m_nGameGrid, 9 * sizeof(int));

	// register a WNDCLASS
	CString strWndClass = AfxRegisterWndClass(CS_DBLCLKS,
		AfxGetApp()->LoadStandardCursor(IDC_ARROW),
		(HBRUSH)(COLOR_3DFACE+1),
		AfxGetApp()->LoadStandardIcon(IDI_WINLOGO));
	
	// create a window
	this->CreateEx(0, strWndClass, _T("TicTacToe -L, R, M mouse buttons"),
		WS_OVERLAPPED|WS_SYSMENU|WS_CAPTION|WS_MINIMIZEBOX, CW_USEDEFAULT,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL);

	//size the window
	CRect rect (0, 0, 352, 352);
	CalcWindowRect(&rect);
	this->SetWindowPos(NULL, 0, 0, rect.Width(), rect.Height(), SWP_NOZORDER|SWP_NOMOVE|SWP_NOREDRAW);
}

void CMainWindow::PostNcDestroy()
{
	delete this;
}

void CMainWindow::OnPaint()
{
	CPaintDC dc(this);
	DrawBoard(&dc);
}

void CMainWindow::OnLButtonDown(UINT nFlags, CPoint point)
{
	// Do nothing if it is O's turn, if the click occured outside
	// the tic-tac-toe grid, or if a non empty was clicked
	if(m_nNextChar != EX)
		return;

	int nPos = GetRectID(point);
	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 and X on the screen and see if there's a winner
	CClientDC dc(this);
	DrawX(&dc, nPos);
	CheckForGameOver();
}

void CMainWindow::OnRButtonDown(UINT nFlags, CPoint point)
{
	// Do nothing if it is X's turn, if the click occured outside
	// the tic-tac-toe grid, or if a non empty was clicked
	if(m_nNextChar != OH)
		return;

	int nPos = GetRectID(point);
	if((nPos == -1) || (m_nGameGrid[nPos] != 0))
		return;

	// add an O to the game grid and toggle m_nNextChar
	m_nGameGrid[nPos] = OH;
	m_nNextChar = EX;

	// draw and O on the screen and see if there's a winner
	CClientDC dc(this);
	DrawO(&dc, nPos);
	CheckForGameOver();
}

void CMainWindow::OnMButtonDown(UINT nFlags, CPoint point)
{
	// reset the game when the middle button is clicked
	ResetGame();
}

int CMainWindow::GetRectID(CPoint point)
{
	// hit-test each of the 9 squares and return a rectangle
	// ID (0-8) if (point.x , point.y) lies inside a square
	for(int i = 0; i < 9; i++)
	{
		if(m_rcSquares[i].PtInRect(point))
			return i;
	}
	return -1;
}

void CMainWindow::DrawBoard(CDC *pDC)
{
	// draw the lines
	CPen pen(PS_SOLID, 16, RGB(0, 0, 0));
	CPen *pOldPen = pDC->SelectObject(&pen);

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

	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 X's and O's
	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);
}

void CMainWindow::DrawX(CDC *pDC, int nPos)
{
	CPen pen(PS_SOLID, 16, RGB(255,0,0));
	CPen *pOldPen = pDC->SelectObject(&pen);

	CRect rect = m_rcSquares[nPos];
	rect.DeflateRect(16, 16);
	pDC->MoveTo(rect.left, rect.top);
	pDC->LineTo(rect.right, rect.bottom);
	pDC->MoveTo(rect.left, rect.bottom);
	pDC->LineTo(rect.right, rect.top);

	pDC->SelectObject(pOldPen);
}

void CMainWindow::DrawO(CDC *pDC, int nPos)
{
	CPen pen(PS_SOLID, 16, RGB(0,0,255));
	CPen *pOldPen = pDC->SelectObject(&pen);
	pDC->SelectStockObject(NULL_BRUSH);

	CRect rect = m_rcSquares[nPos];
	rect.DeflateRect(16, 16);
	pDC->Ellipse(rect);
	pDC->SelectObject(pOldPen);
}

void CMainWindow::CheckForGameOver()
{
	int nWinner;

	//if the grid contains three consecutive X's or O's, declare a winner and start a new game
	if(nWinner = IsWinner())
	{
		CString string = (nWinner == EX) ? _T("X wins!") : _T("O wins!");
		MessageBox(string,_T("Game Over"), MB_ICONEXCLAMATION|MB_OK);
		ResetGame();
	}
	else if(IsDraw())
	{
		MessageBox(_T("It's a draw!"), _T("Game Over"), MB_ICONEXCLAMATION|MB_OK);
		ResetGame();
	}
}

int CMainWindow::IsWinner()
{
	static int nPattern[8][3] = {
		0, 1, 2, 
		3, 4, 5, 
		6, 7, 8, 
		0, 3, 6, 
		1, 4, 7, 
		2, 5, 8,
		0, 4, 8,
		2, 4, 6
	};

	for(int i = 0; i < 8 ; i++)
	{
		if((m_nGameGrid[nPattern[i][0]] == EX) &&
			(m_nGameGrid[nPattern[i][1]] == EX) &&
			(m_nGameGrid[nPattern[i][2]] == EX))
			return EX;
		if((m_nGameGrid[nPattern[i][0]] == OH) &&
			(m_nGameGrid[nPattern[i][1]] == OH) &&
			(m_nGameGrid[nPattern[i][2]] == OH))
			return OH;
	}
	return 0;
}

BOOL CMainWindow::IsDraw()
{
	for(int i = 0; i < 9; i++)
	{
		if(m_nGameGrid[i] == 0)
			return FALSE;
	}
	return TRUE;
}

void CMainWindow::ResetGame()
{
	m_nNextChar = EX;
	::ZeroMemory(m_nGameGrid, 9 * sizeof(int));
	this->Invalidate();
}