-1

I've been trying to implement minimax algorithm in my C# chess engine for a week now and it makes legal moves but not meaningful moves. I cannot find the error, hopefully someone can spot it. I have tried to isolate the problem by testing each method and they all seem to work correctly except minimax.


public enum Piece
{
    Empty, Pawn_W, Pawn_B, Knight_W, Knight_B,
    Bishop_W, Bishop_B, Rook_W, Rook_B,
    Queen_W, Queen_B, King_W, King_B
}

public bool IsPieceWhite(Piece piece)
{
    if (piece == Piece.Pawn_W || piece == Piece.Knight_W ||
        piece == Piece.Bishop_W || piece == Piece.Rook_W ||
        piece == Piece.Queen_W || piece == Piece.King_W)
        return true;
    else return false;
}

public bool IsPieceBlack(Piece piece)
{
    if (piece == Piece.Pawn_B || piece == Piece.Knight_B ||
        piece == Piece.Bishop_B || piece == Piece.Rook_B ||
        piece == Piece.Queen_B || piece == Piece.King_B)
        return true;
    else return false;
}

public int GetPieceWorth(Piece piece)
{
    if (piece == Piece.Pawn_W || piece == Piece.Pawn_B)
        return 1;
    if (piece == Piece.Knight_W || piece == Piece.Knight_B)
        return 3;
    if (piece == Piece.Bishop_W || piece == Piece.Bishop_B)
        return 3;
    if (piece == Piece.Rook_W || piece == Piece.Rook_B)
        return 5;
    if (piece == Piece.Queen_W || piece == Piece.Queen_B)
        return 9;
    if (piece == Piece.King_W || piece == Piece.King_B)
        return 9999999;

    return 0;
}
Piece[,] CurrentBoard = GetStartingBoard();
Piece[,] bestMove;
public int depthB = 3;

public double minimax(Piece[,] board, int depth, bool maximizingPlayer)
{
    if (depth == 0)
    {
        double result = EvaluatePosition(board, maximizingPlayer);
        return result;
    }

    if (maximizingPlayer)
    {
        double best = Double.MinValue;
        double value = Double.MinValue;
        foreach (var move in GenerateMoves(board, maximizingPlayer))
        {
            Piece[,] clonedMove = CloneBoard(move);
            value = Math.Max(value, minimax(clonedMove, depth - 1, false));
            if (depth == depthB && value >= best)
            {
                best = value;
                bestMove = clonedMove;
            }
        }
        return value;
    }
    else
    {
        double best = Double.MaxValue;
        double value = Double.MaxValue;
        foreach (var move in GenerateMoves(board, maximizingPlayer))
        {
            Piece[,] clonedMove = CloneBoard(move);
            value = Math.Min(value, minimax(clonedMove, depth - 1, true));
            if (depth == depthB && value <= best)
            {
                best = value;
                bestMove = clonedMove;
            }
        }
        return value;
    }
}

public Piece[,] CloneBoard(Piece[,] boardPos)
{
    Piece[,] copy = boardPos.Clone() as Piece[,];
    return copy;
}

public double EvaluatePosition(Piece[,] boardPos, bool ForWhite)
{
    double eval = 0;
    for (int i = 0; i < 8; i++)
    {
        for (int j = 0; j < 8; j++)
        {
            if (boardPos[i, j] != Piece.Empty)
            {
                if (IsPieceWhite(boardPos[i, j]))
                {
                    eval += GetPieceWorth(boardPos[i, j]);
                }
                else if (IsPieceBlack(boardPos[i, j]))
                {
                    eval -= GetPieceWorth(boardPos[i, j]);
                }
            }
        }
    }

    if (ForWhite)
        return eval;
    else
        return eval * -1;
}

//a-h,0-7
//Piece[,] board = new Piece[8, 8];

public static Piece[,] GetStartingBoard()
{
    Piece[,] board = new Piece[8, 8];
    for (int i = 0; i < 8; i++)
    {
        //initiate pawns
        board[1, i] = Piece.Pawn_W;
        board[6, i] = Piece.Pawn_B;
    }

    //white pieces
    board[0, 0] = Piece.Rook_W;
    board[0, 1] = Piece.Knight_W;
    board[0, 2] = Piece.Bishop_W;
    board[0, 3] = Piece.Queen_W;
    board[0, 4] = Piece.King_W;
    board[0, 5] = Piece.Bishop_W;
    board[0, 6] = Piece.Knight_W;
    board[0, 7] = Piece.Rook_W;

    //black pieces
    board[7, 0] = Piece.Rook_B;
    board[7, 1] = Piece.Knight_B;
    board[7, 2] = Piece.Bishop_B;
    board[7, 3] = Piece.Queen_B;
    board[7, 4] = Piece.King_B;
    board[7, 5] = Piece.Bishop_B;
    board[7, 6] = Piece.Knight_B;
    board[7, 7] = Piece.Rook_B;

    //test
    //board[1, 4] = Piece.Pawn_B;
    //board[6, 2] = Piece.Pawn_W;

    return board;
}

I have uploaded a short clip of the engine playing against itself, to show the wierd moves: https://www.youtube.com/watch?v=A0HVgXYSciY

Toni
  • 1,555
  • 4
  • 15
  • 23
foRei
  • 9
  • 1
  • The evaluation function for chess is not trivial. The function in your code won't give any meaningful results unless the `depth` is so large that there's a significant non-transient difference in piece values. At the beginning of the game, that means `depth` needs to be so large that it's impractical to use minimax. – user3386109 Feb 06 '21 at 22:58
  • But is it not strange that Black does not take the Pawn with the Bishop as shown in the video? – foRei Feb 06 '21 at 23:03
  • Minimax is notorious for doing unexpected things when the evaluation function is flawed. The simplest example is [this tic tac toe position](https://i.stack.imgur.com/uzg8n.png). Computer plays top-center when it could just win. Why? Because top-center is evaluated before top-right, depth is unlimited, and the evaluation function is based solely on who wins. By playing top-center, the computer is guaranteed to win. There is no better outcome than that, so that's the move the computer chooses. – user3386109 Feb 07 '21 at 00:03
  • If you want to know what your minimax algorithm is seeing, then hardcode that position in your `GetStartingBoard` function. Then print out the score for each possible move. – user3386109 Feb 07 '21 at 00:05
  • It is not making legal moves, in the middle of the video it promotes to a queen next to the queen, and black then plays a rook move even when the only move is to take the queen. – eligolf Feb 07 '21 at 11:17
  • It is also strange that you are using the condition 'if depth == depthB', why are you doing this? – eligolf Feb 07 '21 at 11:19
  • To test your evaluation function you can print the evaluation of the board after each move to see if it looks correct. It also needs to be symmetrical for black and white. – eligolf Feb 07 '21 at 11:19
  • Yeah, I guess the evaluation function is too simple. I think my expectations of what results minimax would produce on 4 depth, were too great. The reason I do 'if depth == depthB', is to save "bestMove" only on the highest depth. – foRei Feb 07 '21 at 20:12
  • No no, it should still give better results. It gives away pieces! It should see that at depth 2. You have some error in your evaluation, do some testing to find out if you send back the desired results. – eligolf Feb 08 '21 at 16:01

1 Answers1

0

Finally was able to make the minimax function work. Thanks for all the help!

Working method:

        public int minimax(Piece[,] board, int depth, bool maximizingPlayer, bool WhiteToPlay)
        {
            if (depth == 0)
            {
                int result = EvaluatePosition(board, WhiteToPlay);
                return result;
            }

            var moves = GenerateMoves(board, WhiteToPlay);
            if (maximizingPlayer)
            {
                int value = int.MinValue;
                foreach (var move in moves)
                {
                    int minmaxResult = minimax(move, depth - 1, false, !WhiteToPlay);
                    value = Math.Max(value, minmaxResult);
                    if (depth == depthB)
                    {
                        moveScores.Add(move, minmaxResult);
                    }
                }
                return value;
            }
            else
            {
                int value = int.MaxValue;
                foreach (var move in moves)
                {
                    int minmaxResult = minimax(move, depth - 1, true, !WhiteToPlay);
                    value = Math.Min(value, minmaxResult);
                    if (depth == depthB)
                    {
                        moveScores.Add(move, minmaxResult);
                    }
                }
                return value;
            }
        }
foRei
  • 9
  • 1