├── MyBot.cs ├── MyBot_readable.cs └── README.md /MyBot.cs: -------------------------------------------------------------------------------- 1 | /* Game */ // Story: 2 | /* Tech */ using System; // https://youtu.be/5vsLmM756LA 3 | /* Explained */ using ChessChallenge.API; 4 | /* Bot */ using static System.Math;public 5 | class MyBot:IChessBot{static E[]tr= 6 | new E[16777216];float bc=121,b;bool o 7 | ;Timer t;Board bo;float eg(bool wh){int 8 | s=0;foreach(var pl in bo.GetAllPieceLists 9 | ())if(pl.IsWhitePieceList==wh)s+=(0x0942200 10 | >>4*(int)pl.TypeOfPieceInList&0xf)*pl.Count 11 | ;return Min(1,s*0.04f);}int EB(){float e=eg 12 | (false),og=eg(true),s=0.0f;foreach(var pl 13 | in bo.GetAllPieceLists())s+=0b00010000010 14 | >>(int)pl.TypeOfPieceInList!=0x00000000 15 | ?(pl. IsWhitePieceList ?e:- og)*T(S, pl)+(pl. 16 | IsWhitePieceList?1.0f-e:og-1.0f)*T(Es,pl):T(S,pl) 17 | ;return (int)(bo.IsWhiteToMove?s:-s);}ulong T(ulong[] 18 | []t,PieceList pl){ulong v=0;foreach(var p in pl){var sq= 19 | p.Square;v+=t[(int)p.PieceType][sq.File>=0x4?07-sq.File:sq. 20 | File]<<8*(pl.IsWhitePieceList?07-sq.Rank:sq.Rank)>>56;}return 21 | 25*v;}(int,Move,bool)SE(int dL,int cL,bool co,int a,int b){if( 22 | bo.IsInCheckmate())return(-32_100,default,true);if(bo.IsDraw()) 23 | return(0,default,bo.IsInStalemate()||bo.IsInsufficientMaterial() 24 | );if(dL==0){++dL;if(bo.IsInCheck()&&cL>0)--cL;else if(!co&&cL==4) 25 | return SE(0x08,cL,true,a,b);else return(EB(),default,true);}ulong 26 | k=bo.ZobristKey;E ta=tr[k%16_7_7_7_2_16];int bS=-32150,s;Move be= 27 | default;if(ta.K==k&&Abs(ta.D)>=dL){bo.MakeMove(ta.M);bool tD=bo. 28 | IsDraw();bo.UndoMove(ta.M);if(tD)ta=default;else{a=Max(a,bS=ta. 29 | S);be=ta.M;if(b=0)return(ta.S,ta.M,true);}}if(co&&(s 30 | =EB())>bS&&b<(a =Max(a,bS=s))) return(s,default,true);Span< 31 | Move>le=stackalloc Move[256];bo.GetLegalMovesNonAlloc(ref 32 | le,co);Span<(int,Move)>prioms=stackalloc(int,Move)[le. 33 | Length];int lv=0;foreach(var lm in le)prioms[lv++]= 34 | ((ta.K==k&&lm==ta.M?5_0_0_0:km.Contains(lm)?500:0)+(lm. 35 | PromotionPieceType==PieceType.Queen?5:0)+(0x953310>>4*(int)lm 36 | .CapturePieceType&0xf),lm);prioms.Sort((a,b)=>-a.Item1.CompareTo(b. 37 | Item1));bool cT=true,ax=false,cU;lv=0;foreach(var(_,m)in prioms){if(o=t 38 | .MillisecondsElapsedThisTurn>=this.b)return(bS,be,cT);bo.MakeMove(m);try{if( 39 | dL>=3&&++lv>=4&&!m.IsCapture){s=-SE(dL-2,cL,co,-b,-a).Item1;if(o)break;if(s=30000?Sign(s):0); 41 | if(s<=bS)continue;bS=s;be =m;a=Max(a,s);cT=cU;if(ax=bkm=new();static readonly ulong[]Ks={0x3234363_636363432ul, 47 | 0x3438_3c3d3_c3d38_34ul,0x363_c3e3f_3f3e3_c36ul,0x363_c3f40_403f3_d36ul},Bs={0x3c3_e3e3e_3e3e_3e3cul, 48 | 0x3e40_4041_404241_3eul,0x3e_40414_14242_403eul,0x3e_40424_2424240_3eul},Rs={0x646_5636_3636_363_64ul, 49 | 0x646_6646_46464_646_4ul,0x6_4666_46464_646_46_4ul,0x6466_64646_46464_65ul},qS={0xb_0b2b2b3_b4b2_b2b0ul, 50 | 0xb_2b4b_4b4_b4_b5b4_b2ul, 0xb_2b4_b5b_5b5_b_5b5b_2ul,0xb3_b4b_5b_5b5_b5b4_b3ul};ulong[][]S={null,new[]{ 51 | 0x141e161514151514ul,0x141e16151_4131614ul,0x141e181614_121614ul,0x141e1a1_918141014ul},Ks,Bs,Rs,qS,new[]{ 52 | 0x0004_080a0c0e1414ul,0x02040608_0a0c1416ul,0x02040_6080a0c0f12ul,0x02040_406080_c0f10ul}},Es={null,new[]{ 53 | 0x14241e1a181_61614ul,0x14241e_1a18161614ul,0x14241e1a1_8161614ul,0x14241e1_a18161614ul},Ks,Bs,Rs,qS,new[]{ 54 | 0x0c0f0e0d0c0b0a06ul,0x0e100f0e0d0c0b0aul,0x0e1114171614100aul,0x0e1116191815100aul}};struct E{public ulong 55 | K;public short S,D; public Move M;}} /* Thank you for hosting this competition, Sebastian! <3 <3 <3 <3 */ 56 | 57 | -------------------------------------------------------------------------------- /MyBot_readable.cs: -------------------------------------------------------------------------------- 1 | // Game Tech Explained Bot 2 | // 3 | // Story: 4 | // https://youtu.be/5vsLmM756LA 5 | // 6 | // This is a "readable" version of the code. You can find the original code, which I show in the video, and which I 7 | // submitted to the competition, at https://github.com/GameTechExplained/Chess-Challenge. It is prettier for some 8 | // definition of pretty. 9 | 10 | using System; 11 | using ChessChallenge.API; 12 | using static System.Math; 13 | 14 | public class MyBot : IChessBot 15 | { 16 | static Entry[] _transpositions = new Entry[16777216]; 17 | float _budgetCounter = 121, budget; 18 | bool _outOfTime; 19 | Timer timer; 20 | Board board; 21 | System.Collections.Generic.HashSet _killerMoves = new(); 22 | 23 | float OneMinusEndgameT(bool white) 24 | { 25 | int endgameWeightSum = 0; 26 | foreach (var pl in board.GetAllPieceLists()) 27 | if (pl.IsWhitePieceList == white) 28 | endgameWeightSum += (0x942200 >> 4 * (int)pl.TypeOfPieceInList & 0xf) * pl.Count; 29 | 30 | return Min(1, endgameWeightSum * 0.04f); 31 | } 32 | 33 | int EvaluateBoard() 34 | { 35 | float ownOneMinusEndgameT = OneMinusEndgameT(false), otherOneMinusEndgameT = OneMinusEndgameT(true), 36 | score = 0.0f; 37 | foreach (var pl in board.GetAllPieceLists()) 38 | score += 0b1000010 >> (int)pl.TypeOfPieceInList != 0 39 | ? (pl.IsWhitePieceList ? ownOneMinusEndgameT : -otherOneMinusEndgameT ) * EvaluatePieceSquareTable(Starts, pl) 40 | + (pl.IsWhitePieceList ? 1.0f - ownOneMinusEndgameT : otherOneMinusEndgameT - 1.0f) * EvaluatePieceSquareTable(Ends, pl) 41 | : EvaluatePieceSquareTable(Starts, pl); 42 | return (int)(board.IsWhiteToMove ? score : -score); 43 | } 44 | 45 | 46 | ulong EvaluatePieceSquareTable(ulong[][] table, PieceList pl) 47 | { 48 | ulong value = 0; 49 | foreach (var p in pl) 50 | { 51 | var sq = p.Square; 52 | value += table[(int)p.PieceType][sq.File >= 4 ? 7 - sq.File : sq.File] << 8 * (pl.IsWhitePieceList ? 7 - sq.Rank : sq.Rank) >> 56; 53 | } 54 | return 25 * value; 55 | } 56 | 57 | (int, Move, bool) Search(int depthLeft, int checkExtensionsLeft, bool isCaptureOnly, int alpha = -32200, int beta = 32200) 58 | { 59 | if (board.IsInCheckmate()) 60 | return (-32100, default, true); 61 | 62 | if (board.IsDraw()) 63 | return (0, default, board.IsInStalemate() || board.IsInsufficientMaterial()); 64 | 65 | if (depthLeft == 0) 66 | { 67 | ++depthLeft; 68 | if (board.IsInCheck() && checkExtensionsLeft > 0) 69 | --checkExtensionsLeft; 70 | else if (!isCaptureOnly && checkExtensionsLeft == 4) 71 | return Search(8, checkExtensionsLeft, true, alpha, beta); 72 | else 73 | return (EvaluateBoard(), default, true); 74 | } 75 | 76 | ulong key = board.ZobristKey; 77 | Entry trans = _transpositions[key % 16777216]; 78 | int bestScore = -32150, score; 79 | Move best = default; 80 | if (trans.Key == key && Abs(trans.Depth) >= depthLeft) { 81 | board.MakeMove(trans.Move); 82 | bool toDraw = board.IsDraw(); 83 | board.UndoMove(trans.Move); 84 | 85 | if (toDraw) 86 | trans = default; 87 | else 88 | { 89 | alpha = Max(alpha, bestScore = trans.Score); 90 | best = trans.Move; 91 | if (beta < alpha || trans.Depth >= 0) 92 | return (trans.Score, trans.Move, true); 93 | } 94 | } 95 | 96 | if (isCaptureOnly && (score = EvaluateBoard()) > bestScore && beta < (alpha = Max(alpha, bestScore = score))) 97 | return (score, default, true); 98 | 99 | Span legal = stackalloc Move[256]; 100 | board.GetLegalMovesNonAlloc(ref legal, isCaptureOnly); 101 | 102 | Span<(int, Move)> prioritizedMoves = stackalloc (int, Move)[legal.Length]; 103 | int loopvar = 0; 104 | foreach (var lmove in legal) 105 | prioritizedMoves[loopvar++] = ( 106 | (trans.Key == key && lmove == trans.Move ? 5000 :_killerMoves.Contains(lmove) ? 500 : 0) 107 | + (lmove.PromotionPieceType == PieceType.Queen ? 5 : 0) 108 | + (0x0953310 >> 4 * (int)lmove.CapturePieceType & 0xf), 109 | lmove); 110 | 111 | prioritizedMoves.Sort((a, b) => -a.Item1.CompareTo(b.Item1)); 112 | 113 | bool canUseTranspositions = true, approximate = false, canUse; 114 | loopvar = 0; 115 | foreach (var (_, move) in prioritizedMoves) 116 | { 117 | if (_outOfTime = timer.MillisecondsElapsedThisTurn >= budget) 118 | return (bestScore, best, canUseTranspositions); 119 | 120 | board.MakeMove(move); 121 | try { 122 | if (depthLeft >= 3 && ++loopvar >= 4 && !move.IsCapture) 123 | { 124 | score = -Search(depthLeft - 2, checkExtensionsLeft, isCaptureOnly, -beta, -alpha).Item1; 125 | 126 | if (_outOfTime) 127 | break; 128 | 129 | if (score < bestScore) 130 | continue; 131 | } 132 | (score, _, canUse) = Search(depthLeft - 1, checkExtensionsLeft, isCaptureOnly, -beta, -alpha); 133 | 134 | if (_outOfTime) 135 | break; 136 | 137 | score = -score + (Abs(score) >= 30000 ? Sign(score) : 0); 138 | 139 | if (score <= bestScore) 140 | continue; 141 | 142 | bestScore = score; 143 | best = move; 144 | alpha = Max(alpha, score); 145 | canUseTranspositions = canUse; 146 | 147 | if (approximate = beta < alpha) 148 | { 149 | _killerMoves.Add(move); 150 | break; 151 | } 152 | } 153 | finally 154 | { 155 | board.UndoMove(move); 156 | } 157 | } 158 | 159 | if (!_outOfTime && !isCaptureOnly && canUseTranspositions && bestScore != 0) 160 | _transpositions[key % 16777216] = new Entry { Key=key, Depth = (short)(approximate ? -depthLeft : depthLeft), Score = (short)bestScore, Move = best }; 161 | 162 | return (bestScore, best, canUseTranspositions); 163 | } 164 | 165 | public Move Think(Board b, Timer t) 166 | { 167 | board = b; 168 | timer = t; 169 | budget = Min(0.033333333333333333333333f, 2.0f / --_budgetCounter) * t.MillisecondsRemaining; 170 | _outOfTime = false; 171 | 172 | _killerMoves.Clear(); 173 | Move bestMove = default, move; 174 | 175 | int depth = 0; 176 | while (++depth <= 15 && !_outOfTime) 177 | if ((move = Search(depth, 4, false).Item2) != default) 178 | bestMove = move; 179 | 180 | return bestMove == default ? b.GetLegalMoves()[0] : bestMove; 181 | } 182 | 183 | static readonly ulong[] Knights = { 0x3234363636363432ul, 0x34383c3d3c3d3834ul, 0x363c3e3f3f3e3c36ul, 0x363c3f40403f3d36ul }, 184 | Bishops = { 0x3c3e3e3e3e3e3e3cul, 0x3e4040414042413eul, 0x3e4041414242403eul, 0x3e4042424242403eul }, 185 | Rooks = { 0x6465636363636364ul, 0x6466646464646464ul, 0x6466646464646464ul, 0x6466646464646465ul }, 186 | Queens = { 0xb0b2b2b3b4b2b2b0ul, 0xb2b4b4b4b4b5b4b2ul, 0xb2b4b5b5b5b5b5b2ul, 0xb3b4b5b5b5b5b4b3ul }; 187 | ulong[][] Starts = { null, new[] { 0x141e161514151514ul, 0x141e161514131614ul, 0x141e181614121614ul, 0x141e1a1918141014ul }, Knights, Bishops, Rooks, 188 | Queens, new[] { 0x0004080a0c0e1414ul, 0x020406080a0c1416ul, 0x020406080a0c0f12ul, 0x02040406080c0f10ul } }, 189 | Ends = { null, new[] { 0x14241e1a18161614ul, 0x14241e1a18161614ul, 0x14241e1a18161614ul, 0x14241e1a18161614ul }, Knights, Bishops, Rooks, 190 | Queens, new[] { 0x0c0f0e0d0c0b0a06ul, 0x0e100f0e0d0c0b0aul, 0x0e1114171614100aul, 0x0e1116191815100aul } }; 191 | 192 | struct Entry 193 | { 194 | public ulong Key; 195 | public short Score, Depth; 196 | public Move Move; 197 | } 198 | 199 | } 200 | 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repo contains my contribution to [Sebastian Lague's 2023 Chess programming competition](https://youtu.be/iScy18pVR58). 2 | 3 | **For more details, please check out [my YouTube video about the bot](https://youtu.be/5vsLmM756LA).** 4 | 5 | Note that these files do nothing by themselves. They need [Sebastian's chess program](https://github.com/SebLague/Chess-Challenge) to run. 6 | 7 | | File | Description | 8 | |---------------------|------------------------------------------------------------------------------------------------| 9 | | `MyBot.cs` | My Bot. The file I submitted to the competition. Looks like a pawn. | 10 | | `MyBot_readable.cs` | The same file, but formatted in a readable way. | 11 | --------------------------------------------------------------------------------