1

I am trying to write an algorithm using alpha-beta pruning for Checkers game (AI vs AI). You can see the code & comments below or in this PasteBin.

The game itself works fine but the AI (the alpha-beta pruning algorithm) seems to have an error in it, because the bots basically feed the checkers to each other (no calculations showed at all). The code contains 2 different versions of alpha-beta algorithm functions (more detailed and less detailed).

I've tried tracking value of tmp in alphabeta() and it seems to have normal values (ranging from -3 to 3 in case of depth = 5).

I've also tried implementing this code into mine, but got the same results.

My best guess is that the problem is in bool whiteTurn, which declares whose turn it is now, but I can't find any problems with it - the turns switch correctly.

Second best guess - Move bestMove. I am not sure if it is right to rip it out of recursive function.

What is the error?

#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;
}
Community
  • 1
  • 1
Zoomba
  • 11
  • 1
  • 2
  • Stack Overflow is not a debugging service. Additionally, we try to avoid external links as they may break in the future. If you can reduce your problem to a single specific question, please distill the question into a manageable code size, and make a new post. – Giewev Nov 11 '15 at 19:28
  • Please heed @Giewev's comment, however you should [edit] this question rather than create a new one. If the question has been put on hold, appropriate changes will trigger a re-open review. – Mogsdad Nov 11 '15 at 20:28

1 Answers1

0

The way alpha-beta cutoff is typically described in literature is needlessly convoluted. You don't need two limits, nor is it a good idea to evaluate move from the same players point of view. Here is a more clear description:

for all moves:
    evaluate the move  
    give it a temporary score from the point of view of the player at move  
    for all counter moves:
        evaluate the move
        give is a temporary score from the point of view of the player at move
        for all counter-counter moves:
            evaluate the move
            give it a score from the point of view of the player at move
            undo the counter-counter move
            update best counter-counter move if better
            if the counter-counter move is so good, that current
            counter move cant be best, (other is better) then break
        subtract best counter-counter-move score from temporary counter score
        undo the counter move
        update best counter move if better
        if the counter move is so good, that current move
        cant be best, (other is better) then break
    subtract best counter-move score from temporary score
    undo the move
    update best move if better

The logic is like this:
Lets say you have evaluated a couple of moves, and the best so far is worth 3
The temporary score of current move is 5.
The counter-move you currently evaluating is worth 4.
That means the current move can be worth at best 1. (5-4)
Since the current move can't be best, even better counter moves need not be found

sp2danny
  • 7,488
  • 3
  • 31
  • 53