1人工智能project报告学号:20091000608姓名:王沙沙班级:191091指导老师:赵老师2011年10月252目录1.自动扫雷…………………………………………………………3需求分析…………………………………………………………3游戏规则…………………………………………………………3扫雷游戏设计……………………………………………………3自动扫雷设计……………………………………………………5用户手册…………………………………………………………5运行结果…………………………………………………………6结论………………………………………………………………7主要算法代码……………………………………………………731自动扫雷1.问题描述、需求分析扫雷是Windows操作系统自带的桌面小游戏之一,由于其规则简单,长期以来一直受到电脑用户的欢迎。自动扫雷是开始扫雷后根据扫雷规则利用计算机进行扫雷。开始棋局,单击第一个按钮,因为执行第一个动作时所有按钮是雷的概率都相等,为了方便起见每次开局后都按下第一个按钮,如果不幸第一个按钮就是雷,那么单击--开始--重新开局。2.游戏规则游戏界面如下图所示,游戏区域是由MXN个格子组成的“雷区”,左上方的数字显示雷区中未被标出的地雷的个数,右上方的数字显示用户从第一次点击开始所用的时间,单位是秒。中间的图标指示游戏的状态(完成:笑脸;失败:哭脸)。用户需要通过鼠标操作来确定雷区中所有地雷的位置:左键单击:尝试某个格子是否有地雷。如果该格子下有地雷,则游戏失败·如果该格子下没有地雷但其周围八个格子下有地雷,该格子会显示其周围八个格子中地雷的个数;如果该格子下没有地雷且其周围八个格子下也没有地雷,则此处被挖开。右键单击:改变某个格子是否有雷的标志(工)。或左右键同时单击:当该格子周围已标地雷的个数与格子中的数字相同时,则挖开周围的其余格子。当提示该格子周围已标地雷的个数少于格子;3.扫雷游戏设计每个格子有如下几种状态:{MS_NORMAL,MS_OPEN,MS_FLAG,MS_QUEST,MS_EXPLODE}MINESTATUS1)格子类共有变量如下:MINESTATUSm_nStatus;//格子状态intm_nMineNum;//格子周围的雷数,即格子上显示的数字CWnd*m_pParent;//父窗口intm_nX,m_nY;//按下按钮的横纵坐标成员函数SetStatus(MINESTATUSnStatus)//设置状态GetStatus()//获取状态4SetMineNum(intnMineNum)//设置雷数GetMineNum()//获取雷数DrawItem(BOOLbFail=TRUE);//绘制格子2)布雷类共有变量如下intm_nRow,m_nCol;//棋盘有多少行多少列intm_nMineNum;//棋盘上的雷数intm_nX,m_nY;//棋盘的位置intm_nOldMine;intm_aMineTest[9];intm_nMineTest;CMineButton*m_aLandMine[10000];//棋盘CWnd*m_pParent;成员函数Create(intnRow,intnCol,intnMineNum,intnX,intnY,CWnd*pParent);Reset(intnRow,intnCol,intnMineNum);//重新布局ReDraw(BOOLbFail);//刷新棋局LButtonDown(CPointpoint);//左键按下BothButtonDown(CPointpoint);//左键与右键同时按下RButtonDown(CPointpoint);//右键按下MouseMove(CPointpoint,intbBothDown);//鼠标移动LButtonUp(CPointpoint);//左键弹起BothButtonUp(CPointpoint);//左键与右键同时弹起OpenMine(intnCurMine);//开局函数IsSuccess();//判断是否扫雷成功RndBtShdDn(intnCurMine);GetMineNum()//获取棋盘的雷数GetRight(intnCurMine)//获取四邻域的中心点像素正右方像素点的位置GetLeft(intnCurMine)//获取四邻域的中心点像素正下左像素点的位置GetTop(intnCurMine)//获取四邻域的中心点像素正上方像素点的位置GetBot(intnCurMine)//获取四邻域的中心点像素正下方像素点的位置3)布雷算法:首先定义一个n*n的二维数组,该数组的i-1到i+1,j-1到j+1除去i,j本身。值为周围有几个雷。如果该数组的值为10则本身是雷。然后自动生成m个雷,让这m个雷分布在界面上。被选中的值i,j点的值为10,从i-1到i+1,从j-1到j+1的值都+1;然后在界面上想办法区分出n*n个格子,然后把格子的坐标和二维数组关联起来做扫雷那首先一定是生成一个矩阵,比如10*10的区域,里面再用数字来表示有没有雷用一个结构体数组保存雷的隐藏,翻开和标志情况关于你所说的扩展,我想是不是数组的边界问题?到了数组边界上,遇到下标越界错误,为矩阵再加一道边界比如你要生成3*3的雷区,而实际上,需要定义5*5的数组,而数组的四周定义为没有雷的空地,如下:0代表空地,1代表雷50000001010001000000000000搜索的时候就在数组中间这3*3的区域内进行,比如点击了ime(2,2)的区域,也就是显示出来的雷区中的第一块空地,则程序首先需要判断这里有没有雷,然后再计算周围八个区域的雷的情况,将累加结果放到这个区域内,如下:Ifime(2,2)=1then踩到地雷的代码else'没踩到地雷fori=1to3forj=1to3ifi=2andj=2thennextimesum=imesum+ime(i,j)j++i++这样就统计出了该区域周围雷的数量.4)为程序添加背景音乐PlaySound(MAKEINTRESOURCE(IDR_WAVE1),::GetModuleHandle(NULL),SND_ASYNC|SND_RESOURCE|SND_NODEFAULT|SND_LOOP);注意:要播放背景音乐必须加入以下两个头文件#includemmsystem.h//导入声音头文件#pragmacomment(lib,WINMM.LIB)//导入声音头文件库4.自动扫雷设计在第一次点击前,所有格子下面有地雷的概率相同,因此随意点击任何一个即可,定义一个IsChange变量,用于循环扫雷。当某个格子(X)显示的数字(Mine(X))等于它周围被标为地雷的格子的个数,那么它周围未被标示的格子下一定没有地雷。此时可以单击它周围初始状态的格子,或者同时左、右键单击这个显示数字格子。如果中心点的格子数值与周围不知状态的格子数相等,那么它周围没有被标为地雷的格子下面一定有地雷。此时可以右键单击它周围没有被标为地雷的格子。以上两步分别对应一个for循环,第一重循环用于标识肯定是雷的格子(即插旗子),第二重循环用于寻找中心点的格子数值与周围不知状态的格子数相等的格子(即根据第一重循环的结果,找到肯定不是雷的格子并单击),如果第一重循环执行了插旗子的动作IsChange=1,第二种循环如果执行了单击的动作IsChange=1;当IsChange!=0时一直执行上两重循环,直到扫完为止,或者出现猜的情况(踩到雷的情况只有在第一步时才有可能出现,也就是说如果第一步不是雷,那么接下来的整盘棋都不会踩到雷)。4.用户手册6运行程序,单击游戏--开始正常情况下可以扫雷成功,偶然情况即出现猜的情况时给出提示对话框;程序运行结果:第一次就踩到雷扫雷成功扫雷出现猜的情况如下:7结论:此扫雷算法大部分情况可以完成扫雷任务;主要算法程序代码:voidCMineDlg::OnCommandRestart(){bFail=FALSE;if(bStart)KillTimer(1);bStart=FALSE;LandMine.Reset(LandMine.GetRow(),LandMine.GetCol(),LandMine.GetMineNum());TimeCount.SetNumber(0);MineCount.SetNumber(LandMine.GetMineNum());m_Face.SetBitmap(&m_FaceNormal);RedrawWindow();inti,j;intMineNum=LandMine.GetMineNum();intIsChange;//用于退出循环intt,Num,Num1;//Num用于存储获取8邻域MS_EXPLODE状态的个数,t用于获取8领域点的位置intAllItem=LandMine.GetRow()*LandMine.GetCol();//获得棋盘的大小,即一位数组的长度OnLButtonDown(1,CPoint(LandMine.m_aLandMine[0]-m_nX,LandMine.m_aLandMine[0]-m_nY));//每次都是按下第一个按钮,因为第一次按下哪个概率都是相等的OnLButtonUp(1,CPoint(LandMine.m_aLandMine[0]-m_nX,LandMine.m_aLandMine[0]-m_nY));while(MS_EXPLODE&&LandMine.m_aLandMine[1]-m_nStatus==MS_NORMAL&&LandMine.m_aLandMine[10]-m_nStatus==MS_NORMAL)8{if(LandMine.m_aLandMine[0]-m_nStatus==MS_EXPLODE)break;LandMine.Reset(LandMine.GetRow(),LandMine.GetCol(),LandMine.GetMineNum());OnLButtonDown(1,CPoint(LandMine.m_aLandMine[0]-m_nX,LandMine.m_aLandMine[0]-m_nY));//每次都是按下第一个按钮,因为第一次按下哪个概率都是相等的OnLButtonUp(1,CPoint(LandMine.m_aLandMine[0]-m_nX,LandMine.m_aLandMine[0]-m_nY));}do{//第一种情况IsChange=0;for(i=0;iAllItem;i++)//状态MS_NORMAL,MS_OPEN,MS_FLAG,MS_QUEST,MS_EXPLODE{Num=0;//每次在累加的时候千万别忘记初始化if(LandMine.m_aLandMine[i]-m_nStatus==MS_OPEN&&LandMine.m_aLandMine[i]-m_nMineNum0)//对于打开的按钮来说{t=LandMine.GetLeft(i);//4邻域if(t=0&&LandMine.m_aLandMine[t]-m_nStatus!=MS_OPEN)Num++;t=LandMine.GetTop(i);if(t=0){if(LandMine.m_aLandMine[t]-m_nStatus!=MS_OPEN)Num++;t=LandMine.GetLeft(LandMine.GetTop(i));if(t=0&&LandMine.m_aLandMine[t]-m_nStatus!=MS_OPEN)Num++;t=LandMine.GetRight(LandMine.GetTop(i));if(t=0&&LandMine.m_aLandMine[t]-m_nStatus!=MS_OPEN)Num++;}t=LandMine.GetRight(i);if(t