C语言游戏2五子棋人机对战.docx

上传人:b****5 文档编号:4364047 上传时间:2022-11-30 格式:DOCX 页数:28 大小:1.43MB
下载 相关 举报
C语言游戏2五子棋人机对战.docx_第1页
第1页 / 共28页
C语言游戏2五子棋人机对战.docx_第2页
第2页 / 共28页
C语言游戏2五子棋人机对战.docx_第3页
第3页 / 共28页
C语言游戏2五子棋人机对战.docx_第4页
第4页 / 共28页
C语言游戏2五子棋人机对战.docx_第5页
第5页 / 共28页
点击查看更多>>
下载资源
资源描述

C语言游戏2五子棋人机对战.docx

《C语言游戏2五子棋人机对战.docx》由会员分享,可在线阅读,更多相关《C语言游戏2五子棋人机对战.docx(28页珍藏版)》请在冰豆网上搜索。

C语言游戏2五子棋人机对战.docx

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);

//如果

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高中教育 > 高中教育

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1