面向对象以及运用最大最小搜索的井字棋设计(附源码) | 您所在的位置:网站首页 › 井字棋游戏方案设计 › 面向对象以及运用最大最小搜索的井字棋设计(附源码) |
二:实验目的 ① 熟悉运用c++的封装、继承和多态。 ② 掌握c++通过虚函数实现多态的办法。 ③ 学习通过基类指针绑定派生类对象的方法。 ④ 熟悉工程的建立和测试。 ⑤ 掌握最大最小搜索法,进行博弈。 三:程序设计与测试 先大体说一下思路,具体算法后面补上。首先,分析了一下每个类应该具有的作用: Board类: 1.储存一个每个点可以有三种状态的3×3矩阵, 2.画出棋盘 3.访问棋盘上各点 4.设置棋盘上某一点 因此用一个3×3的int矩阵储存信息,-1和1代表已经落子,0代表未落子。然后设置Draw、Get、Set三个函数来分别画出、访问和设置棋盘。 Player类: 这是个比较开放的,为了方便,我的设置是这样的 1. name,根据不同时候创建的时候给一个name,方便输出结果,比如说,同样是player_human,假如在人人对弈中,他可能叫“player1”或者“player2”,而在人机对弈中,他就叫“You”,再加上一个Get_name的函数,这样在输出游戏结果的时候据更加合理而且方便。 2. movement,记录当前走子(1~9),本来设置了悔棋这个功能,但是觉得程序冗余了很多(每次下的时候都要问一句悔棋否?),一个这么几步的游戏没必要设置,所以后来纯粹是作为记录走子使用,可有可无。 3. mark,这是一个静态成员变量,用来记录当前有多少个玩家,作用下面说。 4. hand,这是个用来记录先后手的成员变量,hand的设定是这样的 hand = pow(-1,mark),也就是说先后手是根据玩家创建的顺序确定的,因为每个游戏只有两个玩家,所以他们的hand一定是一正一负的,而且game结束之后,player会被析构掉,所以保证了player一定不超过两个。 5.一个move的虚函数,用来确定落子,根据player类型不同,这个move函数实现不同,实现多态。 6.check函数,判定玩家落子是否正确,保证玩家不会在已经下了的位置或者棋盘外的位置下。 Player_human类:继承于player 1. 没有自己独有的成员变量 成员函数: 1. Read_input读入玩家的输入 2. Move实现:利用check函数保证输入合法后返回走子值 Player_weak_computer类: 1. move实现:产生一个1到9的随机数,用check函数检查到合法即返回。 Player_strong_computer类: 1. 利用极大极小搜索寻求最佳策略。(详细算法后面解释) Game类: 设计思路是,一个game需要一个棋盘和两名玩家,game的成员变量就是两个player指针组,加上一个board,以及一个计算第几轮的round变量。 成员函数: 1. 构造函数:需要变量是三个int值,分别指玩家选定的游戏选项。 2. Get_round:获取当前游戏回合数 3. Add_round:在玩家走子后,让回合数加一 4. Check:逐行逐列还有对角线检查有没有三子连线 5. Check_result:根据check的结果,判定当前局势(玩家1/2获胜,平局,进行中) 6. Get_result:根据check_result的结果,输出对应的语句,并且如果游戏结束返回true,否则返回false 7. Move:根据回合数交替调用对应玩家的move函数进行走棋,这里是整个game函数的一个关键,为了发挥player类多态的优势,上面game的成员变量是两个由player类指针指向的不同的派生player,然后就可以通过round回合数确定该轮的玩家,进而通过这个指针调用基类的move函数接口,最终调用对应派生类的move函数。 8. Main函数: 因为game函数设置的比较全面,所以主函数要做的工作非常少,就只要让玩家输入选取游戏模式,然后对应地实例化一个game,接下来就是让game一直move走棋,add_round,然后利用Get_result判断游戏是否结束,如果结束了再询 问玩家是否继续玩。 Main函数+game构造函数思路: 1:询问玩家是否开始游戏 2:选择模式:①人人对弈②人机对弈③机机对弈 3:再次选择模式:如果是人机对弈,则选择对弈弱电脑或强电脑,如果是机机对弈则选择弱弱、强强、强弱对弈 4:如果是人人对弈、机机对弈中的弱弱对弈或强强对弈则不需要选择谁先手,因为都一样。如果是人机对弈或机机对弈中的强弱对弈则会询问先手顺序。 下面详细说一下强AI用到的算法——极大极小搜索 极小极大的定义 Minimax算法 又名极小极大算法,是一种找出失败的最大可能性中的最小值的算法(即最小化对手的最大得益)。通常以递归形式来实现。 Minimax算法常用于棋类等由两方较量的游戏和程序。该算法是一个零总和算法,即一方要在可选的选项中选择将其优势最大化的选择,另一方则选择令对手优势最小化的一个,其输赢的总和为0(有点像能量守恒,就像本身两个玩家都有1点,最后输家要将他的1点给赢家,但整体上还是总共有2点)。井字棋博弈就是一个对极大极小搜索的经典应用。 使用Minimax算法首先要设定不同玩家的最大利益,比如X玩家为正无穷(+∞),O玩家的最大利益为负无穷(-∞),这样我们称X玩家为MAX(因为他总是追求更大的值),成O玩家为MIN(她总是追求更小的值),各自都为争取自己的最大获益而努力。这里要注意,同一个局面,无论对X玩家还是O玩家,它的评分都应该一样。 现在,让我们站在Min的立场来分析局势(当然,也可以选择Max立场,不过我的程序里面用的是Min,那就以Min为最大利益)。于是构建出来的博弈树如下(前面两层,由于对称性,只列举三种情况): 程序测试图: 游戏设定选择: 游戏选择输入异常处理
游戏过程输入异常处理: AI走棋后等待玩家确认:
四:实验总结与心得 这次的实验算得上是一个小工程了,在试验中学到了不少实际中的工程方法。另外,整个程序的设计充分利用了C++面向对象设计的特点,对封装、继承、多态有了更深入的了解。下面是一些具体的收获: 1. 类的声明实现和主函数分离,工程上的需要,而且一些情况下,避免了代码重写,易于使用和维护。 2. 学会建立工程(虽然是sublime帮我link的)。 3. 学会了使用ifdef、endif语句和pragma once语句防止重编译。 4. 对面向对象程序设计有更深入的了解,懂得根据一个对象的属性和功能进行分析,设计合适的成员变量和成员函数实现。 5. 对C++的继承和多态,掌握更加熟练。 6. 掌握了极大极小算法和Alpha-Beta算法。 下面是一些实验中的小发现: 1. 因为实验中我的user数组是基类player*类型的,在悔棋功能设计中,为了区别human和computer(只有human有悔棋功能),我尝试使用sizeof(*user[0])这样的方式获得指针指向对象的大小(我在computer类里面故意加了一个无用的int数组,增加它的大小),结果发现,无论指针指向computer对象还是human对象,被指向的对象大小一样(不是指针大小,是对象大小!)然后我用*player_human和*player_computer指针分别实例化了human和computer对象,发现这次他们的大小是不一样的!回想起上次Homework写的实验报告,基类指针可以指向派生类对象,但是只继承了基类的成员变量和成员函数, 源代码: board类 //board.h #pragma once #include #include #include using namespace std; #define cls() system("cls") class board { private: int chessboard[4][4]; // for convenience public: board() { for(int i = 1; i 3) return -2; return chessboard[row][col]; } void Set_board(int row,int col,int hand) { if(row > 3 || row < 1 || col > |
CopyRight 2018-2019 实验室设备网 版权所有 |