SebLague / Chess-Challenge

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

Reverse moves #408

Open NotBobo55 opened 11 months ago

NotBobo55 commented 11 months ago

So I sometimes get an illegal move when my bot plays and when I go to check it out it is trying to move the piece in reverse like instead of e2e4 it is e4e2 here are the positions that made that happen Illegal move: d5e6 in position: 8/2p3k1/np2Q1p1/8/r6P/5P1R/8/3K2N1 w - - 1 51 Illegal move: f6f3 in position: 1r4k1/1p5p/8/4p1q1/3p4/5r2/4R3/B2RK3 b - - 1 42 MyBotCode: using System; using ChessChallenge.API;

public class MyBot : IChessBot { private int maxDepth = 3; private Move bestMove; private Board board; // Point values for each piece type for evaluation int[] pointValues = {100, 320, 330, 500, 900, 99999}; // Big table packed with data from premade piece square tables

private readonly ulong[,] PackedEvaluationTables = {
    { 58233348458073600, 61037146059233280, 63851895826342400, 66655671952007680 },
    { 63862891026503730, 66665589183147058, 69480338950193202, 226499563094066 },
    { 63862895153701386, 69480338782421002, 5867015520979476,  8670770172137246 },
    { 63862916628537861, 69480338782749957, 8681765288087306,  11485519939245081 },
    { 63872833708024320, 69491333898698752, 8692760404692736,  11496515055522836 },
    { 63884885386256901, 69502350490469883, 5889005753862902,  8703755520970496 },
    { 63636395758376965, 63635334969551882, 21474836490,       1516 },
    { 58006849062751744, 63647386663573504, 63625396431020544, 63614422789579264 }
};

public int GetSquareBonus(PieceType type, bool isWhite, int file, int rank)
{
    // Because arrays are only 4 squares wide, mirror across files
    if (file > 3)
        file = 7 - file;

    // Mirror vertically for white pieces, since piece arrays are flipped vertically
    if (isWhite)
        rank = 7 - rank;

    // First, shift the data so that the correct byte is sitting in the least significant position
    // Then, mask it out
    // Use unchecked to preserve the sign in case of an overflow
    sbyte unpackedData = unchecked((sbyte)((PackedEvaluationTables[rank, file] >> 8 * ((int)type - 1)) & 0xFF));

    // Invert eval scores for black pieces
    return isWhite ? unpackedData : -unpackedData;
}

// Negamax algorithm with alpha-beta pruning
public int Search(int depth, int alpha, int beta, int color)
{
    // If the search reaches the desired depth or the end of the game, evaluate the position and return its value
    if (depth == 0 || board.IsDraw() || board.IsInCheckmate())
    {
        if (board.IsDraw()) return 0;

        if (board.IsInCheckmate()) return -999999 + (maxDepth - depth);

        return EvaluateBoard();
    }
    Move[] legalMoves = board.GetLegalMoves();
    Random random = new Random();
    int bestEval = -999999;
    int eval;
    // Generate and loop through all legal moves for the current player
    foreach(Move move in legalMoves)
    {
        // Make the move on a temporary board and call search recursively
        board.MakeMove(move);
        eval = -Search(depth - 1, -beta, -alpha, -color);
        board.UndoMove(move);

        // Update the best move and prune if necessary
        if (eval > bestEval)
        {
            bestEval = eval;
            if (depth == maxDepth)
            {   
                bestMove = move;
            }
            // Improve alpha
            alpha = Math.Max(alpha, eval);

            if (alpha >= beta) break;

        }
    }

    return bestEval;
}

public int EvaluateBoard()
{
    int materialValue = 0;
    int mobilityValue = board.GetLegalMoves().Length;
    PieceList[] pieceLists = board.GetAllPieceLists();
    int color = board.IsWhiteToMove ? 1 : -1;
    int pieceCount = 0;
    // Loop through each piece type and add the difference in material value to the total
    int squereBonus = 0;
    foreach(PieceList pList in pieceLists)
    {
        pieceCount += pList.Count;
    }

    if(pieceCount<= 10)
    {
        maxDepth = 5;
    }
    foreach(PieceList pList in pieceLists)
    {
        foreach(Piece piece in pList)
        {
            squereBonus += GetSquareBonus(piece.PieceType,piece.IsWhite,piece.Square.File, piece.Square.Rank);
        }
    }
    for(int i = 0;i < 5; i++){
        materialValue += (pieceLists[i].Count - pieceLists[i + 6].Count) * pointValues[i];
    }
    return materialValue * color + mobilityValue * color + squereBonus;
}
public Move Think(Board board, Timer timer)
{
    this.board = board;
    // Call the Minimax algorithm to find the best move
    Console.WriteLine(Search(maxDepth, -999999, 999999, board.IsWhiteToMove ? 1 : -1) + "  " + bestMove + " is white turn: " + board.IsWhiteToMove);
    return bestMove;
}

} EVILBOTCODE: using ChessChallenge.API; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using System; using System.Diagnostics; using System.Linq; namespace ChessChallenge.Example; public class EvilBot : IChessBot { public int[] pieceVals = { 0, 100, 300, 320, 500, 900, 10000 }; // nothing, pawn, knight, bishop, rook, queen, king ///

/// Blind Bot:
/// Probably the best chess bot that CANNOT look ahead!
/// This was a fun challenge!
/// This bot can only check evaluation for the current move using a very complex hand-made evaluation function
/// This took a while!
/// The most major advantage is that it finishes each move in ~5 ms.
/// In the massive fight, this can only win against bots who check up to like 30 moves.
/// However I think this would be an intresting experiment and would be fun for the grand finale video.

/// </summary>
int movesSinceLastPawnMove = 0;

int kingDSTfromOpponentKing(Board board)
{
    Square myKingSquare = board.GetKingSquare(board.IsWhiteToMove);
    Square oKingSquare = board.GetKingSquare(!board.IsWhiteToMove);
    int fileDist = Math.Abs(myKingSquare.File - oKingSquare.File);
    int rankDist = Math.Abs(myKingSquare.Rank - oKingSquare.Rank);
    int dst = fileDist + rankDist;
    return dst;
}

public Move[] GetEnemyMoves(Board board)
{
    board.MakeMove(Move.NullMove);
    Move[] enemyMoves = board.GetLegalMoves();
    board.UndoMove(Move.NullMove);
    return enemyMoves;
}

int piecesLeft(Board board)
{
    int count = 0;
    for (var i = 0; i < 64; i++)
        if (board.GetPiece(new Square(i)).PieceType != PieceType.None) count++;
    return count;
}

bool moveIsCheckmate(Board board, Move move)
{
    board.MakeMove(move);
    bool isMate = board.IsInCheckmate();
    board.UndoMove(move);
    return isMate;
}

bool moveIsCheck(Board board, Move move)
{
    board.MakeMove(move);
    bool isCheck = board.IsInCheck();
    board.UndoMove(move);
    return isCheck;
}

bool moveHasBeenPlayed(Board board, Move move) 
{
    board.MakeMove(move);
    bool hasBeenPlayed = board.IsDraw();
    board.UndoMove(move);
    return hasBeenPlayed;
}

int evaluateMove(Move move, Board board) // evaluates the move
{
    int piecesLeftnow = piecesLeft(board);
    PieceType capturedPiece = move.CapturePieceType;
    int eval = 0;
    eval = pieceVals[(int)capturedPiece];
    if (eval > 0) { eval += 5; }
    if (board.SquareIsAttackedByOpponent(move.TargetSquare)) // uh oh here come the piece square tables
    {
        eval -= pieceVals[(int)move.MovePieceType];
    }
    ///<summary>
    /// Piece square tables for all pieces except king.
    /// This also includes that you should push out your queen early game.
    /// This will priortise "good" moves like castling and promoting over worse moves.
    /// It will also transition into endgame tables where your queen and rook are more important.
    /// This is responsible for more than half of the tokens BTW.
    /// </summary>
    if (move.MovePieceType == PieceType.Knight)// Knight piece square table, will prefer to be in the middle.
    {
        eval += 15;
        if (move.TargetSquare.File == 7 || move.TargetSquare.File == 6 || move.TargetSquare.File == 0 || move.TargetSquare.File == 1) eval -= 60;
        if (move.TargetSquare.File == 2 || move.TargetSquare.File == 3 || move.TargetSquare.File == 4 || move.TargetSquare.File == 5) if (move.TargetSquare.Rank == 2 || move.TargetSquare.Rank == 3 || move.TargetSquare.Rank == 4 || move.TargetSquare.Rank == 5) eval += 45;
    }
    if (move.MovePieceType == PieceType.Bishop)// Bishop piece square table
    {
        if (piecesLeftnow > 28) eval -= 30;
        eval += 15;
        if (move.TargetSquare.File == 2 || move.TargetSquare.File == 3 || move.TargetSquare.File == 4 || move.TargetSquare.File == 5)
        {
            if (move.TargetSquare.Rank == 2 || move.TargetSquare.Rank == 3 || move.TargetSquare.Rank == 4 || move.TargetSquare.Rank == 5) eval += 45;
        }
    }
    if (move.MovePieceType == PieceType.Rook)// Rook piece square table + transition to endgame
    {
        if (board.IsWhiteToMove) { if (move.TargetSquare.Rank == 7) eval += 40; }
        else if (move.TargetSquare.Rank == 2) eval += 40;
        if (move.TargetSquare.File == 3 || move.TargetSquare.File == 4) eval += 30;
        if (piecesLeftnow > 28) eval -= 30;
        eval -= 20;
    }
    if (move.MovePieceType == PieceType.Queen)// Queen piece square table + transition to mid/endgame
    {
        if (piecesLeftnow < 14) eval += 25;
        else eval -= 90;
        if (move.TargetSquare.File == 2 || move.TargetSquare.File == 3 || move.TargetSquare.File == 4 || move.TargetSquare.File == 5)
        {
            if (move.TargetSquare.Rank == 2 || move.TargetSquare.Rank == 3 || move.TargetSquare.Rank == 4 || move.TargetSquare.Rank == 5) eval += 45;
        }
    }

    if (move.MovePieceType == PieceType.Pawn)// Pawn "piece square table" This is mainly for early game
    {
        if(movesSinceLastPawnMove >= 25) eval += 25;
        if (piecesLeftnow < 14 || piecesLeftnow > 28) eval += 10;
        if (move.TargetSquare.File == 4 || move.TargetSquare.File == 5) eval += 30;
        eval += 5;
        if (piecesLeftnow < 8) eval += 70;
    }

    if(move.IsCastles) eval += 50; // castling is encouraged

    // We're out of the piece square tables!
    // This is for the flags to buff certain moves and nerf others
    // e.g. Checkmate is the highest priority move tied with en passant
    // Drawing is discouraged massively.
    // Checks are encouraged.
    // Moving away a piece that is attack is encouraged heavily.
    // Promotions are worth sacrificing a rook

    if (moveIsCheckmate(board, move)) eval = 999999999;
    if (moveHasBeenPlayed(board, move)) eval -= 1000;
    if (moveIsCheck(board, move)) eval += 20;
    if (board.SquareIsAttackedByOpponent(move.StartSquare)) eval += 120;
    if(move.IsPromotion) eval += 600;

    int currentDist = kingDSTfromOpponentKing(board);
    board.MakeMove(move);
    int newDist = kingDSTfromOpponentKing(board);
    board.UndoMove(move);
    if (piecesLeftnow < 6)
    {            
        if (newDist < currentDist) eval += 99;
    } else if (piecesLeftnow < 10) eval += 55;
    foreach (Move move2 in GetEnemyMoves(board))
    {
        if (moveIsCheckmate(board, move2)) eval -= 10000000;
        if (moveIsCheck(board, move2)) eval -= 60;
        if (moveHasBeenPlayed(board, move2)) eval -= 50000;
        if (move2.IsCapture) eval -= pieceVals[(int)move2.CapturePieceType];
        if (GetEnemyMoves(board).Length == 1) eval += 80;
    }

    return eval;

}

public Move Think(Board board, Timer timer)
{
    Move[] moves = board.GetLegalMoves();
    Move moveToPlay = moves[0];
    int bestEvaluation = -999999;
    foreach (Move move in moves)
    {
        if (evaluateMove(move, board) > bestEvaluation)
        {
            bestEvaluation = evaluateMove(move, board);
            moveToPlay = move;
        }
    }
    if (moveToPlay.MovePieceType == PieceType.Pawn) movesSinceLastPawnMove = 0;
    else movesSinceLastPawnMove++;
    return moveToPlay;
}

}