简述
在学习了C++ MFC编程后,就萌生了编写一个五子棋程序的想法。诚然,用C#来编写的话可能界面会好很多,但是我的主要目标不在界面而是算法,所以利用了VS的MFC来编写程序。
程序特点
- 鼠标移动过程中有选中框跟随,下棋位置实时自动反馈
选中框位置只会在合法的下棋位置出现(即棋线的交叉处并且没有下过的位子),所以即使你的鼠标不在棋线交叉处,程序也会实时自动反馈鼠标在当前位置将要下棋的位置,这避免了下棋过程中的错下。 - 提供悔棋功能
为了进一步避免错下,提高程序的可用性,本程序还设计了一个悔棋按钮(程序设置最大悔棋次数为1次) - 提供多种棋盘大小
标准的五子棋棋盘为15x15,为了提供更多样的棋盘大小,本程序还设有17x17和19x19两种非常规棋盘。 - 提供复盘功能
在棋局结束后,程序会给出棋局的复盘结果,可以从中分析出棋局胜败的原因。 - 解决了MFC对话框类型工程绘图窗口移出屏幕图像消失的问题
你可以在完整代码中看到解决刷新问题的解决方案 - 代码较为基础,只需要掌握基础的MFC编程知识就可以自己实现
算法设计比较基础和简单,只掌握基础的C++编程知识就可以看懂代码
程序实现
完整代码可访问我的github: https://github.com/Vaczzy/SimpleGobang
step1:在VS中创建一个MFC对话框程序工程。
以VS2017为例:(本例中的工程名称为"MyGobang_DHK")
(创建好后也可以直接将我提供的源码复制到工程中即可运行,如遇问题可以下方留言)
- 打开Visual Studio 2017在 文件-新建工程 中选择"Visual C++ MFC"选项(1,2),设置工程名称(3)和存储位置(4)后点击确定(5),如图:
- 选择应用程序类型为"基于对话框",点击完成就成功创建了一个MFC对话框工程。如图:
step2:为工程添加一个新类,并在类中编写算法实现的主要代码
- 添加新类。在"项目"中点击 添加类 ,在弹出窗口中设置类名(本例添加的类名为"MainGobang"),点击确定即可。本例中,VS将自动生成一个名为"MainGobang.h"的头文件和一个"MainGobang.cpp"的实现文件
- 设计实现五子棋程序的算法类,在MainGobang.h中编写类的成员变量和函数
(这里需要设计一个表示棋子信息的结构体和表示鼠标移动过程下棋位置的矩形框结构体)
struct Pieces//棋子判断单元
{
bool bIsPieces;//是否已有棋
bool bIsBlack;//黑棋或白棋
int x,y;//棋子所在格
};
struct MyRectangle
{
int x,y;//选中框核心坐标
};
在类中编写好具体的成员变量和成员函数。
1.棋盘大小参数(本例中成员变量均设为private类型)
int iSize;//棋盘大小参数
int xmax,xmin,ymax,ymin;//棋盘大小参数
int idx;//棋盘每小格长度
2.棋子和矩形框以及相关参数
【因为棋子的数量有一个最大值(下满棋盘时的棋子数)和当前值,所以这里用了两个int变量来表达棋子的数量】
int iPieces;//棋子计数//当前棋子计数为iPieces-1
int iPiecesNum;//棋子总数
Pieces *pPieces;//棋子
int iRectangleNum;//选中框个数
MyRectangle *pmyRectangle;//选中框
3.判断当前状态的成员函数。(设为private类型)
bool NoRectangle(int x,int y);//判断该位置上是否有选中框
bool NoPieces(int x,int y);//判断该位置上是否有棋子
bool IsBlack(int x,int y);//判断该位置上棋子颜色
bool IsConnect(int iDir,int x,int y);//查找相连的棋子
int ConnectNum();//返回相连棋子个数
int ConnectDir();//返回相连棋子方向
tips:由于五子棋只要四个方向有5颗棋子相连就能胜利,所以需要对当前棋子周围8个方向进行搜索,这一功能用int ConnectDir函数来实现,返回的值为1-8,分别代表上、下、左、右、左上、右下、右上、左下8个方向。而判断是否胜利的函数因为与下棋者操作有关,所以设为pubilc类型,见下方bool Success函数。
4.对类中的成员变量进行操作的函数、绘制棋盘、绘制棋子、绘制矩形框、清除棋子、判断胜利、复盘标记等函数都设为public类型
public:
void DrawMainTable(CDC *pDC,CRect rect,int iTableSize);//绘制棋盘函数
void SetPieces();//设参函数//将初始化代码与绘制棋盘代码分开
void DrawRectangle(CDC *pDC,CPoint pt);//绘制选中框/返回是否绘制
void DrawPieces(CDC *pDC,CRect rect,CPoint pt);//绘制棋子函数
bool ClearPieces(CDC *pDC,bool &bBlack);//清除棋子函数/用于悔棋
void KeepPieces(CDC *pDC,CRect rect,int iTableSize);//循环绘制棋子 //用以解决刷新问题
void MachineDraw(CDC *pDC);//机器下棋函数
bool Success();//判断胜利函数
void ReplayMark(CDC *pDC);//复盘标记函数
- 在MainGobang.cpp文件中添加函数实现
限于文章篇幅,这里只给出了部分的函数实现以供参考,实现代码全文我存放在:https://github.com/Vaczzy/SimpleGobang 的 MyGobang_DHK文件夹的MainGobang.cpp文件中
1.绘制棋盘函数
void CMainGobang::DrawMainTable(CDC *pDC,CRect rect,int iTableSize)
{
iSize=iTableSize;
//先覆盖背景
CRect Back;
Back.top=rect.top+1;
Back.bottom=rect.bottom-1;
Back.left=rect.left+1;
Back.right=rect.right-1;
pDC->FillSolidRect(&Back,RGB(240,240,240));//填充
//计算每格长度
idx=(((rect.right-rect.left)/(iTableSize+1))>((rect.bottom-rect.top)/(iTableSize+1)))?
(rect.bottom-rect.top)/(iTableSize+1):
(rect.right-rect.left)/(iTableSize+1);
CBrush brush(RGB(0,0,0));
CBrush *OldBrush=pDC->SelectObject(&brush);
int ixp,iyp;//棋盘星坐标
//绘制天元
ixp=(rect.right-rect.left)/2;
iyp=(rect.bottom-rect.top)/2;
pDC->Ellipse(ixp-3,iyp-3,ixp+3,iyp+3);
//绘制星
ixp-=4*idx;
iyp-=4*idx;//左上角
pDC->Ellipse(ixp-3,iyp-3,ixp+3,iyp+3);
iyp+=8*idx;//左下角
pDC->Ellipse(ixp-3,iyp-3,ixp+3,iyp+3);
ixp+=8*idx;//右下角
pDC->Ellipse(ixp-3,iyp-3,ixp+3,iyp+3);
iyp-=8*idx;//右上角
pDC->Ellipse(ixp-3,iyp-3,ixp+3,iyp+3);
pDC->SelectObject(OldBrush);//恢复设备环境中原来的画笔
brush.DeleteObject();//释放绘图资源
//计算棋盘大小并放置于窗口中间
ymin=(rect.bottom-rect.top)/2-(iTableSize-1)/2*idx;
ymax=ymin+(iTableSize-1)*idx;
xmin=(rect.right-rect.left)/2-(iTableSize-1)/2*idx;
xmax=xmin+(iTableSize-1)*idx;
for(int x=xmin;x<=xmax;x+=idx)
{
pDC->MoveTo(x,ymin);
pDC->LineTo(x,ymax);
}
for(int y=ymin;y<=ymax;y+=idx)
{
pDC->MoveTo(xmin,y);
pDC->LineTo(xmax,y);
}
}
2.查找相连棋子函数
//查找相连的棋子
//up1,down2,left3,right4,up-left5,down-right6,up-right7,down-left8
bool CMainGobang::IsConnect(int iDir,int x,int y)
{
bool bIsConnect=false;
if(((iDir==1)&&(IsBlack(x,y)==IsBlack(x,y-idx))&&(!NoPieces(x,y-idx)))//向上查找
||((iDir==2)&&(IsBlack(x,y)==IsBlack(x,y+idx))&&(!NoPieces(x,y+idx)))//向下查找
||((iDir==3)&&(IsBlack(x,y)==IsBlack(x-idx,y))&&(!NoPieces(x-idx,y)))//向左查找
||((iDir==4)&&(IsBlack(x,y)==IsBlack(x+idx,y))&&(!NoPieces(x+idx,y)))//向右查找
||((iDir==5)&&(IsBlack(x,y)==IsBlack(x-idx,y-idx))&&(!NoPieces(x-idx,y-idx)))//向左上查找
||((iDir==6)&&(IsBlack(x,y)==IsBlack(x+idx,y+idx))&&(!NoPieces(x+idx,y+idx)))//向右下查找
||((iDir==7)&&(IsBlack(x,y)==IsBlack(x+idx,y-idx))&&(!NoPieces(x+idx,y-idx)))//向右上查找
||((iDir==8)&&(IsBlack(x,y)==IsBlack(x-idx,y+idx))&&(!NoPieces(x-idx,y+idx))))//向左下查找
{
bIsConnect=true;
}
return bIsConnect;
}
step3:在xxxx(xxxx为你创建的工程名)Dlg.cpp中编写与界面有关的代码
这一步主要是编写一些与界面有关的代码。限于篇幅,不再赘述。
(完整代码可参考 https://github.com/Vaczzy/SimpleGobang)
tips:如果你对这个程序有任何疑问或者改进的建议,欢迎大家下方留言。
程序界面
打开程序
棋局进行中
棋局结束
最后,如果你对这个程序的算法有任何改进意见可以到我的github地址:https://github.com/Vaczzy/SimpleGobang对代码进行修改和优化。在这个地址中,你将在README.md看到对这个程序更详细的描述,我还在其中贴上了我的邮箱地址,如有任何问题,你还可以通过这个地址联系我 :)
希望这篇文章能够帮助到大家,也希望看到这篇文章的你能够提出自己的建议,大家共同进步!