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

项目仓库地址

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

游戏界面展示:

c++五子棋项目(Qt界面)_第1张图片

一、前言

这是原项目的新整合。两年前,我为c++实践课程做了这个项目,但游戏本身有太多“不必要”的部分,比如登录或注册模块。这些对于项目的核心贡献来说是多余的,但有助于显示我的工作量让我拿个好成绩。

因此,现在我只保留了项目的核心部分:阿尔法-贝塔算法和Qt绘制的棋盘。

打开方式:使用Qt Creator 6.3打开项目,然后构建并运行代码。

项目内容:利用C++的知识和QT以及一些自行拓展的新知识,实现AI模式对战的五子棋游戏,尝试设计启发式算法,实现AI模式的五子棋游戏。

二、关键设计

2.1 游戏界面设计

首先我们设计一个类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.2 AI模式下minmax及alphabet剪枝搜索算法设计

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

c++五子棋项目(Qt界面)_第2张图片

核心算法设计: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;
}

备注:项目仓库地址

你可能感兴趣的:(c++学习笔记,c++,游戏,qt)