SebLague / Chess-Challenge

Create your own tiny chess bot!
https://www.youtube.com/watch?v=Ne40a5LkK6A
MIT License
1.78k stars 1.06k forks source link

how can i turn an evaluation integer to then get the move from that evaluation #147

Open chavchilon opened 12 months ago

chavchilon commented 12 months ago

i have been trying to implement a simple evaluation into my chess bot and right now i have the search for the best move function return a integer of the highest evaluation possible from each move however i cant quite seem to figure out how to turn that evaluation into the corosponding move that got that evaluation to then play the move.

here is the code for the search function:

    int Search (int depth)
    {
        if (depth == 0)
        {
            return Evaluate(board);
        }

        Move[] moves = board.GetLegalMoves();
        if (moves.Length == 0)
        {
            if (board.IsInCheck())
            {
                return int.MinValue;;
            }
            return 0;
        }

        int bestEvaluation = int.MinValue;

        foreach (Move move in moves)
        {
            board.MakeMove(move);
            int evaluation = -Search (depth - 1);
            bestEvaluation = Math.Max(evaluation, bestEvaluation);
            board.UndoMove(move);
        }

        return bestEvaluation;
    }

any help would be appreciated

CaptainDeathead commented 12 months ago

hi, have you watched Sebastian's videos? He covers this stuff in depth, however i managed to come up with a function that did just what you want.

  1. Initialize a integer called score
  2. loop through the pieces on the board
  3. check what color the pieces are and add if white, take if black
  4. return score
WhiteMouse1 commented 12 months ago

I would replace foreach() with for() or added a counter variable to track index of which Move from moves is being evaluated. I would add a variable containing index of current best move. I would replace Math.Max with if(evaluation > bestEvaluation) and if it is true, I would set best move index to current index. Then instead of bestEvaluation I would return best move index.

CaptainDeathead commented 12 months ago

I would also like to add that my code definitely isn't efficient as i got stuck on looping through the pieces. I first used foreach loop to loop through all the pieces in the board.GetAllPieceLists() and the looped through each piece in each of those lists. Not very good but does the job.

WhiteMouse1 commented 12 months ago

EDIT: This is catastrophically wrong. Closer to working, but still horribly wrong!

Move[] moves = board.GetLegalMoves();
int Search(int depth)
        {
            if (depth == 0)
            {
                return Evaluate(board);
            }

            if (moves.Length == 0)
            {
                if (board.IsInCheck())
                {
                    return int.MinValue; ;
                }
                return 0;
            }

            int bestEvaluation = int.MinValue;
            int bestMoveIndex = 0;
            for(int i = 0; i < moves.Length; i++)
            {
                board.MakeMove(moves[i]);
                int evaluation = -Search(depth - 1);

                if(evaluation > bestEvaluation)
                {
                    bestEvaluation = evaluation;
                    bestMoveIndex = i;
                }
                board.UndoMove(moves[i]);
            }
            return bestMoveIndex;
        }
// A few lines later...
board.MakeMove(move[Search(5)]);
chavchilon commented 12 months ago
Move[] moves = board.GetLegalMoves();
int Search(int depth)
        {
            if (depth == 0)
            {
                return Evaluate(board);
            }

            if (moves.Length == 0)
            {
                if (board.IsInCheck())
                {
                    return int.MinValue; ;
                }
                return 0;
            }

            int bestEvaluation = int.MinValue;
            int bestMoveIndex = 0;
            for(int i = 0; i < moves.Length; i++)
            {
                board.MakeMove(moves[i]);
                int evaluation = -Search(depth - 1);

                if(evaluation > bestEvaluation)
                {
                    bestEvaluation = evaluation;
                    bestMoveIndex = i;
                }
                board.UndoMove(moves[i]);
            }
            return bestMoveIndex;
        }
// A few lines later...
board.MakeMove(move[Search(5)]);

thank you for this, this is exactly what i was looking for however for some reason i am getting this error right at the start of a game

image

i think this has to do with the move not being undone properly or an illegal move is attempted but i dont see what in the code could have caused this issue.

chavchilon commented 12 months ago

hi, have you watched Sebastian's videos? He covers this stuff in depth, however i managed to come up with a function that did just what you want.

  1. Initialize a integer called score
  2. loop through the pieces on the board
  3. check what color the pieces are and add if white, take if black
  4. return score

please could you tell me which video you mean

CaptainDeathead commented 12 months ago

this video is good for the basic algorithm: ### https://www.youtube.com/watch?v=U4ogK0MIzqk&t=1058s

while this one goes into more depth on different strategies of optimisation: ### https://www.youtube.com/watch?v=_vqlIPDR2TU&t=2565s

CaptainDeathead commented 12 months ago

I'd watch the top link first, and implement it off of that

chavchilon commented 12 months ago

I'd watch the top link first, and implement it off of that

that is were i got the code for the search and evaluation algorithm but i cant seem to see what he does with the search function outpur at 15:05

CaptainDeathead commented 12 months ago

I see, its pretty simple, something like this: `private int Evaluate(Board board) { int score = 0;

    foreach (PieceList pieceList in board.GetAllPieceLists())
    {
        foreach (Piece piece in pieceList)
        {
            if (piece.IsWhite)
            {
                if (piece.IsPawn) score += 1;
                else if (piece.IsKnight) score += 3;
                else if (piece.IsBishop) score += 3;
                else if (piece.IsRook) score += 5;
                else if (piece.IsQueen) score += 9;
                else if (piece.IsKing) score += 20000;
            }
            else
            {
                if (piece.IsPawn) score -= 1;
                else if (piece.IsKnight) score -= 3;
                else if (piece.IsBishop) score -= 3;
                else if (piece.IsRook) score -= 5;
                else if (piece.IsQueen) score -= 9;
                else if (piece.IsKing) score -= 20000;
            }
        }
    }

    return score;
}`
CaptainDeathead commented 12 months ago

And in the video he shows the if statement saying if depth == 0: evaluate(board)

WhiteMouse1 commented 12 months ago

Okay, I made a terrible mistake. Search function must return evaluations when run recursively, but I made it return move indexes. So the logic is utter nonsense inside it.

Exception happens when evaluation returns an int which is out of bounds for moves[] array, but still gets plugged into it. So if Evaluate returns something like -1, Think function tries to return moves[-1]; Which causes a crash.

chavchilon commented 12 months ago

I see, its pretty simple, something like this: `private int Evaluate(Board board) { int score = 0;

    foreach (PieceList pieceList in board.GetAllPieceLists())
    {
        foreach (Piece piece in pieceList)
        {
            if (piece.IsWhite)
            {
                if (piece.IsPawn) score += 1;
                else if (piece.IsKnight) score += 3;
                else if (piece.IsBishop) score += 3;
                else if (piece.IsRook) score += 5;
                else if (piece.IsQueen) score += 9;
                else if (piece.IsKing) score += 20000;
            }
            else
            {
                if (piece.IsPawn) score -= 1;
                else if (piece.IsKnight) score -= 3;
                else if (piece.IsBishop) score -= 3;
                else if (piece.IsRook) score -= 5;
                else if (piece.IsQueen) score -= 9;
                else if (piece.IsKing) score -= 20000;
            }
        }
    }

    return score;
}`

i have somthing very similar to this in my code:

public static int Evaluate(Board board)
{
    const int pawnValue = 100;
    const int knightValue = 250;
    const int bishopValue = 300;
    const int rookValue = 500;
    const int queenValue = 900;    

    PieceList[] pieces = board.GetAllPieceLists();

    int whitePoints = 0;
    whitePoints += pieces[0].Count * pawnValue;
    whitePoints += pieces[1].Count * knightValue;
    whitePoints += pieces[2].Count * bishopValue;
    whitePoints += pieces[3].Count * rookValue;
    whitePoints += pieces[4].Count * queenValue;

    int blackPoints = 0;
    blackPoints += pieces[5].Count * pawnValue;
    blackPoints += pieces[6].Count * knightValue;
    blackPoints += pieces[7].Count * bishopValue;
    blackPoints += pieces[8].Count * rookValue;
    blackPoints += pieces[9].Count * queenValue;

    int eval = whitePoints - blackPoints;

    int mapRange = (board.IsWhiteToMove) ? 1: -1;

    Console.WriteLine(eval * mapRange);
    return eval * mapRange;
}

but i use this in the score function but what would i do with the best evaluation value in order to get the move from it?

chavchilon commented 12 months ago

Okay, I made a terrible mistake. Search function must return evaluations when run recursively, but I made it return move indexes. So the logic is utter nonsense inside it.

Exception happens when evaluation returns an int which is out of bounds for moves[] array, but still gets plugged into it. So if Evaluate returns something like -1, Think function tries to return moves[-1]; Which causes a crash.

would you happen to know a solution to this??

CaptainDeathead commented 12 months ago

No, unfortunately but would you be able to post your whole code so its easier to understand?

chavchilon commented 12 months ago

No, unfortunately but would you be able to post your whole code so its easier to understand?

ok, here is the whole piece of code:

using ChessChallenge.API; using System; using System.Collections; using System.Collections.Generic;

public class MyBot : IChessBot { public Move Think(Board board, Timer timer) { Move[] captures = board.GetLegalMoves(true); Move[] moves = board.GetLegalMoves();

    Move mov = new();
    System.Random rnd = new();
    //Move[] LondonSystem = {new Move("d2d4",board),new Move("g1f3",board),new Move("c1f4",board),new Move("e2e3",board),new Move("f1d3",board),new Move("b1d2",board),new Move("c2c3",board),};

    Move BestMove = mov;

    int Search(int depth)
    {
        if (depth == 0)
        {
            return Evaluate(board);
        }

        if (moves.Length == 0)
        {
            if (board.IsInCheck())
            {
                return int.MinValue; ;
            }
            return 0;
        }

        int bestEvaluation = int.MinValue;
        int bestMoveIndex = 0;
        for(int i = 0; i < moves.Length; i++)
        {
            board.MakeMove(moves[i]);
            int evaluation = -Search(depth - 1);

            if(evaluation > bestEvaluation)
            {
                bestEvaluation = evaluation;
                bestMoveIndex = i;
            }
            board.UndoMove(moves[i]);
        }
        return bestMoveIndex;
    }

    Move bestMove = moves[Search(4)];

    return bestMove;
}

public static int Evaluate(Board board)
{
    const int pawnValue = 100;
    const int knightValue = 250;
    const int bishopValue = 300;
    const int rookValue = 500;
    const int queenValue = 900;    

    PieceList[] pieces = board.GetAllPieceLists();

    int whitePoints = 0;
    whitePoints += pieces[0].Count * pawnValue;
    whitePoints += pieces[1].Count * knightValue;
    whitePoints += pieces[2].Count * bishopValue;
    whitePoints += pieces[3].Count * rookValue;
    whitePoints += pieces[4].Count * queenValue;

    Console.WriteLine("White Score: ", whitePoints);

    int blackPoints = 0;
    blackPoints += pieces[5].Count * pawnValue;
    blackPoints += pieces[6].Count * knightValue;
    blackPoints += pieces[7].Count * bishopValue;
    blackPoints += pieces[8].Count * rookValue;
    blackPoints += pieces[9].Count * queenValue;

    Console.WriteLine("Black Score: ", blackPoints);        

    int eval = whitePoints - blackPoints;

    int mapRange = (board.IsWhiteToMove) ? 1: -1;

    Console.WriteLine("Evaluation: ", eval * mapRange);
    return eval * mapRange;
}

}

there are a couple of things from previous experiments left behind but they do not effect any of the code to my knowledge.

WhiteMouse1 commented 12 months ago

Search returns evaluation of a current position, affected by evaluations of positions that can be created from it to the selected depth. So, to get evaluations for all legal moves of current side, one must make each of the moves, run Search and save the results, in an Array, for example. Then, find the largest evaluation for a move and play corresponding move.

public Move Think(Board board, Timer timer)
    {
        Move[] moves = board.GetLegalMoves();

        // array of the same size as moves array
        // index i of evals array contains evaluation of a move at index i in moves array
        int[] evals = new int[moves.Length];

        // Make move, evaluate the board
        // Save evaluation at the same index as played move's index
        for(int i = 0; i < moves.Length; i++)
        {
            board.MakeMove(moves[i]);
            evals[i] = Search(1);
            board.UndoMove(moves[i]);
        }

        int Search(int depth)
        {
            // Legal moves for different board states are different
            // Therefore each cycle of recursion must use its own move array
            Move[] s_moves = board.GetLegalMoves();

            if (depth == 0)
            {
                return Evaluate(board);
            }

            int bestEvaluation = int.MinValue;
            for(int i = 0; i < s_moves.Length; i++)
            {
                board.MakeMove(s_moves[i]);

                int evaluation = -Search(depth - 1);
                bestEvaluation = Math.Max(evaluation, bestEvaluation);

                board.UndoMove(s_moves[i]);
            }
            return bestEvaluation;
        }

        // Evaluation function
        int Evaluate(Board board)
        {
           // Create your function here
            return 0;
        }
        // Array.IndexOf(some_value) returns index of element with provided value
        // evals.Max() returns value of largest element
        // I did it like this instead of a simple loop
       // to save some tokens
        return moves[Array.IndexOf(evals, evals.Max())];
    }
CaptainDeathead commented 12 months ago

@WhiteMouse1 I just tested that on my end and it works. @chavchilon you should be good, here is the full working code:

CaptainDeathead commented 12 months ago

`using ChessChallenge.API; using System; using System.Collections; using System.Collections.Generic; using System.Linq;

public class MyBot : IChessBot { public Move Think(Board board, Timer timer) { Move[] moves = board.GetLegalMoves();

    // array of the same size as moves array
    // index i of evals array contains evaluation of a move at index i in moves array
    int[] evals = new int[moves.Length];

    // Make move, evaluate the board
    // Save evaluation at the same index as played move's index
    for (int i = 0; i < moves.Length; i++)
    {
        board.MakeMove(moves[i]);
        evals[i] = Search(1);
        board.UndoMove(moves[i]);
    }

    int Search(int depth)
    {
        // Legal moves for different board states are different
        // Therefore each cycle of recursion must use its own move array
        Move[] s_moves = board.GetLegalMoves();

        if (depth == 0)
        {
            return Evaluate(board);
        }

        int bestEvaluation = int.MinValue;
        for (int i = 0; i < s_moves.Length; i++)
        {
            board.MakeMove(s_moves[i]);

            int evaluation = -Search(depth - 1);
            bestEvaluation = Math.Max(evaluation, bestEvaluation);

            board.UndoMove(s_moves[i]);
        }
        return bestEvaluation;
    }

    // Evaluation function
    int Evaluate(Board board)
    {
        // Create your function here
        return 0;
    }
    // Array.IndexOf(some_value) returns index of element with provided value
    // evals.Max() returns value of largest element
    // I did it like this instead of a simple loop
    // to save some tokens
    return moves[Array.IndexOf(evals, evals.Max())];
}

public static int Evaluate(Board board)
{
    const int pawnValue = 100;
    const int knightValue = 250;
    const int bishopValue = 300;
    const int rookValue = 500;
    const int queenValue = 900;

    PieceList[] pieces = board.GetAllPieceLists();

    int whitePoints = 0;
    whitePoints += pieces[0].Count * pawnValue;
    whitePoints += pieces[1].Count * knightValue;
    whitePoints += pieces[2].Count * bishopValue;
    whitePoints += pieces[3].Count * rookValue;
    whitePoints += pieces[4].Count * queenValue;

    Console.WriteLine("White Score: ", whitePoints);

    int blackPoints = 0;
    blackPoints += pieces[5].Count * pawnValue;
    blackPoints += pieces[6].Count * knightValue;
    blackPoints += pieces[7].Count * bishopValue;
    blackPoints += pieces[8].Count * rookValue;
    blackPoints += pieces[9].Count * queenValue;

    Console.WriteLine("Black Score: ", blackPoints);

    int eval = whitePoints - blackPoints;

    int mapRange = (board.IsWhiteToMove) ? 1 : -1;

    Console.WriteLine("Evaluation: ", eval * mapRange);
    return eval * mapRange;
}

}`

CaptainDeathead commented 12 months ago

@chavchilon You probably want to look into adding some more in depth (pun intended) alpha beta pruning as i don't think you have any, just minimax at the moment. That is also outlined in his video and he made a whole video on alpha beta pruning with the minimax algorithm here: ### https://www.youtube.com/watch?v=l-hh51ncgDI

chavchilon commented 12 months ago

@WhiteMouse1 @CaptainDeathead, Thank you guys so much the code now works fine with a couple of tweaks to check whether the bot is white or black. @CaptainDeathead i am now thinking of adding alpha beta tweaking and eval ordering but i just wanted to get the raw min max working. i cant thank you guys enough you have saved me multiple hours or potentially days of head scratching.

WhiteMouse1 commented 12 months ago

I will add a strong warning about signs. Don't mix 'em up or you will end up like my bot, with King always rushing into enemy pieces.

Start with depth 0, "run" code by eyes, then add one depth and make sure everything that must flip, flips correctly.

About Kamikaze King situation: I used "avoid moves that end with checkmate to us" rule lifted and slightly edited from EvilBot, but flipped signs for if (board.IsInCheckmate()) intEval = int.MinValue; resulted in potentially fatal moves ending up evaluated as best.

CaptainDeathead commented 12 months ago

Yeah i seemed to get lucky with mine and it worked, but after seeing Sebastian's second chess video i tried making a chess ai in python c# java and c++ so i kind of flew through this as I'd already done one in C# but my python one was troublesome, it got stuck a lot blundering pieces and not caring about king safety but practice has helped a lot so just keep trying new things out and exploring new ideas. (Also you probably want to close the thread, and good luck!)