使用alpha-beta修剪的C++检查器

C++ checkers using alpha-beta pruning

本文关键字:检查 C++ alpha-beta 修剪 使用      更新时间:2023-10-16

我正在尝试为跳棋游戏(AI对AI)编写一个使用阿尔法-贝塔修剪的算法。你可以看到代码&评论下面或在此粘贴框中。

游戏本身运行良好,但人工智能(阿尔法-贝塔修剪算法)似乎有错误,因为机器人基本上是互相喂跳棋的(根本没有显示计算结果)。该代码包含两个不同版本的alpha-beta算法函数(更详细和不太详细)。

我尝试过在alphabeta()中跟踪tmp的值,它似乎有正常值(在深度=5的情况下,范围从-3到3)。

我也尝试过在我的代码中实现这个代码,但得到了相同的结果。

我最好的猜测是问题出在bool whiteTurn中,它声明现在轮到谁了,但我找不到任何问题——匝数切换正确。

第二个最佳猜测——Move bestMove。我不确定把它从递归函数中剥离出来是否正确。

错误是什么?

#include "stdafx.h"
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Move
{
public:
    pair<int, int> start;
    pair<int, int> end;
    bool lethal;
    Move(int x, int y, int x1, int y1, bool kill)
    {
        start.first = x; start.second = y;
        end.first = x1; end.second = y1;
        lethal = kill;
    }
};

char **initBoard(int size)
{
    char **board = new char*[size];
    for (int count = 0; count < size; count++)
        board[count] = new char[size];
    return board;
}
void newGame(char **board, int size)
{
    for (int i = 0; i < size; i++)
        for (int j = 0; j < size; j++)
        {
            board[i][j] = '-';
            if ((i == 0 || i == 2) && j % 2 == 1) board[i][j] = 'O';
            if (i == 1 && j % 2 == 0) board[i][j] = 'O';
            if ((i == size - 3 || i == size - 1) && j % 2 == 0) board[i][j] = 'X';
            if (i == size - 2 && j % 2 == 1) board[i][j] = 'X';
        }
}
void printBoard(char **board, int size)
{
    for (int i = 0; i < size; i++)
    {
        for (int j = 0; j < size; j++)
        {
            cout << board[i][j] << " ";
        }
        cout << endl;
    }
    cout << endl;
}
void do_move(char **board, Move play)
{
    char temp = board[play.start.first][play.start.second];
    board[play.start.first][play.start.second] = board[play.end.first][play.end.second];
    board[play.end.first][play.end.second] = temp;
    if (play.lethal)
        board[(play.end.first + play.start.first) / 2][(play.end.second + play.start.second) / 2] = '-';
}
void undo_move(char **board, Move play)
{
    board[play.start.first][play.start.second] = board[play.end.first][play.end.second];
    board[play.end.first][play.end.second] = '-';
    if (play.lethal)
    {
        if (board[play.start.first][play.start.second] == 'X')
            board[(play.end.first + play.start.first) / 2][(play.end.second + play.start.second) / 2] = 'O';
        if (board[play.start.first][play.start.second] == 'O')
            board[(play.end.first + play.start.first) / 2][(play.end.second + play.start.second) / 2] = 'X';
    }
}
vector<Move> findMoves(char **board, int size, bool whiteTurn)
{
    vector<Move> moves;
    //first jump (if possible)
    for (int x = 0; x < size; x++)
    {
        for (int y = 0; y < size; y++)
        {
            if (whiteTurn && board[x][y] == 'X')
            {   
                if (x > 1 && y > 1 && board[x - 1][y - 1] == 'O' && board[x - 2][y - 2] == '-')
                    moves.push_back(Move(x, y, x - 2, y - 2, true));
                if (x > 1 && y < size - 2 && board[x - 1][y + 1] == 'O' && board[x - 2][y + 2] == '-')
                    moves.push_back(Move(x, y, x - 2, y + 2, true));
                if (x < size - 2 && y > 1 && board[x + 1][y - 1] == 'O' && board[x + 2][y - 2] == '-')
                    moves.push_back(Move(x, y, x + 2, y - 2, true));
                if (x < size - 2 && y < size - 2 && board[x + 1][y + 1] == 'O' && board[x + 2][y + 2] == '-')
                    moves.push_back(Move(x, y, x + 2, y + 2, true));
            }
            if (!whiteTurn && board[x][y] == 'O')
            {
                if (x > 1 && y > 1 && board[x - 1][y - 1] == 'X' && board[x - 2][y - 2] == '-')
                    moves.push_back(Move(x, y, x - 2, y - 2, true));
                if (x > 1 && y < size - 2 && board[x - 1][y + 1] == 'X' && board[x - 2][y + 2] == '-')
                    moves.push_back(Move(x, y, x - 2, y + 2, true));
                if (x < size - 2 && y > 1 && board[x + 1][y - 1] == 'X' && board[x + 2][y - 2] == '-')
                    moves.push_back(Move(x, y, x + 2, y - 2, true));
                if (x < size - 2 && y < size - 2 && board[x + 1][y + 1] == 'X' && board[x + 2][y + 2] == '-')
                    moves.push_back(Move(x, y, x + 2, y + 2, true));
            }
        }
    }
    //then move
    for (int x = 0; x < size; x++)
    {
        for (int y = 0; y < size; y++)
        {
            if (whiteTurn && board[x][y] == 'X')
            {
                if (x > 0 && y > 0 && board[x - 1][y - 1] == '-')
                    moves.push_back(Move(x, y, x - 1, y - 1, false));
                if (x > 0 && y < size - 1 && board[x - 1][y + 1] == '-')
                    moves.push_back(Move(x, y, x - 1, y + 1, false));
            }
            if (!whiteTurn && board[x][y] == 'O')
            {
                if (x < size - 1 && y > 0 && board[x + 1][y - 1] == '-')
                    moves.push_back(Move(x, y, x + 1, y - 1, false));
                if (x < size - 1 && y < size - 1 && board[x + 1][y + 1] == '-')
                    moves.push_back(Move(x, y, x + 1, y + 1, false));
            }
        }
    }
    return moves;
}
//plain score calculation function
int getScore(char **board, int size, bool whiteTurn)
{
    int whiteNum = 0, blackNum = 0;
    for (int i = 0; i < size; i++)
    {
        for (int j = 0; j < size; j++)
        {
            if (board[i][j] == 'X') whiteNum++;
            if (board[i][j] == 'O') blackNum++;
        }
    }
    if (whiteTurn)
        return whiteNum - blackNum;
    else
        return blackNum - whiteNum;
}
//old function, doesnt work as intended too
/*Move getBestMove(char **board, int size, bool whiteTurn)
{
    int score, tmp;
    Move bestMove(0, 0, 0, 0, false);
    vector<Move> movelist = findMoves(board, size, whiteTurn);
    score = getScore(board, size, whiteTurn);
    for (unsigned int i = 0; i < movelist.size(); i++)
    {
        do_move(board, movelist.at(i));
        tmp = getScore(board, size, whiteTurn);
        undo_move(board, movelist.at(i));
        if (tmp >= score)
        {
            score = tmp;
            bestMove = movelist.at(i);
        }
    }
    return bestMove;
}*/
//made this global - no idea how to avoid it being global with recursion in alphabeta
Move bestMove(0, 0, 0, 0, false);
//alphabeta function with more detailed calculations
/*int AlphaBeta(char **board, int size, bool whiteTurn, int depth, int alpha, int beta)
{
    if (depth == 0) return getScore(board, size, whiteTurn);
    int score = -100;
    vector<Move> movelist = findMoves(board, size, whiteTurn);
    for (unsigned int i = 0; i < movelist.size(); i++)
    {
        do_move(board, movelist.at(i));
        int tmp = -AlphaBeta(board, size, !whiteTurn, depth - 1, alpha, beta);
        undo_move(board, movelist.at(i));
        if (tmp > score)
        {
            if (whiteTurn)
            {
                if (score > alpha) 
                {
                    alpha = score;
                }
                if (-alpha <= beta)
                {
                    return alpha;
                }
            }
            else
            {
                if (score > beta)
                {
                    beta = score;
                }
                if (-beta <= alpha)
                {
                    return beta;
                }
            }
        }
    }
    return score;
}*/
//generic alphabeta function
int alphabeta(char **board, int size, bool whiteTurn, int depth, int alpha, int beta)
{
    if (depth == 0) return getScore(board, size, whiteTurn);
    vector<Move> movelist = findMoves(board, size, whiteTurn);
    for (const Move &move : movelist)
    {
        do_move(board, move);
        int tmp = -alphabeta(board, size, !whiteTurn, depth - 1, -beta, -alpha);
        undo_move(board, move);
        if (tmp > alpha)
        {
            if (depth == 5)
                bestMove = move;
            alpha = tmp;
        }
    }
    return alpha;
}
//main game loop
void game(char **board, int size, bool &whiteTurn)
{
    newGame(board, size);
    printBoard(board, size);
    system("PAUSE");
    int a = -std::numeric_limits<int>::max();
    int b = std::numeric_limits<int>::max();
    do
    {
        alphabeta(board, size, whiteTurn, 5, a, b);
        do_move(board, bestMove);
        whiteTurn = !whiteTurn;
        system("cls");
        printBoard(board, size);
        system("PAUSE");
    } while (!findMoves(board, size, whiteTurn).empty());
}
int main()
{   
    int n = 8;
    bool whTurn = true;
    char **board=initBoard(n);
    game(board, n, whTurn);
    return 0;
}

文献中通常描述α-β截止值的方式为不必要的复杂。你不需要两个限制,这也不是一个好主意从相同球员的角度来评估移动。这是更多清晰描述:

对于所有移动:评估移动从移动球员的角度给它一个临时的分数对于所有反向移动:评估移动give是从移动球员的角度来看的临时得分对于所有计数器移动:评估移动从移动球员的角度给它打分撤消反向移动如果更好,更新最佳计数器移动 如果计数器计数器移动得很好,那么电流反击不可能是最好的,(另一个更好)然后突破从临时计数器得分中减去最佳计数器移动得分撤消反动作如果更好,则更新最佳对策 如果反向移动非常好,则当前移动不可能是最好的,(另一个更好)然后打破从临时得分中减去最佳反击得分撤消移动更新最佳移动(如果更好)

逻辑如下:
假设你已经评估了几步,到目前为止最好的一步值得3步
当前移动的临时分数为5。
您当前评估的反动作值4。
这意味着当前的移动最多值1。(5-4)
由于当前移动不可能是最好的,因此不需要找到更好的反移动