Tiny Chess Bot Coding Challenge

Discussion of chess software programming and technical issues.

Moderator: Ras

User avatar
leanchess
Posts: 181
Joined: Sun Dec 08, 2019 8:16 pm
Full name: Dmitry Shechtman

Re: Tiny Chess Bot Coding Challenge

Post by leanchess »

You mean Tyrant's code? It will only work with his compression. Have a look at his constructor:

https://github.com/Tyrant7/Chess-Challe ... t/MyBot.cs
Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: Tiny Chess Bot Coding Challenge

Post by Mike Sherwin »

leanchess wrote: Sat Sep 02, 2023 7:24 pm You mean Tyrant's code? It will only work with his compression. Have a look at his constructor:

https://github.com/Tyrant7/Chess-Challe ... t/MyBot.cs
Tyrant is something special. The C# code is something that I can really learn from. That the author squeezed all that functionality into less than 1025 tokens is utterly amazing. There are 28 days left so I'm going to learn what I can about C# and probably restart from scratch.

The elo at the live games is obviously very compressed. Tyrant is in my opinion at least 2500 elo. And may be more than that.

The good news, although maybe soon to be obsolete, is that iPlayChess v2 is 1086 elo after only 7 games and has won all 7! :D
  • iPlayChess v2
    1086 ELO
    Uploaded on 2023-09-02T14:55:22.050Z
    7 Games
    +16
    iPlayChess v2
    GammaCheck
    Black timed out [debug 1]
    2023-09-02T17:44:53.950Z
    +16
    iPlayChess v2
    LargeNeuralNet(withSigmoid)
    Checkmate
    2023-09-02T16:35:39.939Z
    +11
    iPlayChess v2
    DullCloud1.3(MD Chng)
    Checkmate
    2023-09-02T16:28:17.279Z
    +9
    TeddyBot v3.0
    iPlayChess v2
    White made an illegal move
    2023-09-02T15:44:59.560Z
    +8
    GM Manneken pis v2.2
    iPlayChess v2
    Checkmate
    2023-09-02T15:23:16.065Z
    +22
    iPlayChess v2
    røhgryd me' fløe IDS[R]3q BTBD
    Checkmate
    2023-09-02T15:08:30.990Z
    +6
    PrincessAtta v3
    iPlayChess v2
    Checkmate
    2023-09-02T15:07:12.561Z
User avatar
leanchess
Posts: 181
Joined: Sun Dec 08, 2019 8:16 pm
Full name: Dmitry Shechtman

Re: Tiny Chess Bot Coding Challenge

Post by leanchess »

Actually, it looks like Tyrant's PeSTO tables are flipped, just as mine are, so you may use his Evaluate() with my pst.

Mike Sherwin wrote: Sat Sep 02, 2023 7:51 pm Tyrant is something special. The C# code is something that I can really learn from. That the author squeezed all that functionality into less than 1025 tokens is utterly amazing. There are 28 days left so I'm going to learn what I can about C# and probably restart from scratch.

Indeed. I thought I knew C# until I saw what he did. Note that that's V5, and V8 is 6th place, so the competition is pretty tough.

As for restarting from scratch, you don't have to. Send me your most recent code, and I'll make it about twice as slim.
Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: Tiny Chess Bot Coding Challenge

Post by Mike Sherwin »

Ten games in and iPlayChess v2 has ten wins, 1121 elo! :D
Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: Tiny Chess Bot Coding Challenge

Post by Mike Sherwin »

It was trivial to incorporate the Qsearch() in the regular search to save almost 200 tokens. :D
That should leave quite a bit of room free for hash tables and killers and a few other things.
iPlayChess v2 has completed 14 games, +13, =1, -0, 1149 elo. The draw was a 3-fold in a completely won game. I'm baffled why that can happen. Oh well that is a great start nonetheless!

Code: Select all

using ChessChallenge.API;
using System;

public class MyBot : IChessBot
{
    int[] PieceValues = { 0, 100, 320, 330, 500, 900, 10000 };
    int CheckmateScore = 9999;

    ulong[] pst = {
        0x04BE53FA54055052,0x30C0140E5C47C052,0x2D41E3FA46C97852,0x1DC0D42052090052,
        0x0943C438550C7052,0x1442D3CC50C78052,0x2642C3F85D0A1052,0x2BC2E41059473052,
        0x33BE93F054C840B4,0x24BDA3FA5F4940D8,0x1B3FC42E56CCC88F,0x21C02436580BA8B1,
        0x213F145A62CB4096,0x2343A4406A0C78D0,0x1241D3EE5FCAC074,0x16C374124F8A0047,
        0x20BF43B05749104C,0x313F03E0648C6859,0x264083EE660BB06C,0x1D409402654C9071,
        0x1B41E3DC640D2893,0x2843941467CE908A,0x30430434648CD06B,0x1A43A3DA5ACBE83E,
        0x1CBE638A5A4A4044,0x1B3E63A45C8B105F,0x1F3F13C8600B2058,0x17BF13EE67CC3067,
        0x164003EA648BB069,0x18C12400648CB05E,0x1E3FF3AA5D0B1863,0x134023925ACB383B,
        0x0CBF837259CA2037,0x24BE73865E8AA850,0x17BF83A25E8B084D,0x11BF73B861CAF05E,
        0x0E3FF3CC63CB6863,0x0F3FD3AC5E4B2058,0x14C043C65DCB305C,0x0BBFE38C5C4A4839,
        0x1E3F33605B49D038,0x1E4033885F0A404E,0x1A3F639A5F0AE84E,0x0E3FF3985F0AD848,
        0x0F3FC3C05ECB2055,0x164033BA620B1055,0x1DC0F3B05FCB5073,0x17C063785DCA0846,
        0x25BDE3625C49A02F,0x28BF939A5F08E051,0x2140C3925F4A283E,0x054033A85B4A703B,
        0x0FC093B85D0A8043,0x1D4103D0608B186A,0x29BFE3AE638A1878,0x2940232C5B89F03C,
        0x1DC0039453074052,0x373EF3A05A89E052,0x2B3F83BC57C8B852,0x0A40B3DC56098052,
        0x293F23DA580A0052,0x173E83C85849A852,0x313E23705189F052,0x2C3CF3865609D052,
        0x0039F41A46C6F85E,0x13BBE4144507985E,0x1C3BE4244788605E,0x1C3C341E4847E85E,
        0x1FBC34184887D05E,0x2CBBB4184807F05E,0x273B24104606D05E,0x1CBBC40A4445B05E,
        0x1F39741648480110,0x2DBBC41A4948890B,0x2C3C841A4C0800FC,0x2DBD14164748B8E4,
        0x2DBE23FA498880F1,0x383C1406470800E2,0x30BC641049480903,0x2ABA840646C72919,
        0x2A39440E4AC808BC,0x2DBAE40E484828C2,0x30BB140E4A4918B3,0x2CBD940A4A0910A1,
        0x2F3D740849C8C096,0x3BBCB3FA4BC88093,0x3B3BB3F64A4830B0,0x2BBB13FA4B4780B2,
        0x213AB4084988407E,0x303BE4064C88E076,0x313C041A4D49786B,0x32BD54024C897863,
        0x323E14044DC9785C,0x35BD04024CC92062,0x323E13FE4B09086F,0x26BCC4044AC8386F,
        0x1C39640648C8386B,0x233C440A4B089867,0x2FBBB4104D89485B,0x313D74084F099057,
        0x32BC73F64C094857,0x30BCA3F44CC95056,0x29BCF3F04988E861,0x1FBBF3EA4808385D,
        0x1BB983F847481062,0x23B8D4004988B065,0x2ABB73F64C48C058,0x2FBAE3FE4CC9405F,
        0x30BB13F24D89185E,0x2D3B93E84B08B059,0x28BB23F04888285D,0x20BAD3E046881856,
        0x17B923F446C7786B,0x1FB913F445C82866,0x2738A40048887866,0x2BB984044A08A068,
        0x2C3983EE4B48B86B,0x273913EE4808285E,0x22B843EA46881060,0x1CB883FA43876857,
        0x0AB873EE4487E05E,0x1438C4044807305E,0x1AB924064488105E,0x1FB7D3FE4908505E,
        0x173A33F64808185E,0x1E3883E64648385E,0x193944084907385E,0x0FB7F3D84606C85E
    };

    public Move Think(Board board, Timer timer)
    {
        Move bestMove = default;
        int alpha;
        int beta = CheckmateScore;
        int score;
        Move[] move = board.GetLegalMoves();
        for (int depth = 1; depth < 99; depth++)
        {
            Console.WriteLine(depth);
            alpha = -CheckmateScore;
            bestMove = default;
            for (int i = 0; i < move.Length; i++)
            {
                board.MakeMove(move[i]);
                if (board.IsInCheckmate())
                {
                    board.UndoMove(move[i]);
                    return move[i];
                }
                if (board.IsDraw())
                {
                    score = 0;
                }
                else score = -NegaMax(-beta, -alpha, depth, board);
                board.UndoMove(move[i]);

                if (score > alpha)
                {
                    bestMove = move[i];
                    move[i] = move[0];
                    move[0] = bestMove;
                    alpha = score;
                }
            }
            if (timer.MillisecondsElapsedThisTurn > timer.MillisecondsRemaining / 100) break;
        }
        return bestMove;
    }

    private int NegaMax(int alpha, int beta, int depth, Board board)
    {
        int score;
        bool q = depth <= 0;
        int eval = Eval(board);

        if (q)
        {
            if (eval >= beta) return beta;
            alpha = Math.Max(alpha, eval);
        }

        if (!q && depth > 1 && eval - 10 >= beta && board.TrySkipTurn())
        {
            if (-NegaMax(-beta, -beta + 1, depth - 3 - depth / 4, board) >= beta)
            {
                board.UndoSkipTurn();
                return beta;
            }
            board.UndoSkipTurn();
        }

        Move[] move = board.GetLegalMoves(q);
        for (int i = 0; i < move.Length; i++)
        {
            Move tempMove;
            Piece capturedPiece1 = board.GetPiece(move[i].TargetSquare);
            int capturedPieceValue1 = PieceValues[(int)capturedPiece1.PieceType];
            for (int j = i + 1; j < move.Length; j++)
            {
                Piece capturedPiece2 = board.GetPiece(move[j].TargetSquare);
                int capturedPieceValue2 = PieceValues[(int)capturedPiece2.PieceType];
                if (capturedPieceValue2 > capturedPieceValue1)
                {
                    tempMove = move[i];
                    move[i] = move[j];
                    move[j] = tempMove;
                }
            }

            board.MakeMove(move[i]);
            if (board.IsInCheckmate())
            {
                board.UndoMove(move[i]);
                return CheckmateScore - board.PlyCount;
            }
            if (board.IsDraw())
                score = 0;
            else
                if (beta - alpha > 1)
            {
                score = -NegaMax(-alpha - 1, -alpha, depth - 2, board);
                if (score > alpha)
                    score = -NegaMax(-beta, -alpha, depth - 1 - i/4, board);
            }
            else
            {
                score = -NegaMax(-beta, -alpha, depth - 1, board);
            }
            board.UndoMove(move[i]);

            if (score > alpha)
            {
                if (score >= beta)
                    return beta;
                alpha = score;
            }
        }
        return alpha;
    }

    private int Eval(Board board)
    {
        int eval = 0, mg = 0, eg = 0;
        foreach (PieceList list in board.GetAllPieceLists())
        {
            foreach (Piece piece in list)
            {
                int square = piece.Square.Index ^ (piece.IsWhite ? 0x38 : 0),
                    type = (int)piece.PieceType - 1,
                    mul = piece.IsWhite ? 1 : -1;
                mg += mul * (int)(pst[square] >> type * 11 & 0b11111111111);
                eg += mul * (int)(pst[square + 64] >> type * 11 & 0b11111111111);
                eval += 0x00042110 >> type * 4 & 0x0F;
            }
        }
        eval = eval < 24 ? eval : 24;
        return (mg * eval + eg * (24 - eval)) / 24 * (board.IsWhiteToMove ? 1 : -1);
    }
}
User avatar
leanchess
Posts: 181
Joined: Sun Dec 08, 2019 8:16 pm
Full name: Dmitry Shechtman

Re: Tiny Chess Bot Coding Challenge

Post by leanchess »

Mike Sherwin wrote: Sun Sep 03, 2023 5:49 am It was trivial to incorporate the Qsearch() in the regular search to save almost 200 tokens. :D
Oh no! You ruined my primary optimisation target :twisted:

What happened to the older iPlayChess upload? It started losing all of the sudden!
User avatar
leanchess
Posts: 181
Joined: Sun Dec 08, 2019 8:16 pm
Full name: Dmitry Shechtman

Re: Tiny Chess Bot Coding Challenge

Post by leanchess »

Something long overdue:


https://github.com/EvalBot/Chess-Challenge


Anyone willing to contribute is most welcome. Thomas, are you with us?
User avatar
lithander
Posts: 915
Joined: Sun Dec 27, 2020 2:40 am
Location: Bremen, Germany
Full name: Thomas Jahn

Re: Tiny Chess Bot Coding Challenge

Post by lithander »

leanchess wrote: Sun Sep 03, 2023 9:44 am Anyone willing to contribute is most welcome. Thomas, are you with us?
I haven't spent any time on the challenge since my last post here. Only today I logged into the Discord for the first time and also checked out the Stockfish-equivalent of the competition called Tyrant: https://github.com/Tyrant7/Chess-Challenge

It's open source, strong and there seem to be many clones already. If we plan to submit something as a community effort I think our contribution should win a decent amount of games against but without taking it's ideas. Otherwise it's just too embarrassing. ;)

Spoiler: Currently Tyrant wins all games against the EvalBot. So how could we get there? What are the next steps?

A transposition table might be a good idea. Or we could try a mobility based eval like OliThink?
Minimal Chess (simple, open source, C#) - Youtube & Github
Leorik (competitive, in active development, C#) - Github & Lichess
Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: Tiny Chess Bot Coding Challenge

Post by Mike Sherwin »

lithander wrote: Sun Sep 03, 2023 3:20 pm
leanchess wrote: Sun Sep 03, 2023 9:44 am Anyone willing to contribute is most welcome. Thomas, are you with us?
I haven't spent any time on the challenge since my last post here. Only today I logged into the Discord for the first time and also checked out the Stockfish-equivalent of the competition called Tyrant: https://github.com/Tyrant7/Chess-Challenge

It's open source, strong and there seem to be many clones already. If we plan to submit something as a community effort I think our contribution should win a decent amount of games against but without taking it's ideas. Otherwise it's just too embarrassing. ;)

Spoiler: Currently Tyrant wins all games against the EvalBot. So how could we get there? What are the next steps?

A transposition table might be a good idea. Or we could try a mobility based eval like OliThink?
Myself not being familiar with C# quickly used up almost all the token allotment. So the last day or so we have been working to reduce the number of tokens. The count is down to 666/1024. Now more goodies can be added. Thomas, if you are back with us then just jump in and make it better, add to it, change it, rewrite it, refactor it, whatever. A stronger bot is a stronger bot and we will go with the strongest. I like the idea of a mobility based Eval() like in OliThink! The rules allow two bots if they are sufficiently different.
Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: Tiny Chess Bot Coding Challenge

Post by Mike Sherwin »

Here is the most up to date code. And there is a mystery. After the refactoring which does not appear to change the functionality at all is all of a sudden searching 4 to 5 ply deeper. My guess is that move ordering was not functioning properly and now it is.

Code: Select all

// iPlayChess

// Contributors:

// Thomas Jahn - Leorik
// Mike Sherwin - RomiChess
// Dmitry Shechtman - LeanChess

using ChessChallenge.API;
using System.Linq;

public class MyBot : IChessBot
{
    ulong[] pst = {
        0x04BE53FA54055052,0x30C0140E5C47C052,0x2D41E3FA46C97852,0x1DC0D42052090052,
        0x0943C438550C7052,0x1442D3CC50C78052,0x2642C3F85D0A1052,0x2BC2E41059473052,
        0x33BE93F054C840B4,0x24BDA3FA5F4940D8,0x1B3FC42E56CCC88F,0x21C02436580BA8B1,
        0x213F145A62CB4096,0x2343A4406A0C78D0,0x1241D3EE5FCAC074,0x16C374124F8A0047,
        0x20BF43B05749104C,0x313F03E0648C6859,0x264083EE660BB06C,0x1D409402654C9071,
        0x1B41E3DC640D2893,0x2843941467CE908A,0x30430434648CD06B,0x1A43A3DA5ACBE83E,
        0x1CBE638A5A4A4044,0x1B3E63A45C8B105F,0x1F3F13C8600B2058,0x17BF13EE67CC3067,
        0x164003EA648BB069,0x18C12400648CB05E,0x1E3FF3AA5D0B1863,0x134023925ACB383B,
        0x0CBF837259CA2037,0x24BE73865E8AA850,0x17BF83A25E8B084D,0x11BF73B861CAF05E,
        0x0E3FF3CC63CB6863,0x0F3FD3AC5E4B2058,0x14C043C65DCB305C,0x0BBFE38C5C4A4839,
        0x1E3F33605B49D038,0x1E4033885F0A404E,0x1A3F639A5F0AE84E,0x0E3FF3985F0AD848,
        0x0F3FC3C05ECB2055,0x164033BA620B1055,0x1DC0F3B05FCB5073,0x17C063785DCA0846,
        0x25BDE3625C49A02F,0x28BF939A5F08E051,0x2140C3925F4A283E,0x054033A85B4A703B,
        0x0FC093B85D0A8043,0x1D4103D0608B186A,0x29BFE3AE638A1878,0x2940232C5B89F03C,
        0x1DC0039453074052,0x373EF3A05A89E052,0x2B3F83BC57C8B852,0x0A40B3DC56098052,
        0x293F23DA580A0052,0x173E83C85849A852,0x313E23705189F052,0x2C3CF3865609D052,
        0x0039F41A46C6F85E,0x13BBE4144507985E,0x1C3BE4244788605E,0x1C3C341E4847E85E,
        0x1FBC34184887D05E,0x2CBBB4184807F05E,0x273B24104606D05E,0x1CBBC40A4445B05E,
        0x1F39741648480110,0x2DBBC41A4948890B,0x2C3C841A4C0800FC,0x2DBD14164748B8E4,
        0x2DBE23FA498880F1,0x383C1406470800E2,0x30BC641049480903,0x2ABA840646C72919,
        0x2A39440E4AC808BC,0x2DBAE40E484828C2,0x30BB140E4A4918B3,0x2CBD940A4A0910A1,
        0x2F3D740849C8C096,0x3BBCB3FA4BC88093,0x3B3BB3F64A4830B0,0x2BBB13FA4B4780B2,
        0x213AB4084988407E,0x303BE4064C88E076,0x313C041A4D49786B,0x32BD54024C897863,
        0x323E14044DC9785C,0x35BD04024CC92062,0x323E13FE4B09086F,0x26BCC4044AC8386F,
        0x1C39640648C8386B,0x233C440A4B089867,0x2FBBB4104D89485B,0x313D74084F099057,
        0x32BC73F64C094857,0x30BCA3F44CC95056,0x29BCF3F04988E861,0x1FBBF3EA4808385D,
        0x1BB983F847481062,0x23B8D4004988B065,0x2ABB73F64C48C058,0x2FBAE3FE4CC9405F,
        0x30BB13F24D89185E,0x2D3B93E84B08B059,0x28BB23F04888285D,0x20BAD3E046881856,
        0x17B923F446C7786B,0x1FB913F445C82866,0x2738A40048887866,0x2BB984044A08A068,
        0x2C3983EE4B48B86B,0x273913EE4808285E,0x22B843EA46881060,0x1CB883FA43876857,
        0x0AB873EE4487E05E,0x1438C4044807305E,0x1AB924064488105E,0x1FB7D3FE4908505E,
        0x173A33F64808185E,0x1E3883E64648385E,0x193944084907385E,0x0FB7F3D84606C85E
    };

    public Move Think(Board board, Timer timer)
    {
        int alpha, depth = 1;
        var moves = GetOrderedMoves(false);
        for (; timer.MillisecondsElapsedThisTurn <= timer.MillisecondsRemaining / 100;
            depth++)
        {
            System.Console.WriteLine(depth); //TODO remove
            alpha = -99999;
            for (int i = 0; i < moves.Length; i++)
            {
                Move move = moves[i];
                board.MakeMove(move);
                if (board.IsInCheckmate())
                {
                    board.UndoMove(move);
                    return move;
                }
                int score = board.IsDraw()
                    ? 0
                    : -NegaMax(-99999, -alpha, depth);
                board.UndoMove(move);

                if (score > alpha)
                {
                    moves[i] = moves[0];
                    moves[0] = move;
                    alpha = score;
                }
            }
        }
        return moves[0];

        int NegaMax(int alpha, int beta, int depth)
        {
            int score = Eval();
            bool q = depth <= 0;

            if (q)
               if (score > alpha)
                {
                    if (score >= beta) return beta;
                    alpha = score;
                }
            else if (depth > 1 && score - 10 >= beta && board.TrySkipTurn())
            {
                //Hack: reuse the boolean for beta cutoff
                q = -NegaMax(-beta, -beta + 1, depth - 3 - depth / 4) >= beta;
                board.UndoSkipTurn();
                if (q)
                    return beta;
            }

            var moves = GetOrderedMoves(q);
            for (int i = 0; i < moves.Length; i++)
            {
                Move move = moves[i];
                board.MakeMove(move);
                if (board.IsInCheckmate())
                {
                    board.UndoMove(move);
                    return 99999 - board.PlyCount;
                }
                if (board.IsDraw())
                    score = 0;
                else if (beta - alpha <= 1)
                    score = -NegaMax(-beta, -alpha, --depth);
                else if ((score = -NegaMax(-alpha - 1, -alpha, depth - 2)) > alpha)
                    score = -NegaMax(-beta, -alpha, --depth);
                board.UndoMove(move);

                if (score > alpha)
                {
                    if (score >= beta)
                        return beta;
                    alpha = score;
                }
            }
            return alpha;
        }

        int Eval()
        {
            int eval = 0, mg = 0, eg = 0;
            foreach (PieceList list in board.GetAllPieceLists())
                foreach (Piece piece in list)
                {
                    int square = piece.Square.Index ^ (piece.IsWhite ? 0x38 : 0),
                        type = (int)piece.PieceType - 1,
                        mul = piece.IsWhite ? 1 : -1;
                    mg += mul * (int)(pst[square] >> type * 11 & 0b11111111111);
                    eg += mul * (int)(pst[square + 64] >> type * 11 & 0b11111111111);
                    eval += 0x00042110 >> type * 4 & 0x0F;
                }
            if (eval > 24) eval = 24;
            return (mg * eval + eg * (24 - eval)) / 24 * (board.IsWhiteToMove ? 1 : -1);
        }

        Move[] GetOrderedMoves(bool capturesOnly) =>
            board.GetLegalMoves(capturesOnly)
                .OrderByDescending(m => m.CapturePieceType)
                .ToArray();
    }
}