【问题标题】:C++ Connect 4 Minimax + Alpha Beta Pruning AI making poor decisionsC++ Connect 4 Minimax + Alpha Beta Pruning AI 做出糟糕的决定
【发布时间】:2021-10-22 23:12:30
【问题描述】:

这篇文章是我上一篇文章的后续。为了提高 minimax connect-4 AI 算法的效率,我决定使用 alpha-beta 剪枝。这无疑有助于延长程序的运行时间(我之前认为这是一个无限递归),但 AI 并没有按照我的意愿工作。

AI 只是选择下一个可用的空白点进行标记,即使这会导致失败。

我已经尝试增加和减少深度级别,并确保检查获胜者的功能确实有效。此外,我将之前用于棋盘的二维向量转换为一维向量,并相应地更新了其他函数。

任何有关 AI 为何如此行事的帮助将不胜感激。

代码:

#include <iostream>
#include <vector>

using namespace std;

bool isFull(std::vector<char>& grid) { //just checks if no empty spaces
    for(int i = 0; i < 16; i++) {
        if(grid[i] == '-') { 
            return false;
        }
    } 

    return true;
}

pair<bool, char> isWinner(std::vector<char>& grid, char aiMark, char hMark) {
    pair<bool, char> temp; // the pair of: whether the game is over, and who won(if any.)
    //'X' if AI wins, 'O' if human wins, '-' if tie/game not over.

    //horizontal check
    for (int i = 0; i < 16; i += 4) {
        if (grid[i] == aiMark && grid[i + 1] == aiMark && 
            grid[i + 2] == aiMark && grid[i + 3] == aiMark) {
            temp.first = true;
            temp.second = aiMark;
            return temp;
        }
        else if (grid[i] == hMark && grid[i + 1] == hMark && 
                 grid[i + 2] == hMark && grid[i + 3] == hMark) {
            temp.first = true;
            temp.second = hMark;
            return temp;
        }
    }

    //vertical check
    for (int i = 0; i < 4; i++) {
        if (grid[i] == aiMark && grid[i + 4] == aiMark && 
            grid[i + 8] == aiMark && grid[i + 12] == aiMark) {
            temp.first = true;
            temp.second = aiMark;
            return temp;
        } 
        else if (grid[i] == hMark && grid[i + 4] == hMark && 
                 grid[i + 8] == hMark && grid[i + 12] == hMark) {
            temp.first = true;
            temp.second = hMark;
            return temp;
        }
    }

    //diagonal checks
    if (grid[0] == aiMark && grid[5] == aiMark && 
        grid[10] == aiMark && grid[15] == aiMark) {
        temp.first = true;
        temp.second = aiMark;
        return temp;
    } 
    else if (grid[0] == hMark && grid[5] == hMark && 
             grid[10] == hMark && grid[15] == hMark) {
        temp.first = true;
        temp.second = hMark;
        return temp;
    }

    if (grid[3] == aiMark && grid[6] == aiMark && 
        grid[9] == aiMark && grid[12] == aiMark) {
        temp.first = true;
        temp.second = aiMark;
        return temp;
    } 
    else if (grid[3] == hMark && grid[6] == hMark && 
             grid[9] == hMark && grid[12] == hMark) {
        temp.first = true;
        temp.second = hMark;
        return temp;
    }

    if (isFull(grid) == true) {
        temp.first = true;
        temp.second = '-';
        return temp;
    }

    temp.first = false;
    temp.second = '-';
    return temp;
}

int minimax(std::vector<char>& grid, int depth, bool maxim, 
            char aiMark, char hMark, int al, int be) {
    pair<bool, char> result = isWinner(grid, aiMark, hMark);
    
    // result.first will be true if game is over, and result.second is:
    // 'X' if ai wins, 'O' if human wins, '-' if game is not over or if it ends with tie
   
    if (result.first != false || depth == 0) {
        if (result.second == aiMark) {
            return depth; // AI wins (maximizing)
        } 
        else if (result.second == hMark) {
            return -depth; // Human wins (minimizing)
        } 
        else {
            return 0; // Tie or depth = 0
        }
    } 
    else {
        if (maxim == true) {
            int best = INT_MIN;

            for (int i = 0; i < 16; i++) {
                if (grid[i] == '-') { // is space empty?
                    grid[i] = aiMark; // editing board
                    int score = minimax(grid, depth - 1, !maxim, aiMark, hMark, al, be); // call minimax with "new" board
                    best = max(best, score); // update max
                    grid[i] = '-'; // backtrack
                    al = best; // update alpha
                        
                    if (al >= be) {
                        break; // pruning     
                    }
                }
            }
            
            return best; //return max score
        } 
        else {
            int worst = INT_MAX;

            for (int i = 0; i < 16; i++) {
                if (grid[i] == '-') {
                    grid[i] = hMark;
                    int score = minimax(grid, depth - 1, !maxim, aiMark, hMark, al, be);
                    worst = min(worst, score);
                    grid[i] = '-';
                    be = worst;

                    if (be <= al) {  //same as the maximizing player but is minimizing instead
                        break;
                    }
                }
            }

            return worst; //return min score
        }
    }
}

void bestMove(std::vector<char>& grid, char aiMark, char hMark) {
    int best = INT_MIN; //best score for ai
    int finalSpot = -1; //place where ai will put mark
    
    pair<bool, char> result = isWinner(grid, aiMark, hMark); // explained in minimax function comments
    
    if (result.first != false) {
        return; // if game is supposed to be over
    } 

    for (int i = 0; i < 16; i++) {
        if (grid[i] == '-') {
            grid[i] = aiMark;
            int score = minimax(grid, 8, true, aiMark, hMark, INT_MIN, INT_MAX);
             
            if (score > best) {
                best = score;
                finalSpot = i; // update best score and best spot
            }
                
            grid[i] = '-'; // backtrack
        }
    }
    
    grid[finalSpot] = aiMark; // AI finally updates grid
    return;
}

【问题讨论】:

  • 即使在深度级别 7 和修剪下,节点数也可能超过 100 万。所以代码的优化是关键。避免使用容器并尽量减少 for 循环的使用,使用基本数据类型,使用位级操作。查看 GNU chess 等国际象棋程序中使用的示例数据类型。
  • 问题不在于代码运行时间过长,而在于人工智能没有按照算法告诉它的方式工作。

标签: c++ minimax alpha-beta-pruning connect-four


【解决方案1】:

该算法可以选择失败的移动,因为在bestMove() 中,您放置了一个aiMark,然后调用minmax() 并将maxim 设置为true,这将连续放置第二个aiMark。在 IA 之后,人类不会玩。

关于 alpha beta,您还可以使用 alpha = max(alpha, best) 更新 alpha,以及使用 beta 的等效方式。你做的方式没有错,但没有优化,因为 alpha 的值可能会下降,而它应该只会上升。

我认为解决您的游戏的最佳方法是添加一个转置表。实施起来有点繁重,但 IA 将避免研究两次相同的位置。您可以先将代码转换为简单方便的 Negamax 版本。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-13
    • 1970-01-01
    • 2018-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-26
    相关资源
    最近更新 更多