软件综合实习报告题目:五子棋人机博弈游戏院(系):计算机学院专业:计算机科学与技术姓名:杨祺鹏班级学号:191072-0320071000566指导教师:王改芳2010年9月18日目录一.系统需求分析与总体设计1.需求分析①问题描述②数据流程图2.总体设计①开发背景②开发语言③开发平台和运行平台④总体功能设计二.详细设计与系统实现1.类和类的方法设计①类的设计②类的方法设计2.算法描述三.系统测试1.功能测试2.性能测试四.总结1.系统仍存在的不足2.结论和体会一.系统需求分析与总体设计1.需求分析①问题描述题目:五子棋人机博弈游戏题目要求:实现五子棋游戏的人机博弈。要求:友好的人机图形化界面、方便的操作方式;自动判断输、赢或平;可选择黑白;可悔棋;可以基于人工智能方面的知识进行设计,如:启发式搜索、搜索函数的设置、α_β算法、知识的表示及知识库,推理机等。五子棋简介:五子棋是起源于中国古代的传统黑白棋种之一。顾名思义,只要连成五子即可获得胜利。听上去好像很简单,但是不用技巧好似很难获胜的,这其中就有“活三”,“冲三”等等专业名词,这些我会在后续的程序中进行介绍。五子棋不仅能增强思维能力,提高智力,而且富含哲理,有助于修身养性。五子棋既有现代休闲的明显特征“短、平、快”,又有古典哲学的高深学问“阴阳易理”;它既有简单易学的特性,为人民群众所喜闻乐见。通过问题的描述,初步分析可得出此软件应具有以下的功能:1.友好的界面,方面的操作方式——》可以快速开始游戏,功能明确而且界面明朗2.自动判断输赢——》计算机可以根据棋子的状态判断胜负情况3.可选择黑白——》可以先手下棋和后手下棋4.可悔棋——》具有悔棋功能5.计算机AI——》计算机需要具有一定的AI,需要AI攻守兼备②数据流程图数据流动很少,主要是在算法内部进行计算,实际进行交互的数据是没有的。要求开始游戏落子,33禁手棋子坐标玩家落子取最佳位置并落子(若不分胜负)再来一盘胜负已分玩家计算机AI:即算法判断谁是先手输赢判断是否再来一盘2.总体设计①开发背景软件工程综合实习,因为我对算法很有兴趣,就决定通过这个程序学习一下。②语言选择使用C#平台在设计人机界面时更人性化,画图工具也更为简洁易用。③开发环境及运行环境开发环境:操作系统名称MicrosoftWindows7旗舰版版本6.1.7600版本7600处理器AMDTurion(tm)X2Dual-CoreMobileRM-72,2100Mhz,物理内存(RAM)3.00GB开发平台:MicrosoftVisualStudio2008专业版-简体中文MicrosoftVisualC#2008运行平台:Intel®Pentium®2及以上处理器,32M以上内存,4G以上硬盘Microsoft®Windows™9X/NT操作系统800*600或以上的屏幕分辨率Net.framework2.0及以上版本④具体功能设计主要函数:1.画棋子2.画棋盘3.先手判断4.胜负判断5.计算机AI算法(权值赋值,权值计算,找最大权值)界面设计:1.快速开始游戏2.先手选择3.悔棋,认输,电脑提示4.棋子的坐标显示(计算电脑提示的附加功能)5.帮助这几个模块的重点是计算机AI算法的设计。在参阅了很多资料以后,我发现解决五子棋问题的算法非常之多,遗传算法,α—β剪枝算法等等,这些算法的特点是对后续步骤进行无数次的推演并一一评分,最后去评分最高的一种方案的第一步,在下完一子之后再进行算法计算,虽然AI非常聪明但是计算繁琐且计算时间其长无比。在网上发现的算法有更多的程序员是采用了权值计算的方法,具体来说就是根据每种情况每种可能对每个点都赋不同的权值,AI在判断应该进攻还是应该防守就是依靠权值的大小,权值最大的位置放棋子,采用这种方法虽然笨是笨了些,但是计算速度快,尤其是在棋盘比较小的时候(15*15棋盘),而且在对比中发现这种AI也很聪明。在速度和计算精度之间权衡之后,我准备选择计算权值的方法来做,如果时间仍有富裕的话再使用别的算法来计算并进行对比。二.详细设计与系统实现1.类和类的方法设计①类的设计(后面的方法设计也遵从这个顺序逐个进行详细注释)棋盘类Chessboard棋盘类主要实现对棋盘棋子,以及电脑和人下棋的显示,对虚拟棋盘的定义,对下棋路径的堆栈的初始化,对棋局的初始化。棋子类Stone棋子类主要实现对棋子的画图操作。计算机类Computer计算机类是本程序的核心模块,主要实现计算机对每个棋子每个位置的权值赋值,对每个棋盘位置的权值计算,求最大权值。规则类Rule规则类主要实现对胜负平局的判断,对黑棋禁手的判断,对每个方向上的连子数的计算,对棋局判断是依据连子数计算的。主函数类frmMain主函数主要是对界面上每个按键的功能进行实现,另实现对鼠标的move和click事件的定义,获取鼠标的坐标。②类的方法的具体实现1.Chessboard类中方法的实现一.Chessboard中的变量定义所谓的在棋盘上下棋,其实就是在相应坐标进行绘图,实际是不存在棋盘的,这里我定义一个15,15的二维数组来表示虚拟的棋盘进行下棋,其他见程序注释。publicint[,]arrchessboard=newint[15,15];//arrchessboard为棋盘情况数组,虚拟棋盘的定义,arrchessboard[i,j]=2表示此处无子arrchessboard[i,j]=0表示此处为黑子,arrchessboard[i,j]=1表示此处为白子privateGraphicsmg;//绘制的对象publicStonestone;//棋子对象publicComputercomputer;//电脑对象privateboolstoneflag=true;//判断当前棋子是黑(true)是白(false)privateboolmplayfirstflag=false;//判断先手玩家(是电脑(true)还人(false),先手下黑棋)privateStackmStarckHistory=newStack();//历史记录堆栈二.Chessboard中对棋盘和棋子的绘制对棋子和棋盘的绘图要用到system.draw这个系统自带的画图类,我使用的方式是先读取文件流中的文件,然后在每次画图的时候就不用再次读取了,直接可以进行绘图,这样处理速度比较快。详细见注释。publicvoidDraw(){//从资源中获取,读取棋盘图像,就是在当前项目中读取文件System.Reflection.AssemblythisExe;thisExe=System.Reflection.Assembly.GetExecutingAssembly();System.IO.Streamfile=thisExe.GetManifestResourceStream(FiveStones.chessboard.gif);//棋盘文件ImageimgChessboard=System.Drawing.Image.FromStream(file);//棋盘图片大小*600像素,每个棋子的点的坐标,个棋子,个空,两边线留空file.Close();mg.DrawImage(imgChessboard,0,0,imgChessboard.Width,imgChessboard.Height);//绘制棋子for(inti=0;i15;i++){for(intj=0;j15;j++){if(arrchessboard[i,j]==0)//对棋盘上的点进行判断后,根据虚拟数组的值来判断绘制哪一种棋子{stone.DrawStone(i,j,true);//Stone里面具体的绘制方法}if(arrchessboard[i,j]==1){stone.DrawStone(i,j,false);}}}}三.Chessboard中人下棋和电脑下棋就是获取下棋的坐标,然后在该坐标进行相应的绘图,然后把该点的虚拟棋盘置成2,表示已经下过棋了。publicvoidPersonDownStone(intx,inty){if(x600&&y600)//取下棋点{intm=(int)Math.Floor((double)x/40);//求浮点数的“地板数”,就是求小于等于该点浮点数的整数intn=(int)Math.Floor((double)y/40);if(!Rule.IsExist(m,n,arrchessboard))//如果这个位置没有子,则下子{DownStone(m,n);//人下子ComputerDownStone();//电脑下子}}}privatevoidComputerDownStone(){intm,n,count=0;//count是计数器,每下一子就加1do{computer.Down(arrchessboard);m=computer.X;n=computer.Y;count++;if(count100)//如果下一百个子还不分胜负,就直接异常退出{MessageBox.Show(异常!);Start();return;}}while(Rule.IsExist(m,n,arrchessboard));//当判断该点没有子的时候下子DownStone(m,n);}四.Chessboard对棋局的初始化把初始化贴上来的目的是为了让每次下棋的流程更明显,构造对象啊,还有清空堆栈呀,这些看似很简单,但是其中的顺序是不可颠倒的,详细见注释。privatevoidInitialization()//棋盘初始化{stoneflag=true;//置当前要走的棋为黑棋(黑棋先走)for(inti=0;i15;i++){for(intj=0;j15;j++){arrchessboard[i,j]=2;//把棋盘所有位置置为空(未下子)}}mStarckHistory.Clear();//清空历史记录堆栈stone=newStone(mg);//构造棋子对象}privatevoidStart()//这个才是初始化函数{Initialization();computer=newComputer(mplayfirstflag);//构造电脑对象Draw();//画棋盘if(mplayfirstflag)//如果是电脑先手的话就电脑下棋,否则等待{ComputerDownStone();}}五.Chessboard中每下一子都要进行的处理做法,更改虚拟棋盘的数值,对禁手进行判断,将棋子坐标压入堆栈,下棋完成后交换下棋。这个过程显示了下棋的步骤,在下棋之后,对虚拟棋盘也就是二维数组进行赋值,然后把该点压入堆栈,然后对黑棋是否禁手进行判断,这里用到了Rule传过来的值,详细见注释。privatevoidDownStone(intm,intn){stone.DrawStone(m,n,stoneflag);//下棋if(stoneflag)//记录情况{arrchessboard[m,n]=0;//根据黑白标记对虚拟棋盘数组进行赋值}else{arrchessboard[m,n]=1;}if(stoneflag)//记录历史记录压入堆栈{//压入堆栈的是棋子颜色和坐标mStarckHistory.Push(黑:+m.ToString()+,+n.ToString());}else{mStarckHistory.Push(白:+m.ToString()+,+n.ToString());}if(Rule.Result(m,n,arrchessboard)6)//判断结果,这个要调用Rule中的方法{switch(Rule.Result(m,n,arrchessboard))//禁手失败直接判黑棋负,重新开始{case1:MessageBox.Show(黑棋双三禁手失败!);break;case2:MessageBox.Show(黑棋双四禁手失败!);break;case3:MessageBox.Show(黑棋长连禁