c++五子棋项目(Qt界面)

tech2026-04-03  0

项目文件仓库地址:https://github.com/Kenny-ting/Chess_Game_2020

注:项目设计时时间有限,并且独立完成,很多时间花在算法的编写上,界面设计和模块整合有待改正和优化。如果对您有帮助,客官不要吝惜star呀!

结果展示: (1)开始界面

(2)个人中心界面

(3)游戏界面

一、项目简介

1.1 项目内容

利用C++的知识和QT以及一些自行拓展的新知识,实现双人对战及AI模式的五子棋游戏,尝试设计启发式算法,实现AI模式的五子棋游戏。 主要功能: (1)用户注册、登录和找回密码 (2)双人模式五子棋对战 (3)AI模式五子棋游戏

1.2 运行步骤

编译执行环境:Qt 5.9 注意:除了界面设计外,都是在vscode2019中调试好再在Qt中新建的工程,由于字符编码方式不同,需要按照Qt的“System”编码重新载入(如果是在Qt编辑器下查看)

运行步骤:项目设计为可视化界面,直接点击按钮执行即可。 可行步骤简述为:登录→ 输入用户名及密码(测试过程中可以用Kenny,密码是123456)→(提示登录成功)个人中心 → 选择进入游戏 → 选择游戏模式(双人模式或与AI对战)

1.3 系统整体结构

一个游戏的设计与实现,不仅要包括游戏本身的界面,还需要有玩家的信息,可以存储读取玩家的信息,并且完善一个游戏的基本功能。基于开发五子棋游戏这一任务,我的系统主要由登录界面登入后选择进入游戏或是用户基础操作(如修改密码),在进入游戏后选择一个游戏模式(双人模式或AI模式),最终进入游戏界面。

在登录界面需要实现登录功能,注册账号按钮,以及退出按钮,然后分别进入后能够实现各自按钮所实现的功能,例如注册账号按钮点击后,弹出注册账号的对话窗口,实现注册账号功能,并给出提示信息(成功与否)。登录模块在验证个人账户名及密码后,进入个人中心。然后是个人中心模块,在这里可以选择进入游戏还是修改密码。如选择进入游戏则进入游戏模式选择界面,如果选择修改密码,则弹出修改密码对话窗口,并给出提示(成功与否)然后是游戏模式选择界面,需要实现双人对战模式和AI游戏模式,并给出返回按钮。选择任意游戏模式均进入游戏界面。最后也是最为关键的游戏界面,该界面需要实现给出当前鼠标位置的选择框和对方最后一次落子的位置,并根据不同模式进行游戏。

系统的整体程序流程图如图所示:

二、关键设计

2.1 如何实现界面的切换

我们将每一个界面都设成一个类,接下来用开始界面、游戏选择界面和游戏界面之间的切换进行阐述。以游戏选择模式界面至游戏界面为例,首先我们将这两个界面各写成一个类,继承至qt的QMainWindow。在各自写完各自的类以后,我们通过设置功能按钮,并连接按钮的信号槽,在槽中设置关闭当前界面,打开游戏界面,并设置全局变量mood来标识游戏模式(1为AI模式,2为双人模式),从而实现了界面的切换以及根据按钮选择进入不同的游戏模式。

2.2 如何获取用户输入的信息

为了在登录、注册及修改密码时可以获得用户输入的信息,这里采用的是Qt的对话窗口获取信息。通过Qt的connect函数将按钮功能与编写的对话窗口连接起来(如dialog_change即连接修改密码函数)。然后声明一个修改密码窗口类的对象,用从QDialog(Qt设计中的基类)继承得到的读取函数得到输入,并将输入类型由Qstring转换为string类型,最后用QMessageBox给出提示信息,如修改成功,或者注册失败!该用户已存在等提示信息。 学习参考:http://blog.51cto.com/devbean/217694

2.3 3. 游戏界面设计

首先我们设计一个类gamewindow,继承至qt的QMainWindow,数据成员为Chess类的指针。对于画出已下的棋子,Chess类的数据成员是个二维数组用来标记下棋位置及对应玩家,所以可以通过指针调用成员函数访问Chess类的二维数组获得下棋位置,之后用Qt的画笔和画刷实现绘画棋子,棋盘网格和棋盘的五个黑点等。 对于当前选择框的移动,这里采用鼠标移动事件,捕获捕获鼠标移动的位置,并在就近的位置画出红色的选择框。由于Qt的鼠标移动事件节省资源,实际的移动事件只有在按下鼠标才会捕捉,但为了设计界面红框随鼠标移动,需要setMouseTracking(true)。 对于选择位置后的下棋需要鼠标点击才可以下棋,这里的捕捉鼠标并需要按下鼠标才选择,所以并不需要setMouseTracking(true)。 对于最后执棋方最后落子位置采用全局的lastx和lasty来标记,采用Qt绘画时间在对应棋子中心画出红色十字。

学习参考:(1)https://blog.csdn.net/weixin_39788534/article/details/79496905 (2)https://blog.csdn.net/qq_19727693/article/details/105694194

2.4 AI模式下minmax及alphabet剪枝搜索算法设计

设计AI模式采用的是极大极小和剪枝算法,由于棋盘为15行15列,运算量巨大,这里采用了剪枝算法减少计算量。在待搜索队列地设计上,首先排除周围一颗棋子都没有的位置,再对敌方最后落子位置周围的位置插入搜素队列头部(他们周围是最可能我方落子地位置),来尽可能地剪枝。 此外,对于棋局局势的评估采用了启发式的搜索算法,如对于“活三”,“活四”等给出不同的评价,并根据我方得分减去对手得分来评估该落子位置。而且可以根据调整敌方得分及我方得分地比例,将AI设置为进攻型或者防守型。 评估模型如下(1代表棋子位置,0为空):

核心算法设计:minmax及剪枝搜索: //负值极大算法搜索 alpha + beta剪枝, 合并极大节点和极小节点两种情况, 减少代码量 //在一盘棋局中, 若到棋手A走棋, alpha 相当于棋手A得到的最好的值, 对于棋手A的值, 从对手的角度看就要取负值. int Chess::alpha_beta(int player, int h, int alpha, int beta) //h搜索深度,player=1表示电脑,player=0表示人类 { if (vec1.begin() != vec1.end() && vec2.begin() != vec2.end())//不空 { vector<Position>::iterator pter1 = vec1.end()-1; vector<Position>::iterator pter2 = vec2.end()-1; //判断最新添加后是否赢棋 if (h == 1 || judge_victory(*pter1,chessFlag1) || judge_victory(*pter2, chessFlag2)) { //若到达深度 或是出现胜负 return evaluate(player); } } if (vec3.begin() == vec3.end()) //尚未下棋。居中坐 { vector<Position>::iterator vter = vec_blank.begin(); vec_blank.erase(vter + 7 * 15 + 7); //先删除居中棋子 (*p).row = 7; //第八行第八列,执先 (*p).col = 7; return 0; } // vec_blank 为可以落子的集合 order(); //按搜索顺序排序 提高剪枝效率 //遍历每一个候选步 vector<Position>::iterator vter = vec_blank.begin(); while (vter != vec_blank.end()) { int tempv = vter - vec_blank.begin(); //如果要评估的位置没有相邻的子, 则不去评估 减少计算 if (!has_neightnor(vter)) { vter++; continue; } Position *pos = new Position(); pos->col = (*vter).col; pos->row = (*vter).row; //判断是否为ai走棋 if (player){ vec1.push_back(*pos); vec_blank.erase(vter); } else{ vec2.push_back(*pos); vec_blank.erase(vter); //从空白记录删除 } vec3.push_back(*pos); //返回到上一层节点时,会给出分数的相反数(因为返回的值相当于是在对手的选择下对手对当前棋盘的评估分数 //而作为他的对立方, 要把分数取反 //alpha可以视为当前情况下,当前棋手可以得到的最好值,当前棋手得到最好值 == 对手不愿意接受的最差值 int value = -alpha_beta(!player, h - 1, -beta, -alpha); vector<Position>::iterator vter1 = vec1.end() - 1; vector<Position>::iterator vter2 = vec2.end() - 1; vector<Position>::iterator vter3 = vec3.end() - 1; vter = vec_blank.insert(vec_blank.begin() + tempv, *pos); //从空白记录原先位置插入 //将刚才走棋移除 if (player) { //是电脑 vec1.erase(vter1); } else{ //是玩家 vec2.erase(vter2); } vec3.erase(vter3); //vec3.erase(vter); 这么写不行,超出范围 delete pos; if (value > alpha) { //当depth == DEPTH时, 由于在循环内不断迭代, 总会在考虑后三步棋的情况下逐渐找到最好的走子方式; if (h == Depth) //找到路径了 { p->row = (*vter).row; p->col = (*vter).col; } //alpha + beta剪枝点 //当当前value > beta 时相当于 对手的 value < -alpha, 对手肯定不会考虑这个选择 if (value >= beta) { //因为当前 depth中仍需要遍历候选点, return 后直接返回上层, 上层将返回值取负值 //因为当调用该函数时alpah > - beta, 必定返回上层迭代时, value < alpha, 达到剪枝 return beta; } alpha = value; //更新alpha值 } vter++; } return alpha; }
最新回复(0)