Final submission
This commit is contained in:
parent
ec7cf32665
commit
a6d0c5901e
2 changed files with 315 additions and 40 deletions
|
@ -1,54 +1,99 @@
|
|||
using ChessChallenge.API;
|
||||
using System;
|
||||
/*
|
||||
* Stockfish UCI implementation
|
||||
* for evaluating changes to MyBot.
|
||||
*/
|
||||
|
||||
namespace ChessChallenge.Example
|
||||
{
|
||||
// A simple bot that can spot mate in one, and always captures the most valuable piece it can.
|
||||
// Plays randomly otherwise.
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using ChessChallenge.API;
|
||||
|
||||
public class EvilBot : IChessBot
|
||||
{
|
||||
// Piece values: null, pawn, knight, bishop, rook, queen, king
|
||||
int[] pieceValues = { 0, 100, 300, 300, 500, 900, 10000 };
|
||||
Process stockfish;
|
||||
|
||||
public EvilBot()
|
||||
{
|
||||
stockfish = new();
|
||||
stockfish.StartInfo.RedirectStandardInput = true;
|
||||
stockfish.StartInfo.RedirectStandardOutput = true;
|
||||
stockfish.StartInfo.FileName = "/bin/stockfish";
|
||||
stockfish.Start();
|
||||
|
||||
WriteLine("uci");
|
||||
if (!IsOk())
|
||||
{
|
||||
throw new Exception("Stockfish is not ok");
|
||||
}
|
||||
|
||||
// WriteLine($"setoption name Skill Level value {3}");
|
||||
WriteLine("setoption name UCI_LimitStrength value true");
|
||||
WriteLine("setoption name UCI_ELO value 1650"); // min 1320 max 3190
|
||||
WriteLine("setoption name Threads value 16");
|
||||
WriteLine("setoption name Ponder value false");
|
||||
WriteLine("ucinewgame");
|
||||
}
|
||||
|
||||
public Move Think(Board board, Timer timer)
|
||||
{
|
||||
Move[] allMoves = board.GetLegalMoves();
|
||||
WriteLine($"position fen {board.GetFenString()}");
|
||||
|
||||
// Pick a random move to play if nothing better is found
|
||||
Random rng = new();
|
||||
Move moveToPlay = allMoves[rng.Next(allMoves.Length)];
|
||||
int highestValueCapture = 0;
|
||||
var ourTeam = board.IsWhiteToMove ? "w" : "b";
|
||||
var enemyTeam = board.IsWhiteToMove ? "b" : "w";
|
||||
WriteLine($"go {ourTeam}time {timer.MillisecondsRemaining} {enemyTeam}time {timer.OpponentMillisecondsRemaining}");
|
||||
|
||||
foreach (Move move in allMoves)
|
||||
Move? move = GetBestMove(board);
|
||||
if (move == null)
|
||||
{
|
||||
// Always play checkmate in one
|
||||
if (MoveIsCheckmate(board, move))
|
||||
throw new Exception("Stockfish returned no move");
|
||||
}
|
||||
return (Move)move;
|
||||
}
|
||||
|
||||
string? ReadLine()
|
||||
{
|
||||
moveToPlay = move;
|
||||
var line = stockfish.StandardOutput.ReadLine();
|
||||
// if (line != null)
|
||||
// {
|
||||
// Console.WriteLine($"stockfish: {line}");
|
||||
// }
|
||||
return line;
|
||||
}
|
||||
|
||||
void WriteLine(string line)
|
||||
{
|
||||
stockfish.StandardInput.WriteLine(line);
|
||||
}
|
||||
|
||||
private bool IsOk()
|
||||
{
|
||||
string? line;
|
||||
var ok = false;
|
||||
while ((line = ReadLine()) != null)
|
||||
{
|
||||
if (line == "uciok")
|
||||
{
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Find highest value capture
|
||||
Piece capturedPiece = board.GetPiece(move.TargetSquare);
|
||||
int capturedPieceValue = pieceValues[(int)capturedPiece.PieceType];
|
||||
|
||||
if (capturedPieceValue > highestValueCapture)
|
||||
private Move? GetBestMove(Board board)
|
||||
{
|
||||
moveToPlay = move;
|
||||
highestValueCapture = capturedPieceValue;
|
||||
}
|
||||
}
|
||||
|
||||
return moveToPlay;
|
||||
}
|
||||
|
||||
// Test if this move gives checkmate
|
||||
bool MoveIsCheckmate(Board board, Move move)
|
||||
string? line;
|
||||
Move? move = null;
|
||||
while ((line = ReadLine()) != null)
|
||||
{
|
||||
board.MakeMove(move);
|
||||
bool isMate = board.IsInCheckmate();
|
||||
board.UndoMove(move);
|
||||
return isMate;
|
||||
if (line.StartsWith("bestmove"))
|
||||
{
|
||||
move = new Move(line.Split()[1], board);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return move;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,240 @@
|
|||
using ChessChallenge.API;
|
||||
/*
|
||||
* Sebastian Lague's Tiny Chess Challenge
|
||||
* https://github.com/SebLague/Chess-Challenge
|
||||
* https://youtu.be/iScy18pVR58
|
||||
*
|
||||
* Submission by nullprop
|
||||
* https://nullprop.sh/
|
||||
*
|
||||
* PVS with hand written eval.
|
||||
* Somewhat sketchy adaptive depth,
|
||||
* based on previous think time and EBF in alpha-beta.
|
||||
* Roughly 1650 elo according to Stockfish UCI_ELO.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using ChessChallenge.API;
|
||||
|
||||
public class MyBot : IChessBot
|
||||
{
|
||||
int[] pieceValues;
|
||||
int lastThinkTime;
|
||||
int maxDepth = 5;
|
||||
int lastEval;
|
||||
Move[] moveList;
|
||||
int nodesThisTurn;// #DEBUG
|
||||
|
||||
public Move Think(Board board, Timer timer)
|
||||
{
|
||||
Move[] moves = board.GetLegalMoves();
|
||||
return moves[0];
|
||||
if (lastThinkTime > 0)
|
||||
{
|
||||
if (lastThinkTime > timer.MillisecondsRemaining * 0.1f)
|
||||
maxDepth--;
|
||||
else
|
||||
{
|
||||
// b ^ ceil(n/2) + b ^ floor(n/2) when b = 40 => 1.95, 20.5 alternating
|
||||
var timeEstimate = lastThinkTime * (maxDepth % 2 == 0 ? 20.5f : 1.95f);
|
||||
if (timeEstimate < timer.MillisecondsRemaining * 0.05f && lastEval < 999)
|
||||
maxDepth++;
|
||||
}
|
||||
// going below this is no bueno, fast anyways.
|
||||
// probably shouldn't happen unless running on a potato.
|
||||
if (maxDepth < 2) maxDepth = 2;
|
||||
}
|
||||
|
||||
// Change piece values based on how far we are in the game
|
||||
var pieceValueMult = MathF.Min(1.0f, board.PlyCount / 60.0f);
|
||||
pieceValues = new int[]{
|
||||
88 + (int)(pieceValueMult * 24), // pawn
|
||||
394 - (int)(pieceValueMult * 55), // knight
|
||||
428 - (int)(pieceValueMult * 71), // bishop
|
||||
572 + (int)(pieceValueMult * 42), // rook
|
||||
1240 - (int)(pieceValueMult * 116), // queen
|
||||
0 // king
|
||||
};
|
||||
|
||||
moveList = new Move[maxDepth];
|
||||
var eval = Search(board, -10001, 10000, maxDepth);
|
||||
|
||||
Console.WriteLine($"\n\n"); // #DEBUG
|
||||
Console.WriteLine(board.GetFenString()); // #DEBUG
|
||||
Console.WriteLine($"Depth: {maxDepth}"); // #DEBUG
|
||||
Console.WriteLine($"Speed: {nodesThisTurn / (timer.MillisecondsElapsedThisTurn + 1)}k nodes/s"); // #DEBUG
|
||||
Console.WriteLine($"Think: {timer.MillisecondsElapsedThisTurn}ms / {timer.MillisecondsRemaining}ms"); // #DEBUG
|
||||
Console.WriteLine($"Eval: {eval / 100f}"); // #DEBUG
|
||||
for (int i = maxDepth - 1; i >= 0; i--) // #DEBUG
|
||||
Console.WriteLine($" {(i % 2 == 0 ? "" : "#")}{moveList[i]} ({moveList[i].MovePieceType})"); // #DEBUG
|
||||
nodesThisTurn = 0; // #DEBUG
|
||||
|
||||
lastThinkTime = timer.MillisecondsElapsedThisTurn + 5; // fudge
|
||||
lastEval = eval;
|
||||
return moveList[maxDepth - 1];
|
||||
}
|
||||
|
||||
private int Search(Board board, int alpha, int beta, int depth)
|
||||
{
|
||||
nodesThisTurn++; // #DEBUG
|
||||
|
||||
if (depth == 0)
|
||||
{
|
||||
// quiesce
|
||||
int eval = EvalBoard(board);
|
||||
|
||||
if (eval >= beta) return beta;
|
||||
if (alpha < eval) alpha = eval;
|
||||
|
||||
foreach (var capture in board.GetLegalMoves(true))
|
||||
{
|
||||
board.MakeMove(capture);
|
||||
eval = -Search(board, -beta, -alpha, 0);
|
||||
board.UndoMove(capture);
|
||||
if (eval >= beta) return beta;
|
||||
if (alpha < eval) alpha = eval;
|
||||
}
|
||||
|
||||
return alpha;
|
||||
}
|
||||
|
||||
var searchPv = true;
|
||||
|
||||
foreach (var move in board.GetLegalMoves().OrderByDescending(m => (m.IsCapture ? 100 * (int)m.CapturePieceType - (int)m.MovePieceType : 0) + (m.IsPromotion ? 1 : 0)))
|
||||
{
|
||||
board.MakeMove(move);
|
||||
|
||||
int score = 0;
|
||||
if (searchPv)
|
||||
{
|
||||
score = -Search(board, -beta, -alpha, depth - 1);
|
||||
moveList[depth - 1] = move;
|
||||
}
|
||||
else
|
||||
{
|
||||
score = -Search(board, -alpha - 1, -alpha, depth - 1);
|
||||
if (score > alpha)
|
||||
score = -Search(board, -beta, -alpha, depth - 1);
|
||||
}
|
||||
|
||||
board.UndoMove(move);
|
||||
|
||||
if (score >= beta)
|
||||
{
|
||||
moveList[depth - 1] = move;
|
||||
return beta;
|
||||
}
|
||||
if (score > alpha)
|
||||
{
|
||||
moveList[depth - 1] = move;
|
||||
alpha = score;
|
||||
searchPv = false;
|
||||
}
|
||||
}
|
||||
|
||||
return alpha;
|
||||
}
|
||||
|
||||
private int EvalBoard(Board board)
|
||||
{
|
||||
if (board.IsInCheckmate())
|
||||
return -10000;
|
||||
|
||||
if (board.IsDraw())
|
||||
return -50; // contempt
|
||||
|
||||
// token optimization
|
||||
var whiteToMove = board.IsWhiteToMove ? 1 : -1;
|
||||
var pieces = board.GetAllPieceLists();
|
||||
var openingWeight = 3.0f / board.PlyCount;
|
||||
|
||||
var GetPieceBitboard = board.GetPieceBitboard;
|
||||
var GetNumberOfSetBits = BitboardHelper.GetNumberOfSetBits;
|
||||
|
||||
var whitePawnBB = GetPieceBitboard(PieceType.Pawn, true);
|
||||
var blackPawnBB = GetPieceBitboard(PieceType.Pawn, false);
|
||||
var whiteKnightBB = GetPieceBitboard(PieceType.Knight, true);
|
||||
var blackKnightBB = GetPieceBitboard(PieceType.Knight, false);
|
||||
var whiteBishopBB = GetPieceBitboard(PieceType.Bishop, true);
|
||||
var blackBishopBB = GetPieceBitboard(PieceType.Bishop, false);
|
||||
var whiteQueenBB = GetPieceBitboard(PieceType.Queen, true);
|
||||
var blackQueenBB = GetPieceBitboard(PieceType.Queen, false);
|
||||
var whitePiecesBB = board.WhitePiecesBitboard;
|
||||
var blackPiecesBB = board.BlackPiecesBitboard;
|
||||
|
||||
// mobility
|
||||
// GetLegalMoves is very slow here.
|
||||
// Will never be cached since we call MakeMove right before eval.
|
||||
// Estimate legal moves by checked state and number of pieces.
|
||||
int eval = (
|
||||
(board.IsInCheck() ? 2 : GetNumberOfSetBits(board.IsWhiteToMove ? whitePiecesBB : blackPiecesBB))
|
||||
) * 25 * whiteToMove;
|
||||
|
||||
// material
|
||||
for (int i = 0; i < 5; i++)
|
||||
eval += (pieces[i].Count - pieces[i + 6].Count) * pieceValues[i];
|
||||
|
||||
// pawns
|
||||
ulong pawnFiles = 0;
|
||||
var wpCount = pieces[0].Count;
|
||||
for (int i = 0; i < wpCount; i++)
|
||||
{
|
||||
var pawn = pieces[0][i];
|
||||
// advance & structure
|
||||
eval += 10 * (2 * pawn.Square.Rank + GetNumberOfSetBits(whitePawnBB & BitboardHelper.GetPawnAttacks(pawn.Square, true)));
|
||||
// doubling
|
||||
pawnFiles |= 1U << pawn.Square.File;
|
||||
}
|
||||
eval -= wpCount - GetNumberOfSetBits(pawnFiles);
|
||||
pawnFiles = 0;
|
||||
var bpCount = pieces[6].Count;
|
||||
for (int i = 0; i < bpCount; i++)
|
||||
{
|
||||
var pawn = pieces[6][i];
|
||||
eval -= 10 * (2 * (7 - pawn.Square.Rank) + GetNumberOfSetBits(blackPawnBB & BitboardHelper.GetPawnAttacks(pawn.Square, false)));
|
||||
pawnFiles |= 1U << pawn.Square.File;
|
||||
}
|
||||
eval += bpCount - GetNumberOfSetBits(pawnFiles);
|
||||
|
||||
// center occupancy
|
||||
// extended 4x4 center
|
||||
eval += (20 + (int)(openingWeight * 50)) * (
|
||||
GetNumberOfSetBits(whitePawnBB & 0x00003C3C3C3C0000) -
|
||||
GetNumberOfSetBits(blackPawnBB & 0x00003C3C3C3C0000)
|
||||
);
|
||||
// 2x2 center
|
||||
eval += (10 + (int)(openingWeight * 50)) * (
|
||||
GetNumberOfSetBits(whitePawnBB & 0x0000001818000000) -
|
||||
GetNumberOfSetBits(blackPawnBB & 0x0000001818000000)
|
||||
);
|
||||
|
||||
// king protecting pawns / king pawn shield
|
||||
eval += 20 * (
|
||||
GetNumberOfSetBits(whitePawnBB & BitboardHelper.GetKingAttacks(board.GetKingSquare(true))) -
|
||||
GetNumberOfSetBits(blackPawnBB & BitboardHelper.GetKingAttacks(board.GetKingSquare(false)))
|
||||
);
|
||||
|
||||
// development
|
||||
eval += (20 + (int)(openingWeight * 4)) * (
|
||||
GetNumberOfSetBits((whiteKnightBB | whiteBishopBB | whiteQueenBB) & 0xFFFFFFFFFFFF00) -
|
||||
GetNumberOfSetBits((blackKnightBB | blackBishopBB | blackQueenBB) & 0x00FFFFFFFFFFFF)
|
||||
);
|
||||
|
||||
// edges are generally bad for these pieces
|
||||
eval += 30 * (
|
||||
GetNumberOfSetBits((blackKnightBB | blackBishopBB | blackQueenBB) & 0xFF818181818181FF) -
|
||||
GetNumberOfSetBits((whiteKnightBB | whiteBishopBB | whiteQueenBB) & 0xFF818181818181FF)
|
||||
);
|
||||
|
||||
// Rooks are generally good on low ranks
|
||||
eval += 5 * (
|
||||
GetNumberOfSetBits(GetPieceBitboard(PieceType.Rook, true) & 0x00000000FFFFFFFF) -
|
||||
GetNumberOfSetBits(GetPieceBitboard(PieceType.Rook, false) & 0xFFFFFFFF00000000)
|
||||
);
|
||||
|
||||
// Having both bishops is good
|
||||
if (pieces[2].Count > 1) eval += 5;
|
||||
if (pieces[8].Count > 1) eval -= 5;
|
||||
|
||||
// signed for the side playing
|
||||
return eval * whiteToMove;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue