使用 Min-Max 搜索和启发式评估函数实现五子棋 AI

本文介绍了如何使用极大极小搜索策略和启发式评估函数来设计五子棋AI。AI分析棋局并在有限深度内寻找最佳落子,通过评估函数判断棋局优势。Alpha-Beta剪枝优化搜索效率,实验结果显示AI能够有效应对各种局面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述

五子棋AI。

设计一个交互式的应用,用户用鼠标在棋盘上单击左键表示落子,然后五子棋AI分析棋局,并在它认为最好的地方落子,双方交替,直到分出胜负或者和棋。

在分析问题的过程中,我们假定图形用户界面已经完成,并且支持“开始游戏”、“重新开始”、“调整先后手”、“调整难度”等功能,获取鼠标的输入以及显示棋盘布局的功能也都正常,那么我们可以把精力放在五子棋AI类的具体实现上。

现在,问题被抽象成,在一个15*15的二维数组中,1表示黑棋,0表示白棋,-1表示还没有落子的空格,AI程序要做的是分析当前的局面,运用启发式评估函数进行搜索,找到对自己最有利(包括对对手限制最多)的地方落子,找到以后AI类返回这个点的坐标。

深度优先搜索似乎是可以完成这个任务的,但是很明显,就算是将大量的不可能是最佳落子点的部分去掉,形成的搜索树也是庞大到不可能在短时间内搜索完成。

人下棋的时候实际上用的是一种试探性的方法。

首先假定在这个位置走了一步棋,然后思考对方会采取哪些策略,或者对我的棋进行围追堵截,或者是继续下他的棋,然后我再根据对方可能采取的方法,看看我是不是有更好的回应……

这个过程一直持续下去,直到若干个轮回以后,找到了一个满意的走法为止。然后我在满意的地方落子。

初学者可能只能看一、两个轮次,而高手可以看几个甚至十几个轮次。

极大极小搜索策略,就是模拟人的这样一种思维过程。

算法描述

极大极小搜索策略

这个搜索策略是考虑双方对弈若干步以后,从可能的走法中找到一个相对较好的来落子,即在有限的搜索深度范围内进行求解。

T:=(s,MAX)
把s加入到OPEN表
CLOSED表为空
LOOP1:
IF OPEN EQ ()
THEN GO LOOP2
n:=FIRST(OPEN)
并将n加入到CLOSED表
IF n可以判断输赢
THEN f(x):=INF OR -INF OR 0, GO LOOP1
ELSE EXPAND(n) to {n_i}, ADD({n_i},T)
IF d(n_i)<k
THEN ADD({n_i},OPEN), GO LOOP1
ELSE 计算f(n_i), GO LOOP1
LOOP2:
IF CLOSED EQ NIL
THEN GO LOOP3
ELSE n_p:=FIRST(CLOSED)
IF n_p in MAX AND f(n_ci) in MIN 有值
THEN f(n_p):=max{f(n_cj)}, 从CLOSED删除n_p
IF n_p in MIN AND f(n_ci) in MAX 有值
THEN f(n_p):=min{f(n_cj)}, 从CLOSED删除n_p
GO LOOP2
LOOP3:

前面的代码都是分别用两部分代码处理了极大节点和极小节点两种情况,其实,可以只用一部分代码,既处理极大节点也处理极小节点。

不同的是,前面的评估函数是针对指定的一方来给出分数的,这里的评估函数是根据当前搜索节点来给出分数的。

每个人都会选取最大的分数,然后,返回到上一层节点时,会给出分数的相反数。

int AI::MINMAX_Search_With_AlphaBetaCutOff(int depth, int player) {
    int best = NEGATIVE_INFINITY;
    if (depth == this->depth) {
        return heuristic(player); 
    }
    list<Point> children; 
    for (int i = 0; i < GRID_NUM; ++i)
        for (int j = 0; j < GRID_NUM; ++j) {
            if (chessBoard[i][j] == NONE && nearby(i, j)) {
                children.emplace_back(Point(i, j));
            }
        }
    for (list<Point>::iterator it = children.begin(); it != children.end(); it++) {
        setPos(*it, player);
        int val = -MINMAX_Search_With_AlphaBetaCutOff(depth + 1, 1 - player); // 注意这里有个负号
        setPos(*it, NONE);
        if (val > best) {
            best = val;
            next = *it;
        }
    }
    return best;
}

MINMAX搜索的过程是把搜索树的生成和格局估值这两个过程分开来进行,即先生成全部搜索树,然后再进行端结点静态估值和倒退值的计算,这显然会导致低效率。

事实上,如果生成某个结点A以后,马上进行静态估值,得知f(A)=-∞之后,就可以断定再生成其余结点即进行静态计算是多余的,可以马上对MIN结点赋倒推值-∞,而丝毫不会影响MAX的最好优先走步的选择。

Alpha-Beta剪枝用于裁剪搜索树中没有意义的不需要搜索的树枝,以提高运算速度。

它的基本思想是根据上一层已经得到的当前最优结果,决定目前的搜索是否要继续下去。

如果某个着法的结果小于或等于Alpha,那么它就是很差的着法,因此可以抛弃。

如果某个着法的结果大于或等于Beta,那么整个节点就作废了,因为对手不希望走到这个局面,而它有别的着法可以避免到达这个局面。因此如果我们找到的评价大于或等于Beta,就证明了这个结点是不会发生的,因此剩下的合理着法没有必要再搜索。

如果某个着法的结果大于Alpha但小于Beta,那么这个着法就是走棋一方可以考虑走的,除非以后有所变化。

if  depth = 0 or node is a terminal node
    return the heuristic value of node
if  Player = MaxPlayer // 极大节点
    for each child of node // 极小节点
        alpha := max(alpha, alphabeta(child, depth-1, alpha, beta, not(Player) ))   
        if beta <= alpha // 该极大节点的值>=alpha>=beta,该极大节点后面的搜索到的值肯定会大于beta,因此不会被其上层的极小节点所选用了。对于根节点,beta为正无穷
            break                            
    return alpha
else // 极小节点
    for each child of node // 极大节点
        beta := min(beta, alphabeta(child, depth-1, alpha, beta, not(Player) ))        if beta <= alpha // 该极大节点的值<=beta<=alpha,该极小节点后面的搜索到的值肯定会小于alpha,因此不
### Minimax算法五子棋游戏中的实现 Minimax算法是一种用于博弈树搜索的递归算法,在零博弈中表现良好,其中一方玩家的收益即为另一方玩家的损失。对于像五子棋这样的双人交替下子的游戏来说非常适用。 #### 算法概述 该算法通过模拟所有可能的状态来预测最佳走步[^1]。具体而言: - 对于当前节点,如果是最大化层(轮到己方行动),则尝试找到能带来最高得分的动作; - 如果是最小化层(对方回合),那么假设对手会采取最优策略对抗自己,因此寻找使分值最小化的动作; 当达到预设的最大探索深度或是遇到终局状态时停止递归,并返回静态估值作为依据进行比较。 #### 实现细节 为了更好地适应五子棋的特点,可以引入一些优化措施: ##### 剪枝技术 Alpha-Beta剪枝可以在不影响最终结果的前提下减少不必要的分支计算量,从而提高效率。 ##### 启发式评估函数设计 考虑到五子棋的目标是在一条直线上连成五个同色棋子获胜,所以应该重点考虑以下几个方面: - 连续相同颜色棋子的数量及其潜在威胁程度; - 阻断敌人的连续排列; - 控制中心区域以及周边有利地形; 这些因素共同构成了复杂的局面评分体系,有助于更精准地判断局势优劣并指导下一步的选择[^3]。 ```cpp // C++版本简化版Minimax算法框架 #include <limits> using namespace std; const int INF = numeric_limits<int>::max(); int evaluatePosition(const Board& board, Color playerColor){ // 计算当前盘面价值... } bool gameOver(const Board& board){ // 判断是否结束... } vector<Move> generateMoves(const Board& board){ // 生成合法移动列表... } void makeMove(Board& board, Move move){ // 应用一步操作... } void undoMove(Board& board, Move move){ // 恢复上一步前的状态... } pair<int, Move> minimaxWithABPruning( const Board& board, Depth depthRemaining, bool maximizingPlayer, Score alpha, Score beta) { if(gameOver(board) || !depthRemaining) return {evaluatePosition(board, /*player*/), {}}; auto moves = generateMoves(board); pair<int, Move> result; for(auto&& mv : moves){ Board copy = board; // 复制一份副本 makeMove(copy, mv); auto scoreAndMove = minimaxWithABPruning( copy, depthRemaining - 1, not maximizingPlayer, alpha, beta ); if(maximizingPlayer){ if(scoreAndMove.first > alpha){ alpha = scoreAndMove.first; result = {alpha, mv}; } if(alpha >= beta) break; // Alpha-beta pruning }else{ if(beta > scoreAndMove.first){ beta = scoreAndMove.first; result = {beta, mv}; } if(alpha >= beta) break; // Alpha-beta pruning } } return result; } ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凝神长老

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值