人工智能课程设计报告姓名:班级:191092学号:*******指导老师:2011年10月序言经过近一周的奋战,我终于初步实现了我的“四子棋”游戏。
起初,我选择这个游戏是因为它很独特、少有人做,而且我想当然的认为四子比五子少一子,实现起来应该相对简单,但是面对一个个头疼的错误,遭受一次又一次失败的打击,我才发现自己想错了,四子棋人机对战比五子棋更让人纠结。
暑期我和班上两名同学xxx,xx一起完成过五子棋和黑白棋的编程。
当时,五子棋我们是参照别人的模板完成的,黑白棋是在透彻研究过五子棋的基础上不断讨论、优化算法最终完成的。
现在,他们一个继续做五子棋一个做黑白棋,将其完善作为人工智能的课程设计课题。
我只得另陌新路,关注起了四子棋。
界面的搭建以及二人对战的实现只用了我两个晚上的时间,当然我的界面构建的相对简单。
二人对战实现关键一步就是输赢判断函数judge()的编写。
人机对战整整折腾了自己四五天的时间,我本来对于博弈过程中alpha—beta剪枝算法很理解的,但是具体要自己编程实现时我觉得自己只了解到其皮毛。
所以,我不断看五子棋程序、单步跟踪、寻问同学,终于算是把其递归和剪枝过程搞得明明白白。
花了两天完成了alpha—beta剪枝算法我本以为核心都解决了,却不知更让人头疼的是评估函数的确定还有统计当前棋局状况的函数Calculate()的编写。
难题终究是可以被客服的,我花了整整一天写完了我的Calculate()函数。
直至10月27日上午我依然没有让机器足够智能,或者说它目前很呆板。
我一直在找原因,自己的模块设计算法思路都是对的,请教过同学她也觉得设计思路没有错。
现在,我整体的想了一下,觉得应该是我的静态评估函数设计的不够合理,或者说目前的设计方法对于五子棋很合理但是对于规则不同的四子棋就可能存在缺陷。
既然目前时间不允许没能实现足够智能,那就只能把这粗略的程序提交。
我一定会继续完善、改进算法,重新设计评估函数,争取让机器达到理想的“智能”。
一、课题选则1.题目概述利用VC++实现四子棋游戏,要求提供可视化界面以及二人对战、人机对战的功能。
四子棋游戏规则如下:四子棋的棋盘共有7行7列,棋盘是垂直摆放,每名玩者只能左右控制落子的位置。
两名玩者轮流每次把一只棋子放进棋盘任何未全满的一行中,棋子会佔据一行中最底未被佔据的位置。
两名玩者任何一方先以四只棋子在横,竖或斜方向联成一条直线,便可获胜,游戏亦结束。
假如棋盘已完全被棋子填满,但仍未有任何一方成功把四只棋子成一直线,则成为和局。
2.选题缘由四子棋游戏规则独特,所以其规则的设计将有别于五子棋等通常的棋类游戏。
四子棋人机对战功能的实现需要用到博弈树搜索中的核心算法:。
与五子棋不同,因为四子棋的独特性网络中根本没有某某“热心”网友的游戏源码,我找了很久也只找到一个二人对战的四子棋,他还是用C语言在dos下实现的,一点也不人性化。
综合以上种种我决定挑战一下自己,用VC实现“我的四子棋”游戏。
二、需求分析1.功能需求1.1提供合理的人机交互界面1.2提供二人对战功能1.3提供人机对战功能2.数据结构2.1棋盘数据采用一二维数组grid[7][7]存储三、模块设计A.整体思路通过对需求的分析,我认为我的设计工作主要可以分为三大块,第一块是主界面的搭建和棋盘的绘制,第二块是落子和输赢判断的实现,第三块就是二人对战和人机对战的实现。
对于第一块,因为有图形学和图像处理的上机经验,我将果断的选择单文档作为界面窗口,用绘制的方式搭建棋盘。
对于第二块,对于落子其实就是在响应鼠标左键按下的消息后记录下所需放子的坐标,然后改变内部存储数据,调用绘图函数ondraw()进行绘制。
输赢判断就是对于每下一颗棋子搜索其横向竖向交叉斜向的棋子,如若有四颗连成一线的就判为赢。
对于第三模块,这是整个游戏的核心,二人对战相对好实现。
人机对战需要用到博弈树搜索需要用到alphabeta剪枝算法,还需要设定以静态评估函数,这两方面是最困难也是最重要的部分。
B.具体设计1.主界面设计1.1单文档窗口大小的限定我们知道通过VC++工程创建的单文档都有默认的大小,那个大小太大不适合作为游戏的界面窗口大小。
所以我希望自定义其大小,而且在此游戏中不需要用工具栏,所以我希望通过人为操作去掉工具栏。
具体实现:在Frame框架类里面的PreCreateWindow( )创建窗口函数总添加以下代码cs.style&=~WS_MAXIMIZEBOX; //禁止对文档最大化操作cs.style&=~WS_THICKFRAME;cs.cx=700;/ /自定义文档大小cs.cy=500;cs.y=180;cs.x=300;cs.style&=~FWS_ADDTOTITLE;//将FWS_ADDTOTITLE去掉cs.lpszName="我的四子棋"; //改变文档标题将OnCreate(LPCREATESTRUCT lpCreateStruct)函数中以下代码屏蔽即可去掉工具栏/*if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||!m_wndToolBar.LoadToolBar(IDR_MAINFRAME)){TRACE0("Failed to create toolbar\n");return -1; // fail to create}*/1.2棋盘绘制棋盘绘制在view类里面的ondraw函数里实现,创建画笔蓝色画笔通过MoveTo、LineTo函数画出棋格,创建天蓝色画刷绘制棋格背景色。
创建红色、黄色画刷用于绘制双方棋手棋子。
具体代码如下:CRect rect;CBrush *brush,*brush1,*brush2,*brush3;brush = new CBrush;brush1 = new CBrush;brush2 = new CBrush;brush3 = new CBrush;brush->CreateSolidBrush(RGB(150, 205 ,205));brush1->CreateSolidBrush(RGB(255, 0 ,0));//红色画刷brush2->CreateSolidBrush(RGB(255, 255 ,0));//黄色画刷brush3->CreateSolidBrush(RGB(128, 128 ,28));//bai色画刷this->GetClientRect(&rect);pDC->FillRect(&rect,brush);CClientDC dc(this);CPen m_pen(PS_SOLID,1,RGB(0,0,255));//将画笔选入设备列表CPen *pOldPen = dc.SelectObject(&m_pen);int i;for(i=0;i<8;i++){dc.MoveTo(bx,by+width*i);dc.LineTo(bx+width*7,by+width*i);}for(i=0;i<8;i++){dc.MoveTo(bx+width*i,by);dc.LineTo(bx+width*i,by+width*7);}for (i=0;i<7;i++){for (int j=0;j<7;j++){switch(grid[i][j]){case 1:pDC->SelectObject(brush1);pDC->Ellipse(bx+width*(i+0.5)-r,by+width*(j+0.5)-r,bx+width*(i+0.5)+r,by+width*(j+0.5)+r);break;case 2://pDC->SelectStockObject(WHITE_PEN);pDC->SelectObject(brush2);pDC->Ellipse(bx+width*(i+0.5)-r,by+width*(j+0.5)-r,bx+width*(i+0.5)+r,by+widt h*(j+0.5)+r);//pDC->SelectStockObject(BLACK_PEN);break;case 3:pDC->SelectObject(brush3);pDC->Ellipse(bx+width*(i+0.5)-(r+5),by+width*(j+0.5)-(r+5),bx+width*(i+0.5)+( r+5),by+width*(j+0.5)+(r+5));break;default:break;}}}2.模式选择设计2.1更改菜单按钮在菜单栏中添加模式按钮“二人对战”和“人机对战”2.2响应选择按钮消息通过MFC类向导在View类中响应“二人对战”和“人机对战”的按钮消息,实现方式如下:void CMYSIZIQIView::OnPvsp(){// TODO: Add your command handler code herereset();pvsp=true;Invalidate();//是当前窗口无效,需要重绘}void CMYSIZIQIView::OnPvsc(){// TODO: Add your command handler code herereset();pvsc=true;RorY=true;pvsp=false;Invalidate();//是当前窗口无效,需要重绘}3.Onleftbuttondown()函数设计3.1根据鼠标坐标转换出在存储数组中对应的下标int px=(point.x-bx)/width;int py=(point.y-by)/width;3.2限定落子只可以在该列最下面一个空格for(int j=6;j>=0;j--){if(grid[px][j]==0){py=j;break;}}3.3二人对战、人机对战程序响应if(0<=px&&px<7&&0<=py&&py<7&&grid[px][py]==0){if(!pvsp&&!pvsc) MessageBox("请选择对战模式");else if(pvsp) //二人对战{grid[px][py]=c;//根据该点颜色选择对应的画笔color=!color;//换位另一颜色r.SetRect(point.x-8*width,point.y-8*width,point.x+8*width,point.y+8*width);InvalidateRect(&r);judge(c,px,py);//判断有没有取胜}else if (pvsc)//人机对战模式{color=!color;if (!judge(c,px,py)) // if player doesn't win{ alphabeta(LONG_MIN,LONG_MAX,depth,false); //computer puts the color=!color;InvalidateRect(&r);judge(c,bestmove[0],bestmove[1]);}}}4.judge()函数设计4.0每一个方向定义一个变量用以记录当前棋子个数:HWIN——横方向SWIN——竖方向LRWIN——左上右下方向RLWIN——右上左下方向4.1横向判断是否四子连线int HWIN=1;for ( i=x-1;i>=0;i--)//&&&&&&&&&棋子左边{//MessageBox("红赢");if(grid[i][y]==color) HWIN++;if(grid[i][y]!=color) break;}if(HWIN>=4&&color==1)//左边可能已经三个棋子{for ( i=x-1;i>=0;i--)//一方赢了则改变其颜色if(grid[i][y]==color){grid[i][y]=3;}grid[x][y]=3;MessageBox("红赢");reset();Invalidate();return true;}else if (HWIN>=4&&color==2){for ( i=x-1;i>=0;i--)//一方赢了则改变其颜色if(grid[i][y]==color){grid[i][y]=3;}grid[x][y]=3;MessageBox("黄赢");reset();Invalidate();return true;}t0=HWIN;for (i=x+1;i<=6;i++)//&&&&&&&&&&&&棋子右边{//MessageBox("红赢");if(grid[i][y]==color) HWIN++;if(grid[i][y]!=color) break;}if(HWIN>=4&&color==1){for ( i=x+1;i<=6;i++)//一方赢了则改变其颜色if(grid[i][y]==color){grid[i][y]=3;}grid[x][y]=3;if(HWIN-t0==2) grid[x-1][y]=3;if(HWIN-t0==1) {grid[x-1][y]=3;grid[x-2][y]=3;}MessageBox("红赢");reset();Invalidate();return true;}else if (HWIN>=4&&color==2){for ( i=x+1;i<=6;i++)//一方赢了则改变其颜色if(grid[i][y]==color){grid[i][y]=3;}grid[x][y]=3;if(HWIN-t0==2) grid[x-1][y]=3;if(HWIN-t0==1) {grid[x-1][y]=3;grid[x-2][y]=3;}Invalidate();return true;}具体判断细节见注释4.2竖向判断是否四子连线//***********************竖向判断***************************// int SWIN=1;for ( j=y+1;j<=6;j++)//&&&&&&&&&棋子下边{//MessageBox("红赢");if(grid[x][j]==color) SWIN++;if(grid[x][j]!=color) break;}if(SWIN>=4&&color==1)//下边可能已经三个棋子{for (j=y+1;j<=6;j++)//一方赢了则改变其颜色if(grid[x][j]==color&&j-y<=3){grid[x][j]=3;}grid[x][y]=3;MessageBox("红赢");reset();Invalidate();return true;}else if (SWIN>=4&&color==2){for (j=y+1;j<=6;j++)//一方赢了则改变其颜色if(grid[x][j]==color&&j-y<=3){grid[x][j]=3;}grid[x][y]=3;MessageBox("黄赢");reset();Invalidate();return true;}具体判断细节见注释4.3左上右下方向判断是否四子连线//**********************左上右下方向*************************// int LRWIN=1;for (i=x-1,j=y-1;i>=0&&j>=0;i--,j--)//&&&&&&&&&棋子左上方{//MessageBox("红赢");if(grid[i][j]==color) LRWIN++;if(grid[i][j]!=color) break;}if(LRWIN>=4&&color==1)//左上可能已经三个棋子{for (i=x-1,j=y-1;i>=0&&j>=0;i--,j--) //一方赢了则改变其颜色if(grid[i][j]==color&&(y-j<=3)){grid[i][j]=3;}grid[x][y]=3;reset();Invalidate();return true;}else if (LRWIN>=4&&color==2){for (i=x-1,j=y-1;i>=0&&j>=0;i--,j--) //一方赢了则改变其颜色if(grid[i][j]==color&&(y-j<=3)){grid[i][j]=3;}grid[x][y]=3;MessageBox("黄赢");reset();Invalidate();return true;}t0=LRWIN;for (i=x+1,j=y+1;i<=6&&j<=6;i++,j++)//&&&&&&&&&&&&棋子右下边{if(grid[i][j]==color) LRWIN++;if(grid[i][j]!=color) break;}if(LRWIN>=4&&color==1){for (i=x+1,j=y+1;i<=6&&j<=6;i++,j++) //一方赢了则改变其颜色if(grid[i][j]==color&&(j-y)<=3){grid[i][j]=3;}grid[x][y]=3;if(LRWIN-t0==2) grid[x-1][y-1]=3;if(LRWIN-t0==1) {grid[x-1][y-1]=3;grid[x-2][y-2]=3;}MessageBox("红赢");reset();Invalidate();return true;}else if (LRWIN>=4&&color==2){for (i=x+1,j=y+1;i<=6&&j<=6;i++,j++) //一方赢了则改变其颜色if(grid[i][j]==color&&(j-y)<=3){grid[i][j]=3;}grid[x][y]=3;if(LRWIN-t0==2) grid[x-1][y-1]=3;if(LRWIN-t0==1) {grid[x-1][y-1]=3;grid[x-2][y-2]=3;}MessageBox("黄赢");reset();Invalidate();return true;}具体判断细节见注释4.4右上左下方向判断是否四子连线//***********************右上左下方向**************************// int RLWIN=1;for (i=x+1,j=y-1;i<=6&&j>=0;i++,j--)//&&&&&&&&&棋子右上方{if(grid[i][j]==color) RLWIN++;if(grid[i][j]!=color) break;}if(RLWIN>=4&&color==1)//可能已经三个棋子{for (i=x+1,j=y-1;i<=6&&j>=0;i++,j--) //一方赢了则改变其颜色if(grid[i][j]==color&&(y-j<=3)){grid[i][j]=3;}MessageBox("红赢");reset();Invalidate();return true;}else if (RLWIN>=4&&color==2){for (i=x+1,j=y-1;i<=6&&j>=0;i++,j--) //一方赢了则改变其颜色if(grid[i][j]==color&&(y-j<=3)){grid[i][j]=3;}grid[x][y]=3;MessageBox("黄赢");reset();Invalidate();return true;}t0=RLWIN;for (i=x-1,j=y+1;i>=0&&j<=6;i--,j++)//&&&&&&&&&&&&棋子左下边{if(grid[i][j]==color) RLWIN++;if(grid[i][j]!=color) break;}if(RLWIN>=4&&color==1){for (i=x-1,j=y+1;i>=0&&j<=6;i--,j++) //一方赢了则改变其颜色if(grid[i][j]==color&&(j-y<=3)){grid[i][j]=3;}grid[x][y]=3;if(RLWIN-t0==2) grid[x+1][y-1]=3;if(RLWIN-t0==1) {grid[x+1][y-1]=3;grid[x+2][y-2]=3;}MessageBox("红赢");reset();Invalidate();return true;}else if (RLWIN>=4&&color==2){for (i=x-1,j=y+1;i>=0&&j<=6;i--,j++) //一方赢了则改变其颜色if(grid[i][j]==color&&(j-y<=3)){grid[i][j]=3;}grid[x][y]=3;if(RLWIN-t0==2) grid[x+1][y-1]=3;if(RLWIN-t0==1) {grid[x+1][y-1]=3;grid[x+2][y-2]=3;}MessageBox("黄赢");reset();Invalidate();return true;}具体判断细节见注释5.reset()函数设计该函数很简单,即是将数组存储空间归零化。