五子棋游戏
一、整体思路
1、目的:通过五子棋,掌握面向对象的思想,这是我们的目的。提升分析问题、解决问题的能力,在做项目之前要有一个系统的思路,第一步干什么,第二步干什么……
2、思路:拿到这个问题,我们首先要设计一个类(CWuZiQi),设计一个类首先要想到它有哪些成员变量,有哪些属性。最核心的一个成员变量是棋盘(用二维数组m_QP[19][19]表示,数组是该程序的核心)。用0表示棋盘上没有棋子,用1表示黑子,2表示白子。我们还需要行(m_H)、列(m_L),以及当前棋子的颜色(m_Color)。
3、核心的三个函数:一个Draw函数;一个下棋(XiaQi)函数,下棋函数在按下鼠标时调用(传过来一个点的坐标);一个判断输赢(PanDuan)的函数。
二、最终实现效果图
三、实现步骤(前期)
1、新建一个MFC单文档应用程序,如下图所示。
2、设计一个类(CWuZiQi),添加一些成员变量并初始化,如下图所示。
3、先从简单的入手,把棋盘画出来。为了让界面更加美观,我们还可以画棋盘的背景(放大后整个屏幕的背景以及棋盘游戏区域背景)以及棋盘上面的九个星位,如下图所示。
①画棋盘背景
画棋盘背景代码如下:
//画屏幕背景和棋盘背景
void CWuZiQi::DrawBackground(CDC *pDC)
{
CBrush brush1,*pOldBrush1;
brush1.CreateSolidBrush(RGB(255,222,173));
pOldBrush1 = pDC->SelectObject(&brush1);
pDC->Rectangle(0,0,1920,960); //整个屏幕大小
brush1.DeleteObject();
pDC->SelectObject(pOldBrush1);
//==============================================
int x,y,r;
x = ydx;
y = ydy;
r = 18 * lenbl;
CBrush brush2,*pOldBrush2;
brush2.CreateSolidBrush(RGB(244,164,96));
pOldBrush2 = pDC->SelectObject(&brush2);
pDC->Rectangle(x,y,x + r,y + r); //棋盘大小
brush2.DeleteObject();
pDC->SelectObject(pOldBrush2);
}
②画棋盘九个星位
画棋盘九个星位代码如下:
//画棋盘上的九个星位
void CWuZiQi::DrawXingWei(CDC *pDC)
{
int x,y,r;
for (int i = 4; i < 19; i+=5)
{
for (int j = 4; j < 19; j+=5)
{
x = ydx + i * lenbl;
y = ydy + j * lenbl;
r = lenbl/8;
CBrush brush,*pOldBrush;
brush.CreateSolidBrush(RGB(0,0,0)); //画星位
pOldBrush = pDC->SelectObject(&brush);
pDC->Ellipse(x - r, y - r, x + r, y + r);
brush.DeleteObject();
pDC->SelectObject(pOldBrush);
}
}
}
③画棋盘
画棋盘代码如下:
//画棋盘
void CWuZiQi::DrawQiPan(CDC *pDC)
{
DrawBackground(pDC); //调用背景要放在画棋盘前面,若放在后面棋盘会被覆盖
int x,y;
for (int i = 0; i < 19; i++)
{
x = ydx;
y = ydy + i * lenbl;
pDC->MoveTo(x,y);
x += 18 * lenbl;
pDC->LineTo(x,y); //画行数
x = ydx + i * lenbl;
y = ydy;
pDC->MoveTo(x,y);
x += 18 * lenbl;
pDC->LineTo(x,y); //画列数
}
DrawXingWei(pDC); //调用画的九个星位
}
4、紧接着就是画棋子喽,用黑色画刷画黑子,白色画刷画白子,如下图所示。
画棋子代码如下:
void CWuZiQi::DrawQiZi(CDC *pDC)
{
int i,j;
int x,y;
int r = lenbl/3;//棋子半径
for (i = 0; i < 19; i++)
{
for (j = 0; j < 19; j++)
{
if (m_Qp[i][j] != 0) //如果棋盘上有子
{
if (m_Qp[i][j] == 1) //并且是黑子
{
CBrush brush,*pOldBrush;
brush.CreateSolidBrush(RGB(0,0,0)); //画黑子
pOldBrush = pDC->SelectObject(&brush);
x = ydx + i * lenbl;
y = ydy + j * lenbl;
pDC->Ellipse(x - r, y - r, x + r, y + r);
brush.DeleteObject();
pDC->SelectObject(pOldBrush);
}
else if (m_Qp[i][j] == 2) //并且是白子
{
CBrush brush,*pOldBrush;
brush.CreateSolidBrush(RGB(255,255,255)); //画白子
pOldBrush = pDC->SelectObject(&brush);
x = ydx + i * lenbl;
y = ydy + j * lenbl;
pDC->Ellipse(x - r, y - r, x + r, y + r);
brush.DeleteObject();
pDC->SelectObject(pOldBrush);
}
}
}
}
}
5、再添加一个成员函数Draw(CDC *pDC),调用上面画好的棋盘、棋子,如下图所示。
调用代码如下:
void CWuZiQi::Draw(CDC *pDC)
{
DrawQiPan(pDC);
DrawQiZi(pDC);
}
6、步步为营,我们可以先在CGobangGameView里将#include “WuZiQi.h”头文件嵌入进来,并引入成员变量,如下图所示。
7、编译运行就能看见前期的效果了,由于还没有添加鼠标响应函数,所以棋子暂时没法显示。如下图所示。
四、实现步骤(中期)
中期主要就是两个工作,一个下棋函数,一个判断函数。
1、添加下棋成员函数,并编写代码(下棋函数在按下鼠标时调用(传过来一个点的坐标)),如下图所示。
下棋代码:
void CWuZiQi::XiaQi(CPoint pt)
{
//确定下棋边界
if (pt.x > (ydx - lenbl/3) && pt.x < (ydx + 18 * lenbl + (lenbl/3)) &&\
pt.y > (ydy - lenbl/3) && pt.y < (ydy + 18 * lenbl + (lenbl/3)))
//注:lenbl/3是棋子半径,当棋子边缘碰到棋盘线时,也默认在棋盘内。
{
m_H = (pt.x - ydx + lenbl/4)/lenbl; //用当前点击位置减去原点距离,加上1/4小格的结果,整除小方格,确定在哪一行
m_L = (pt.y - ydy + lenbl/4)/lenbl; //获取落子位置
}
if (m_Qp[m_H][m_L] == 0 && m_Color == 1) //落子位置没有棋子,并且是黑子下
{
m_Qp[m_H][m_L] = 1; //将当前点击的位置设置为黑子
PanDuan(); //判断添加棋子后是否有五子
if (m_Qp[m_H][m_L] == 1) //如果当前点击位置显示的是黑子,即黑子刚刚下过
{
m_Color = 2; //立即换白子下棋
}
else if (m_Qp[m_H][m_L] == 2) //如果当前点击位置显示的是白子,即白子刚刚下过
{
m_Color = 1; //立即换黑子下棋
}
}
else if (m_Qp[m_H][m_L] == 0 && m_Color == 2) //落子位置没有棋子,并且是白子下
{
m_Qp[m_H][m_L] = 2; //将当前点击的位置设置为白子
PanDuan(); //判断添加棋子后是否有五子
if (m_Qp[m_H][m_L] == 1) //如果当前点击位置显示的是黑子,即黑子刚刚下过
{
m_Color = 2; //立即换白子下棋
}
else if (m_Qp[m_H][m_L] == 2) //如果当前点击位置显示的是白子,即白子刚刚下过
{
m_Color = 1; //立即换黑子下棋
}
}
}
2、添加判断函数,并编写代码,如下图所示。
判断代码:
void CWuZiQi::PanDuan()
{
int i; //用来循环遍历的
int left = 0,right = 0; //左右下棋
for (i = 1; i <= 4; i++)
{
if (m_Qp[m_H][m_L - i] == m_Color) //以(m_H,m_L)为中心,向左找,注意是列在减小,而不是行!
left++;
else
break; //碰到一个不是当前颜色的就停止
}
for (i = 1; i <= 4; i++)
{
if(m_Qp[m_H][m_L + i] == m_Color) //以(m_H,m_L)为中心,向右找,注意是列在增大,而不是行!
right++;
else
break;
}
//=================================================================================================================
int up = 0,down = 0; //上下下棋
for (i = 1; i <= 4; i++)
{
if (m_Qp[m_H - i][m_L] == m_Color) //以(m_H,m_L)为中心,向上找,注意是行在减小,而不是列!(竖直向下为正)
up++;
else
break; //碰到一个不是当前颜色的就停止
}
for (i = 1; i <= 4; i++)
{
if(m_Qp[m_H + i][m_L] == m_Color) //以(m_H,m_L)为中心,向下找,注意是行在增大,而不是列!
down++;
else
break;
}
//=================================================================================================================
int leftup = 0,rightdown = 0; //左上、右下,“\”下棋
for (i = 1; i <= 4; i++)
{
if (m_Qp[m_H - i][m_L - i] == m_Color) //以(m_H,m_L)为中心,向左上找,行列均在减小
leftup++;
else
break; //碰到一个不是当前颜色的就停止
}
for (i = 1; i <= 4; i++)
{
if(m_Qp[m_H + i][m_L + i] == m_Color) //以(m_H,m_L)为中心,向右下找,行列均在增大
rightdown++;
else
break;
}
//=================================================================================================================
int leftdown = 0,rightup = 0; //左下、右上,“/”下棋
for (i = 1; i <= 4; i++)
{
if (m_Qp[m_H + i][m_L - i] == m_Color) //以(m_H,m_L)为中心,向左下找,行在增大,列在减小
leftdown++;
else
break; //碰到一个不是当前颜色的就停止
}
for (i = 1; i <= 4; i++)
{
if(m_Qp[m_H - i][m_L + i] == m_Color) //以(m_H,m_L)为中心,向右上找,行在减小,列在增大
rightup++;
else
break;
}
//=================================================================================================================
if (left + right >= 4 || up + down >= 4 || leftup + rightdown >= 4 || leftdown + rightup >= 4)
{
if(m_Color == 1)
AfxMessageBox("黑棋获胜!");
if(m_Color == 2)
AfxMessageBox("白棋获胜!");
}
}
五、实现步骤(后期)
1、在CGobangGameView里添加WM_LBUTTONDOWN
句柄,实现下棋时的消息响应,如下图所示。
代码如下:
void CGobangGameView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
WZQ.XiaQi(point); //下棋响应
Invalidate(TRUE);
CView::OnLButtonDown(nFlags, point);
}
2、为了更方便的显示当前是谁正在下棋,我们还可以定义两个鼠标(黑、白),若是黑子正在下,则显示黑棋鼠标;若是白子正在下,则显示白棋鼠标。如下图所示。
3、在CGobangGameView里添加WM_SETCURSOR
句柄,实现下棋时的消息响应,如下图所示。
4、在CGobangGameView里定义黑白鼠标的成员变量,并在构造函数里加载刚刚画的黑白棋子图像,如下图所示。
代码如下:
BOOL CGobangGameView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
// TODO: Add your message handler code here and/or call default
if (nHitTest == HTCLIENT)
{
//黑子下,显示黑棋鼠标
if(WZQ.m_Color == 1)
SetCursor(m_csrblack);
//白子下,显示白棋鼠标
else
SetCursor(m_csrwhite);
return 1;
}
return CView::OnSetCursor(pWnd, nHitTest, message);
}
六、运行结果
七、总结
本设计基本实现了一个简单的人人对战五子棋游戏,整体设计难度偏小,对刚刚接触MFC的初学者来说是很好的一篇参考素材。
通过该游戏的设计,对面向对象的程序设计思想有了更加深刻的认识,也渐渐明白在做项目之前形成系统思路的重要性。
本文最满意的地方是对成员变量的巧妙运用,只需在构造函数里改变原点坐标位置(ydx,ydy)和棋盘上每个小方格的大小比例(lenbl)就能改变与之相关的内容,比如棋盘大小,游戏边界,棋子大小,游戏背景大小,等等,更加方便了程序的后期运行和维护。