• 206.30 KB
  • 36页

五子棋、贪吃蛇c++大作业项目报告

  • 36页
  • 当前文档由用户上传发布,收益归属用户
  1. 1、本文档共5页,可阅读全部内容。
  2. 2、本文档内容版权归属内容提供方,所产生的收益全部归内容提供方所有。如果您对本文有版权争议,可选择认领,认领后既往收益都归您。
  3. 3、本文档由用户上传,本站不保证质量和数量令人满意,可能有诸多瑕疵,付费之前,请仔细先通过免费阅读内容等途径辨别内容交易风险。如存在严重挂羊头卖狗肉之情形,可联系本站下载客服投诉处理。
  4. 文档侵权举报电话:19940600175。
'“C++程序设计与训练”课程大作业项目报告项目名称:五子贪吃蛇棋姓名:学号:1班级:自45日期:2015/09/20 目录1系统功能设计32系统总体结构33本人工作内容34项目总结35相关问题的说明4 1系统功能设计这是进行复杂软件开发的第一步,即需求分析。此部分需要详细描述清楚系统将要实现的功能。(此部分的子标题和结构自行拟定。此部分内容同一小组可以共用。)1.1总体功能描述1、概述众所周知,五子棋和贪吃蛇都是在各种电子设备上的经典游戏,堪称电子游戏之鼻祖。五子棋考验人们的理性思维,在安静的思考中获得获胜的思路;而贪吃蛇考验人们的观察力和敏捷度,在速度的决斗中增加自己的长度。而我们这个项目将两款游戏融合起来,成为一个动静结合的新型游戏。2、项目具体功能本游戏采取两种切换策略,两种策略共享同样的五子棋代码。第一种切换策略中,五子棋和贪吃蛇采取无限循环的简单模式。在每一轮中,玩家需先作为黑棋方完成一局五子棋游戏,若任意一局五子棋失败,则整个游戏以失败计,弹出窗口显示失败信息。若该局五子棋获胜,则弹出窗口显示胜利信息,并且棋盘变为贪吃蛇的跑道,初始化蛇头(黄色)、蛇身(绿色),并随机出现食物(红色)且上局中的白棋(电脑方)作为贪吃蛇的障碍物,贪吃蛇碰到自身、墙壁与障碍物,游戏均以失败计,弹出窗口显示失败信息。因此,五子棋的通关速度也会影响到最终的结果,因为随着五子棋获胜步数增加,贪吃蛇游戏中障碍物数量也会相应增加。如果贪吃蛇成功吃到食物,则本局游戏获胜,弹出窗口显示胜利信息,并且界面恢复为五子棋的棋盘,进行下一局的五子棋的游戏。第二种切换策略中,五子棋和贪吃蛇采取单局计分的多窗口信息传递模式。在每一轮中,玩家需先作为黑棋方完成一局五子棋游戏,若任意一局五子棋失败,则整个游戏以失败计,弹出窗口显示失败信息。若该局五子棋获胜,则弹出窗口显示胜利信息,并且弹出贪吃蛇游戏窗口,并把上局中的白棋(电脑方)坐标传入贪吃蛇游戏的跑道上,作为贪吃蛇的障碍物。因此,五子棋的通关速度也会影响到最终的结果,因为随着五子棋获胜步数增加,贪吃蛇游戏中障碍物数量也会相应增加。吃到一个食物则记录分数。由于没有分数上限,所以当贪吃蛇长度过长时,玩家游戏失败,直接结束游戏。 1.2功能点说明(本部分是大作业考核和评分的主要依据)大作业功能点说明(示例)功能类型功能点名称实现方式1)自己编写C++代码2)使用C++标准库3)使用第三方库4)使用SQL语句功能点描述基本信息处理1.产生随机数自己编写C++代码使用C++标准库产生随机数,提供给五子棋“随机下棋“模式中落子坐标及贪吃蛇食物的随机生成2.读取玩家的所下五子棋的位置自己编写C++代码使用C++标准库通过鼠标点击信息获取,读取玩家所下五子棋的位置并将其显示在界面上3.计算机智能下棋自己编写C++代码使用C++标准库五子棋的实现核心,通过对多种情况的考虑分析让计算机对人的下棋情况进行分析并进行堵、攻等算法实现4.五子棋的获胜判定自己编写C++代码使用C++标准库五子棋的实现关键,通过对人方最后落子附近的扫描与全棋盘的扫描判断五子棋是否获得胜利5.格内棋子显示切换自己编写C++代码使用C++标准库在第一种切换模式中,通过对各种颜色赋值,实现棋子换色功能,从而达到切换为贪吃蛇、清屏等效果6.初始化贪吃蛇的位置数据自己编写C++代码使用C++标准库在第一种切换模式中,初始化贪吃蛇的位置数据7.实时更新贪吃蛇的位置数据自己编写C++代码使用C++标准库使用QTimer库,每隔一定时间实时更新贪吃蛇的位置数据 使用第三方库8.贪吃蛇的食物获得判定自己编写C++代码使用C++标准库根据游戏规则判定玩家的贪吃蛇阶段是否获得食物并进行其他操作(在第一种模式中直接获胜,在第二种模式中蛇长加长)9.读取玩家对于贪吃蛇的位置移动自己编写C++代码使用C++标准库通过键盘敲击信息获取,读取玩家对贪吃蛇的上下左右移动10.贪吃蛇长度增加自己编写C++代码使用C++标准库在第二种切换模式中,贪吃蛇碰到自己所下的五子棋会得到身体增长的奖励11.贪吃蛇的获胜/失败判定自己编写C++代码使用C++标准库根据游戏规则判定玩家的贪吃蛇阶段是否获胜/失败统计功能12.统计贪吃蛇的得分自己编写C++代码使用C++标准库在第二种切换模式中,根据游戏规则统计贪吃蛇的得分界面部分13.界面设计自己编写C++代码使用C++标准库使用第三方库通过QT自带库,设计五子棋棋盘界面、贪吃蛇跑道界面和五子棋、贪吃蛇的形态设计14.鼠标点击信息获取自己编写C++代码使用C++标准库使用第三方库通过QT自带库,获取鼠标点击信息15.键盘敲击信息获取自己编写C++代码使用C++标准库使用第三方库通过QT自带库,获取键盘敲击信息16.鼠标点击信息处理自己编写C++代码使用C++标准库使用第三方库通过QT自带库,处理鼠标点击信息并向其它函数传递相关参数17.键盘敲击信息处理自己编写C++代码通过QT自带库,处理键盘敲击信息并向其它函数传 使用C++标准库使用第三方库递相关参数18.胜败信息显示弹窗自己编写C++代码使用C++标准库使用第三方库通过QT自带库,实现弹窗显示胜利、失败信息19.五子棋与贪吃蛇的界面切换自己编写C++代码使用C++标准库使用第三方库通过QT自带库,在第二种切换模式中,五子棋成功后弹出贪吃蛇窗口20.显示贪吃蛇的得分自己编写C++代码使用C++标准库使用第三方库通过QT自带UI设计功能,在第二种切换模式中,显示贪吃蛇的得分 2系统总体结构这是进行复杂软件开发的第二步,即概要设计。此部分需要说清楚整个软件系统包含哪些模块(或功能部件),模块之间的关系和是怎样的;包含哪些主要的类,类之间的关系是怎样的(用UML类图或对象图表达)。此部分还需要说清楚工作任务在小组内是如何划分的(用如下表格)。表1小组成员分工说明小组成员姓名小组成员班级小组成员学号小组成员分工林子坤自45总界面设计、五子棋界面设计、协助编写五子棋智能化设计、五子棋规则设计、第一种切换模式的游戏切换、第一种切换模式下的贪吃蛇设计【对应功能点:1、2、3、4、5、6、7、8、9、11、13、14、15、16、17、18】彭心宇自45编写、完善并优化五子棋智能化设计、五子棋规则设计、第二种切换模式的游戏切换、第二种切换模式下的贪吃蛇设计【对应功能点:3、4、7、8、9、10、11、12、13、14、15、16、17、18、19、20】(此部分的子标题和结构自行拟定。此部分内容同一小组可以共用。) 2.1概要设计对于要实现的游戏,我们的想法是利用多类的相互组合,以线性的结构使得整个游戏的进行思路显得更加清晰。大的功能部件分为1.游戏开始界面2.五子棋部分3.贪吃蛇部分。而每一个部分又通过根据游戏规则设计小的类来实现。贪吃蛇界面Tanchishe类的对象。胜利后结束游戏开始界面mydlg1类的对象五子棋界面MainWindow类的对象第一种切换第二种切换胜利后在MainWindow中进入贪吃蛇2.1.1游戏开始界面游戏开始界面mydlg1类的对象在main函数中通过判断分别指向:退出游戏利用close()“五子棋”界面MainWindow类的对象“游戏规则”界面Dialog类的对象游戏开始界面的类名为:mydlg1.在游戏开始界面又有三个按钮:enterbutton,pushbutton和offbutton,效果分别为将游戏开始界面转向“五子棋游戏“界面,将游戏开始画面转到“游戏规则”界面,退出游戏。其中五子棋界面的类名为:MainWindow。游戏规则界面的类名为:Dialog。这三个按钮都是通过建立类与游戏开始界面的信号和槽机制来实现的。 2.1.2五子棋模块“五子棋”界面MainWindow类的对象随机数computer类的对象五子棋规则Rule类的对象在五子棋模块,我们根据五子棋的游戏规则设计了computer类和rule类,分别来实现“输出随机数”和“游戏规则的判定和实现”。在智能下棋(AI)部分(实现在MainWindow.cpp里),分别调用computer类和rule类的对象,通过类的组合来实现游戏。2.1.2贪吃蛇模块(一)针对模式一的说明由于在我们自行编写的第一种模式下,贪吃蛇模块直接编写在了MainWindow中,与MainWindow深度交融,所以并没有构成一个独立的模块,也没有编写一个独立的类来实现它,因此模式一无法单独为贪吃蛇模块给出一个模块的图示说明。(二)针对模式二的说明诚实的说在第二种切换模式下贪吃蛇模块的编写上我们参考了网上能找到的模板,并借鉴了其上的功能实现,进一步加以改良来作为我们产品的一部分。在这一模块,我们将贪吃蛇整体功能的实现集中在tanchishe类中实现。根据我们开始时的游戏设想:五子棋胜利后游戏将自动转为贪吃蛇模式(白棋的位置我们利用数组的形式实现传输),所以很自然是以组合的形式将这两个类联系在一起的。“五子棋”界面MainWindow类的对象贪吃蛇的具体实现tanchishe类的对象 3本人工作内容这是进行复杂软件开发的第三步,即详细设计。此部分需要针对自己的工作内容说清楚具体的模块是如何设计和实现的,类是如何具体实现的,类中的重点方法和算法是如何设计和实现的,界面是如何设计的,容错功能是如何设计的,典型功能的逻辑处理流程以及各种设计思路等等,可能需要用到UML对象图、UML状态图、UML序列图和流程图等图形化工具。详细设计是编写代码前的最后一步设计工作,因而需要在需求分析和概要设计的基础上,说清楚所有需要在编码前明确的设计事项。(此部分的子标题和结构自行拟定。此部分内容每人是不一样的,不可共用。)第一部分:界面设计(一)总界面设计1.1代码呈现(此处代码位于文件mainwindow.cpp中,如无特别注明下同)MainWindow::MainWindow(QWidget*parent):QMainWindow(parent),ui(newUi::MainWindow){ui->setupUi(this);this->setWindowTitle("FIR_Plus_Snake");//标题名resize(640,640);//设置窗体大小//锁定窗体大小,实现美观效果this->setMaximumSize(640,640);//窗体最大640*640this->setMinimumSize(640,640);//窗体最小640*640}MainWindow::~MainWindow(){deleteui;}1.2代码说明1.2.1MainWindow函数MainWindow是Qt中的主界面类,MainWindow函数是MainWindow类的构造函数,在Qt可视化编程中起到总界面初始化作用。在查阅了“Qt的类”相关参考资料(资料地址:http://www.kuqin.com/qtdocument/classes.html,下同)后, 对主界面进行如下相关设计:1、使用setWindowTitle函数设置应用程序标题名称。2、使用resize函数更改应用程序窗体大小。3、使用setMaximumSize函数和setMinimumSize函数锁定应用程序窗体大小的最大值和最小值,锁定了窗体大小,防止误触改变大小导致的界面不美观、棋盘遮挡等情况。1.2.2~MainWindow()函数~MainWindow()函数是MainWindow类的析构函数,维护程序的正常运行。(二)棋盘界面设计、界面更新等界面绘制工作2.1代码呈现voidMainWindow::paintEvent(QPaintEvent*e){/**********画棋盘界面**********/QPainterpainter(this);//定义绘图类QPainter的对象painterpainter.setRenderHint(QPainter::Antialiasing,true);//反锯齿效果inti,j;for(i=0;i<16;i++){//绘制16条起点为(20+i*40,20),终点为(20+i*40,620)的横线painter.drawLine(20+i*40,20,20+i*40,620);//绘制16条起点为(20,20+i*40),终点为(620,20+i*40)的纵线painter.drawLine(20,20+i*40,620,20+i*40);}/**********设置棋子和蛇的画刷模式和颜色**********/QBrushbrush;//定义画刷(即棋子)brush.setStyle(Qt::SolidPattern);//定义填充为实心:solid(100%)fillpatternfor(i=0;i<15;i++){for(j=0;j<15;j++){if(ChessType[i][j]==1)//如果人手落子则执行以下操作{brush.setColor(Qt::black);//画刷颜色:黑painter.setBrush(brush);//设置绘图工具的画刷为brush//在所点坐标处绘制圆(即长短轴均为15的椭圆) painter.drawEllipse(QPoint((i+1)*40,(j+1)*40),15,15);}【注:其他颜色的代码实现方式与上面相同,为了节省报告空间略去相近的内容,完整代码请于“代码全文”中查找】/**********五子棋游戏后,进入贪吃蛇游戏的初始化工作**********/if(WinFlag==1&&food==0)initfood();//初始化食物if(WinFlag==1&&flag==0){initsnake();//初始化蛇flag=1;}if(WinFlag==1){speed();//通过speed()函数进行updatesnake()槽函数的执行}}2.2代码说明2.2.1棋盘界面绘制为了实现棋盘界面的绘制,本人定义了一个绘图类QPainter的对象painter,并调用了其中的以下几个函数:1、使用setRenderHint函数为画笔设计反锯齿效果,实现棋盘绘制的美观性。2、通过多次循环调用,使用drawLine函数绘制棋盘的网格。2.2.2棋子、蛇与食物的位置参数传递使用了ChessType数组,该数组记录了某个坐标下的点的存在形态。2.2.3棋子、蛇与食物的绘制为了实现棋子、蛇与食物的绘制,本人定义了一个画刷类QBrush的对象brush,并调用了其中的以下几个函数:1、使用setColor函数区别设置棋子、蛇和食物的颜色。2、使用setStyle函数设置填充模式。在本次设计中要求为实心填充。3、使用setBrush函数与drawEllipse函数在指定位置使用“画刷”工具进行绘制。 2.2.4贪吃蛇游戏的初始化及实时更新在第一种切换模式下,在五子棋结束后要求对贪吃蛇、食物进行初始化,并对贪吃蛇进行实时更新。为实现此效果调用了其中的initfood()、initsnake()、speed()几个函数。具体内容详见第八、第九部分第二部分:鼠标控制由于在mouseReleaseEvent函数中实现的功能较多,但大多数与鼠标控制关联不大,故此部分仅提及与鼠标控制密切相关的几个功能实现,其余部分在此处略去,但会在后文中重点提及。1.1代码呈现voidMainWindow::mouseReleaseEvent(QMouseEvent*e){【注:此处略去了部分代码】//x,y为我们定义的15*15坐标中的横纵坐标intx,y;//getbx,getby为QMouseEvent类中调用的函数获取像素位置intgetbx=e->x();intgetby=e->y();//在棋盘内下子,则作出反应if(getbx>=20&&getbx<620&&getby>=20&&getby<620){intz=0,t=0;//确定15*15坐标中的下子位置x=(getbx-20)/40;y=(getby-20)/40;if(ChessType[x][y]==0)//如果该点没落子则点击有效,执行以下操作{ChessType[x][y]=1;//下黑子【注:此处略去了部分代码】}1.2代码说明e为传进mouseReleaseEvent的形参,是指向QMouseEvent的对象的指针。我们通过它及其x()、y()函数获取鼠标点击在窗口中的像素点位置。然而,为了将像素点位置转换为棋盘上的坐标位置,我们新定义了x、y两个整型变量实现 这一效果。其中if(ChessType[x][y]==0)达到了容错效果,防止玩家在点击时误触已下过棋子的棋盘造成棋子覆盖、记录错乱等后果。第三部分:五子棋智能化设计(核心部分)(两人共用)值得一提的是,五子棋智能化设计部分是本程序中最为核心的部分,其全部代码均由两个队员根据五子棋下棋经验与后期调试自行编写。不过由于时间有限,考虑难免不周,智能化设计的攻守形式并不算完善。由于该设计为五子棋程序的核心部分,因此两人在此部分参与了共同设计。为方便起见,本部分两人共用,但在本部分内部的“代码说明”中会注明各个小部分的设计人。(一)五子棋攻防形势的判定1.1代码呈现(此处代码位于文件rule.cpp中)voidRule::preWin9(intx,inty)//纵向4子连黑{inti;//一只计数菌而已for(i=0;i<4;i++){if(y-1-i>=0&&y+4-i<0xF&&x>=0&&x<15&&//不越界判定MainWindow::ChessType[x][y-i]==1&&MainWindow::ChessType[x][y+1-i]==1&&MainWindow::ChessType[x][y+2-i]==1&&MainWindow::ChessType[x][y+3-i]==1&&(MainWindow::ChessType[x][y-1-i]!=2||MainWindow::ChessType[x][y+4-i]!=2))//纵向4子连黑判定MainWindow::preWinFlag=9;//电脑危险}}【注:其他方向四子连黑的代码实现方式与上面相同,为了节省报告空间略去相近的内容,完整代码请于“代码全文”中查找】voidRule::preWin1(intx,inty)//纵向三子连黑{inti;//一只计数菌而已for(i=0;i<3;i++){if(y-i>=0&&y+2-i<0xF&&x>=0&&x<15&&//不越界判定MainWindow::ChessType[x][y-i]==1&& MainWindow::ChessType[x][y+1-i]==1&&MainWindow::ChessType[x][y+2-i]==1&&(MainWindow::ChessType[x][y-1-i]!=2&&MainWindow::ChessType[x][y+3-i]!=2))//纵向三子连黑判定MainWindow::preWinFlag=1;//电脑危险}}【注:其他方向三子连黑的代码实现方式与上面相同,为了节省报告空间略去相近的内容,完整代码请于“代码全文”中查找】voidRule::preWin1p(intx,inty)//纵向201黑{if(y-2>=0&&y+2<0xF&&x>=0&&x<15)//不越界判定{//一只计数菌而已if(MainWindow::ChessType[x][y]==1&&MainWindow::ChessType[x][y+1]==1&&MainWindow::ChessType[x][y+2]==0&&MainWindow::ChessType[x][y-1]==0){MainWindow::preWinFlag=-1;}elseif(MainWindow::ChessType[x][y]==1&&MainWindow::ChessType[x][y-1]==1&&MainWindow::ChessType[x][y+1]==0&&MainWindow::ChessType[x][y-2]==0){MainWindow::preWinFlag=-1;}}//纵向2黑判定//电脑危险}【注:其他方向201黑的代码实现方式与上面相同,为了节省报告空间略去相近的内容,完整代码请于“代码全文”中查找】voidRule::preWin5(int&z,int&t)//纵向三子连白{for(z=0;z<15;z++){for(t=0;t<15;t++){if(t>=0&&t+3<15&&z>=0&&z<15&&//不越界判定MainWindow::ChessType[z][t]==2&&MainWindow::ChessType[z][t+1]==2&&MainWindow::ChessType[z][t+2]==2&&MainWindow::ChessType[z][t+3]!=1&& (MainWindow::ChessType[z][t-1]!=1||MainWindow::ChessType[z][t+4]!=1))//纵向三子连白判定{MainWindow::preWinFlag=5;break;}//电脑进攻}if(MainWindow::preWinFlag==5){break;}}}【注:其他方向三子连白的代码实现方式与上面相同,为了节省报告空间略去相近的内容,完整代码请于“代码全文”中查找】voidRule::preWin5p(int&z,int&t)//纵向201白{for(z=0;z<15;z++){for(t=0;t<15;t++){if(t-2>=0&&t+2<0xF&&z>=0&&z<15)//不越界判定{//一只计数菌而已if(MainWindow::ChessType[z][t]==2&&MainWindow::ChessType[z][t+1]==2&&MainWindow::ChessType[z][t+2]==0&&MainWindow::ChessType[z][t-1]==0){MainWindow::preWinFlag=-5;break;}elseif(MainWindow::ChessType[z][t]==2&&MainWindow::ChessType[z][t-1]==2&&MainWindow::ChessType[z][t+1]==0&&MainWindow::ChessType[z][t-2]==0){MainWindow::preWinFlag=-5;break;}}}if(MainWindow::preWinFlag==-5)break;}}【注:其他方向201白的代码实现方式与上面相同,为了节省报告空间略去相近的内容,完整代码请于“代码全文”中查找】1.2代码说明1.2.1三子连黑(代码编写人:林子坤)较有经验的五子棋玩家都知道,一方如果下出了三子连棋,如果另一方不在一侧进行阻挡,那么则相当于自我宣告大势已去,缴械投降。因此,对三子连黑 的预防是计算机守卫的最基本部分。三子连黑判定函数从MainWindow中获取了最近黑棋落子的横纵坐标,并在其上下、左右、右上斜、右下斜四个方向分别搜索连续的三颗黑棋。当发现连续的三颗黑棋即为危险信号,此时通过修改MainWindow::preWinFlag参数使得智能化实现程序获得信息,并进行相关操作。1.2.2四子连黑(代码编写人:林子坤)稍懂规则的五子棋玩家都知道,一方如果下出了四子连棋,如果另一方不采取任何措施,任凭其下出第五颗棋,游戏直接失败。因此,四子连黑是对电脑的一个危险信号,如果不采取任何措施,下一步极有可能直接败北。四子连黑判定函数由三子连黑判定函数改写而得,从MainWindow中获取了最近黑棋落子的横纵坐标,并在其上下、左右、右上斜、右下斜四个方向分别搜索连续的四颗黑棋。当发现连续的四颗黑棋即为危险信号,此时通过修改MainWindow::preWinFlag参数使得智能化实现程序获得信息,并进行相关操作。1.2.3201黑(代码编写人:彭心宇)较为精通五子棋的玩家都知道,不少高手的下棋方法较为诡诈,他们通过分别在两处下两颗子和一颗子来让你产生疏忽(因为并没有连成三颗子),但当他们在中间填上一颗棋,连成四子连珠,神色未定的对手只能在风中凌乱,哀叹自己tooyoung,toosimple。201黑判定函数对这种情况进行了有效的预防,它从MainWindow中获取了最近黑棋落子的横纵坐标,并在其附近搜索是否产生了形如···的情况。发现这种情况后,此时通过修改MainWindow::preWinFlag参数使得智能化实现程序获得信息,并进行相关操作。1.2.4三子连白(代码编写人:彭心宇)聪明的五子棋玩家不仅能够观察场上的敌方局势对其进行打击与阻挠从而守住自己的大本营,更会在自己的大本营中燃起星星之火,在自己形势有利的情况下乘胜追击,获得胜利。当场上出现己方三子连白的情况,这就代表着胜利的号角即将在棋盘上吹响。此时正是乘胜追击的大好时机。如果敌方罔顾事实,不分是非,不承认我方行将胜利这一客观事实,执意要在其它地方做无谓的挣扎,我方更可闷声发大财。 三子连白判定函数通过两重循环遍历了整个棋盘,“不抛弃,不放弃”地寻找一切己方生机。当找到三子连白之时,我们要把这胜利的号角传递回MainWindow,此时通过修改MainWindow::preWinFlag参数使得智能化实现程序获得信息,并进行相关操作。1.2.5201白(代码编写人:彭心宇)虽然本智能化设计的下棋方式存在一定的机械性与不完善性,并不会通过一些诡诈的手法来获得胜利,但是如果场上偶然出现了一些不易被敌方发现的我方有利形势,我们当然也是支持的。如果我方出现了ooo的落子情况,我们真的可以偷偷地在中间落上那么一颗子,形成美妙的四子连珠。201白判定函数通过两重循环遍历了整个棋盘,默默地寻找一切己方生机。当找到形如ooo的情况之时,我们要把这宝贵的情报传递回MainWindow,此时通过修改MainWindow::preWinFlag参数使得智能化实现程序获得信息,并进行相关操作。(二)五子棋攻防动作的实现2.1代码呈现RuleR0;//定义一个判定对象R0Rule*r0=&R0;//定义一个判定对象指针r0/**********第一阶段:智能判定**********//**********判定过程**********///preWin5~preWin8:判断是否有白棋三字连珠r0->preWin5(z,t);if(MainWindow::preWinFlag!=5){z=0;t=0;r0->preWin6(z,t);if(MainWindow::preWinFlag!=6){z=0;t=0;r0->preWin7(z,t);if(MainWindow::preWinFlag!=7){z=0;t=0;r0->preWin8(z,t);if(MainWindow::preWinFlag!=8){z=0;t=0;r0->preWin5p(z,t);if(MainWindow::preWinFlag!=-5){z=0;t=0;r0->preWin6p(z,t);if(MainWindow::preWinFlag!=-6){z=0;t=0;r0->preWin7p(z,t);if(MainWindow::preWinFlag!=-7){z=0;t=0;r0->preWin8p(z,t);}}}}}}}//preWin9~preWin12:判断是否有黑棋4子连珠r0->preWin9(x,y);r0->preWin10(x,y);r0->preWin11(x,y);r0->preWin12(x,y);//preWin1~preWin4:判断是否有黑棋三子连珠if(preWinFlag!=9&&preWinFlag!=10&&preWinFlag!=11&&preWinFlag!=12) {r0->preWin1p(x,y);r0->preWin2p(x,y);r0->preWin3p(x,y);r0->preWin4p(x,y);r0->preWin1(x,y);r0->preWin2(x,y);r0->preWin3(x,y);r0->preWin4(x,y);}/**********判定后功能实现过程**********/if(preWinFlag==9)//纵向黑棋4子连珠,在纵向堵一边{for(inti=0;i<4;i++){if(ChessType[x][y-i]==0){ChessType[x][y-i]=2;break;}elseif(ChessType[x][y+i]==0){ChessType[x][y+i]=2;break;}}}【注:其他方向守卫的代码实现方式与上面类似,为了节省报告空间略去相近的内容,完整代码请于“代码全文”中查找】elseif(preWinFlag==5)//纵向白棋三子连珠,在纵向乘胜追击{for(inti=1;i<3;i++){if(ChessType[z][t-i]==0&&ChessType[z][t-i+1]!=1){ChessType[z][t-i]=2;break;}elseif(ChessType[z][t+2+i]==0&&ChessType[z][t+2+i-1]!=1){ChessType[z][t+2+i]=2;break;}}}【注:其他方向攻击的代码实现方式与上面类似,为了节省报告空间略去相近的内容,完整代码请于“代码全文”中查找】/**********第二阶段:随机下法**********/else//没有三连珠,落子边随机下{Computerc;//定义了产生随机数的Computer类的对象intdrop=c.random();//将落子位置与随机数一一对应if(drop==0)//情况一,下在左上角{if(x-1>=0&&x-1<0xF&&y-1<0xF&&y-1>=0&&ChessType[x-1][y-1]==0) ChessType[x-1][y-1]=2;elsedrop=1;}【注:其他位置的随机下棋的代码实现方式与上面相同,为了节省报告空间略去相近的内容,完整代码请于“代码全文”中查找】if(drop==8)//既没三连珠,落子边也被全部堵死,那么在棋盘随机下棋{Computerc1,c2;//定义了产生随机数的Computer类的对象intdrop1=c1.random3();//drop1代表落子的横坐标intdrop2=c2.random5()%c2.random3();//drop2代表落子的纵坐标while(ChessType[drop1][drop2]!=0)//如果要落子处已被占用,那么重新产生随机落子点,直到能够落子为止{drop1=c1.random3();inttemp2=c2.random3();if(temp2==0)temp=10;else{drop2=c2.random5()%temp;}}ChessType[drop1][drop2]=2;}}}MainWindow::preWinFlag=0;2.2代码说明2.2.1判定过程(代码编写人:林子坤、彭心宇)为了使用我们在Rule类中定义的多种形势判定函数(preWin函数),我们在这里定义了一个Rule类的对象R0及其指针r0。通过依次调用该对象的成员函数实现判定过程。最后在MainWindow类中获得最终的preWinFlag值,使得计算机意识到场上的形势并准备接下来的智能化落子过程。2.2.2智能化落子过程(代码编写人:林子坤、彭心宇)根据上面步骤中获得的preWinFlag值,计算机可以根据场上形势进行落子。例如,当preWinFlag值为9时,计算机意识到这是纵向黑棋四子连珠的信号, 于是计算机作为白棋方会通过修改相关坐标的ChessType,在四子一端堵上白棋。2.2.3随机落子过程(代码编写人:林子坤、彭心宇)在以上攻守形势都进行过考虑后,如果场上还没有出现较为明显的有利条件或者不利条件,我们便会在最近的黑棋落子附近8格随机落子。此时我们采用的是产生随机数的方式,定义Computer类(将在第四部分:随机数的产生中进行说明)的对象c1、c2,通过调用其成员函数产生随机数0~7。数字0~7分别代表黑棋附近的8格。语句while(ChessType[drop1][drop2]!=0)实现了一种容错的功能,因为如果产生的随机数对应的格子已被占用,直接进行落子会造成覆盖。此时我们会将该随机数加1,以此类推,顺序遍历对应格子。如果不巧,当随机数已经为7或已被加到了7,计算机依然无处可下,我们便可认为场上形势不明显,希望计算机自己在棋盘上另开战场。此时,我们通过Computer类的对象c1、c2产生两个0~14的随机数,对应棋盘上的横纵坐标,在相应坐标上落子。2.2.4还原判定落子结束后,MainWindow::preWinFlag将被恢复为0,等待玩家的下一次落子。第四部分:随机数生成1代码呈现(此处代码位于文件computer.cpp中)intComputer::random()//根据当前时间产生0~7随机数{QTimetime;time=QTime::currentTime();qsrand(time.msec()+time.second()*1000);intrandom=qrand()%8;returnrandom;}【注:其他根据时间产生随机数的代码实现方式与上面相同,为了节省报告空间略去相近的内容,完整代码请于“代码全文”中查找】intComputer::random5()//直接随机产生0~999随机数{qsrand(1); intrandom=qrand()%1000;returnrandom;}2代码说明由于程序需要大量产生随机数,因此我们在Computer类根据需要定义了多种随机数生成函数。随机数的生成方式有两种,一种是通过QTime调用当前时间,根据当前的时间产生随机数;另外一种是根据系统自带的数据产生“伪随机”形式的数据。使用这两种不同的随机数产生方法的原因是:如果采用同一种产生方法,在同时生成横纵坐标时会产生完全相同的两个数据,则产生的落子都会落在x=y的对角线上。为了实现横纵坐标的真正随机性,在调用随机数时我们使用了一种巧妙的手法。例如以下代码片段:drop1=c1.random3();inttemp2=c2.random3();if(temp2==0)temp=10;else{drop2=c2.random5()%temp;}横坐标(drop1)生成调用了根据时间产生0~14随机数的函数,纵坐标(drop2)生成是采用纵坐标=(随机产生的0~999随机数)%(横坐标)。这很好的保证了两个数据互不干扰的特性。同时,if(temp2==0)temp=10;语句是为了防止如果c2.random3()产生的随机数为0,在最后取模时系统错误引发闪退或者不落子。因此如果产生的随机数为0,我们将其修改为10。这也是容错特性的一个体现。第五部分:五子棋胜负判定1代码呈现(此处代码位于文件rule.cpp中)voidRule::Win1(intx,inty)//纵向判定{inti;//一只计数菌而已for(i=0;i<5;i++){if(y-i>=0&&y-i<0xF&&y+4-i<0xF&&y+4-i>=0&&//不越界判定MainWindow::ChessType[x][y-i]==1&&MainWindow::ChessType[x][y+1-i]==1&&MainWindow::ChessType[x][y+2-i]==1&& MainWindow::ChessType[x][y+3-i]==1&&MainWindow::ChessType[x][y+4-i]==1)//纵向五子连黑判定MainWindow::WinFlag=1;//获胜for(inta=0;a<15;a++){for(intb=0;b<15;b++){if(b-i>=0&&b-i<0xF&&b+4-i<0xF&&b+4-i>=0&&//不越界判定MainWindow::ChessType[a][b-i]==2&&MainWindow::ChessType[a][b+1-i]==2&&MainWindow::ChessType[a][b+2-i]==2&&MainWindow::ChessType[a][b+3-i]==2&&MainWindow::ChessType[a][b+4-i]==2)//纵向五子连白判定MainWindow::WinFlag=2;//失败}}}}【注:其他方向胜负判定的代码实现方式与上面相同,为了节省报告空间略去相近的内容,完整代码请于“代码全文”中查找】2代码说明2.1黑棋胜利判定黑棋胜利判定沿用了第三部分中黑棋三子连珠判定(林子坤)的思想,即在最近的黑棋落子搜索是否存在连续的五个黑棋。如果黑棋形成五子连珠,则将MainWindow中的WinFlag变量修改为1(即人方胜利)。2.2白棋胜利判定白棋胜利判定沿用了第三部分中白棋三字连珠判定(彭心宇)的思想,即在全棋盘中遍历搜索在各个方向是否存在连续的五个白棋。如果白棋形成五子连珠,则将MainWindow中的WinFlag变量修改为2(即计算机胜利)。第六部分:游戏切换(一)五子棋-->贪吃蛇的切换1.1代码呈现 RuleR;//定义一个判定对象RRule*r=&R;//定义一个判定对象指针rr->Win1(x,y);//进行纵向判定r->Win2(x,y);//进行横向判定r->Win3(x,y);//进行右下斜判定r->Win4(x,y);//进行右上斜判定if(WinFlag==1)//获胜{update();//更新屏显//显示胜利信息QMessageBox::information(this,"YouWin!Enteringthesnakegame!","YouWin!Enteringthesnakegame!",QMessageBox::Ok);for(inti=0;i<15;i++){for(intj=0;j<15;j++){if(ChessType[i][j]==1)ChessType[i][j]=0;//获胜后黑棋消失update();//更新屏显}}}if(WinFlag==2)//失败{update();//更新屏显setEnabled(false);//失败了,游戏全盘结束,不许动//显示失败信息QMessageBox::information(this,"YouLose!Gameover!","YouLose!Gameover!",QMessageBox::Ok);}/*胜败判定部分到此结束*/}1.2代码说明根据我们游戏的需要,从五子棋游戏过渡到贪吃蛇游戏需要进行以下几个步骤:1、胜负判定。如果五子棋人方胜利,弹窗提示,并进入贪吃蛇游戏;如果五子棋计算机胜利,弹窗提示,并且屏幕设为无法识别鼠标点击、键盘敲击的状态,使玩家被迫认输,关闭窗口。为了实现这几个功能,我们定义了判定对象R及其指针r ,进行胜负判定。判定后,我们使用Qt内的QMessageBox::information功能弹出窗口。如果计算机胜利,我们还要多进行一步setEnabled(false);来使鼠标点击、键盘敲击等失效,屏幕进入锁定状态(即unable状态)2、棋盘处理。根据我们游戏规则,如果五子棋游戏获得胜利,我们需要删除我们棋盘上的所有黑棋,保留棋盘上的所有白棋作为障碍物。因此我们使用了if(ChessType[i][j]==1)ChessType[i][j]=0;语句让黑棋位置初始化。(二)贪吃蛇-->五子棋的切换2.1代码呈现voidMainWindow::SnakeWin()//获胜处理{update();//更新屏显WinFlag=5;for(inti=0;i<15;i++){for(intj=0;j<15;j++){ChessType[i][j]=0;//逐行逐列清屏}}//显示胜利信息QMessageBox::information(this,"YouWin!EnteringtheFIRgame!","YouWin!EnteringtheFIRgame!",QMessageBox::Ok);flag=0;clear=0;}voidMainWindow::mouseReleaseEvent(QMouseEvent*e){/**********贪吃蛇游戏后,进入五子棋游戏的初始化工作**********/if(WinFlag==5&&clear==0){for(inti=0;i<15;i++){for(intj=0;j<15;j++){ChessType[i][j]=0;//逐行逐列清屏}}clear=1; }【注:此处略去了部分代码】2.2代码说明2.2.1贪吃蛇清屏、弹窗处理根据我们的游戏规则,如果贪吃蛇吃到了食物,那么游戏获胜,屏幕清空,转为五子棋的棋盘。此处采用了与(一)中相同的方式,故在此不再赘述。2.2.2五子棋初始化工作由于一些我们尚未明白为什么的原因,在某些系统下,有时候上述的清屏、弹窗处理并不能按预想中的那样立刻进行。因此,我们对此进行了一个弥补的容错措施。我们在mouseReleaseEvent函数中对WinFlag和clear进行判定,如果WinFlag==5且clear==0,那么说明贪吃蛇游戏获得胜利且未进行清屏。那么我们在点击第一次鼠标后,就再执行一次清屏。第七部分:键盘控制1代码呈现voidMainWindow::keyPressEvent(QKeyEvent*e){switch(e->key()){caseQt::Key_Up:{direction=6;break;}//向上键盘功能设定caseQt::Key_Left:{direction=8;break;}//向左键盘功能设定caseQt::Key_Down:{direction=2;break;}//向下键盘功能设定caseQt::Key_Right:{direction=4;break;}//向右键盘功能设定default:;}}2代码说明该段代码使用了Qt中的键盘特性,它通过读取我们的键盘敲击对direction值进行相应修改,从而达到下文中蛇的拐弯等功能。第八部分:贪吃蛇游戏初始化 (一)食物初始化1.1代码呈现voidMainWindow::initfood(){Computercf1,cf2;//定义了产生随机数的Computer类的对象intfoodx=cf1.random2();//foodx代表落子的横坐标intfoody;inttemp=cf2.random2();if(temp==0)temp=10;else{foody=cf2.random5()%temp;}//foody代表落子的纵坐标if(foody==0)foody=1;//防止产生的食物被蛇覆盖ChessType[foodx][foody]=3;//将食物设置为红色圆点food=1;update();}1.2代码说明随机初始化食物(红色)沿用了上文中“随机落子”的方法,使用了两种随机数生成方式进行生成。其容错特性也与上文相同。在此处不再赘述。(二)蛇的初始化2.1代码呈现voidMainWindow::initsnake(){(BodyLen)=1;//初始蛇长:蛇身(不含蛇头)长度为1direction=4;//初始方向:向右IsOver=false;//初始状态:运行headx=1;heady=0;//初始蛇的位置:蛇头(1,0)ChessType[1][0]=5;//蛇头设为黄色ChessType[0][0]=4;//蛇身设为绿色body[0]=0;//第一个蛇身初始位置:(0,0)update();flag=1;}2.2代码说明 本人记录贪吃蛇某点坐标的方式是使用一个四位数,前两位记录横坐标,后两位记录纵坐标,即记录值=100*横坐标+纵坐标。本人的贪吃蛇蛇头为黄色,蛇身为绿色,且蛇身并不包含蛇头。在本段代码中,我们将贪吃蛇的几个重要参数(如身长BodyLen、运行方向direction、停止判定IsOver)进行了初始化,并且指定了蛇头的横纵坐标、蛇身的横纵坐标及其记录号。第九部分:贪吃蛇游戏运动控制1代码呈现voidMainWindow::updatesnake(){//以下三句是为了将蛇尾,即body[(BodyLen)-1]还原为空inttempx=body[(BodyLen)-1]/100;//由记录值得出横坐标inttempy=body[(BodyLen)-1]%100;//由记录值得出纵坐标ChessType[tempx][tempy]=0;//还原该点的颜色为空//body的处理body[0]=100*headx+heady;//蛇身第一格设为原蛇头坐标,并转换为四位数记录方式for(inti=0;i<(BodyLen)-1;i++){body[i+1]=body[i];//蛇身的每一格变成原来的前一格}//head的处理switch(direction){case6:heady=heady-1;//上case2:heady=heady+1;//下case8:headx=headx-1;//左case4:headx=headx+1;//右default:;}//吃食物->获胜处理if(ChessType[headx][heady]==3){food=0;//食物消失SnakeWin();//判定获胜}else{//判断是否死亡 if(headx<0||heady<0||headx>14||heady>14||ChessType[headx][heady]==2){IsOver=true;setEnabled(false);//失败了,游戏全盘结束,不许动//显示失败信息QMessageBox::information(this,"YouLose!Gameover!","YouLose!Gameover!",QMessageBox::Ok);WinFlag=4;}//处理过后进行上色ChessType[headx][heady]=5;//为蛇头上色for(inti=0;i<(BodyLen);i++)//为蛇身上色{inttempx=body[i]/100;//获取蛇身横坐标inttempy=body[i]%100;//获取蛇身纵坐标ChessType[tempx][tempy]=4;//上色}}update();}voidMainWindow::speed()//通过speed()函数进行updatesnake()槽函数的执行{if(IsOver==false){timer=newQTimer(this);timer->setSingleShot(true);//将会只启动定时器一次,启动后每经过一次设定的时间就发送一次timeout()timer->start(1000);//1秒connect(timer,SIGNAL(timeout()),SLOT(updatesnake()));}}voidMainWindow::SnakeWin()//获胜处理{update();//更新屏显WinFlag=5;for(inti=0;i<15;i++){for(intj=0;j<15;j++){ChessType[i][j]=0;//逐行逐列清屏 }}//显示胜利信息QMessageBox::information(this,"YouWin!EnteringtheFIRgame!","YouWin!EnteringtheFIRgame!",QMessageBox::Ok);flag=0;clear=0;}2代码说明2.1后台清空蛇尾在蛇的运行过程中,每秒钟原来蛇尾的位置都会被还原为空格子,因此每调用一次updatesnake()函数,原来蛇尾的坐标所对应的ChessType都会被恢复为0。2.2后台处理蛇身在蛇的运行过程中,蛇身的第一格就是上一秒中蛇头的位置。因此每调用一次updatesnake()函数,我们都会将蛇身坐标进行上述更新。2.3后台处理蛇头蛇头的位置由键盘处理后获得的方向信息决定。如果“下”键被敲击,那么蛇将会向下运动(即y值越来越大);如果“右”键被敲击,那么蛇将会向右运动(即x值越来越大)。2.4吃到食物当最新蛇头的位置与食物的放置位置重叠,我们即可判定贪吃蛇已经成功吃到了食物。此时我们进入获胜步骤(见上文第六部分)。2.5没吃到食物2.5.1游戏继续,屏幕显示操作如果没有吃到食物但是并没有碰撞到障碍物或者墙壁,游戏继续进行。此时我们对2.1~2.3中蛇的后台处理进行上色表示。2.5.2游戏失败如果游戏失败,我们进行弹窗,并且多进行一步setEnabled(false);来使鼠标点击、键盘敲击等失效,屏幕进入锁定状态(即unable状态) 4项目总结总结设计、开发及调试工作中的问题及解决方法、难点、亮点和心得体会等。(此部分的子标题和结构自行拟定。此部分内容每人是不一样的,不可共用。)(一)设计、开发及调试工作中的问题及解决方法问题1:对开发环境不了解,无法巧妙地利用其中的自带类,对界面编写一无所知。解决方法:为了解决对Qt5开发环境的不了解,我购买了《Qt5编程入门》一书并根据自己的需要查找所需要学习的功能。为了对Qt中的类及其用法进行快速查找与学习,我找到了一个“Qt中的类”网站(http://www.kuqin.com/qtdocument/classes.html)。为了学习界面编写技能,我到网上搜索了多个示例程序并进行试运行。对于含有不懂的英文单词的函数或类,我会到“Qt中的类”网站上查询其用法和功能。通过这些途径,虽然我现在并没有完全精通Qt开发环境,但是对不少类与函数的用法已经有所掌握。问题2:编程中出现多次难以发现原因的bug,如程序编译时出现不明原因的错误,运行时五子棋白棋不能正确跟棋,贪吃蛇有时候出现闪退情况。解决方法:认真审读程序,注释掉部分程序来筛选出引起错误的语句。例如,在MacOSX的Qt5版本中,在.h文件中声明的函数如果在.cpp文件中没有进行定义,那么在编译时会报错,且错误信息不明,难以通过搜索引擎查明原因。这时我就通过注释部分代码发现了这个问题。虽然这个过程很艰难,但是查明bug原因的成就感是难以言表的。同理,我也通过多次调整程序发现了五子棋白棋不能正确跟棋、贪吃蛇有时候出现闪退情况等异常状况的原因。问题3:与队友交流思路困难,且很有可能出现缺漏和错误理解。解决方法:为了解决这个问题,我在自己编的代码内加入了大量注释。这既是对我今后审阅程序时的方便之举,也让我的队友清楚地明白我的思路。(二)设计、开发及调试工作中的难点在我们的设计、开发及调试过程中,存在以下几个难点:1、时间紧,任务 重。我们的项目进行相当于两款游戏的开发,因此每一款游戏的开发时间都仅仅是其他组的一半。2、我们的游戏是前人从未编写过,甚至从未想到过的,其造成的弊端就是缺少相关经验,网上也缺少相关的参考资料。3、我们打算根据五子棋对战经验编写属于我们自己的五子棋智能对战AI,在相关资料缺乏的情况下,编写和调试是比较困难的。(三)设计、开发及调试工作中的亮点1、游戏创意:本游戏思路比较奇特,相当于糅合了两款游戏。但是它衔接自然,使得Qt的鼠标和键盘控制功能都得到了使用。2、自编AI:值得一提的是,五子棋智能化设计部分是本程序中最为核心的部分,虽然考虑并不全面,但是其全部代码均由两个队员根据五子棋下棋经验与后期调试自行编写。其中我编写了几个AI策略并进行调试后,我的队友彭心宇提出了更多的解决方案。因此在这“聪明”的五子棋游戏背后是两个队员的思路。(四)心得体会本项目从9月7日早上开始进行,9月19日下午正式结束程序编写工作。9月19日晚上开始进行大作业报告的起草。在写大报告的过程中我们又对程序进行了几处小变动。现在,横跨14天的大作业终于即将画上句号。下面,我简要谈谈这个过程中我的心得体会。首先,这个过程让我体会到了合作的重要性,因为如果仅仅通过我的一己之力是根本不可能在这么短时间内完成这么浩大的工程。如上文所言,我们程序的最核心部分是两个人共同编写调试得出的。如果没有队友的帮助,我也不会考虑得如此的全面。其次,这个过程提高了我的表达能力。为了和队友说明我的思路,我必须要用科学、严谨的程序设计语言来与他交流。同时,我努力培养自己的注释能力,尽量用最简练的语言表达出最易懂的意思。最后,这个过程培养了我的耐心,因为在屡次编译错误的debug过程中,焦躁的我我甚至会产生“明朝散发弄扁舟”的想法。然而,当我发现程序出现的问题并加以改正后,我感觉到自己的精神得到了升华,有种“挟飞仙以遨游”的舒 畅感。而当最终程序运行成功,终稿敲定之时,我觉得自己的耐心与付出获得了回报。总之,这次编写大作业的经历是我一个难得的经历,它让我的程序编写水平得到了提高,并且在我大学生活中画上了浓墨重彩的一笔。 5相关问题的说明如果需要的话,请在此部分说明程序开发环境和执行环境的搭建方法及操作实例等相关问题。(此部分内容可以为空)相关操作步骤详见文件“操作指导与测试说明”'