五子棋ai:极大极小搜索和α-β剪枝算法的思想和实现(qt和c++)(一)引言和界面设计

源代码

GitHub上:Github livingsu/Gobang-ai:极大极小搜索和α-β剪枝

引言

alphaGo击败围棋冠军李世石的新闻让我对棋类博弈产生了浓厚的兴趣,无奈本人不会围棋,但算是一个五子棋业余爱好者,于是乎希望通过实现一个五子棋ai来了解相关算法和知识。

五子棋在英文中又称Gobang,five-in-row,还有两个称呼:gomoku和renju(连珠)。我来简要介绍一下区别。由于五子棋已经被机器严格证明了是一种“不公平”的游戏,先手黑子是绝对占优的,并且先手有必胜的套路。由于先手必胜,必须对先手进行一定的规则限制,于是就出现了“禁手”一说,即某些位置(比如能形成双活3、活3冲4)黑子不能下,不然就算黑子输。在日本,五子棋的理论得到极大发展,有禁手规则的五子棋被称为“连珠”(renju),而一般无禁手规则的叫做gomoku。由于大部分普通人下五子棋只是图一个乐趣,并不需要深入研究其中的理论规则,所以反而是gomoku在学生之间非常流行(我高二时就经常和同学玩五子棋,了解了一些套路但不深入)。

我实现的五子棋ai是无禁手规则的(考虑到有禁手的太复杂。。),标准棋盘大小为1515,规则是黑子先下*,由于黑子占据优势,我一般是人执黑子,ai执白子。之前说过黑子绝对占优且有必胜套路,所以实现的无禁手规则的ai执后手时,如果人用必胜套路,ai肯定会输。但是我们业余玩家不用考虑这些,我的目的是想实现一个能打败大部分执先手普通人的ai。所以,如果你能战胜按照我的博客实现的ai,也不用太过惊讶。

实现的效果图如下所示。
双人(玩家)模式:
在这里插入图片描述
人机(ai)模式:
在这里插入图片描述

界面设计

qt的界面设计非常方便。widget类是开始的欢迎界面,有两个按钮来选择玩家模式或者ai模式。gameWidget类用来绘制棋盘、棋子、分数等,并需要对鼠标事件进行相应以便落子。由于我写的ai需要反复查看棋盘信息,所以我把棋盘放在了chessAi类里面作为public成员,gameWidget可以通过访问ai.chesses来访问棋子信息并进行绘制。

重要效果的实现:
1.在棋盘内时屏蔽鼠标光标,鼠标在某个可以落子的位置附近时,在那个位置上显示一个有颜色(哪一方)的小正方形表示光标,鼠标在某个不可以落子的位置附近时显示禁止光标。

gameWidget类中有成员:

    int cursorRow;//光标位置
    int cursorCol;

然后重写鼠标移动函数:

void gameWidget::mouseMoveEvent(QMouseEvent *event){
    
    
    if(event->x()>=5&&event->x()<=455&&event->y()>=5&&event->y()<=455){
    
    //5=20-15,455=20+14*30+15
        setCursor(Qt::BlankCursor);
        for(int i=0;i<15;++i)
            for(int j=0;j<15;++j){
    
    
                float x=event->x(),y=event->y();
                //判断鼠标落在哪一个点附近(正方形范围)
                if((x>=(chessboard[i][j].x()-15))&&(x<(chessboard[i][j].x()+15))&&
                   (y>=(chessboard[i][j].y()-15))&&(y<(chessboard[i][j].y()+15))){
    
    
                    cursorRow=j;
                    cursorCol=i;
                    if(ai.chesses[cursorRow][cursorCol]!=C_NONE)
                        setCursor(Qt::ForbiddenCursor);

                    //展示图标坐标
                    QString str="坐标:";
                    str+=QString::number(j);
                    str+=",";
                    str+=QString::number(i);
                    if(turn==T_BLACK)ui->lb_black_position->setText(str);
                    else ui->lb_white_position->setText(str);
                    break;
                }
            }
    }
    else setCursor(Qt::ArrowCursor);
    update();
}

解释一下,棋盘左上点位置为(20,20),棋盘每格长度为30。判断鼠标落在哪个位置附近用一个正方形区域(边长也为30)去判断。设置鼠标光标用setCursor()就可以了。

有一个问题要注意:监视鼠标移动函数是默认关闭的,要鼠标点击一下才开启,为了避免这个问题,只需要在gameWidget的构造函数中添加setMouseTracking(true)。

    setMouseTracking(true);//不用点击鼠标也一直追踪 

2.由于玩家和ai都要下棋,模拟走一步棋可以用一个函数oneChessMove()来写:

void gameWidget::oneChessMove(int row, int col){
    
    
    if(turn==T_BLACK){
    
    
        turn=T_WHITE;
        ai.chesses[row][col]=C_BLACK;
    }
    else{
    
    
        turn=T_BLACK;
        ai.chesses[row][col]=C_WHITE;
    }

    gameResult result=ai.evaluate(ai.chesses).result;

    QMessageBox msg;
    msg.setIcon(QMessageBox::Critical);
    msg.setStandardButtons(QMessageBox::Yes);
    if(result!=R_DRAW){
    
    
        status=FINISH;
        if(result==R_BLACK){
    
    
            msg.setText("黑棋赢");
            score_black++;
        }
        else {
    
    
            msg.setText("白棋赢");
            score_write++;
        }
        msg.exec();

        ui->lcd_black->display(score_black);
        ui->lcd_write->display(score_write);
    }
    else if(isDeadGame()){
    
    
        status=FINISH;
        msg.setText("平局");
        msg.exec();
    }
    update();
}

3.玩家落子的位置获取:
重写鼠标释放函数:

void gameWidget::mouseReleaseEvent(QMouseEvent *event){
    
    
    if(mode==PLAYER){
    
    
        if(chessOneByPlayer()){
    
    
            if(status==FINISH)initializeGame();
        }
    }else{
    
    
        if(chessOneByPlayer()){
    
    
            if(status==UNDERWAY){
    
    
                chessOneByAi();
                if(status==FINISH)initializeGame();
            }
            else initializeGame();
        }
    }
}

其中chessOneByPlayer()用当前的cursorRow和cursorCol来走一步棋。

bool gameWidget::chessOneByPlayer(){
    
    
    if(ai.chesses[cursorRow][cursorCol]==C_NONE){
    
    
        oneChessMove(cursorRow,cursorCol);
        return true;
    }
    return false;
}

结束

简单实现了界面,但还没实现胜负判断和ai。我将胜负判断放到了ai的评估函数中实现。

下一篇:五子棋ai:极大极小搜索和α-β剪枝算法的思想和实现(qt和c++)(二)贪心算法和评估函数

猜你喜欢

转载自blog.csdn.net/livingsu/article/details/104536005