1. 游戏功能简介:
1) 3×3棋盘,9宫格,每格可放一个棋子;
2) 鼠标左键落X右键落O,保证X和O轮流出现防止作弊,并且设定X为先手;
3) 棋盘是井字形的框,鼠标双击井字框将重置棋局;
4) 水平、垂直或对角线3连线即赢得棋局;
2. TicTac.h:
// TicTac.h
class CMyApp: public CWinApp
{
public:
virtual BOOL InitInstance();
};
#define EX 1 // 先手X
#define OH 2 // 后手O
class CMyWindow: public CWnd // 注意!从CWnd继承而不是CFrameWnd继承
{
protected:
static const CRect m_rcSquares[9]; // 格子的坐标(只需要左上角和右下角坐标即可)
int m_nGameGrid[9]; // 格子中的内容,是哪个玩家的棋子
int m_nNextChar; // 下一个落子者,初始化成EX
protected:
int GetRectID(CPoint point); // 根据点的位置返回点落在哪个方格中
void DrawBoard(CDC* pDC); // 画整个棋局板,包括方块内的棋子
void DrawX(CDC* pDC, int nPos); // 在nPos号方格中画X
void DrawO(CDC* pDC, int nPos); // 在nPos号方格中画O
void ResetGame(); // 重置棋局
void CheckForGameOver(); // 检查棋局是否结束(哪方赢或者平局)
int IsWinner(); // 返回谁是胜者,还没决出就返回0,否则返回EX或者OH
BOOL IsDraw(); // 检查是否平局
public:
CMyWindow();
protected:
virtual void PostNcDestroy(); // 从CWnd继承的窗口必须覆盖该函数,用以销毁窗口非客户区
afx_msg void OnPaint();
afx_msg void OnLButtonDown(UINT nFlags, CPoint point); // EX落子
afx_msg void OnRButtonDown(UINT nFlags, CPoint point); // OH落子
afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point); // 双击井字线条重新开一局
DECLARE_MESSAGE_MAP()
};
// TicTac.cpp
#include <afxwin.h>
#include "TicTac.h"
CMyApp myApp;
BOOL CMyApp::InitInstance()
{
m_pMainWnd = new CMyWindow;
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
BEGIN_MESSAGE_MAP(CMyWindow, CWnd)
ON_WM_PAINT()
ON_WM_LBUTTONDOWN()
ON_WM_RBUTTONDOWN()
ON_WM_LBUTTONDBLCLK()
END_MESSAGE_MAP()
const CRect CMyWindow::m_rcSquares[9] = { // 9个格子在客户区中的坐标
CRect ( 16, 16, 112, 112), // 格子按照从上到下从左到右编号0-8
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)
};
4. GetRectID:
1) 用以判断鼠标击键是否位于9个方格内;
2) 使用到CRect的成员函数来判断一个点是否位于矩形区域内:BOOL CRect::PtInRect(POINT point) const;
int CMyWindow::GetRectID(CPoint point)
{// 判断点point落在几号格子中
for (int i = 0; i < 9; i++) {
if (m_rcSquares[i].PtInRect(point))
return i;
}
return -1; // 落在所有格子的外面
}
5. DrawX:
1) 使用到了CRect的成员函数用来将缩小矩形:void CRect::DeflateRect(int x, int y);,意义是使矩形左右两边分别向中心靠拢x个单位,上下两边分别向中心靠拢y个单位;
2) 函数将在一个缩小过的方格内画叉,这样不会使叉和井字棋盘边框重叠,更加美观;
void CMyWindow::DrawX(CDC* pDC, int nPos)
{// 在nPos号格子中画X
CPen pen(PS_SOLID, 16, RGB(255, 0, 0)); // X是红色16宽的实线
CPen* pOldPen = pDC->SelectObject(&pen);
CRect rect = m_rcSquares[nPos]; // 将nPos号格子的坐标下载到本地处理
rect.DeflateRect(16, 16); // 四周向中心收缩16像素以避免X和格角重合,更加美观
// 画X
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); // !记得还原默认的画笔
}
6. DrawY:
1) 在方格中画圆,要求使圆中的填充色为透明,所以需要使用透明画刷;
2) 之前讲过通过CBrush的构造函数、CreateSolidBrush、CreateHatchBrush只能创建单色或者阴影线型的画刷,但是要指定透明画刷只能通过创建逻辑画刷将参数lbStyle设为BS_NULL或BS_HOLLOW,但是这样做实在非常繁琐,但是还有更加简便指定画刷样式的方式,就是使用SelectStockObject将一个库存中定义过的指定样式的画刷直接选入设备环境,连创建画刷的步骤都不需要:
virtual CGdiObject* CDC::SelectStockObject(int nIndex);
!其中nIndex是系统预定好的GDI对象的ID号,比如NULL_BRUSH,就是我们需要用的透明画刷,BLACK_BRUSH即黑色画刷,BLACK_PEN即黑色画笔,如果需要用到这些简单的预订好的GDI对象则可以直接使用该函数将相关对象选入设备环境;
!该函数可选中的GDI对象有画笔、画刷、字体这三