C语言游戏2五子棋人机对战.docx
- 文档编号:4364047
- 上传时间:2022-11-30
- 格式:DOCX
- 页数:28
- 大小:1.43MB
C语言游戏2五子棋人机对战.docx
《C语言游戏2五子棋人机对战.docx》由会员分享,可在线阅读,更多相关《C语言游戏2五子棋人机对战.docx(28页珍藏版)》请在冰豆网上搜索。
C语言游戏2五子棋人机对战
五子棋人机对战,AI很低,做参考用,仅仅为大家提供一下思路。
如果有什么好的建议或发现什么问题,希望能告诉我,我的邮箱是caoweizhou@
开发环境:
VisualC++6.0
游戏界面:
C语言游戏2-五子棋(人机对战)
一、开始工作2
二、画图5
三、下棋8
四、判断胜负10
五、人工智能13
六、附加功能17
一、开始工作
新建工程,选MFCAppWizard(exe),添上工程名,确定。
选基于对话框,完成,确定。
插入位图
网上的源码一般都是将棋盘和棋子用画图程序画出来,但我不会弄。
我的方法是直接贴图。
先插入位图(BMP格式),以下是我用的位图,当然你也可以用自己的位图:
插入位图流程:
有时会弹出下面这个窗口,这是完全没有问题的:
位图插入后会自动赋予ID值,我们可以修改一下:
二、画图
///////////////////////////Draw函数/////////////////////////////////////////
添加成员函数Draw:
Draw(intx,inty,UINTbitmap,CDC*pDC)
解释一下:
x,y是画图的坐标
bitmap是图片ID,比如我的黑棋图片ID就是IDB_BLACK
pDC是显示图片窗口的句柄
我的画图函数是下面这样的,其中要注意两个函数BitBlt和TransparentBlt,程序后有解释:
voidCMyDlg:
:
Draw(intx,inty,UINTbitmap,CDC*pDC)
{
//装载图片
CBitmapm_bmp;
m_bmp.LoadBitmap(bitmap);
//创建画布,比如要在窗口显示,则pDC为窗口句柄
CDCdc;
dc.CreateCompatibleDC(pDC);
//将位图选到dc中,顺便保存画刷到pOldbmp
//保存画刷、恢复画刷为规范操作,但可以不用
CBitmap*pOldbmp=dc.SelectObject(&m_bmp);
//创建bm,用来获取图片信息,这里是为了获取图片尺寸
BITMAPbm;
m_bmp.GetObject(sizeof(BITMAP),&bm);
//画图
if(IDB_BOARD==bitmap)//画棋盘
pDC->BitBlt(x,y,bm.bmWidth,bm.bmHeight,&dc,0,0,SRCCOPY);
else
{//每个图片里有4X4个棋子,我只要画出一个就行了
intw=bm.bmWidth/4;
inth=bm.bmHeight/4;
TransparentBlt(pDC->m_hDC,x,y,w,h,dc.m_hDC,0,0,w,h,RGB(255,255,255));
}
dc.SelectObject(pOldbmp);//恢复画刷
}
pDC->BitBlt(x,y,bm.bmWidth,bm.bmHeight,&dc,0,0,SRCCOPY);
功能是贴图:
将dc中的位图,截取大小bm.bmWidth,bm.bmHeight,粘贴到pDC所指的设备,贴图坐标x,y。
最后一个参数为粘贴方式,我们是直接粘贴,所以是SRCCOPY
TransparentBlt(pDC->m_hDC,x,y,w,h,dc.m_hDC,0,0,w,h,RGB(255,255,255));
功能也是贴图,但图片背景透明:
将dc中的位图(dc.m_hDC是dc的句柄),截取大小w,h,粘贴到pDC所指的设备,贴图坐标x,y,贴图大小为w,h,如果图片大小不符则拉伸或压缩图片。
最后一项是背景色,可以将图片背景透明化。
使用TransparentBlt必须包含头文件和类库,否则编译错误:
#include
#pragmacomment(lib,"msimg32.lib")
函数弄好后就调用这个函数画图了。
先在OnInitDialog函数中加入以下代码,调整对话框大小,并隐藏按钮:
//TODO:
Addextrainitializationhere
MoveWindow(0,0,520,540);//窗口定位
CenterWindow();//居中窗口
GetDlgItem(IDOK)->ShowWindow(SW_HIDE);
GetDlgItem(IDCANCEL)->ShowWindow(SW_HIDE);
或者直接在资源窗口中调整对话框:
然后在OnPaint函数中加入以下代码画图:
CDC*pDC=GetDC();//获取当前窗口句柄
Draw(13,13,IDB_BOARD,pDC);//画棋盘
//Draw(0,0,IDB_BLACK,pDC);//画黑棋,只是为了查看显示效果
调整一下界面,希望你没有强迫症,可不要在调整上花太多时间了。
下面是我做出的效果:
三、下棋
现在要将棋子准确下到各个点上,我用的棋盘,间距为34,点击鼠标时获取点击坐标x,y,然后x/34,y/34,确定棋子下到了哪个点上。
////////////////////////OnLButtonUp函数///////////////////////////////////
添加消息处理函数:
我用的消息是WM_LBUTTONUP,也就是当鼠标左键抬起来的时候,函数响应:
以下是函数代码:
voidCMyDlg:
:
OnLButtonUp(UINTnFlags,CPointpoint)
{
//TODO:
Addyourmessagehandlercodehereand/orcalldefault
if(point.x>0&&point.x<480&&point.y>0&&point.y<480)
{
intj=point.x/34;
inti=point.y/34;
intx=j*34;
inty=i*34;
CDC*pDC=GetDC();//获取当前设备句柄
Draw(x,y,IDB_BLACK,pDC);
}
CDialog:
:
OnLButtonUp(nFlags,point);
}
函数中,point.x,point.y为点击鼠标时的坐标
现在可以将棋子准确地下到点上了,可是就算点上有已经有棋子了,点鼠标后也会下棋。
所以我们用一个二维数组存储棋盘上的棋子。
添加成员变量:
初始化成员变量,在OnPaint函数里加入下面代码。
画完棋盘的同时,初始化棋盘。
for(inti=0;i<15;i++)
for(intj=0;j<15;j++)
chess[i][j]=0;
修改OnLButtonUp函数中的下棋代码原来是直接用Draw(x,y,IDB_BLACK,pDC);画棋,现在改成下面的内容,当点上没有棋时才下棋:
if(0==chess[i][j])
{
Draw(x,y,IDB_BLACK,pDC);
chess[i][j]=1;//下的黑棋,这一点变成1
}
下棋功能完成了。
四、判断胜负
////////////////////////////iswin函数/////////////////////////////////////////
先添加一个BOOL类型的成员函数iswin(inti,intj),每下一个棋子都通过这个棋子判断是否赢了。
是则返回TRUE(或1),否则返回FALSE(或0)。
默认返回FALSE。
在OnLButtonUp函数中调用iswin。
如果赢了弹出窗口"我赢了,结束战斗!
",初始化棋盘:
voidCMyDlg:
:
OnLButtonUp(UINTnFlags,CPointpoint)
{
//TODO:
Addyourmessagehandlercodehereand/orcalldefault
if(point.x>0&&point.x<480&&point.y>0&&point.y<480)
{
intj=point.x/34;
inti=point.y/34;
intx=j*34;
inty=i*34;
CDC*pDC=GetDC();//获取当前设备句柄
if(0==chess[i][j])
{
Draw(x,y,IDB_BLACK,pDC);
chess[i][j]=1;
}
//判断输赢
if(iswin(i,j))
{
MessageBox("我赢了,结束战斗!
","提示",MB_OK);
Invalidate(FALSE);
}
}
CDialog:
:
OnLButtonUp(nFlags,point);
}
MessageBox弹出窗口"我赢了,结束战斗!
"
Invalidate(FALSE)会放出一个WM_PAINT消息,间接调用OnPaint函数,初始化棋盘。
/////////////////////////////search函数//////////////////////////////////////
那到底怎么判断输赢呢?
我采用的是下面这个方法:
申明变量alive1,alive2,用来判断活棋还是死棋。
申明变量count用来判断连子的个数。
以横向四子为例,我刚下了第三个棋子。
先从第三个棋子位置开始,往右边扫描,如果下的也是黑棋,则count++。
如果遇到的不是黑棋,判断是不是空地。
如果是空地alive1=1,表示右边一头是活的。
否则alive=0。
然后回到第三子位置,往左边扫描。
对于扫描,我这里添加一个成员函数search用来扫描
代码比较长,注意不要出错了,不然调试很麻烦的。
intCMyDlg:
:
search(inti,intj,intm,intn)
{
inttempi,tempj,count=-1;
intalive1=0,alive2=0;
//第一次扫描
tempi=i;tempj=j;
while(tempi>0&&tempi<15&&
tempj>0&&tempj<15&&
chess[tempi][tempj]==chess[i][j])
{
tempi+=m;tempj+=n;
count++;
}
if(chess[tempi][tempj]==0)alive1=1;
//第二次扫描
tempi=i;tempj=j;
while(tempi>0&&tempi<15&&
tempj>0&&tempj<15&&
chess[tempi][tempj]==chess[i][j])
{
tempi-=m;tempj-=n;
count++;
}
if(chess[tempi][tempj]==0)alive2=1;
if(count>=5)return5;
elseif(alive1&&alive2)returncount;
elseif((alive1||alive2)&&count!
=1)returncount+4;
return0;
}
函数的参数i,j是下棋的位置,m,n表示扫描的方向。
比如从左下角往右上角扫描,坐标上是x+1,y-1,对应的n=1,m=-1(x对应的是j,n,y对应的x,m)。
m,n与方向的对应关系:
//m,n
//1,0从上到下
//0,1从左到右
//1,1左上到右下
//1,-1右上到左下
还有count的值,活2、3、4还有5都直接返回值,死的返回count+4
//返回8死4:
deadfour
//返回7死3:
deadthree
//返回6死2:
deadtwo
//返回5成5:
five
//返回4活4:
alivefour
//返回3活3:
alivethree
//返回2活2:
alivetwo
//返回0,
完成搜索函数search后,在iswin函数中调用,如果search返回的是5,赢了:
BOOLCMyDlg:
:
iswin(inti,intj)
{
if(search(i,j,0,1)==5||search(i,j,1,0)==5
||search(i,j,1,1)==5||search(i,j,1,-1)==5)
returnTRUE;
returnFALSE;
}
判断胜负功能完成
五、人工智能
/////////////////////////////AIplay函数//////////////////////////////////////
添加成员函数AIplay,电脑下棋函数
每次人下完后,电脑就下。
所以把AIplay放进OnLButtonUp函数的if(0==chess[i][j])下面:
if(0==chess[i][j])
{
Draw(x,y,IDB_BLACK,pDC);
chess[i][j]=1;
AIplay();
}
此时的AIplay函数还是个空函数。
电脑下棋应该找到最有利的位置,不仅要找电脑有利的位置,还要找人有利的位置,然后比较谁更有利。
如果电脑有利,电脑进攻;如果人有利,电脑防守。
为了寻找这个有利位置,添加成员函数searchvalue。
searchvalue(int&best_i,int&best_j,intcolor)
best_i,best_j是最有利的位置,注意这里用的是&best_i,&best_j,即“引用参数”,引用参数可以在函数中改变参数数据,普通参数不行。
color是棋子的颜色,1为黑,-1为白。
在AIplay函数中调用searchvalue函数:
voidCMyDlg:
:
AIplay()
{
//白棋和黑棋的分数
intwhite_value,black_value;
//白棋和黑棋的有利位置
intwi,wj,bi,bj;
//得到分数和有利位置
white_value=searchvalue(wi,wj,-1);
black_value=searchvalue(bi,bj,1);
//准备画棋
CDC*pDC=GetDC();
intx,y;
//如果黑棋更有利,电脑防守
if(white_value { x=bj*34;y=bi*34; Draw(x,y,IDB_WHITE,pDC); chess[bi][bj]=-1; if(iswin(bi,bj)) { MessageBox("电脑胜利,结束战斗! ","提示",MB_OK); Invalidate(FALSE); } } //如果白棋更有利,电脑进攻 else { x=wj*34;y=wi*34; Draw(x,y,IDB_WHITE,pDC); chess[wi][wj]=-1; if(iswin(wi,wj)) { MessageBox("电脑胜利,结束战斗! ","提示",MB_OK); Invalidate(FALSE); } } } 为了测试,我们先为这个researchvalue函数设置一些值: intCMyDlg: : searchvalue(int&best_i,int&best_j,intcolor) { //白棋,有利位置设为(1,1),返回值设为0。 if(-1==color) { best_i=1;best_j=1; return0; } //黑棋,有利位置设为(2,2),返回值设为0. if(1==color) { best_i=2;best_j=2; return1; } } 因为黑棋的分数高,所以白棋防守,应该下到(2,2)位置。 /////////////////////////////getscore函数///////////////////////////////////// 为了得到黑棋和白棋的分数,添加成员函数getscore: 这个函数的代码非常多,我分开讲。 首先是声明变量,这个是根据实际情况变的: //状态 //返回8死4: deadfour //返回7死3: deadthree //返回6死2: deadtwo //返回5成5: five //返回4活4: alivefour //返回3活3: alivethree //返回2活2: alivetwo //返回0, intdeadfour=0,deadthree=0,deadtwo=0; intfive=0,alivefour=0,alivethree=0,alivetwo=0; intstatus[4],score; 然后,在这个位置下个棋,用search函数判断这个棋子各个方向的状态。 判断结束后记得把chess[i][j]变回0。 chess[i][j]=color; //从左到右 status[0]=search(i,j,0,1); //从上到下 status[1]=search(i,j,1,0); //从左上到右下 status[2]=search(i,j,1,1); //从左下到右上 status[3]=search(i,j,1,-1); chess[i][j]=0 统计各种情况的数目(活2、3、4,死2、3、4,成5) for(intn=0;n<4;n++) { switch(status[n]) { case8: deadfour++;break; case7: deadthree++;break; case6: deadtwo++;break; case5: five=1;break; case4: alivefour=1;break; case3: alivethree++;break; case2: alivetwo++;break; } } 给这个位置打分,并在最后记得返回分数score。 //成5 if(five)score=100000; //活4 elseif(alivefour)score=10000; //双死4 elseif(deadfour>=2)score=10000; //死4活3 elseif(deadfour&&alivethree) score=10000; //双活3 elseif(alivethree>=2)score=5000; //活3双活2 elseif(alivethree&&alivetwo>=2) score=5000; //活3死3 elseif(alivethree&&deadthree) score=1000; //单死4 elseif(1==deadfour)score=500; //单活3 elseif(1==alivethree)score=200; //双活2 elseif(alivetwo>=2)score=100; //双死3 elseif(deadthree>=2)score=50; //单活2 elseif(1==alivetwo)score=10; //单死3 elseif(1==deadthree)score=5; returnscore; getscore函数完成 /////////////////////////////searchvalue函数////////////////////////////////// 然后修改searchvalue函数: intCMy1Dlg: : searchvalue(int&best_i,int&best_j,intcolor) { intmaxvalue=0,value; for(inti=1;i<14;i++) for(intj=1;j<14;j++) { if(color==chess[i][j]) { for(intm=i-1;m<=i+1;m++) for(intn=j-1;n<=j+1;n++) { if(0==chess[m][n]) { value=getscore(m,n,color); if(maxvalue { maxvalue=value; best_i=m; best_j=n; } } }//结束for } }//结束for returnmaxvalue; } 现在解释一下这最后完成的函数。 假设搜索黑棋最佳位置,整个棋盘从左往右从上到下扫描,当遇到黑棋时,分析黑棋周围8个位置是否为空,如果是空则判断这个空位的分数。 找到分数最高的位置,并返回这个分数。 五子棋人机对战完成 六、附加功能 存档、读档功能 我们可以把对话框上的两个按钮改造成存档按钮和读档按钮,注意修改ID。 在OnInitDialog函数中,重新调整对话框大小,和按钮位置 //TODO: Addextrainitializationhere MoveWindow(0,0,520,560);//窗口定位 CenterWindow();//居中窗口 GetDlgItem(IDC_SAVE)->MoveWindow(10,500,50,20); GetDlgItem(IDC_OPEN)->MoveWindow(70,500,50,20); 查看->建立类向导 给两个按钮添加关联函数 /////////////////////////////OnSave函数////////////////////////////////// 存档函数: voidCMyDlg: : OnSave() { //TODO: Addyourcontrolnotificationhandlercodehere //设置保存的文件,后缀名为.wzq CFileDialogdlg(FALSE,"wzq", NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT, "(*.WZQ)|*.wzq|AllFiles|*.*||",this); //如果
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 语言 游戏 五子棋 人机