diff --git a/.gitignore b/.gitignore index 26f2c7b..8626380 100644 --- a/.gitignore +++ b/.gitignore @@ -349,3 +349,4 @@ MigrationBackup/ .ionide/ /cloc.exe /cloc.bat +/Cosette.Arbiter/settings.json diff --git a/CHANGELOG.md b/CHANGELOG.md index ac3ad8b..cedb88c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,28 @@ -# Version 1.0 (Aqua), 19-09-2020 - * Initial version +# Version 2.0 (Darkness), 19.10.2020 + - Added fifty-move rule detection + - Added new evaluation functions: pawn shield, bishop pair, doubled rooks, a rook on open file + - Added "evaluate" command to get FEN position evaluation + - Added ability to postpone moves generation before PV move check + - Added evaluation hash table + - Added Arbiter app to speed up the process of testing engine + - Added support for UCI's winc and binc + - Fixed PV node detection + - Fixed invalid detection of passing pawns + - Fixed invalid best move when a search has been aborted + - Fixed static exchange evaluation - in rare cases the table was returning an invalid score + - Improved method of probing piece type at the specified field + - Improved time management - now allocated time depends on the moves count + - Improved move ordering: castling and better promotions are now prioritized + - Improved transposition tables: entries are now smaller, have proper checkmate scores (relative to position) and are used between moves (aging) + - Redefined and reduced the size of Move structure (from 4 bytes to 2 bytes) + - Reduced size of transposition table entry (from 16 bytes to 12 bytes), evaluation hash table entry (from 8 bytes to 4 bytes) and pawn hash table entry (from 8 bytes to 4 bytes) + - Optimized printing UCI output + - Adjusted move ordering scores + - Updated .NET Core runtime version to 3.1.403 + +Estimated strength: 1950 ELO + +# Version 1.0 (Aqua), 19.09.2020 + - Initial version Estimated strength: 1900 ELO \ No newline at end of file diff --git a/Cosette.Arbiter.Tests/Cosette.Arbiter.Tests.csproj b/Cosette.Arbiter.Tests/Cosette.Arbiter.Tests.csproj new file mode 100644 index 0000000..776d0eb --- /dev/null +++ b/Cosette.Arbiter.Tests/Cosette.Arbiter.Tests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + diff --git a/Cosette.Arbiter.Tests/HashKeyTests.cs b/Cosette.Arbiter.Tests/HashKeyTests.cs new file mode 100644 index 0000000..ca7d7b6 --- /dev/null +++ b/Cosette.Arbiter.Tests/HashKeyTests.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using Cosette.Arbiter.Book; +using Xunit; + +namespace Cosette.Arbiter.Tests +{ + public class HashKeyTests + { + [Theory] + [InlineData(new string[] { }, 5060803636482931868)] + [InlineData(new[] { "e2e4" }, 9384546495678726550)] + [InlineData(new[] { "e2e4", "d7d5" }, 528813709611831216)] + [InlineData(new[] { "e2e4", "d7d5", "e4e5", }, 7363297126586722772)] + [InlineData(new[] { "e2e4", "d7d5", "e4e5", "f7f5" }, 2496273314520498040)] + [InlineData(new[] { "e2e4", "d7d5", "e4e5", "f7f5", "e1e2" }, 7289745035295343297)] + [InlineData(new[] { "e2e4", "d7d5", "e4e5", "f7f5", "e1e2", "e8f7" }, 71445182323015129)] + [InlineData(new[] { "a2a4", "b7b5", "h2h4", "b5b4", "c2c4" }, 4359805404264691255)] + [InlineData(new[] { "a2a4", "b7b5", "h2h4", "b5b4", "c2c4", "b4c3", "a1a3" }, 6647202560273257824)] + public void HashKey_FromInitialPosition(string[] moves, ulong expectedHashKey) + { + var polyglotBoard = new PolyglotBoard(); + polyglotBoard.InitDefaultState(); + + foreach (var move in moves) + { + polyglotBoard.MakeMove(move); + } + + Assert.Equal(expectedHashKey, polyglotBoard.CalculateHash()); + } + } +} diff --git a/Cosette.Arbiter/Book/CastlingFlags.cs b/Cosette.Arbiter/Book/CastlingFlags.cs new file mode 100644 index 0000000..7d51a9a --- /dev/null +++ b/Cosette.Arbiter/Book/CastlingFlags.cs @@ -0,0 +1,17 @@ +using System; + +namespace Cosette.Arbiter.Book +{ + [Flags] + public enum CastlingFlags + { + None = 0, + WhiteShort = 1, + WhiteLong = 2, + BlackShort = 4, + BlackLong = 8, + WhiteCastling = WhiteShort | WhiteLong, + BlackCastling = BlackShort | BlackLong, + Everything = WhiteShort | WhiteLong | BlackShort | BlackLong + } +} diff --git a/Cosette.Arbiter/Book/ColorType.cs b/Cosette.Arbiter/Book/ColorType.cs new file mode 100644 index 0000000..ae23ffc --- /dev/null +++ b/Cosette.Arbiter/Book/ColorType.cs @@ -0,0 +1,8 @@ +namespace Cosette.Arbiter.Book +{ + public enum ColorType + { + White, + Black + } +} diff --git a/Cosette.Arbiter/Book/PieceType.cs b/Cosette.Arbiter/Book/PieceType.cs new file mode 100644 index 0000000..08abb29 --- /dev/null +++ b/Cosette.Arbiter/Book/PieceType.cs @@ -0,0 +1,19 @@ +namespace Cosette.Arbiter.Book +{ + public enum PieceType + { + None = -1, + BlackPawn, + WhitePawn, + BlackKnight, + WhiteKnight, + BlackBishop, + WhiteBishop, + BlackRook, + WhiteRook, + BlackQueen, + WhiteQueen, + BlackKing, + WhiteKing + } +} diff --git a/Cosette.Arbiter/Book/PolyglotBoard.cs b/Cosette.Arbiter/Book/PolyglotBoard.cs new file mode 100644 index 0000000..df62541 --- /dev/null +++ b/Cosette.Arbiter/Book/PolyglotBoard.cs @@ -0,0 +1,211 @@ +using System; + +namespace Cosette.Arbiter.Book +{ + public class PolyglotBoard + { + private PieceType[,] _state; + private CastlingFlags _castlingFlags; + private ColorType _colorToMove; + private int _enPassantFile; + + public PolyglotBoard() + { + _state = new PieceType[8,8]; + _castlingFlags = CastlingFlags.Everything; + _colorToMove = ColorType.White; + _enPassantFile = -1; + } + + public void InitDefaultState() + { + for (var file = 0; file < 8; file++) + { + for (var rank = 0; rank < 8; rank++) + { + _state[file, rank] = PieceType.None; + } + } + + _state[0, 0] = PieceType.WhiteRook; + _state[1, 0] = PieceType.WhiteKnight; + _state[2, 0] = PieceType.WhiteBishop; + _state[3, 0] = PieceType.WhiteQueen; + _state[4, 0] = PieceType.WhiteKing; + _state[5, 0] = PieceType.WhiteBishop; + _state[6, 0] = PieceType.WhiteKnight; + _state[7, 0] = PieceType.WhiteRook; + + _state[0, 1] = PieceType.WhitePawn; + _state[1, 1] = PieceType.WhitePawn; + _state[2, 1] = PieceType.WhitePawn; + _state[3, 1] = PieceType.WhitePawn; + _state[4, 1] = PieceType.WhitePawn; + _state[5, 1] = PieceType.WhitePawn; + _state[6, 1] = PieceType.WhitePawn; + _state[7, 1] = PieceType.WhitePawn; + + _state[0, 6] = PieceType.BlackPawn; + _state[1, 6] = PieceType.BlackPawn; + _state[2, 6] = PieceType.BlackPawn; + _state[3, 6] = PieceType.BlackPawn; + _state[4, 6] = PieceType.BlackPawn; + _state[5, 6] = PieceType.BlackPawn; + _state[6, 6] = PieceType.BlackPawn; + _state[7, 6] = PieceType.BlackPawn; + + _state[0, 7] = PieceType.BlackRook; + _state[1, 7] = PieceType.BlackKnight; + _state[2, 7] = PieceType.BlackBishop; + _state[3, 7] = PieceType.BlackQueen; + _state[4, 7] = PieceType.BlackKing; + _state[5, 7] = PieceType.BlackBishop; + _state[6, 7] = PieceType.BlackKnight; + _state[7, 7] = PieceType.BlackRook; + } + + public void MakeMove(string move) + { + var (fromFile, fromRank) = (move[0] - 'a', move[1] - '1'); + var (toFile, toRank) = (move[2] - 'a', move[3] - '1'); + var pieceType = _state[fromFile, fromRank]; + var oldEnPassant = _enPassantFile; + + // Pieces + _state[toFile, toRank] = _state[fromFile, fromRank]; + _state[fromFile, fromRank] = PieceType.None; + _enPassantFile = -1; + + // Castling + if (pieceType == PieceType.WhiteKing) + { + _castlingFlags &= ~CastlingFlags.WhiteCastling; + + if (Math.Abs(fromFile - toFile) == 2) + { + // Short castling + if (fromFile < toFile) + { + _state[7, 0] = PieceType.None; + _state[5, 0] = PieceType.WhiteRook; + } + // Long castling + else + { + _state[0, 0] = PieceType.None; + _state[3, 0] = PieceType.WhiteRook; + } + } + } + else if (pieceType == PieceType.BlackKing) + { + _castlingFlags &= ~CastlingFlags.BlackCastling; + + if (Math.Abs(fromFile - toFile) == 2) + { + // Short castling + if (fromFile < toFile) + { + _state[7, 7] = PieceType.None; + _state[5, 7] = PieceType.BlackRook; + } + // Long castling + else + { + _state[0, 7] = PieceType.None; + _state[3, 7] = PieceType.BlackRook; + } + } + } + else if (pieceType == PieceType.WhiteRook && fromFile == 0 && fromRank == 0) + { + _castlingFlags &= ~CastlingFlags.WhiteLong; + } + else if (pieceType == PieceType.WhiteRook && fromFile == 7 && fromRank == 0) + { + _castlingFlags &= ~CastlingFlags.WhiteShort; + } + else if (pieceType == PieceType.BlackRook && fromFile == 0 && fromRank == 7) + { + _castlingFlags &= ~CastlingFlags.BlackLong; + } + else if (pieceType == PieceType.BlackRook && fromFile == 7 && fromRank == 7) + { + _castlingFlags &= ~CastlingFlags.BlackShort; + } + + // En passant + if (pieceType == PieceType.WhitePawn || pieceType == PieceType.BlackPawn) + { + if (Math.Abs(fromRank - toRank) == 2) + { + var targetPieceType = _colorToMove == ColorType.White ? PieceType.BlackPawn : PieceType.WhitePawn; + if (toFile > 0 && _state[toFile - 1, toRank] == targetPieceType || + toFile < 7 && _state[toFile + 1, toRank] == targetPieceType) + { + _enPassantFile = toFile; + } + } + else if (Math.Abs(fromFile - toFile) == 1 && toFile == oldEnPassant) + { + if (_colorToMove == ColorType.White) + { + _state[toFile, toRank - 1] = PieceType.None; + } + else if (_colorToMove == ColorType.Black) + { + _state[toFile, toRank + 1] = PieceType.None; + } + } + } + + // Color + _colorToMove = _colorToMove == ColorType.White ? ColorType.Black : ColorType.White; + } + + public ulong CalculateHash() + { + ulong result = 0; + + for (var file = 0; file < 8; file++) + { + for (var rank = 0; rank < 8; rank++) + { + if (_state[file, rank] != PieceType.None) + { + result ^= PolyglotConstants.Keys[64 * (int)_state[file, rank] + 8 * rank + file]; + } + } + } + + if ((_castlingFlags & CastlingFlags.WhiteShort) != 0) + { + result ^= PolyglotConstants.Keys[768]; + } + if ((_castlingFlags & CastlingFlags.WhiteLong) != 0) + { + result ^= PolyglotConstants.Keys[769]; + } + if ((_castlingFlags & CastlingFlags.BlackShort) != 0) + { + result ^= PolyglotConstants.Keys[770]; + } + if ((_castlingFlags & CastlingFlags.BlackLong) != 0) + { + result ^= PolyglotConstants.Keys[771]; + } + + if (_enPassantFile != -1) + { + result ^= PolyglotConstants.Keys[772 + _enPassantFile]; + } + + if (_colorToMove == ColorType.White) + { + result ^= PolyglotConstants.Keys[780]; + } + + return result; + } + } +} diff --git a/Cosette.Arbiter/Book/PolyglotBook.cs b/Cosette.Arbiter/Book/PolyglotBook.cs new file mode 100644 index 0000000..e46bb73 --- /dev/null +++ b/Cosette.Arbiter/Book/PolyglotBook.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Cosette.Arbiter.Settings; + +namespace Cosette.Arbiter.Book +{ + public class PolyglotBook + { + private Random _random; + + public PolyglotBook() + { + _random = new Random(); + } + + public List GetRandomOpening() + { + var movesList = new List(); + var polyglotBoard = new PolyglotBoard(); + polyglotBoard.InitDefaultState(); + + for (var moveIndex = 0; moveIndex < SettingsLoader.Data.PolyglotMaxMoves; moveIndex++) + { + var availableMoves = GetBookEntries(polyglotBoard.CalculateHash()); + if (availableMoves.Count == 0) + { + break; + } + + availableMoves = availableMoves.OrderBy(p => p.Weight).ToList(); + var weightSum = availableMoves.Sum(p => p.Weight); + + var probabilityArray = new double[availableMoves.Count]; + for (var availableMoveIndex = 0; availableMoveIndex < availableMoves.Count; availableMoveIndex++) + { + probabilityArray[availableMoveIndex] = (double)availableMoves[availableMoveIndex].Weight / weightSum; + } + + var randomValue = _random.NextDouble(); + for (var availableMoveIndex = 0; availableMoveIndex < availableMoves.Count; availableMoveIndex++) + { + if (probabilityArray[availableMoveIndex] > randomValue || availableMoveIndex == availableMoves.Count - 1) + { + movesList.Add(availableMoves[availableMoveIndex]); + polyglotBoard.MakeMove(availableMoves[availableMoveIndex].Move.ToString()); + break; + } + } + } + + return movesList.Select(p => p.Move.ToString()).ToList(); + } + + public unsafe List GetBookEntries(ulong hash) + { + var foundEntries = new List(); + + var entrySize = sizeof(PolyglotBookEntry); + var bookInfo = new FileInfo(SettingsLoader.Data.PolyglotOpeningBook); + var entriesCount = bookInfo.Length / entrySize; + long left = 0; + long right = entriesCount - 1; + + using (var binaryReader = new BinaryReader(new FileStream(SettingsLoader.Data.PolyglotOpeningBook, FileMode.Open))) + { + var buffer = new byte[16]; + var bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); + var bufferPtr = bufferHandle.AddrOfPinnedObject(); + + while (left <= right) + { + long middle = (left + right) / 2; + var entry = ReadEntry(binaryReader, buffer, bufferPtr, middle, entrySize); + + if (entry.Hash < hash) + { + left = middle + 1; + } + else + { + right = middle - 1; + } + } + + while (true) + { + var entry = ReadEntry(binaryReader, buffer, bufferPtr, left++, entrySize); + if (entry.Hash == hash) + { + if (entry.Move != PolyglotBookMove.Zero) + { + foundEntries.Add(entry); + } + } + else + { + break; + } + } + + bufferHandle.Free(); + } + + return foundEntries; + } + + private PolyglotBookEntry ReadEntry(BinaryReader binaryReader, byte[] buffer, IntPtr bufferPtr, long position, int entrySize) + { + binaryReader.BaseStream.Seek(position * entrySize, SeekOrigin.Begin); + binaryReader.Read(buffer, 0, 16); + + // Swap big endian to little endian + Array.Reverse(buffer, 0, 8); + Array.Reverse(buffer, 8, 2); + Array.Reverse(buffer, 10, 2); + Array.Reverse(buffer, 12, 4); + + return (PolyglotBookEntry)Marshal.PtrToStructure(bufferPtr, typeof(PolyglotBookEntry)); + } + } +} diff --git a/Cosette.Arbiter/Book/PolyglotBookEntry.cs b/Cosette.Arbiter/Book/PolyglotBookEntry.cs new file mode 100644 index 0000000..e344ace --- /dev/null +++ b/Cosette.Arbiter/Book/PolyglotBookEntry.cs @@ -0,0 +1,17 @@ +namespace Cosette.Arbiter.Book +{ + public struct PolyglotBookEntry + { + public ulong Hash; + public PolyglotBookMove Move; + public ushort Weight; + public int Learn; + + public static PolyglotBookEntry Zero = new PolyglotBookEntry(); + + public override string ToString() + { + return Move.ToString(); + } + } +} diff --git a/Cosette.Arbiter/Book/PolyglotBookMove.cs b/Cosette.Arbiter/Book/PolyglotBookMove.cs new file mode 100644 index 0000000..e20af91 --- /dev/null +++ b/Cosette.Arbiter/Book/PolyglotBookMove.cs @@ -0,0 +1,76 @@ +#pragma warning disable 649 + +namespace Cosette.Arbiter.Book +{ + public struct PolyglotBookMove + { + public byte ToFile => (byte)(_data & 0x7); + public byte ToRank => (byte)((_data >> 3) & 0x7); + public byte FromFile => (byte)((_data >> 6) & 0x7); + public byte FromRank => (byte)((_data >> 9) & 0x7); + public byte PromotionPiece => (byte)((_data >> 12) & 0x7); + + public static PolyglotBookMove Zero = new PolyglotBookMove(); + + private ushort _data; + + public static bool operator ==(PolyglotBookMove a, PolyglotBookMove b) + { + return a._data == b._data; + } + + public static bool operator !=(PolyglotBookMove a, PolyglotBookMove b) + { + return a._data != b._data; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(obj, null)) + { + return false; + } + + if (GetType() != obj.GetType()) + { + return false; + } + + return this == (PolyglotBookMove)obj; + } + + public override int GetHashCode() + { + return _data; + } + + public override string ToString() + { + var from = $"{(char)(FromFile + 'a')}{(char)(FromRank + '1')}"; + var to = $"{(char)(ToFile + 'a')}{(char)(ToRank + '1')}"; + var result = from + to; + + if (result == "e1h1") + { + return "e1g1"; + } + + if (result == "e8h8") + { + return "e8g8"; + } + + if (result == "e1a1") + { + return "e1c1"; + } + + if (result == "e8a8") + { + return "e8c8"; + } + + return result; + } + } +} diff --git a/Cosette.Arbiter/Book/PolyglotConstants.cs b/Cosette.Arbiter/Book/PolyglotConstants.cs new file mode 100644 index 0000000..368833f --- /dev/null +++ b/Cosette.Arbiter/Book/PolyglotConstants.cs @@ -0,0 +1,205 @@ +namespace Cosette.Arbiter.Book +{ + public static class PolyglotConstants + { + public static ulong[] Keys = + { + 0x9D39247E33776D41, 0x2AF7398005AAA5C7, 0x44DB015024623547, 0x9C15F73E62A76AE2, + 0x75834465489C0C89, 0x3290AC3A203001BF, 0x0FBBAD1F61042279, 0xE83A908FF2FB60CA, + 0x0D7E765D58755C10, 0x1A083822CEAFE02D, 0x9605D5F0E25EC3B0, 0xD021FF5CD13A2ED5, + 0x40BDF15D4A672E32, 0x011355146FD56395, 0x5DB4832046F3D9E5, 0x239F8B2D7FF719CC, + 0x05D1A1AE85B49AA1, 0x679F848F6E8FC971, 0x7449BBFF801FED0B, 0x7D11CDB1C3B7ADF0, + 0x82C7709E781EB7CC, 0xF3218F1C9510786C, 0x331478F3AF51BBE6, 0x4BB38DE5E7219443, + 0xAA649C6EBCFD50FC, 0x8DBD98A352AFD40B, 0x87D2074B81D79217, 0x19F3C751D3E92AE1, + 0xB4AB30F062B19ABF, 0x7B0500AC42047AC4, 0xC9452CA81A09D85D, 0x24AA6C514DA27500, + 0x4C9F34427501B447, 0x14A68FD73C910841, 0xA71B9B83461CBD93, 0x03488B95B0F1850F, + 0x637B2B34FF93C040, 0x09D1BC9A3DD90A94, 0x3575668334A1DD3B, 0x735E2B97A4C45A23, + 0x18727070F1BD400B, 0x1FCBACD259BF02E7, 0xD310A7C2CE9B6555, 0xBF983FE0FE5D8244, + 0x9F74D14F7454A824, 0x51EBDC4AB9BA3035, 0x5C82C505DB9AB0FA, 0xFCF7FE8A3430B241, + 0x3253A729B9BA3DDE, 0x8C74C368081B3075, 0xB9BC6C87167C33E7, 0x7EF48F2B83024E20, + 0x11D505D4C351BD7F, 0x6568FCA92C76A243, 0x4DE0B0F40F32A7B8, 0x96D693460CC37E5D, + 0x42E240CB63689F2F, 0x6D2BDCDAE2919661, 0x42880B0236E4D951, 0x5F0F4A5898171BB6, + 0x39F890F579F92F88, 0x93C5B5F47356388B, 0x63DC359D8D231B78, 0xEC16CA8AEA98AD76, + 0x5355F900C2A82DC7, 0x07FB9F855A997142, 0x5093417AA8A7ED5E, 0x7BCBC38DA25A7F3C, + 0x19FC8A768CF4B6D4, 0x637A7780DECFC0D9, 0x8249A47AEE0E41F7, 0x79AD695501E7D1E8, + 0x14ACBAF4777D5776, 0xF145B6BECCDEA195, 0xDABF2AC8201752FC, 0x24C3C94DF9C8D3F6, + 0xBB6E2924F03912EA, 0x0CE26C0B95C980D9, 0xA49CD132BFBF7CC4, 0xE99D662AF4243939, + 0x27E6AD7891165C3F, 0x8535F040B9744FF1, 0x54B3F4FA5F40D873, 0x72B12C32127FED2B, + 0xEE954D3C7B411F47, 0x9A85AC909A24EAA1, 0x70AC4CD9F04F21F5, 0xF9B89D3E99A075C2, + 0x87B3E2B2B5C907B1, 0xA366E5B8C54F48B8, 0xAE4A9346CC3F7CF2, 0x1920C04D47267BBD, + 0x87BF02C6B49E2AE9, 0x092237AC237F3859, 0xFF07F64EF8ED14D0, 0x8DE8DCA9F03CC54E, + 0x9C1633264DB49C89, 0xB3F22C3D0B0B38ED, 0x390E5FB44D01144B, 0x5BFEA5B4712768E9, + 0x1E1032911FA78984, 0x9A74ACB964E78CB3, 0x4F80F7A035DAFB04, 0x6304D09A0B3738C4, + 0x2171E64683023A08, 0x5B9B63EB9CEFF80C, 0x506AACF489889342, 0x1881AFC9A3A701D6, + 0x6503080440750644, 0xDFD395339CDBF4A7, 0xEF927DBCF00C20F2, 0x7B32F7D1E03680EC, + 0xB9FD7620E7316243, 0x05A7E8A57DB91B77, 0xB5889C6E15630A75, 0x4A750A09CE9573F7, + 0xCF464CEC899A2F8A, 0xF538639CE705B824, 0x3C79A0FF5580EF7F, 0xEDE6C87F8477609D, + 0x799E81F05BC93F31, 0x86536B8CF3428A8C, 0x97D7374C60087B73, 0xA246637CFF328532, + 0x043FCAE60CC0EBA0, 0x920E449535DD359E, 0x70EB093B15B290CC, 0x73A1921916591CBD, + 0x56436C9FE1A1AA8D, 0xEFAC4B70633B8F81, 0xBB215798D45DF7AF, 0x45F20042F24F1768, + 0x930F80F4E8EB7462, 0xFF6712FFCFD75EA1, 0xAE623FD67468AA70, 0xDD2C5BC84BC8D8FC, + 0x7EED120D54CF2DD9, 0x22FE545401165F1C, 0xC91800E98FB99929, 0x808BD68E6AC10365, + 0xDEC468145B7605F6, 0x1BEDE3A3AEF53302, 0x43539603D6C55602, 0xAA969B5C691CCB7A, + 0xA87832D392EFEE56, 0x65942C7B3C7E11AE, 0xDED2D633CAD004F6, 0x21F08570F420E565, + 0xB415938D7DA94E3C, 0x91B859E59ECB6350, 0x10CFF333E0ED804A, 0x28AED140BE0BB7DD, + 0xC5CC1D89724FA456, 0x5648F680F11A2741, 0x2D255069F0B7DAB3, 0x9BC5A38EF729ABD4, + 0xEF2F054308F6A2BC, 0xAF2042F5CC5C2858, 0x480412BAB7F5BE2A, 0xAEF3AF4A563DFE43, + 0x19AFE59AE451497F, 0x52593803DFF1E840, 0xF4F076E65F2CE6F0, 0x11379625747D5AF3, + 0xBCE5D2248682C115, 0x9DA4243DE836994F, 0x066F70B33FE09017, 0x4DC4DE189B671A1C, + 0x51039AB7712457C3, 0xC07A3F80C31FB4B4, 0xB46EE9C5E64A6E7C, 0xB3819A42ABE61C87, + 0x21A007933A522A20, 0x2DF16F761598AA4F, 0x763C4A1371B368FD, 0xF793C46702E086A0, + 0xD7288E012AEB8D31, 0xDE336A2A4BC1C44B, 0x0BF692B38D079F23, 0x2C604A7A177326B3, + 0x4850E73E03EB6064, 0xCFC447F1E53C8E1B, 0xB05CA3F564268D99, 0x9AE182C8BC9474E8, + 0xA4FC4BD4FC5558CA, 0xE755178D58FC4E76, 0x69B97DB1A4C03DFE, 0xF9B5B7C4ACC67C96, + 0xFC6A82D64B8655FB, 0x9C684CB6C4D24417, 0x8EC97D2917456ED0, 0x6703DF9D2924E97E, + 0xC547F57E42A7444E, 0x78E37644E7CAD29E, 0xFE9A44E9362F05FA, 0x08BD35CC38336615, + 0x9315E5EB3A129ACE, 0x94061B871E04DF75, 0xDF1D9F9D784BA010, 0x3BBA57B68871B59D, + 0xD2B7ADEEDED1F73F, 0xF7A255D83BC373F8, 0xD7F4F2448C0CEB81, 0xD95BE88CD210FFA7, + 0x336F52F8FF4728E7, 0xA74049DAC312AC71, 0xA2F61BB6E437FDB5, 0x4F2A5CB07F6A35B3, + 0x87D380BDA5BF7859, 0x16B9F7E06C453A21, 0x7BA2484C8A0FD54E, 0xF3A678CAD9A2E38C, + 0x39B0BF7DDE437BA2, 0xFCAF55C1BF8A4424, 0x18FCF680573FA594, 0x4C0563B89F495AC3, + 0x40E087931A00930D, 0x8CFFA9412EB642C1, 0x68CA39053261169F, 0x7A1EE967D27579E2, + 0x9D1D60E5076F5B6F, 0x3810E399B6F65BA2, 0x32095B6D4AB5F9B1, 0x35CAB62109DD038A, + 0xA90B24499FCFAFB1, 0x77A225A07CC2C6BD, 0x513E5E634C70E331, 0x4361C0CA3F692F12, + 0xD941ACA44B20A45B, 0x528F7C8602C5807B, 0x52AB92BEB9613989, 0x9D1DFA2EFC557F73, + 0x722FF175F572C348, 0x1D1260A51107FE97, 0x7A249A57EC0C9BA2, 0x04208FE9E8F7F2D6, + 0x5A110C6058B920A0, 0x0CD9A497658A5698, 0x56FD23C8F9715A4C, 0x284C847B9D887AAE, + 0x04FEABFBBDB619CB, 0x742E1E651C60BA83, 0x9A9632E65904AD3C, 0x881B82A13B51B9E2, + 0x506E6744CD974924, 0xB0183DB56FFC6A79, 0x0ED9B915C66ED37E, 0x5E11E86D5873D484, + 0xF678647E3519AC6E, 0x1B85D488D0F20CC5, 0xDAB9FE6525D89021, 0x0D151D86ADB73615, + 0xA865A54EDCC0F019, 0x93C42566AEF98FFB, 0x99E7AFEABE000731, 0x48CBFF086DDF285A, + 0x7F9B6AF1EBF78BAF, 0x58627E1A149BBA21, 0x2CD16E2ABD791E33, 0xD363EFF5F0977996, + 0x0CE2A38C344A6EED, 0x1A804AADB9CFA741, 0x907F30421D78C5DE, 0x501F65EDB3034D07, + 0x37624AE5A48FA6E9, 0x957BAF61700CFF4E, 0x3A6C27934E31188A, 0xD49503536ABCA345, + 0x088E049589C432E0, 0xF943AEE7FEBF21B8, 0x6C3B8E3E336139D3, 0x364F6FFA464EE52E, + 0xD60F6DCEDC314222, 0x56963B0DCA418FC0, 0x16F50EDF91E513AF, 0xEF1955914B609F93, + 0x565601C0364E3228, 0xECB53939887E8175, 0xBAC7A9A18531294B, 0xB344C470397BBA52, + 0x65D34954DAF3CEBD, 0xB4B81B3FA97511E2, 0xB422061193D6F6A7, 0x071582401C38434D, + 0x7A13F18BBEDC4FF5, 0xBC4097B116C524D2, 0x59B97885E2F2EA28, 0x99170A5DC3115544, + 0x6F423357E7C6A9F9, 0x325928EE6E6F8794, 0xD0E4366228B03343, 0x565C31F7DE89EA27, + 0x30F5611484119414, 0xD873DB391292ED4F, 0x7BD94E1D8E17DEBC, 0xC7D9F16864A76E94, + 0x947AE053EE56E63C, 0xC8C93882F9475F5F, 0x3A9BF55BA91F81CA, 0xD9A11FBB3D9808E4, + 0x0FD22063EDC29FCA, 0xB3F256D8ACA0B0B9, 0xB03031A8B4516E84, 0x35DD37D5871448AF, + 0xE9F6082B05542E4E, 0xEBFAFA33D7254B59, 0x9255ABB50D532280, 0xB9AB4CE57F2D34F3, + 0x693501D628297551, 0xC62C58F97DD949BF, 0xCD454F8F19C5126A, 0xBBE83F4ECC2BDECB, + 0xDC842B7E2819E230, 0xBA89142E007503B8, 0xA3BC941D0A5061CB, 0xE9F6760E32CD8021, + 0x09C7E552BC76492F, 0x852F54934DA55CC9, 0x8107FCCF064FCF56, 0x098954D51FFF6580, + 0x23B70EDB1955C4BF, 0xC330DE426430F69D, 0x4715ED43E8A45C0A, 0xA8D7E4DAB780A08D, + 0x0572B974F03CE0BB, 0xB57D2E985E1419C7, 0xE8D9ECBE2CF3D73F, 0x2FE4B17170E59750, + 0x11317BA87905E790, 0x7FBF21EC8A1F45EC, 0x1725CABFCB045B00, 0x964E915CD5E2B207, + 0x3E2B8BCBF016D66D, 0xBE7444E39328A0AC, 0xF85B2B4FBCDE44B7, 0x49353FEA39BA63B1, + 0x1DD01AAFCD53486A, 0x1FCA8A92FD719F85, 0xFC7C95D827357AFA, 0x18A6A990C8B35EBD, + 0xCCCB7005C6B9C28D, 0x3BDBB92C43B17F26, 0xAA70B5B4F89695A2, 0xE94C39A54A98307F, + 0xB7A0B174CFF6F36E, 0xD4DBA84729AF48AD, 0x2E18BC1AD9704A68, 0x2DE0966DAF2F8B1C, + 0xB9C11D5B1E43A07E, 0x64972D68DEE33360, 0x94628D38D0C20584, 0xDBC0D2B6AB90A559, + 0xD2733C4335C6A72F, 0x7E75D99D94A70F4D, 0x6CED1983376FA72B, 0x97FCAACBF030BC24, + 0x7B77497B32503B12, 0x8547EDDFB81CCB94, 0x79999CDFF70902CB, 0xCFFE1939438E9B24, + 0x829626E3892D95D7, 0x92FAE24291F2B3F1, 0x63E22C147B9C3403, 0xC678B6D860284A1C, + 0x5873888850659AE7, 0x0981DCD296A8736D, 0x9F65789A6509A440, 0x9FF38FED72E9052F, + 0xE479EE5B9930578C, 0xE7F28ECD2D49EECD, 0x56C074A581EA17FE, 0x5544F7D774B14AEF, + 0x7B3F0195FC6F290F, 0x12153635B2C0CF57, 0x7F5126DBBA5E0CA7, 0x7A76956C3EAFB413, + 0x3D5774A11D31AB39, 0x8A1B083821F40CB4, 0x7B4A38E32537DF62, 0x950113646D1D6E03, + 0x4DA8979A0041E8A9, 0x3BC36E078F7515D7, 0x5D0A12F27AD310D1, 0x7F9D1A2E1EBE1327, + 0xDA3A361B1C5157B1, 0xDCDD7D20903D0C25, 0x36833336D068F707, 0xCE68341F79893389, + 0xAB9090168DD05F34, 0x43954B3252DC25E5, 0xB438C2B67F98E5E9, 0x10DCD78E3851A492, + 0xDBC27AB5447822BF, 0x9B3CDB65F82CA382, 0xB67B7896167B4C84, 0xBFCED1B0048EAC50, + 0xA9119B60369FFEBD, 0x1FFF7AC80904BF45, 0xAC12FB171817EEE7, 0xAF08DA9177DDA93D, + 0x1B0CAB936E65C744, 0xB559EB1D04E5E932, 0xC37B45B3F8D6F2BA, 0xC3A9DC228CAAC9E9, + 0xF3B8B6675A6507FF, 0x9FC477DE4ED681DA, 0x67378D8ECCEF96CB, 0x6DD856D94D259236, + 0xA319CE15B0B4DB31, 0x073973751F12DD5E, 0x8A8E849EB32781A5, 0xE1925C71285279F5, + 0x74C04BF1790C0EFE, 0x4DDA48153C94938A, 0x9D266D6A1CC0542C, 0x7440FB816508C4FE, + 0x13328503DF48229F, 0xD6BF7BAEE43CAC40, 0x4838D65F6EF6748F, 0x1E152328F3318DEA, + 0x8F8419A348F296BF, 0x72C8834A5957B511, 0xD7A023A73260B45C, 0x94EBC8ABCFB56DAE, + 0x9FC10D0F989993E0, 0xDE68A2355B93CAE6, 0xA44CFE79AE538BBE, 0x9D1D84FCCE371425, + 0x51D2B1AB2DDFB636, 0x2FD7E4B9E72CD38C, 0x65CA5B96B7552210, 0xDD69A0D8AB3B546D, + 0x604D51B25FBF70E2, 0x73AA8A564FB7AC9E, 0x1A8C1E992B941148, 0xAAC40A2703D9BEA0, + 0x764DBEAE7FA4F3A6, 0x1E99B96E70A9BE8B, 0x2C5E9DEB57EF4743, 0x3A938FEE32D29981, + 0x26E6DB8FFDF5ADFE, 0x469356C504EC9F9D, 0xC8763C5B08D1908C, 0x3F6C6AF859D80055, + 0x7F7CC39420A3A545, 0x9BFB227EBDF4C5CE, 0x89039D79D6FC5C5C, 0x8FE88B57305E2AB6, + 0xA09E8C8C35AB96DE, 0xFA7E393983325753, 0xD6B6D0ECC617C699, 0xDFEA21EA9E7557E3, + 0xB67C1FA481680AF8, 0xCA1E3785A9E724E5, 0x1CFC8BED0D681639, 0xD18D8549D140CAEA, + 0x4ED0FE7E9DC91335, 0xE4DBF0634473F5D2, 0x1761F93A44D5AEFE, 0x53898E4C3910DA55, + 0x734DE8181F6EC39A, 0x2680B122BAA28D97, 0x298AF231C85BAFAB, 0x7983EED3740847D5, + 0x66C1A2A1A60CD889, 0x9E17E49642A3E4C1, 0xEDB454E7BADC0805, 0x50B704CAB602C329, + 0x4CC317FB9CDDD023, 0x66B4835D9EAFEA22, 0x219B97E26FFC81BD, 0x261E4E4C0A333A9D, + 0x1FE2CCA76517DB90, 0xD7504DFA8816EDBB, 0xB9571FA04DC089C8, 0x1DDC0325259B27DE, + 0xCF3F4688801EB9AA, 0xF4F5D05C10CAB243, 0x38B6525C21A42B0E, 0x36F60E2BA4FA6800, + 0xEB3593803173E0CE, 0x9C4CD6257C5A3603, 0xAF0C317D32ADAA8A, 0x258E5A80C7204C4B, + 0x8B889D624D44885D, 0xF4D14597E660F855, 0xD4347F66EC8941C3, 0xE699ED85B0DFB40D, + 0x2472F6207C2D0484, 0xC2A1E7B5B459AEB5, 0xAB4F6451CC1D45EC, 0x63767572AE3D6174, + 0xA59E0BD101731A28, 0x116D0016CB948F09, 0x2CF9C8CA052F6E9F, 0x0B090A7560A968E3, + 0xABEEDDB2DDE06FF1, 0x58EFC10B06A2068D, 0xC6E57A78FBD986E0, 0x2EAB8CA63CE802D7, + 0x14A195640116F336, 0x7C0828DD624EC390, 0xD74BBE77E6116AC7, 0x804456AF10F5FB53, + 0xEBE9EA2ADF4321C7, 0x03219A39EE587A30, 0x49787FEF17AF9924, 0xA1E9300CD8520548, + 0x5B45E522E4B1B4EF, 0xB49C3B3995091A36, 0xD4490AD526F14431, 0x12A8F216AF9418C2, + 0x001F837CC7350524, 0x1877B51E57A764D5, 0xA2853B80F17F58EE, 0x993E1DE72D36D310, + 0xB3598080CE64A656, 0x252F59CF0D9F04BB, 0xD23C8E176D113600, 0x1BDA0492E7E4586E, + 0x21E0BD5026C619BF, 0x3B097ADAF088F94E, 0x8D14DEDB30BE846E, 0xF95CFFA23AF5F6F4, + 0x3871700761B3F743, 0xCA672B91E9E4FA16, 0x64C8E531BFF53B55, 0x241260ED4AD1E87D, + 0x106C09B972D2E822, 0x7FBA195410E5CA30, 0x7884D9BC6CB569D8, 0x0647DFEDCD894A29, + 0x63573FF03E224774, 0x4FC8E9560F91B123, 0x1DB956E450275779, 0xB8D91274B9E9D4FB, + 0xA2EBEE47E2FBFCE1, 0xD9F1F30CCD97FB09, 0xEFED53D75FD64E6B, 0x2E6D02C36017F67F, + 0xA9AA4D20DB084E9B, 0xB64BE8D8B25396C1, 0x70CB6AF7C2D5BCF0, 0x98F076A4F7A2322E, + 0xBF84470805E69B5F, 0x94C3251F06F90CF3, 0x3E003E616A6591E9, 0xB925A6CD0421AFF3, + 0x61BDD1307C66E300, 0xBF8D5108E27E0D48, 0x240AB57A8B888B20, 0xFC87614BAF287E07, + 0xEF02CDD06FFDB432, 0xA1082C0466DF6C0A, 0x8215E577001332C8, 0xD39BB9C3A48DB6CF, + 0x2738259634305C14, 0x61CF4F94C97DF93D, 0x1B6BACA2AE4E125B, 0x758F450C88572E0B, + 0x959F587D507A8359, 0xB063E962E045F54D, 0x60E8ED72C0DFF5D1, 0x7B64978555326F9F, + 0xFD080D236DA814BA, 0x8C90FD9B083F4558, 0x106F72FE81E2C590, 0x7976033A39F7D952, + 0xA4EC0132764CA04B, 0x733EA705FAE4FA77, 0xB4D8F77BC3E56167, 0x9E21F4F903B33FD9, + 0x9D765E419FB69F6D, 0xD30C088BA61EA5EF, 0x5D94337FBFAF7F5B, 0x1A4E4822EB4D7A59, + 0x6FFE73E81B637FB3, 0xDDF957BC36D8B9CA, 0x64D0E29EEA8838B3, 0x08DD9BDFD96B9F63, + 0x087E79E5A57D1D13, 0xE328E230E3E2B3FB, 0x1C2559E30F0946BE, 0x720BF5F26F4D2EAA, + 0xB0774D261CC609DB, 0x443F64EC5A371195, 0x4112CF68649A260E, 0xD813F2FAB7F5C5CA, + 0x660D3257380841EE, 0x59AC2C7873F910A3, 0xE846963877671A17, 0x93B633ABFA3469F8, + 0xC0C0F5A60EF4CDCF, 0xCAF21ECD4377B28C, 0x57277707199B8175, 0x506C11B9D90E8B1D, + 0xD83CC2687A19255F, 0x4A29C6465A314CD1, 0xED2DF21216235097, 0xB5635C95FF7296E2, + 0x22AF003AB672E811, 0x52E762596BF68235, 0x9AEBA33AC6ECC6B0, 0x944F6DE09134DFB6, + 0x6C47BEC883A7DE39, 0x6AD047C430A12104, 0xA5B1CFDBA0AB4067, 0x7C45D833AFF07862, + 0x5092EF950A16DA0B, 0x9338E69C052B8E7B, 0x455A4B4CFE30E3F5, 0x6B02E63195AD0CF8, + 0x6B17B224BAD6BF27, 0xD1E0CCD25BB9C169, 0xDE0C89A556B9AE70, 0x50065E535A213CF6, + 0x9C1169FA2777B874, 0x78EDEFD694AF1EED, 0x6DC93D9526A50E68, 0xEE97F453F06791ED, + 0x32AB0EDB696703D3, 0x3A6853C7E70757A7, 0x31865CED6120F37D, 0x67FEF95D92607890, + 0x1F2B1D1F15F6DC9C, 0xB69E38A8965C6B65, 0xAA9119FF184CCCF4, 0xF43C732873F24C13, + 0xFB4A3D794A9A80D2, 0x3550C2321FD6109C, 0x371F77E76BB8417E, 0x6BFA9AAE5EC05779, + 0xCD04F3FF001A4778, 0xE3273522064480CA, 0x9F91508BFFCFC14A, 0x049A7F41061A9E60, + 0xFCB6BE43A9F2FE9B, 0x08DE8A1C7797DA9B, 0x8F9887E6078735A1, 0xB5B4071DBFC73A66, + 0x230E343DFBA08D33, 0x43ED7F5A0FAE657D, 0x3A88A0FBBCB05C63, 0x21874B8B4D2DBC4F, + 0x1BDEA12E35F6A8C9, 0x53C065C6C8E63528, 0xE34A1D250E7A8D6B, 0xD6B04D3B7651DD7E, + 0x5E90277E7CB39E2D, 0x2C046F22062DC67D, 0xB10BB459132D0A26, 0x3FA9DDFB67E2F199, + 0x0E09B88E1914F7AF, 0x10E8B35AF3EEAB37, 0x9EEDECA8E272B933, 0xD4C718BC4AE8AE5F, + 0x81536D601170FC20, 0x91B534F885818A06, 0xEC8177F83F900978, 0x190E714FADA5156E, + 0xB592BF39B0364963, 0x89C350C893AE7DC1, 0xAC042E70F8B383F2, 0xB49B52E587A1EE60, + 0xFB152FE3FF26DA89, 0x3E666E6F69AE2C15, 0x3B544EBE544C19F9, 0xE805A1E290CF2456, + 0x24B33C9D7ED25117, 0xE74733427B72F0C1, 0x0A804D18B7097475, 0x57E3306D881EDB4F, + 0x4AE7D6A36EB5DBCB, 0x2D8D5432157064C8, 0xD1E649DE1E7F268B, 0x8A328A1CEDFE552C, + 0x07A3AEC79624C7DA, 0x84547DDC3E203C94, 0x990A98FD5071D263, 0x1A4FF12616EEFC89, + 0xF6F7FD1431714200, 0x30C05B1BA332F41C, 0x8D2636B81555A786, 0x46C9FEB55D120902, + 0xCCEC0A73B49C9921, 0x4E9D2827355FC492, 0x19EBB029435DCB0F, 0x4659D2B743848A2C, + 0x963EF2C96B33BE31, 0x74F85198B05A2E7D, 0x5A0F544DD2B1FB18, 0x03727073C2E134B1, + 0xC7F6AA2DE59AEA61, 0x352787BAA0D7C22F, 0x9853EAB63B5E0B35, 0xABBDCDD7ED5C0860, + 0xCF05DAF5AC8D77B0, 0x49CAD48CEBF4A71E, 0x7A4C10EC2158C4A6, 0xD9E92AA246BF719E, + 0x13AE978D09FE5557, 0x730499AF921549FF, 0x4E4B705B92903BA4, 0xFF577222C14F0A3A, + 0x55B6344CF97AAFAE, 0xB862225B055B6960, 0xCAC09AFBDDD2CDB4, 0xDAF8E9829FE96B5F, + 0xB5FDFC5D3132C498, 0x310CB380DB6F7503, 0xE87FBB46217A360E, 0x2102AE466EBB1148, + 0xF8549E1A3AA5E00D, 0x07A69AFDCC42261A, 0xC4C118BFE78FEAAE, 0xF9F4892ED96BD438, + 0x1AF3DBE25D8F45DA, 0xF5B4B0B0D2DEEEB4, 0x962ACEEFA82E1C84, 0x046E3ECAAF453CE9, + 0xF05D129681949A4C, 0x964781CE734B3C84, 0x9C2ED44081CE5FBD, 0x522E23F3925E319E, + 0x177E00F9FC32F791, 0x2BC60A63A6F3B3F2, 0x222BBFAE61725606, 0x486289DDCC3D6780, + 0x7DC7785B8EFDFC80, 0x8AF38731C02BA980, 0x1FAB64EA29A2DDF7, 0xE4D9429322CD065A, + 0x9DA058C67844F20C, 0x24C0E332B70019B0, 0x233003B5A6CFE6AD, 0xD586BD01C5C217F6, + 0x5E5637885F29BC2B, 0x7EBA726D8C94094B, 0x0A56A5F0BFE39272, 0xD79476A84EE20D06, + 0x9E4C1269BAA4BF37, 0x17EFEE45B0DEE640, 0x1D95B0A5FCF90BC6, 0x93CBE0B699C2585D, + 0x65FA4F227A2B6D79, 0xD5F9E858292504D5, 0xC2B5A03F71471A6F, 0x59300222B4561E00, + 0xCE2F8642CA0712DC, 0x7CA9723FBB2E8988, 0x2785338347F2BA08, 0xC61BB3A141E50E8C, + 0x150F361DAB9DEC26, 0x9F6A419D382595F4, 0x64A53DC924FE7AC9, 0x142DE49FFF7A7C3D, + 0x0C335248857FA9E7, 0x0A9C32D5EAE45305, 0xE6C42178C4BBB92E, 0x71F1CE2490D20B07, + 0xF1BCC3D275AFE51A, 0xE728E8C83C334074, 0x96FBF83A12884624, 0x81A1549FD6573DA5, + 0x5FA7867CAF35E149, 0x56986E2EF3ED091B, 0x917F1DD5F8886C61, 0xD20D8C88C8FFE65F, + 0x31D71DCE64B2C310, 0xF165B587DF898190, 0xA57E6339DD2CF3A0, 0x1EF6E6DBB1961EC9, + 0x70CC73D90BC26E24, 0xE21A6B35DF0C3AD7, 0x003A93D8B2806962, 0x1C99DED33CB890A1, + 0xCF3145DE0ADD4289, 0xD0E4427A5514FB72, 0x77C621CC9FB3A483, 0x67A34DAC4356550B, + 0xF8D626AAAF278509, + }; + } +} \ No newline at end of file diff --git a/Cosette.Arbiter/Cosette.Arbiter.csproj b/Cosette.Arbiter/Cosette.Arbiter.csproj new file mode 100644 index 0000000..aedbf75 --- /dev/null +++ b/Cosette.Arbiter/Cosette.Arbiter.csproj @@ -0,0 +1,29 @@ + + + + Exe + netcoreapp3.1 + + + + true + + + + true + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/Cosette.Arbiter/Engine/BestMoveData.cs b/Cosette.Arbiter/Engine/BestMoveData.cs new file mode 100644 index 0000000..c2ee9ca --- /dev/null +++ b/Cosette.Arbiter/Engine/BestMoveData.cs @@ -0,0 +1,8 @@ +namespace Cosette.Arbiter.Engine +{ + public class BestMoveData + { + public string BestMove { get; set; } + public InfoData LastInfoData { get; set; } + } +} diff --git a/Cosette.Arbiter/Engine/EngineOperator.cs b/Cosette.Arbiter/Engine/EngineOperator.cs new file mode 100644 index 0000000..291bec0 --- /dev/null +++ b/Cosette.Arbiter/Engine/EngineOperator.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Cosette.Arbiter.Settings; + +namespace Cosette.Arbiter.Engine +{ + public class EngineOperator + { + private string _name; + private string _enginePath; + private Process _engineProcess; + + public EngineOperator(string name, string path) + { + _name = name; + _enginePath = path; + } + + public void Init() + { + _engineProcess = Process.Start(new ProcessStartInfo + { + FileName = _enginePath, + CreateNoWindow = true, + RedirectStandardInput = true, + RedirectStandardOutput = true + }); + + Write("uci"); + WaitForMessage("uciok"); + + SettingsLoader.Data.Options.ForEach(Write); + + Write("isready"); + WaitForMessage("readyok"); + } + + public void InitNewGame() + { + Write("ucinewgame"); + Write("isready"); + WaitForMessage("readyok"); + } + + public BestMoveData Go(List moves) + { + var bestMoveData = new BestMoveData(); + var movesJoined = string.Join(' ', moves); + + if (moves.Count > 0) + { + Write($"position startpos moves {movesJoined}"); + } + + Write($"go movetime {SettingsLoader.Data.MillisecondsPerMove}"); + + while (true) + { + try + { + var response = Read(); + if (response.StartsWith("info depth")) + { + bestMoveData.LastInfoData = InfoData.FromString(response); + } + else if (response.StartsWith("bestmove")) + { + bestMoveData.BestMove = response.Split(' ')[1]; + break; + } + } + catch + { + Init(); + return null; + } + } + + return bestMoveData; + } + + public void Write(string message) + { + _engineProcess.StandardInput.WriteLine(message); + } + + public string Read() + { + return _engineProcess.StandardOutput.ReadLine(); + } + + public void WaitForMessage(string message) + { + while (Read() != message) ; + } + } +} diff --git a/Cosette.Arbiter/Engine/InfoData.cs b/Cosette.Arbiter/Engine/InfoData.cs new file mode 100644 index 0000000..5f2e677 --- /dev/null +++ b/Cosette.Arbiter/Engine/InfoData.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace Cosette.Arbiter.Engine +{ + public class InfoData + { + public int Depth { get; set; } + public int SelDepth { get; set; } + public int Time { get; set; } + public int ScoreCp { get; set; } + public int ScoreMate { get; set; } + public ulong Nodes { get; set; } + public ulong Nps { get; set; } + + public static InfoData FromString(string data) + { + var splitData = data.Split(' '); + return new InfoData + { + Depth = GetValue("depth", splitData), + SelDepth = GetValue("seldepth", splitData), + Time = GetValue("time", splitData), + ScoreCp = GetValue("cp", splitData), + ScoreMate = GetValue("mate", splitData), + Nodes = GetValue("nodes", splitData), + Nps = GetValue("nps", splitData), + }; + } + + private static T GetValue(string name, string[] splitData) + { + for (var i = 0; i < splitData.Length; i++) + { + if (splitData[i] == name) + { + return (T)Convert.ChangeType(splitData[i + 1], typeof(T)); + } + } + + return default; + } + } +} diff --git a/Cosette.Arbiter/Program.cs b/Cosette.Arbiter/Program.cs new file mode 100644 index 0000000..32317e0 --- /dev/null +++ b/Cosette.Arbiter/Program.cs @@ -0,0 +1,14 @@ +using Cosette.Arbiter.Settings; +using Cosette.Arbiter.Tournament; + +namespace Cosette.Arbiter +{ + class Program + { + static void Main(string[] args) + { + SettingsLoader.Init("settings.json"); + new TournamentArbiter().Run(); + } + } +} diff --git a/Cosette.Arbiter/Settings/EngineData.cs b/Cosette.Arbiter/Settings/EngineData.cs new file mode 100644 index 0000000..be15300 --- /dev/null +++ b/Cosette.Arbiter/Settings/EngineData.cs @@ -0,0 +1,9 @@ +namespace Cosette.Arbiter.Settings +{ + public class EngineData + { + public string Name { get; set; } + public string Path { get; set; } + public int Rating { get; set; } + } +} diff --git a/Cosette.Arbiter/Settings/SettingsLoader.cs b/Cosette.Arbiter/Settings/SettingsLoader.cs new file mode 100644 index 0000000..5dfe43a --- /dev/null +++ b/Cosette.Arbiter/Settings/SettingsLoader.cs @@ -0,0 +1,19 @@ +using System.IO; +using Newtonsoft.Json; + +namespace Cosette.Arbiter.Settings +{ + public static class SettingsLoader + { + public static SettingsModel Data { get; set; } + + public static void Init(string settingsPath) + { + using (var streamReader = new StreamReader(settingsPath)) + { + var content = streamReader.ReadToEnd(); + Data = JsonConvert.DeserializeObject(content); + } + } + } +} diff --git a/Cosette.Arbiter/Settings/SettingsModel.cs b/Cosette.Arbiter/Settings/SettingsModel.cs new file mode 100644 index 0000000..16668bc --- /dev/null +++ b/Cosette.Arbiter/Settings/SettingsModel.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Cosette.Arbiter.Settings +{ + public class SettingsModel + { + public List Engines { get; set; } + public List Options { get; set; } + + [JsonProperty("polyglot_opening_book")] + public string PolyglotOpeningBook { get; set; } + + [JsonProperty("polyglot_max_moves")] + public int PolyglotMaxMoves { get; set; } + + [JsonProperty("milliseconds_per_move")] + public int MillisecondsPerMove { get; set; } + + [JsonProperty("max_moves_count")] + public int MaxMovesCount { get; set; } + + [JsonProperty("games_count")] + public int GamesCount { get; set; } + } +} diff --git a/Cosette.Arbiter/Tournament/ArchivedGame.cs b/Cosette.Arbiter/Tournament/ArchivedGame.cs new file mode 100644 index 0000000..da20c27 --- /dev/null +++ b/Cosette.Arbiter/Tournament/ArchivedGame.cs @@ -0,0 +1,16 @@ +namespace Cosette.Arbiter.Tournament +{ + public class ArchivedGame + { + public GameData GameData { get; } + public TournamentParticipant Opponent { get; } + public GameResult Result { get; } + + public ArchivedGame(GameData gameData, TournamentParticipant opponent, GameResult result) + { + GameData = gameData; + Opponent = opponent; + Result = result; + } + } +} diff --git a/Cosette.Arbiter/Tournament/Color.cs b/Cosette.Arbiter/Tournament/Color.cs new file mode 100644 index 0000000..24fb14f --- /dev/null +++ b/Cosette.Arbiter/Tournament/Color.cs @@ -0,0 +1,9 @@ +namespace Cosette.Arbiter.Tournament +{ + public enum Color + { + None = 0, + White = 1, + Black = -1 + } +} diff --git a/Cosette.Arbiter/Tournament/GameData.cs b/Cosette.Arbiter/Tournament/GameData.cs new file mode 100644 index 0000000..ac1ca9a --- /dev/null +++ b/Cosette.Arbiter/Tournament/GameData.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using Cosette.Arbiter.Engine; +using Cosette.Arbiter.Settings; + +namespace Cosette.Arbiter.Tournament +{ + public class GameData + { + public List HalfMovesDone { get; set; } + public bool GameIsDone { get; private set; } + public Color Winner { get; private set; } + + private BestMoveData _lastBestMove; + private Color _colorToMove; + + public GameData(List opening) + { + HalfMovesDone = opening; + _colorToMove = Color.White; + } + + public void MakeMove(BestMoveData bestMoveData) + { + HalfMovesDone.Add(bestMoveData.BestMove); + _lastBestMove = bestMoveData; + + if (IsCheckmate() || bestMoveData.LastInfoData.ScoreCp >= 2000) + { + GameIsDone = true; + Winner = _colorToMove; + return; + } + + if (IsDraw()) + { + GameIsDone = true; + Winner = Color.None; + return; + } + + _colorToMove = _colorToMove == Color.White ? Color.Black : Color.White; + } + + public bool IsCheckmate() + { + return _lastBestMove != null && _lastBestMove.LastInfoData.ScoreMate == 1; + } + + public bool IsDraw() + { + if (HalfMovesDone.Count > SettingsLoader.Data.MaxMovesCount * 2) + { + return true; + } + + if (HalfMovesDone.Count > 8) + { + return HalfMovesDone[^1] == HalfMovesDone[^5] && HalfMovesDone[^5] == HalfMovesDone[^9]; + } + + return false; + } + } +} diff --git a/Cosette.Arbiter/Tournament/GameResult.cs b/Cosette.Arbiter/Tournament/GameResult.cs new file mode 100644 index 0000000..bc2c032 --- /dev/null +++ b/Cosette.Arbiter/Tournament/GameResult.cs @@ -0,0 +1,9 @@ +namespace Cosette.Arbiter.Tournament +{ + public enum GameResult + { + Draw, + Win, + Loss + } +} diff --git a/Cosette.Arbiter/Tournament/TournamentArbiter.cs b/Cosette.Arbiter/Tournament/TournamentArbiter.cs new file mode 100644 index 0000000..fbed0b2 --- /dev/null +++ b/Cosette.Arbiter/Tournament/TournamentArbiter.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using Cosette.Arbiter.Book; +using Cosette.Arbiter.Engine; +using Cosette.Arbiter.Settings; + +namespace Cosette.Arbiter.Tournament +{ + public class TournamentArbiter + { + private List _participants; + private TournamentScheduler _scheduler; + private PolyglotBook _polyglotBook; + private int _errors; + + public TournamentArbiter() + { + _participants = new List(); + _scheduler = new TournamentScheduler(); + _polyglotBook = new PolyglotBook(); + + foreach (var engineData in SettingsLoader.Data.Engines) + { + var engineOperator = new EngineOperator(engineData.Name, engineData.Path); + var tournamentParticipant = new TournamentParticipant(engineData, engineOperator); + + _participants.Add(tournamentParticipant); + } + + _scheduler.Init(_participants.Count); + } + + public void Run() + { + _participants.ForEach(p => p.EngineOperator.Init()); + for (var gameIndex = 0; gameIndex < SettingsLoader.Data.GamesCount; gameIndex++) + { + var gameData = new GameData(_polyglotBook.GetRandomOpening()); + var (playerA, playerB) = _scheduler.GetPair(gameIndex); + + if (playerA >= _participants.Count || playerB >= _participants.Count) + { + continue; + } + + var participantA = _participants[playerA]; + var participantB = _participants[playerB]; + + var whitePlayer = DateTime.UtcNow.Ticks % 2 == 0 ? participantA : participantB; + var blackPlayer = whitePlayer == participantA ? participantB : participantA; + var (playerToMove, opponent) = (whitePlayer, blackPlayer); + + Console.Clear(); + WriteResults(); + + Console.WriteLine($"Game {gameIndex} ({whitePlayer.EngineData.Name} vs. {blackPlayer.EngineData.Name})"); + Console.Write("Moves: "); + Console.Write(string.Join(' ', gameData.HalfMovesDone)); + Console.Write(" "); + + participantA.EngineOperator.InitNewGame(); + participantB.EngineOperator.InitNewGame(); + + + while (true) + { + var bestMoveData = playerToMove.EngineOperator.Go(gameData.HalfMovesDone); + if (bestMoveData == null) + { + _errors++; + break; + } + + playerToMove.Logs.Add(bestMoveData.LastInfoData); + gameData.MakeMove(bestMoveData); + + Console.Write(bestMoveData.BestMove); + Console.Write(" "); + + if (gameData.GameIsDone) + { + if (gameData.Winner == Color.None) + { + playerToMove.History.Add(new ArchivedGame(gameData, opponent, GameResult.Draw)); + opponent.History.Add(new ArchivedGame(gameData, playerToMove, GameResult.Draw)); + } + else + { + playerToMove.History.Add(new ArchivedGame(gameData, opponent, GameResult.Win)); + opponent.History.Add(new ArchivedGame(gameData, playerToMove, GameResult.Loss)); + } + + break; + } + + (playerToMove, opponent) = (opponent, playerToMove); + } + } + } + + private void WriteResults() + { + foreach (var participant in _participants) + { + var originalRating = participant.EngineData.Rating; + var performance = participant.CalculatePerformanceRating() - originalRating; + var wonGamesPercent = participant.WonGamesPercent(); + + Console.WriteLine($"{participant.EngineData.Name} {originalRating} ELO ({performance:+0;-#}, {wonGamesPercent}%): " + + $"{participant.Wins} wins, {participant.Losses} losses, " + + $"{participant.Draws} draws, {_errors} errors"); + Console.WriteLine($" === {participant.AverageDepth:F1} average depth, {participant.AverageNodesCount} average nodes, " + + $"{participant.AverageNps} average nodes per second"); + Console.WriteLine(); + } + } + } +} diff --git a/Cosette.Arbiter/Tournament/TournamentParticipant.cs b/Cosette.Arbiter/Tournament/TournamentParticipant.cs new file mode 100644 index 0000000..89a8caa --- /dev/null +++ b/Cosette.Arbiter/Tournament/TournamentParticipant.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Linq; +using Cosette.Arbiter.Engine; +using Cosette.Arbiter.Settings; + +namespace Cosette.Arbiter.Tournament +{ + public class TournamentParticipant + { + public EngineData EngineData { get; } + public EngineOperator EngineOperator { get; } + public List History { get; } + public List Logs { get; } + + public int Wins => History.Count(p => p.Result == GameResult.Win); + public int Losses => History.Count(p => p.Result == GameResult.Loss); + public int Draws => History.Count(p => p.Result == GameResult.Draw); + public double AverageDepth => Logs.Count == 0 ? 0 : Logs.Average(p => p.Depth); + public int AverageNodesCount => Logs.Count == 0 ? 0 : (int)Logs.Average(p => (int)p.Nodes); + public int AverageNps => Logs.Count == 0 ? 0 : (int)Logs.Average(p => (int)p.Nps); + + public TournamentParticipant(EngineData engineData, EngineOperator engineOperator) + { + EngineData = engineData; + EngineOperator = engineOperator; + History = new List(); + Logs = new List(); + } + + public int CalculatePerformanceRating() + { + if (History.Count == 0) + { + return EngineData.Rating; + } + + return (History.Sum(p => p.Opponent.EngineData.Rating) + 400 * (Wins - Losses)) / History.Count; + } + + public int WonGamesPercent() + { + if (History.Count == 0 || History.Count == Draws) + { + return 0; + } + + return (int)((float)Wins * 100 / (Wins + Losses)); + } + } +} diff --git a/Cosette.Arbiter/Tournament/TournamentScheduler.cs b/Cosette.Arbiter/Tournament/TournamentScheduler.cs new file mode 100644 index 0000000..3deb249 --- /dev/null +++ b/Cosette.Arbiter/Tournament/TournamentScheduler.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; + +namespace Cosette.Arbiter.Tournament +{ + public class TournamentScheduler + { + private int _participantsCount; + private List<(int playerA, int playerB)> _pairs; + + public void Init(int participantsCount) + { + _participantsCount = participantsCount; + _pairs = new List<(int playerA, int playerB)>(); + + if (_participantsCount % 2 != 0) + { + _participantsCount++; + } + + for (var gameNumber = 0; gameNumber < _participantsCount - 1; gameNumber++) + { + var firstRow = new List(); + var secondRow = new List(); + var teamsCount = _participantsCount / 2; + + firstRow.Add(1); + + var secondParticipant = _participantsCount - gameNumber; + var currentParticipant = secondParticipant; + + for (var i = 1; i < teamsCount; i++) + { + currentParticipant++; + if (currentParticipant > _participantsCount) + { + currentParticipant = 2; + } + + firstRow.Add(currentParticipant); + } + + for (var i = 0; i < teamsCount; i++) + { + currentParticipant++; + if (currentParticipant > _participantsCount) + { + currentParticipant = 2; + } + + secondRow.Add(currentParticipant); + } + + secondRow.Reverse(); + + for (var i = 0; i < teamsCount; i++) + { + _pairs.Add((firstRow[i] - 1, secondRow[i] - 1)); + } + } + } + + public (int playerA, int playerB) GetPair(int gameNumber) + { + var pairIndex = gameNumber % _pairs.Count; + return _pairs[pairIndex]; + } + } +} diff --git a/Cosette.Arbiter/book.bin b/Cosette.Arbiter/book.bin new file mode 100644 index 0000000..9670b17 Binary files /dev/null and b/Cosette.Arbiter/book.bin differ diff --git a/Cosette.Tests/AdvancedPerftTests.cs b/Cosette.Tests/AdvancedPerftTests.cs index 60c6df9..db8f4a0 100644 --- a/Cosette.Tests/AdvancedPerftTests.cs +++ b/Cosette.Tests/AdvancedPerftTests.cs @@ -1,6 +1,4 @@ -using System; -using Cosette.Engine.Board; -using Cosette.Engine.Common; +using Cosette.Engine.Board; using Cosette.Engine.Moves.Magic; using Cosette.Engine.Perft; using Xunit; diff --git a/Cosette.Tests/Cosette.Tests.csproj b/Cosette.Tests/Cosette.Tests.csproj index bafcacf..36db1c9 100644 --- a/Cosette.Tests/Cosette.Tests.csproj +++ b/Cosette.Tests/Cosette.Tests.csproj @@ -1,11 +1,13 @@ - net5.0 + netcoreapp3.1 false Debug;Release;BMI;CCRL + + x64 diff --git a/Cosette.Tests/DividedPerftTests.cs b/Cosette.Tests/DividedPerftTests.cs index 5255260..de198d8 100644 --- a/Cosette.Tests/DividedPerftTests.cs +++ b/Cosette.Tests/DividedPerftTests.cs @@ -1,6 +1,4 @@ -using System; -using Cosette.Engine.Board; -using Cosette.Engine.Common; +using Cosette.Engine.Board; using Cosette.Engine.Moves.Magic; using Cosette.Engine.Perft; using Xunit; diff --git a/Cosette.Tests/FenTests.cs b/Cosette.Tests/FenTests.cs new file mode 100644 index 0000000..ca523f2 --- /dev/null +++ b/Cosette.Tests/FenTests.cs @@ -0,0 +1,23 @@ +using Cosette.Engine.Board; +using Cosette.Engine.Fen; +using Cosette.Engine.Moves.Magic; +using Cosette.Engine.Perft; +using Xunit; + +namespace Cosette.Tests +{ + public class FenTests + { + [Theory] + [InlineData("5r2/2P4p/P5Pp/1b6/P1r1N2N/1pp4K/P7/5k2 w - - 0 10")] + [InlineData("3K2N1/k7/p1p3qB/3p1R1Q/6P1/1pP4r/P6r/8 w KQ - 20 25")] + [InlineData("1b6/1kB5/4p3/KB1p1P2/2Pp1NNP/5p2/1p1P4/8 w kq - 40 40")] + [InlineData("4kb1K/4P3/Rp5p/2P5/8/rr1p1N2/P5P1/2q3n1 w Kk e3 70 1")] + [InlineData("r5R1/8/2Bp3p/2kq4/2N1p3/2KPP3/2pRP1P1/8 w - h3 0 1")] + public void DividedPerft_DefaultBoard(string fen) + { + var boardFromFen = FenToBoard.Parse(fen); + Assert.Equal(fen, boardFromFen.ToString()); + } + } +} \ No newline at end of file diff --git a/Cosette.Tests/SimplePerftTests.cs b/Cosette.Tests/SimplePerftTests.cs index 203aaee..957b3c5 100644 --- a/Cosette.Tests/SimplePerftTests.cs +++ b/Cosette.Tests/SimplePerftTests.cs @@ -1,6 +1,4 @@ -using System; using Cosette.Engine.Board; -using Cosette.Engine.Common; using Cosette.Engine.Fen; using Cosette.Engine.Moves.Magic; using Cosette.Engine.Perft; @@ -42,7 +40,7 @@ public void SimplePerft_DefaultBoard(int depth, ulong expectedLeafsCount) [InlineData(5, 120955130)] public void SimplePerft_MidGameBoard(int depth, ulong expectedLeafsCount) { - var boardState = FenParser.Parse("r2qr1k1/p2n1p2/1pb3pp/2ppN1P1/1R1PpP2/BQP1n1PB/P4N1P/1R4K1 w - - 0 21", out _); + var boardState = FenToBoard.Parse("r2qr1k1/p2n1p2/1pb3pp/2ppN1P1/1R1PpP2/BQP1n1PB/P4N1P/1R4K1 w - - 0 21"); var result = SimplePerft.Run(boardState, depth); Assert.Equal(expectedLeafsCount, result.LeafsCount); @@ -58,7 +56,7 @@ public void SimplePerft_MidGameBoard(int depth, ulong expectedLeafsCount) [InlineData(6, 108146453)] public void SimplePerft_EndGameBoard(int depth, ulong expectedLeafsCount) { - var boardState = FenParser.Parse("7r/8/2k3P1/1p1p2Kp/1P6/2P5/7r/Q7 w - - 0 1", out _); + var boardState = FenToBoard.Parse("7r/8/2k3P1/1p1p2Kp/1P6/2P5/7r/Q7 w - - 0 1"); var result = SimplePerft.Run(boardState, depth); Assert.Equal(expectedLeafsCount, result.LeafsCount); diff --git a/Cosette.Tests/StaticExchangeEvaluationTests.cs b/Cosette.Tests/StaticExchangeEvaluationTests.cs new file mode 100644 index 0000000..3b5a226 --- /dev/null +++ b/Cosette.Tests/StaticExchangeEvaluationTests.cs @@ -0,0 +1,29 @@ +using Cosette.Engine.Ai.Ordering; +using Cosette.Engine.Common; +using Xunit; + +namespace Cosette.Tests +{ + public class StaticExchangeEvaluationTests + { + public StaticExchangeEvaluationTests() + { + StaticExchangeEvaluation.Init(); + } + + [Theory] + [InlineData(Piece.Pawn, Piece.Pawn, (1 << Piece.Pawn), 0, 100)] // 8/8/8/3p4/4P3/8/8/8 w - - 0 1 + [InlineData(Piece.Pawn, Piece.Pawn, (1 << Piece.Pawn) | (1 << Piece.Knight), (1 << Piece.Pawn) | (1 << Piece.Knight), 0)] // 8/2n5/4p3/3p4/4P3/4N3/8/8 w - - 0 1 + [InlineData(Piece.Knight, Piece.Pawn, (1 << Piece.Pawn) | (1 << Piece.Knight), (1 << Piece.Pawn) | (1 << Piece.Knight), -220)] // 8/2n5/4p3/3p4/4P3/4N3/8/8 w - - 0 1 + [InlineData(Piece.Knight, Piece.Pawn, (1 << Piece.Pawn) | (1 << Piece.Knight) | (1 << Piece.Queen), (1 << Piece.Pawn) | (1 << Piece.Knight), -120)] // 8/2n5/4p3/3p4/4P3/4N3/Q7/8 w - - 0 1 + [InlineData(Piece.Pawn, Piece.Pawn, (1 << Piece.Knight) | (1 << Piece.Bishop), (1 << Piece.Queen), 100)] // 3q4/8/8/3p4/8/2N2B2/8/8 w - - 0 1 + [InlineData(Piece.Knight, Piece.Pawn, (1 << Piece.Knight) | (1 << Piece.Bishop), (1 << Piece.Queen), 100)] // 3q4/8/8/3p4/8/2N2B2/8/8 w - - 0 1 + [InlineData(Piece.Queen, Piece.Pawn, (1 << Piece.Knight) | (1 << Piece.Bishop) | (1 << Piece.Queen), (1 << Piece.Rook), -500)] // 3r4/8/8/3p4/8/2N2B2/8/3Q4 w - - 0 1 + [InlineData(Piece.Knight, Piece.Pawn, (1 << Piece.Knight) | (1 << Piece.Rook) | (1 << Piece.Queen), (1 << Piece.Knight) | (1 << Piece.Bishop) | (1 << Piece.Queen), -220)] // 7q/3n4/5b2/4p3/8/3N4/4R3/4Q3 w - - 0 1 + public void StaticExchangeEvaluation_NormalValues(int attackingPiece, int capturedPiece, int attacker, int defender, int expectedScore) + { + var score = StaticExchangeEvaluation.Evaluate(attackingPiece, capturedPiece, attacker, defender); + Assert.Equal(expectedScore, score); + } + } +} \ No newline at end of file diff --git a/Cosette.Tests/VerificationPerftTests.cs b/Cosette.Tests/VerificationPerftTests.cs index a2b3632..77963d4 100644 --- a/Cosette.Tests/VerificationPerftTests.cs +++ b/Cosette.Tests/VerificationPerftTests.cs @@ -1,6 +1,4 @@ -using System; -using Cosette.Engine.Board; -using Cosette.Engine.Common; +using Cosette.Engine.Board; using Cosette.Engine.Fen; using Cosette.Engine.Moves.Magic; using Cosette.Engine.Perft; @@ -28,7 +26,7 @@ public void VerificationPerft_DefaultBoard() [Fact] public void VerificationPerft_MidGameBoard() { - var boardState = FenParser.Parse("r2qr1k1/p2n1p2/1pb3pp/2ppN1P1/1R1PpP2/BQP1n1PB/P4N1P/1R4K1 w - - 0 21", out _); + var boardState = FenToBoard.Parse("r2qr1k1/p2n1p2/1pb3pp/2ppN1P1/1R1PpP2/BQP1n1PB/P4N1P/1R4K1 w - - 0 21"); var result = VerificationPerft.Run(boardState, 5); Assert.True(result.VerificationSuccess); @@ -37,7 +35,7 @@ public void VerificationPerft_MidGameBoard() [Fact] public void VerificationPerft_EndGameBoard() { - var boardState = FenParser.Parse("7r/8/2k3P1/1p1p2Kp/1P6/2P5/7r/Q7 w - - 0 1", out _); + var boardState = FenToBoard.Parse("7r/8/2k3P1/1p1p2Kp/1P6/2P5/7r/Q7 w - - 0 1"); var result = VerificationPerft.Run(boardState, 6); Assert.True(result.VerificationSuccess); diff --git a/Cosette.sln b/Cosette.sln index ca4daf2..8c97576 100644 --- a/Cosette.sln +++ b/Cosette.sln @@ -15,30 +15,82 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cosette.Arbiter", "Cosette.Arbiter\Cosette.Arbiter.csproj", "{DAB140A1-17EB-4688-8107-8C2AEA737AD6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cosette.Arbiter.Tests", "Cosette.Arbiter.Tests\Cosette.Arbiter.Tests.csproj", "{446DB14C-F30C-4A0C-86AB-8CCF3CF7CB53}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution BMI|Any CPU = BMI|Any CPU + BMI|x64 = BMI|x64 CCRL|Any CPU = CCRL|Any CPU + CCRL|x64 = CCRL|x64 Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {B2BA9BD6-E1C2-437E-986D-A6C08568D770}.BMI|Any CPU.ActiveCfg = BMI|Any CPU {B2BA9BD6-E1C2-437E-986D-A6C08568D770}.BMI|Any CPU.Build.0 = BMI|Any CPU + {B2BA9BD6-E1C2-437E-986D-A6C08568D770}.BMI|x64.ActiveCfg = BMI|x64 + {B2BA9BD6-E1C2-437E-986D-A6C08568D770}.BMI|x64.Build.0 = BMI|x64 {B2BA9BD6-E1C2-437E-986D-A6C08568D770}.CCRL|Any CPU.ActiveCfg = CCRL|Any CPU {B2BA9BD6-E1C2-437E-986D-A6C08568D770}.CCRL|Any CPU.Build.0 = CCRL|Any CPU + {B2BA9BD6-E1C2-437E-986D-A6C08568D770}.CCRL|x64.ActiveCfg = CCRL|x64 + {B2BA9BD6-E1C2-437E-986D-A6C08568D770}.CCRL|x64.Build.0 = CCRL|x64 {B2BA9BD6-E1C2-437E-986D-A6C08568D770}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B2BA9BD6-E1C2-437E-986D-A6C08568D770}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2BA9BD6-E1C2-437E-986D-A6C08568D770}.Debug|x64.ActiveCfg = Debug|x64 + {B2BA9BD6-E1C2-437E-986D-A6C08568D770}.Debug|x64.Build.0 = Debug|x64 {B2BA9BD6-E1C2-437E-986D-A6C08568D770}.Release|Any CPU.ActiveCfg = Release|Any CPU {B2BA9BD6-E1C2-437E-986D-A6C08568D770}.Release|Any CPU.Build.0 = Release|Any CPU - {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.BMI|Any CPU.ActiveCfg = BMI|Any CPU - {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.BMI|Any CPU.Build.0 = BMI|Any CPU - {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.CCRL|Any CPU.ActiveCfg = CCRL|Any CPU - {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.CCRL|Any CPU.Build.0 = CCRL|Any CPU - {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.Release|Any CPU.Build.0 = Release|Any CPU + {B2BA9BD6-E1C2-437E-986D-A6C08568D770}.Release|x64.ActiveCfg = Release|x64 + {B2BA9BD6-E1C2-437E-986D-A6C08568D770}.Release|x64.Build.0 = Release|x64 + {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.BMI|Any CPU.ActiveCfg = BMI|x64 + {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.BMI|x64.ActiveCfg = BMI|x64 + {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.BMI|x64.Build.0 = BMI|x64 + {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.CCRL|Any CPU.ActiveCfg = CCRL|x64 + {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.CCRL|x64.ActiveCfg = CCRL|x64 + {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.CCRL|x64.Build.0 = CCRL|x64 + {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.Debug|Any CPU.ActiveCfg = Debug|x64 + {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.Debug|x64.ActiveCfg = Debug|x64 + {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.Debug|x64.Build.0 = Debug|x64 + {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.Release|Any CPU.ActiveCfg = Release|x64 + {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.Release|x64.ActiveCfg = Release|x64 + {DB4710F3-2692-42AD-8B93-C77B823CEFEB}.Release|x64.Build.0 = Release|x64 + {DAB140A1-17EB-4688-8107-8C2AEA737AD6}.BMI|Any CPU.ActiveCfg = Release|Any CPU + {DAB140A1-17EB-4688-8107-8C2AEA737AD6}.BMI|Any CPU.Build.0 = Release|Any CPU + {DAB140A1-17EB-4688-8107-8C2AEA737AD6}.BMI|x64.ActiveCfg = Release|Any CPU + {DAB140A1-17EB-4688-8107-8C2AEA737AD6}.BMI|x64.Build.0 = Release|Any CPU + {DAB140A1-17EB-4688-8107-8C2AEA737AD6}.CCRL|Any CPU.ActiveCfg = Release|Any CPU + {DAB140A1-17EB-4688-8107-8C2AEA737AD6}.CCRL|Any CPU.Build.0 = Release|Any CPU + {DAB140A1-17EB-4688-8107-8C2AEA737AD6}.CCRL|x64.ActiveCfg = Release|Any CPU + {DAB140A1-17EB-4688-8107-8C2AEA737AD6}.CCRL|x64.Build.0 = Release|Any CPU + {DAB140A1-17EB-4688-8107-8C2AEA737AD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DAB140A1-17EB-4688-8107-8C2AEA737AD6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DAB140A1-17EB-4688-8107-8C2AEA737AD6}.Debug|x64.ActiveCfg = Debug|Any CPU + {DAB140A1-17EB-4688-8107-8C2AEA737AD6}.Debug|x64.Build.0 = Debug|Any CPU + {DAB140A1-17EB-4688-8107-8C2AEA737AD6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DAB140A1-17EB-4688-8107-8C2AEA737AD6}.Release|Any CPU.Build.0 = Release|Any CPU + {DAB140A1-17EB-4688-8107-8C2AEA737AD6}.Release|x64.ActiveCfg = Release|Any CPU + {DAB140A1-17EB-4688-8107-8C2AEA737AD6}.Release|x64.Build.0 = Release|Any CPU + {446DB14C-F30C-4A0C-86AB-8CCF3CF7CB53}.BMI|Any CPU.ActiveCfg = Release|Any CPU + {446DB14C-F30C-4A0C-86AB-8CCF3CF7CB53}.BMI|Any CPU.Build.0 = Release|Any CPU + {446DB14C-F30C-4A0C-86AB-8CCF3CF7CB53}.BMI|x64.ActiveCfg = Release|Any CPU + {446DB14C-F30C-4A0C-86AB-8CCF3CF7CB53}.BMI|x64.Build.0 = Release|Any CPU + {446DB14C-F30C-4A0C-86AB-8CCF3CF7CB53}.CCRL|Any CPU.ActiveCfg = Release|Any CPU + {446DB14C-F30C-4A0C-86AB-8CCF3CF7CB53}.CCRL|Any CPU.Build.0 = Release|Any CPU + {446DB14C-F30C-4A0C-86AB-8CCF3CF7CB53}.CCRL|x64.ActiveCfg = Release|Any CPU + {446DB14C-F30C-4A0C-86AB-8CCF3CF7CB53}.CCRL|x64.Build.0 = Release|Any CPU + {446DB14C-F30C-4A0C-86AB-8CCF3CF7CB53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {446DB14C-F30C-4A0C-86AB-8CCF3CF7CB53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {446DB14C-F30C-4A0C-86AB-8CCF3CF7CB53}.Debug|x64.ActiveCfg = Debug|Any CPU + {446DB14C-F30C-4A0C-86AB-8CCF3CF7CB53}.Debug|x64.Build.0 = Debug|Any CPU + {446DB14C-F30C-4A0C-86AB-8CCF3CF7CB53}.Release|Any CPU.ActiveCfg = Release|Any CPU + {446DB14C-F30C-4A0C-86AB-8CCF3CF7CB53}.Release|Any CPU.Build.0 = Release|Any CPU + {446DB14C-F30C-4A0C-86AB-8CCF3CF7CB53}.Release|x64.ActiveCfg = Release|Any CPU + {446DB14C-F30C-4A0C-86AB-8CCF3CF7CB53}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Cosette/Cosette.csproj b/Cosette/Cosette.csproj index 362109d..5f6f977 100644 --- a/Cosette/Cosette.csproj +++ b/Cosette/Cosette.csproj @@ -4,6 +4,9 @@ Exe netcoreapp3.1 Debug;Release;BMI;CCRL + AnyCPU;x64 + 2.0.0 + 2.0.0.0 @@ -12,12 +15,24 @@ TRACE;DEBUG;UCI_DEBUG_OUTPUT;LOGGER + + true + x64 + TRACE;DEBUG;UCI_DEBUG_OUTPUT;LOGGER + + true x64 TRACE;UCI_DEBUG_OUTPUT;LOGGER + + true + x64 + TRACE;UCI_DEBUG_OUTPUT + + true x64 @@ -25,6 +40,13 @@ true + + true + x64 + TRACE;BMI;UCI_DEBUG_OUTPUT + true + + true x64 @@ -32,6 +54,13 @@ true + + true + x64 + TRACE + true + + diff --git a/Cosette/Engine/Ai/Ordering/HistoryHeuristic.cs b/Cosette/Engine/Ai/Ordering/HistoryHeuristic.cs index 3631ee9..0a57e37 100644 --- a/Cosette/Engine/Ai/Ordering/HistoryHeuristic.cs +++ b/Cosette/Engine/Ai/Ordering/HistoryHeuristic.cs @@ -5,29 +5,35 @@ namespace Cosette.Engine.Ai.Ordering { public static class HistoryHeuristic { - private static int[][][] _historyMoves; + private static readonly uint[][][] _historyMoves; + private static uint _max; static HistoryHeuristic() { - _historyMoves = new int[2][][]; - _historyMoves[(int)Color.White] = new int[64][]; - _historyMoves[(int)Color.Black] = new int[64][]; + _historyMoves = new uint[2][][]; + _historyMoves[Color.White] = new uint[64][]; + _historyMoves[Color.Black] = new uint[64][]; - for (var i = 0; i < 64; i++) + for (var from = 0; from < 64; from++) { - _historyMoves[(int)Color.White][i] = new int[64]; - _historyMoves[(int)Color.Black][i] = new int[64]; + _historyMoves[Color.White][from] = new uint[64]; + _historyMoves[Color.Black][from] = new uint[64]; } + + _max = MoveOrderingConstants.HistoryHeuristicMaxScore; } - public static void AddHistoryMove(Color color, byte from, byte to, int value) + public static void AddHistoryMove(int color, int from, int to, int depth) { - _historyMoves[(int)color][from][to] = value; + var newValue = _historyMoves[color][from][to] + (uint)(depth * depth); + + _max = Math.Max(_max, newValue); + _historyMoves[color][from][to] = newValue; } - public static int GetHistoryMoveValue(Color color, byte from, byte to) + public static short GetHistoryMoveValue(int color, int from, int to) { - return _historyMoves[(int) color][from][to]; + return (short)(_historyMoves[color][from][to] / (_max / MoveOrderingConstants.HistoryHeuristicMaxScore)); } public static void Clear() @@ -36,12 +42,11 @@ public static void Clear() { for (var from = 0; from < 64; from++) { - for (var to = 0; to < 64; to++) - { - _historyMoves[color][from][to] = 0; - } + Array.Clear(_historyMoves[color][from], 0, 64); } } + + _max = MoveOrderingConstants.HistoryHeuristicMaxScore; } } } diff --git a/Cosette/Engine/Ai/Ordering/KillerHeuristic.cs b/Cosette/Engine/Ai/Ordering/KillerHeuristic.cs index 4e20fc9..54816f9 100644 --- a/Cosette/Engine/Ai/Ordering/KillerHeuristic.cs +++ b/Cosette/Engine/Ai/Ordering/KillerHeuristic.cs @@ -1,4 +1,5 @@ -using Cosette.Engine.Ai.Search; +using System; +using Cosette.Engine.Ai.Search; using Cosette.Engine.Common; using Cosette.Engine.Moves; @@ -6,36 +7,36 @@ namespace Cosette.Engine.Ai.Ordering { public static class KillerHeuristic { - private static Move[][][] _killerMoves; + private static readonly Move[][][] _killerMoves; static KillerHeuristic() { _killerMoves = new Move[2][][]; - _killerMoves[(int) Color.White] = new Move[SearchConstants.MaxDepth][]; - _killerMoves[(int) Color.Black] = new Move[SearchConstants.MaxDepth][]; + _killerMoves[Color.White] = new Move[SearchConstants.MaxDepth][]; + _killerMoves[Color.Black] = new Move[SearchConstants.MaxDepth][]; - for (var i = 0; i < SearchConstants.MaxDepth; i++) + for (var depth = 0; depth < SearchConstants.MaxDepth; depth++) { - _killerMoves[(int)Color.White][i] = new Move[MoveOrderingConstants.KillerSlots]; - _killerMoves[(int)Color.Black][i] = new Move[MoveOrderingConstants.KillerSlots]; + _killerMoves[Color.White][depth] = new Move[MoveOrderingConstants.KillerSlots]; + _killerMoves[Color.Black][depth] = new Move[MoveOrderingConstants.KillerSlots]; } } - public static void AddKillerMove(Move move, Color color, int depth) + public static void AddKillerMove(Move move, int color, int depth) { - for (var i = MoveOrderingConstants.KillerSlots - 2; i >= 0; i--) + for (var slot = MoveOrderingConstants.KillerSlots - 2; slot >= 0; slot--) { - _killerMoves[(int) color][depth][i + 1] = _killerMoves[(int)color][depth][i]; + _killerMoves[color][depth][slot + 1] = _killerMoves[color][depth][slot]; } - _killerMoves[(int)color][depth][0] = move; + _killerMoves[color][depth][0] = move; } - public static bool KillerMoveExists(Move move, Color color, int depth) + public static bool KillerMoveExists(Move move, int color, int depth) { - for (var i = 0; i < MoveOrderingConstants.KillerSlots; i++) + for (var slot = 0; slot < MoveOrderingConstants.KillerSlots; slot++) { - if (_killerMoves[(int)color][depth][i] == move) + if (_killerMoves[color][depth][slot] == move) { return true; } @@ -50,10 +51,7 @@ public static void Clear() { for (var depth = 0; depth < SearchConstants.MaxDepth; depth++) { - for (var slot = 0; slot < MoveOrderingConstants.KillerSlots; slot++) - { - _killerMoves[color][depth][slot] = new Move(); - } + Array.Clear(_killerMoves[color][depth], 0, MoveOrderingConstants.KillerSlots); } } } diff --git a/Cosette/Engine/Ai/Ordering/MoveOrdering.cs b/Cosette/Engine/Ai/Ordering/MoveOrdering.cs index d1b4157..f0b0292 100644 --- a/Cosette/Engine/Ai/Ordering/MoveOrdering.cs +++ b/Cosette/Engine/Ai/Ordering/MoveOrdering.cs @@ -1,29 +1,25 @@ using System; -using Cosette.Engine.Ai.Ordering; -using Cosette.Engine.Ai.Score; using Cosette.Engine.Ai.Transposition; using Cosette.Engine.Board; using Cosette.Engine.Common; using Cosette.Engine.Moves; -namespace Cosette.Engine.Ai.Search +namespace Cosette.Engine.Ai.Ordering { public static class MoveOrdering { - public static void AssignValues(BoardState board, Span moves, Span moveValues, int movesCount, int depth, TranspositionTableEntry entry) + public static void AssignValues(BoardState board, Span moves, Span moveValues, int movesCount, int depth, Move hashOrPvMove) { - var enemyColor = ColorOperations.Invert(board.ColorToMove); for (var moveIndex = 0; moveIndex < movesCount; moveIndex++) { - if (entry.Type != TranspositionTableEntryType.Invalid && entry.BestMove == moves[moveIndex]) + if (hashOrPvMove == moves[moveIndex]) { moveValues[moveIndex] = MoveOrderingConstants.HashMove; } else if (moves[moveIndex].IsQuiet()) { - if (moves[moveIndex].Piece == Piece.Pawn && - (board.ColorToMove == Color.White && moves[moveIndex].To >= 40 || - board.ColorToMove == Color.Black && moves[moveIndex].To <= 23)) + var pieceType = board.PieceTable[moves[moveIndex].From]; + if (pieceType == Piece.Pawn && IsPawnNearPromotion(board.ColorToMove, moves[moveIndex].To)) { moveValues[moveIndex] = MoveOrderingConstants.PawnNearPromotion; } @@ -36,46 +32,60 @@ public static void AssignValues(BoardState board, Span moves, Span mo moveValues[moveIndex] = HistoryHeuristic.GetHistoryMoveValue(board.ColorToMove, moves[moveIndex].From, moves[moveIndex].To); } } - else if ((moves[moveIndex].Flags & MoveFlags.Kill) != 0) + else if (moves[moveIndex].Flags == MoveFlags.EnPassant) + { + moveValues[moveIndex] = MoveOrderingConstants.EnPassant; + } + else if (((byte)moves[moveIndex].Flags & MoveFlagFields.Capture) != 0) { - var attackingPiece = moves[moveIndex].Piece; - var capturedPiece = board.GetPiece(enemyColor, moves[moveIndex].To); + var enemyColor = ColorOperations.Invert(board.ColorToMove); + + var attackingPiece = board.PieceTable[moves[moveIndex].From]; + var capturedPiece = board.PieceTable[moves[moveIndex].To]; var attackers = board.GetAttackingPiecesWithColor(board.ColorToMove, moves[moveIndex].To); var defenders = board.GetAttackingPiecesWithColor(enemyColor, moves[moveIndex].To); - moveValues[moveIndex] = MoveOrderingConstants.Capture + StaticExchangeEvaluation.Evaluate(attackingPiece, capturedPiece, attackers, defenders); + var seeEvaluation = StaticExchangeEvaluation.Evaluate(attackingPiece, capturedPiece, attackers, defenders); + + moveValues[moveIndex] = (short)(MoveOrderingConstants.Capture + seeEvaluation); } - else if ((int)moves[moveIndex].Flags >= 16) + else if (moves[moveIndex].Flags == MoveFlags.KingCastle || moves[moveIndex].Flags == MoveFlags.QueenCastle) { - moveValues[moveIndex] = MoveOrderingConstants.Promotion; + moveValues[moveIndex] = MoveOrderingConstants.Castling; + } + else if (((byte)moves[moveIndex].Flags & MoveFlagFields.Promotion) != 0) + { + moveValues[moveIndex] = (short)(MoveOrderingConstants.Promotion + (int)moves[moveIndex].Flags); } } } - public static void AssignQValues(BoardState board, Span moves, Span moveValues, int movesCount) + public static void AssignQValues(BoardState board, Span moves, Span moveValues, int movesCount) { var enemyColor = ColorOperations.Invert(board.ColorToMove); - for (var i = 0; i < movesCount; i++) + for (var moveIndex = 0; moveIndex < movesCount; moveIndex++) { - if ((moves[i].Flags & MoveFlags.EnPassant) != 0) + if (moves[moveIndex].Flags == MoveFlags.EnPassant) { - moveValues[i] = MoveOrderingConstants.Capture; + moveValues[moveIndex] = MoveOrderingConstants.EnPassant; } else { - var attackingPiece = moves[i].Piece; - var capturedPiece = board.GetPiece(enemyColor, moves[i].To); + var attackingPiece = board.PieceTable[moves[moveIndex].From]; + var capturedPiece = board.PieceTable[moves[moveIndex].To]; - var attackers = board.GetAttackingPiecesWithColor(board.ColorToMove, moves[i].To); - var defenders = board.GetAttackingPiecesWithColor(enemyColor, moves[i].To); - moveValues[i] = MoveOrderingConstants.Capture + StaticExchangeEvaluation.Evaluate(attackingPiece, capturedPiece, attackers, defenders); + var attackers = board.GetAttackingPiecesWithColor(board.ColorToMove, moves[moveIndex].To); + var defenders = board.GetAttackingPiecesWithColor(enemyColor, moves[moveIndex].To); + var seeEvaluation = StaticExchangeEvaluation.Evaluate(attackingPiece, capturedPiece, attackers, defenders); + + moveValues[moveIndex] = seeEvaluation; } } } - public static void SortNextBestMove(Span moves, Span moveValues, int movesCount, int currentIndex) + public static void SortNextBestMove(Span moves, Span moveValues, int movesCount, int currentIndex) { - var max = int.MinValue; + var max = short.MinValue; var maxIndex = -1; for (var i = currentIndex; i < movesCount; i++) @@ -87,13 +97,19 @@ public static void SortNextBestMove(Span moves, Span moveValues, int } } - var tempMove = moves[maxIndex]; - moves[maxIndex] = moves[currentIndex]; - moves[currentIndex] = tempMove; + (moves[maxIndex], moves[currentIndex]) = (moves[currentIndex], moves[maxIndex]); + (moveValues[maxIndex], moveValues[currentIndex]) = (moveValues[currentIndex], moveValues[maxIndex]); + } + + + private static bool IsPawnNearPromotion(int color, byte to) + { + if (color == Color.White && to >= 40 || color == Color.Black && to <= 23) + { + return true; + } - var tempMoveValue = moveValues[maxIndex]; - moveValues[maxIndex] = moveValues[currentIndex]; - moveValues[currentIndex] = tempMoveValue; + return false; } } } diff --git a/Cosette/Engine/Ai/Ordering/MoveOrderingConstants.cs b/Cosette/Engine/Ai/Ordering/MoveOrderingConstants.cs index 7de6109..e3428c1 100644 --- a/Cosette/Engine/Ai/Ordering/MoveOrderingConstants.cs +++ b/Cosette/Engine/Ai/Ordering/MoveOrderingConstants.cs @@ -4,9 +4,12 @@ public static class MoveOrderingConstants { public const int HashMove = 10000; public const int Promotion = 5000; + public const int Castling = 1000; + public const int PawnNearPromotion = 150; public const int Capture = 100; - public const int KillerMove = 80; - public const int PawnNearPromotion = 50; + public const int EnPassant = 100; + public const int KillerMove = 90; + public const int HistoryHeuristicMaxScore = 80; public const int KillerSlots = 3; } diff --git a/Cosette/Engine/Ai/Ordering/StaticExchangeEvaluation.cs b/Cosette/Engine/Ai/Ordering/StaticExchangeEvaluation.cs index fe71edd..4725608 100644 --- a/Cosette/Engine/Ai/Ordering/StaticExchangeEvaluation.cs +++ b/Cosette/Engine/Ai/Ordering/StaticExchangeEvaluation.cs @@ -1,35 +1,36 @@ -using Cosette.Engine.Ai.Score; +using System.Collections.Generic; +using Cosette.Engine.Ai.Score; using Cosette.Engine.Common; namespace Cosette.Engine.Ai.Ordering { public static class StaticExchangeEvaluation { - private static int[][][][] _table; + private static short[][][][] _table; - static StaticExchangeEvaluation() + public static void Init() { InitTable(); PopulateTable(); } - public static int Evaluate(Piece attackingPiece, Piece capturedPiece, byte attacker, byte defender) + public static short Evaluate(int attackingPiece, int capturedPiece, int attacker, int defender) { - return _table[(int)attackingPiece][(int)capturedPiece][attacker][defender]; + return _table[attackingPiece][capturedPiece][attacker][defender]; } private static void InitTable() { - _table = new int[6][][][]; - for (var attackingPieceIndex = 0; attackingPieceIndex < 6; attackingPieceIndex++) + _table = new short[6][][][]; + for (var attackingPiece = 0; attackingPiece < 6; attackingPiece++) { - _table[attackingPieceIndex] = new int[6][][]; - for (var defendingPieceIndex = 0; defendingPieceIndex < 6; defendingPieceIndex++) + _table[attackingPiece] = new short[6][][]; + for (var capturedPiece = 0; capturedPiece < 6; capturedPiece++) { - _table[attackingPieceIndex][defendingPieceIndex] = new int[64][]; - for (var defenderIndex = 0; defenderIndex < 64; defenderIndex++) + _table[attackingPiece][capturedPiece] = new short[64][]; + for (var attackerIndex = 0; attackerIndex < 64; attackerIndex++) { - _table[attackingPieceIndex][defendingPieceIndex][defenderIndex] = new int[64]; + _table[attackingPiece][capturedPiece][attackerIndex] = new short[64]; } } } @@ -37,65 +38,77 @@ private static void InitTable() private static void PopulateTable() { - for (var attackingPieceIndex = 0; attackingPieceIndex < 6; attackingPieceIndex++) + var gainList = new List(); + for (var attackingPiece = 0; attackingPiece < 6; attackingPiece++) { - for (var capturedPieceIndex = 0; capturedPieceIndex < 6; capturedPieceIndex++) + for (var capturedPiece = 0; capturedPiece < 6; capturedPiece++) { - var attackingPiece = (Piece) attackingPieceIndex; - var capturedPiece = (Piece) capturedPieceIndex; - for (ulong attackerIndex = 0; attackerIndex < 64; attackerIndex++) { for (ulong defenderIndex = 0; defenderIndex < 64; defenderIndex++) { - var attackers = attackerIndex & ~(1ul << attackingPieceIndex); + var attackers = attackerIndex & ~(1ul << attackingPiece); var defenders = defenderIndex; var currentPieceOnField = attackingPiece; - var result = EvaluationConstants.Pieces[(int)capturedPiece]; + var result = EvaluationConstants.Pieces[capturedPiece]; + + gainList.Add(result); if (defenders != 0) { var leastValuableDefenderField = BitOperations.GetLsb(defenders); - defenders = (byte)BitOperations.PopLsb(defenders); - var leastValuableDefenderPiece = (Piece)BitOperations.BitScan(leastValuableDefenderField); + var leastValuableDefenderPiece = BitOperations.BitScan(leastValuableDefenderField); + defenders = BitOperations.PopLsb(defenders); - result -= EvaluationConstants.Pieces[(int)currentPieceOnField]; + result -= EvaluationConstants.Pieces[currentPieceOnField]; currentPieceOnField = leastValuableDefenderPiece; + gainList.Add(result); + while (attackers != 0) { - var updatedResult = result; var leastValuableAttackerField = BitOperations.GetLsb(attackers); - attackers = (byte)BitOperations.PopLsb(attackers); - var leastValuableAttackerPiece = (Piece)BitOperations.BitScan(leastValuableAttackerField); + var leastValuableAttackerPiece = BitOperations.BitScan(leastValuableAttackerField); + attackers = BitOperations.PopLsb(attackers); - updatedResult += EvaluationConstants.Pieces[(int)currentPieceOnField]; + result += EvaluationConstants.Pieces[currentPieceOnField]; currentPieceOnField = leastValuableAttackerPiece; + gainList.Add(result); + + if (gainList[^1] > gainList[^3]) + { + result = gainList[^3]; + break; + } + if (defenders != 0) { leastValuableDefenderField = BitOperations.GetLsb(defenders); - defenders = (byte)BitOperations.PopLsb(defenders); - leastValuableDefenderPiece = (Piece)BitOperations.BitScan(leastValuableDefenderField); + leastValuableDefenderPiece = BitOperations.BitScan(leastValuableDefenderField); + defenders = BitOperations.PopLsb(defenders); - updatedResult -= EvaluationConstants.Pieces[(int)currentPieceOnField]; + result -= EvaluationConstants.Pieces[currentPieceOnField]; currentPieceOnField = leastValuableDefenderPiece; + + gainList.Add(result); + + if (gainList[^1] < gainList[^3]) + { + result = gainList[^3]; + break; + } } else - { - result = updatedResult; - break; - } - - if (updatedResult < result) { break; } } } - _table[attackingPieceIndex][capturedPieceIndex][attackerIndex][defenderIndex] = result; + _table[attackingPiece][capturedPiece][attackerIndex][defenderIndex] = (short)result; + gainList.Clear(); } } } diff --git a/Cosette/Engine/Ai/Score/Evaluation.cs b/Cosette/Engine/Ai/Score/Evaluation.cs index bd7a576..7c3e029 100644 --- a/Cosette/Engine/Ai/Score/Evaluation.cs +++ b/Cosette/Engine/Ai/Score/Evaluation.cs @@ -1,30 +1,31 @@ -using System.Runtime.CompilerServices; -using Cosette.Engine.Ai.Score.Evaluators; -using Cosette.Engine.Ai.Score.PieceSquareTables; -using Cosette.Engine.Ai.Transposition; +using Cosette.Engine.Ai.Score.Evaluators; using Cosette.Engine.Board; -using Cosette.Engine.Board.Operators; using Cosette.Engine.Common; -using Cosette.Engine.Moves.Patterns; namespace Cosette.Engine.Ai.Score { public class Evaluation { - public static int Evaluate(BoardState board, Color color) + public static int Evaluate(BoardState board, EvaluationStatistics statistics) { var openingPhase = board.GetPhaseRatio(); - var endingPhase = 1 - openingPhase; + var endingPhase = 1f - openingPhase; - var result = MaterialEvaluator.Evaluate(board, openingPhase, endingPhase); + var result = MaterialEvaluator.Evaluate(board); result += CastlingEvaluator.Evaluate(board, openingPhase, endingPhase); result += PositionEvaluator.Evaluate(board, openingPhase, endingPhase); - result += PawnStructureEvaluator.Evaluate(board, openingPhase, endingPhase); + result += PawnStructureEvaluator.Evaluate(board, statistics, openingPhase, endingPhase); result += MobilityEvaluator.Evaluate(board, openingPhase, endingPhase); result += KingSafetyEvaluator.Evaluate(board, openingPhase, endingPhase); + result += PiecesEvaluator.Evaluate(board, openingPhase, endingPhase); - var sign = color == Color.White ? 1 : -1; - return sign * result; + return board.ColorToMove == Color.White ? result : -result; + } + + public static int FastEvaluate(BoardState board) + { + var result = MaterialEvaluator.Evaluate(board); + return board.ColorToMove == Color.White ? result : -result; } } } diff --git a/Cosette/Engine/Ai/Score/EvaluationConstants.cs b/Cosette/Engine/Ai/Score/EvaluationConstants.cs index cfb213a..ed70a97 100644 --- a/Cosette/Engine/Ai/Score/EvaluationConstants.cs +++ b/Cosette/Engine/Ai/Score/EvaluationConstants.cs @@ -15,7 +15,11 @@ public static class EvaluationConstants public static int[] PassingPawns = { 10, 50 }; public static int[] Mobility = { 4, 1 }; public static int[] KingInDanger = { -20, -5 }; + public static int[] PawnShield = { 20, 0 }; + public static int[] DoubledRooks = { 40, 10 }; + public static int[] RookOnOpenFile = { 20, 0 }; + public static int[] PairOfBishops = { 30, 20 }; - public const int OpeningEndgameEdge = 2 * 21000; + public const int OpeningEndgameEdge = 20500; } } diff --git a/Cosette/Engine/Ai/Score/EvaluationStatistics.cs b/Cosette/Engine/Ai/Score/EvaluationStatistics.cs new file mode 100644 index 0000000..4e58a4a --- /dev/null +++ b/Cosette/Engine/Ai/Score/EvaluationStatistics.cs @@ -0,0 +1,19 @@ +namespace Cosette.Engine.Ai.Score +{ + public class EvaluationStatistics + { + public ulong PHTAddedEntries { get; set; } + public ulong PHTReplacements { get; set; } + public ulong PHTHits { get; set; } + public ulong PHTNonHits { get; set; } + public float PHTHitsPercent => (float)PHTHits * 100 / (PHTHits + PHTNonHits); + public float PHTReplacesPercent => (float)PHTReplacements * 100 / PHTAddedEntries; + + public ulong EHTAddedEntries { get; set; } + public ulong EHTReplacements { get; set; } + public ulong EHTHits { get; set; } + public ulong EHTNonHits { get; set; } + public float EHTHitsPercent => (float)EHTHits * 100 / (EHTHits + EHTNonHits); + public float EHTReplacesPercent => (float)EHTReplacements * 100 / EHTAddedEntries; + } +} diff --git a/Cosette/Engine/Ai/Score/Evaluators/CastlingEvaluator.cs b/Cosette/Engine/Ai/Score/Evaluators/CastlingEvaluator.cs index c74069d..50a59c4 100644 --- a/Cosette/Engine/Ai/Score/Evaluators/CastlingEvaluator.cs +++ b/Cosette/Engine/Ai/Score/Evaluators/CastlingEvaluator.cs @@ -1,5 +1,4 @@ -using System.Runtime.CompilerServices; -using Cosette.Engine.Board; +using Cosette.Engine.Board; using Cosette.Engine.Common; namespace Cosette.Engine.Ai.Score.Evaluators @@ -8,25 +7,26 @@ public static class CastlingEvaluator { public static int Evaluate(BoardState board, float openingPhase, float endingPhase) { - return Evaluate(board, Color.White, openingPhase, endingPhase) - Evaluate(board, Color.Black, openingPhase, endingPhase); + return Evaluate(board, Color.White, openingPhase, endingPhase) - + Evaluate(board, Color.Black, openingPhase, endingPhase); } - public static int Evaluate(BoardState board, Color color, float openingPhase, float endingPhase) + public static int Evaluate(BoardState board, int color, float openingPhase, float endingPhase) { - var result = 0; - if (board.CastlingDone[(int)color]) + if (board.CastlingDone[color]) { - result = (int)(EvaluationConstants.CastlingDone[(int)GamePhase.Opening] * openingPhase) + - (int)(EvaluationConstants.CastlingDone[(int)GamePhase.Ending] * endingPhase); + return (int)(EvaluationConstants.CastlingDone[GamePhase.Opening] * openingPhase + + EvaluationConstants.CastlingDone[GamePhase.Ending] * endingPhase); } - else if (color == Color.White && (board.Castling & Castling.WhiteCastling) == 0 || - color == Color.Black && (board.Castling & Castling.BlackCastling) == 0) + + if (color == Color.White && (board.Castling & Castling.WhiteCastling) == 0 || + color == Color.Black && (board.Castling & Castling.BlackCastling) == 0) { - result = (int)(EvaluationConstants.CastlingFailed[(int)GamePhase.Opening] * openingPhase) + - (int)(EvaluationConstants.CastlingFailed[(int)GamePhase.Ending] * endingPhase); + return (int)(EvaluationConstants.CastlingFailed[GamePhase.Opening] * openingPhase + + EvaluationConstants.CastlingFailed[GamePhase.Ending] * endingPhase); } - return result; + return 0; } } } diff --git a/Cosette/Engine/Ai/Score/Evaluators/KingSafetyEvaluator.cs b/Cosette/Engine/Ai/Score/Evaluators/KingSafetyEvaluator.cs index b6e2606..df4f602 100644 --- a/Cosette/Engine/Ai/Score/Evaluators/KingSafetyEvaluator.cs +++ b/Cosette/Engine/Ai/Score/Evaluators/KingSafetyEvaluator.cs @@ -1,5 +1,4 @@ -using System.Runtime.CompilerServices; -using Cosette.Engine.Board; +using Cosette.Engine.Board; using Cosette.Engine.Common; using Cosette.Engine.Moves.Patterns; @@ -9,21 +8,25 @@ public static class KingSafetyEvaluator { public static int Evaluate(BoardState board, float openingPhase, float endingPhase) { - return Evaluate(board, Color.White, openingPhase, endingPhase) - Evaluate(board, Color.Black, openingPhase, endingPhase); + return Evaluate(board, Color.White, openingPhase, endingPhase) - + Evaluate(board, Color.Black, openingPhase, endingPhase); } - public static int Evaluate(BoardState board, Color color, float openingPhase, float endingPhase) + public static int Evaluate(BoardState board, int color, float openingPhase, float endingPhase) { - var king = board.Pieces[(int)color][(int)Piece.King]; + var king = board.Pieces[color][Piece.King]; var kingField = BitOperations.BitScan(king); var fieldsAroundKing = BoxPatternGenerator.GetPattern(kingField); var attackersCount = 0; - while (fieldsAroundKing != 0) + var pawnShield = 0; + + var attackedFieldsToCheck = fieldsAroundKing; + while (attackedFieldsToCheck != 0) { - var lsb = BitOperations.GetLsb(fieldsAroundKing); - fieldsAroundKing = BitOperations.PopLsb(fieldsAroundKing); + var lsb = BitOperations.GetLsb(attackedFieldsToCheck); var field = BitOperations.BitScan(lsb); + attackedFieldsToCheck = BitOperations.PopLsb(attackedFieldsToCheck); var attackingPieces = board.IsFieldAttacked(color, (byte)field); if (attackingPieces) @@ -32,8 +35,16 @@ public static int Evaluate(BoardState board, Color color, float openingPhase, fl } } - return (int)(attackersCount * EvaluationConstants.KingInDanger[(int)GamePhase.Opening] * openingPhase + - attackersCount * EvaluationConstants.KingInDanger[(int)GamePhase.Ending] * endingPhase); + var pawnsNearKing = fieldsAroundKing & board.Pieces[color][Piece.Pawn]; + pawnShield = (int)BitOperations.Count(pawnsNearKing); + + var attackersCountScore = attackersCount * EvaluationConstants.KingInDanger[GamePhase.Opening] * openingPhase + + attackersCount * EvaluationConstants.KingInDanger[GamePhase.Ending] * endingPhase; + + var pawnShieldScore = pawnShield * EvaluationConstants.PawnShield[GamePhase.Opening] * openingPhase + + pawnShield * EvaluationConstants.PawnShield[GamePhase.Ending] * endingPhase; + + return (int)(attackersCountScore + pawnShieldScore); } } } diff --git a/Cosette/Engine/Ai/Score/Evaluators/MaterialEvaluator.cs b/Cosette/Engine/Ai/Score/Evaluators/MaterialEvaluator.cs index 5ef0cf7..9c78c5f 100644 --- a/Cosette/Engine/Ai/Score/Evaluators/MaterialEvaluator.cs +++ b/Cosette/Engine/Ai/Score/Evaluators/MaterialEvaluator.cs @@ -1,14 +1,14 @@ -using System.Runtime.CompilerServices; -using Cosette.Engine.Board; +using Cosette.Engine.Board; using Cosette.Engine.Common; namespace Cosette.Engine.Ai.Score.Evaluators { public static class MaterialEvaluator { - public static int Evaluate(BoardState board, float openingPhase, float endingPhase) + public static int Evaluate(BoardState board) { - return board.Material[(int)Color.White] - board.Material[(int)Color.Black]; + return board.Material[Color.White] - + board.Material[Color.Black]; } } } diff --git a/Cosette/Engine/Ai/Score/Evaluators/MobilityEvaluator.cs b/Cosette/Engine/Ai/Score/Evaluators/MobilityEvaluator.cs index 4b213ae..efab005 100644 --- a/Cosette/Engine/Ai/Score/Evaluators/MobilityEvaluator.cs +++ b/Cosette/Engine/Ai/Score/Evaluators/MobilityEvaluator.cs @@ -1,5 +1,4 @@ -using System.Runtime.CompilerServices; -using Cosette.Engine.Board; +using Cosette.Engine.Board; using Cosette.Engine.Board.Operators; using Cosette.Engine.Common; @@ -9,16 +8,19 @@ public static class MobilityEvaluator { public static int Evaluate(BoardState board, float openingPhase, float endingPhase) { - return Evaluate(board, Color.White, openingPhase, endingPhase) - Evaluate(board, Color.Black, openingPhase, endingPhase); + return Evaluate(board, Color.White, openingPhase, endingPhase) - + Evaluate(board, Color.Black, openingPhase, endingPhase); } - public static int Evaluate(BoardState board, Color color, float openingPhase, float endingPhase) + public static int Evaluate(BoardState board, int color, float openingPhase, float endingPhase) { - var mobility = KnightOperator.GetMobility(board, color) + BishopOperator.GetMobility(board, color) + - RookOperator.GetMobility(board, color) + QueenOperator.GetMobility(board, color); + var mobility = KnightOperator.GetMobility(board, color) + + BishopOperator.GetMobility(board, color) + + RookOperator.GetMobility(board, color) + + QueenOperator.GetMobility(board, color); - return (int)(mobility * EvaluationConstants.Mobility[(int)GamePhase.Opening] * openingPhase + - mobility * EvaluationConstants.Mobility[(int)GamePhase.Ending] * endingPhase); + return (int)(mobility * EvaluationConstants.Mobility[GamePhase.Opening] * openingPhase + + mobility * EvaluationConstants.Mobility[GamePhase.Ending] * endingPhase); } } } diff --git a/Cosette/Engine/Ai/Score/Evaluators/PawnStructureEvaluator.cs b/Cosette/Engine/Ai/Score/Evaluators/PawnStructureEvaluator.cs index 98e1a68..20e9744 100644 --- a/Cosette/Engine/Ai/Score/Evaluators/PawnStructureEvaluator.cs +++ b/Cosette/Engine/Ai/Score/Evaluators/PawnStructureEvaluator.cs @@ -1,5 +1,4 @@ -using System.Runtime.CompilerServices; -using Cosette.Engine.Ai.Transposition; +using Cosette.Engine.Ai.Transposition; using Cosette.Engine.Board; using Cosette.Engine.Common; using Cosette.Engine.Moves.Patterns; @@ -39,21 +38,40 @@ static PawnStructureEvaluator() } } - public static int Evaluate(BoardState board, float openingPhase, float endingPhase) + public static int Evaluate(BoardState board, EvaluationStatistics statistics, float openingPhase, float endingPhase) { var entry = PawnHashTable.Get(board.PawnHash); - if (entry.Hash == board.PawnHash) + if (entry.IsKeyValid(board.PawnHash)) { +#if DEBUG + statistics.PHTHits++; +#endif return entry.Score; } +#if DEBUG + else + { + statistics.PHTNonHits++; + + if (entry.Key != 0 || entry.Score != 0) + { + statistics.PHTReplacements++; + } + } +#endif + + var result = Evaluate(board, Color.White, openingPhase, endingPhase) - + Evaluate(board, Color.Black, openingPhase, endingPhase); - var result = Evaluate(board, Color.White, openingPhase, endingPhase) - Evaluate(board, Color.Black, openingPhase, endingPhase); PawnHashTable.Add(board.PawnHash, (short)result); +#if DEBUG + statistics.PHTAddedEntries++; +#endif return result; } - public static int Evaluate(BoardState board, Color color, float openingPhase, float endingPhase) + public static int Evaluate(BoardState board, int color, float openingPhase, float endingPhase) { var doubledPawns = 0; var isolatedPawns = 0; @@ -61,57 +79,58 @@ public static int Evaluate(BoardState board, Color color, float openingPhase, fl var passingPawns = 0; var enemyColor = ColorOperations.Invert(color); - for (var i = 0; i < 8; i++) + for (var file = 0; file < 8; file++) { - var pawnsOnInnerMask = board.Pieces[(int)color][(int)Piece.Pawn] & _innerFileMasks[i]; - var pawnsOnOuterMask = board.Pieces[(int)color][(int)Piece.Pawn] & _outerFileMasks[i]; - var enemyPawnsOnInnerMask = board.Pieces[(int)enemyColor][(int)Piece.Pawn] & _innerFileMasks[i]; + var friendlyPawnsOnInnerMask = board.Pieces[color][Piece.Pawn] & _innerFileMasks[file]; + var friendlyPawnsOnOuterMask = board.Pieces[color][Piece.Pawn] & _outerFileMasks[file]; + var enemyPawnsOnInnerMask = board.Pieces[enemyColor][Piece.Pawn] & _innerFileMasks[file]; + var enemyPawnsOnOuterMask = board.Pieces[enemyColor][Piece.Pawn] & _outerFileMasks[file]; - var pawnsCount = (int)BitOperations.Count(pawnsOnInnerMask); + var pawnsCount = BitOperations.Count(friendlyPawnsOnInnerMask); if (pawnsCount > 1) { - doubledPawns += pawnsCount - 1; + doubledPawns += (int)(pawnsCount - 1); } - if (pawnsOnInnerMask != 0) + if (friendlyPawnsOnInnerMask != 0) { - if (pawnsOnOuterMask == 0) + if (friendlyPawnsOnOuterMask == 0) { - isolatedPawns += (int)BitOperations.Count(pawnsOnInnerMask); + isolatedPawns += (int)BitOperations.Count(pawnsCount); } - if (enemyPawnsOnInnerMask == 0) + if (enemyPawnsOnInnerMask == 0 && enemyPawnsOnOuterMask == 0) { passingPawns++; } } } - var pieces = board.Pieces[(int)color][(int)Piece.Pawn]; + var pieces = board.Pieces[color][Piece.Pawn]; while (pieces != 0) { var lsb = BitOperations.GetLsb(pieces); - pieces = BitOperations.PopLsb(pieces); var field = BitOperations.BitScan(lsb); + pieces = BitOperations.PopLsb(pieces); - var chain = _chainMasks[field] & board.Pieces[(int)color][(int)Piece.Pawn]; + var chain = _chainMasks[field] & board.Pieces[color][Piece.Pawn]; if (chain != 0) { chainedPawns += (int)BitOperations.Count(chain); } } - var doubledPawnsScore = doubledPawns * EvaluationConstants.DoubledPawns[(int) GamePhase.Opening] * openingPhase + - doubledPawns * EvaluationConstants.DoubledPawns[(int) GamePhase.Ending] * endingPhase; + var doubledPawnsScore = doubledPawns * EvaluationConstants.DoubledPawns[GamePhase.Opening] * openingPhase + + doubledPawns * EvaluationConstants.DoubledPawns[GamePhase.Ending] * endingPhase; - var isolatedPawnsScore = isolatedPawns * EvaluationConstants.IsolatedPawns[(int)GamePhase.Opening] * openingPhase + - isolatedPawns * EvaluationConstants.IsolatedPawns[(int)GamePhase.Ending] * endingPhase; + var isolatedPawnsScore = isolatedPawns * EvaluationConstants.IsolatedPawns[GamePhase.Opening] * openingPhase + + isolatedPawns * EvaluationConstants.IsolatedPawns[GamePhase.Ending] * endingPhase; - var chainedPawnsScore = chainedPawns * EvaluationConstants.ChainedPawns[(int)GamePhase.Opening] * openingPhase + - chainedPawns * EvaluationConstants.ChainedPawns[(int)GamePhase.Ending] * endingPhase; + var chainedPawnsScore = chainedPawns * EvaluationConstants.ChainedPawns[GamePhase.Opening] * openingPhase + + chainedPawns * EvaluationConstants.ChainedPawns[GamePhase.Ending] * endingPhase; - var passingPawnsScore = passingPawns * EvaluationConstants.PassingPawns[(int)GamePhase.Opening] * openingPhase + - passingPawns * EvaluationConstants.PassingPawns[(int)GamePhase.Ending] * endingPhase; + var passingPawnsScore = passingPawns * EvaluationConstants.PassingPawns[GamePhase.Opening] * openingPhase + + passingPawns * EvaluationConstants.PassingPawns[GamePhase.Ending] * endingPhase; return (int)(doubledPawnsScore + isolatedPawnsScore + chainedPawnsScore + passingPawnsScore); } diff --git a/Cosette/Engine/Ai/Score/Evaluators/PiecesEvaluator.cs b/Cosette/Engine/Ai/Score/Evaluators/PiecesEvaluator.cs new file mode 100644 index 0000000..e2d1620 --- /dev/null +++ b/Cosette/Engine/Ai/Score/Evaluators/PiecesEvaluator.cs @@ -0,0 +1,64 @@ +using Cosette.Engine.Board; +using Cosette.Engine.Common; +using Cosette.Engine.Moves.Patterns; + +namespace Cosette.Engine.Ai.Score.Evaluators +{ + public static class PiecesEvaluator + { + public static int Evaluate(BoardState board, float openingPhase, float endingPhase) + { + return Evaluate(board, Color.White, openingPhase, endingPhase) - + Evaluate(board, Color.Black, openingPhase, endingPhase); + } + + public static int Evaluate(BoardState board, int color, float openingPhase, float endingPhase) + { + var doubledRooks = 0; + var rooksOnOpenFile = 0; + var pairOfBishops = 0; + var enemyColor = ColorOperations.Invert(color); + + var rooks = board.Pieces[color][Piece.Rook]; + while (rooks != 0) + { + var lsb = BitOperations.GetLsb(rooks); + var field = BitOperations.BitScan(lsb); + rooks = BitOperations.PopLsb(rooks); + + var file = FilePatternGenerator.GetPattern(field) | lsb; + var rooksOnFile = file & board.Pieces[color][Piece.Rook]; + var friendlyPawnsOnFile = file & board.Pieces[color][Piece.Pawn]; + var enemyPawnsOnFile = file & board.Pieces[enemyColor][Piece.Pawn]; + + if (BitOperations.Count(rooksOnFile) > 1) + { + // We don't assume that there will be more than two rooks - even if, then this color is probably anyway winning + doubledRooks = 1; + } + + if (friendlyPawnsOnFile == 0 && enemyPawnsOnFile == 0) + { + rooksOnOpenFile++; + } + } + + var bishops = board.Pieces[color][Piece.Bishop]; + if (BitOperations.Count(bishops) > 1) + { + pairOfBishops = 1; + } + + var doubledRooksScore = doubledRooks * EvaluationConstants.DoubledRooks[GamePhase.Opening] * openingPhase + + doubledRooks * EvaluationConstants.DoubledRooks[GamePhase.Ending] * endingPhase; + + var rooksOnOpenFileScore = rooksOnOpenFile * EvaluationConstants.RookOnOpenFile[GamePhase.Opening] * openingPhase + + rooksOnOpenFile * EvaluationConstants.RookOnOpenFile[GamePhase.Ending] * endingPhase; + + var pairOfBishopsScore = pairOfBishops * EvaluationConstants.PairOfBishops[GamePhase.Opening] * openingPhase + + pairOfBishops * EvaluationConstants.PairOfBishops[GamePhase.Ending] * endingPhase; + + return (int)(doubledRooksScore + rooksOnOpenFileScore + pairOfBishopsScore); + } + } +} \ No newline at end of file diff --git a/Cosette/Engine/Ai/Score/Evaluators/PositionEvaluator.cs b/Cosette/Engine/Ai/Score/Evaluators/PositionEvaluator.cs index 0c5ca5c..f496302 100644 --- a/Cosette/Engine/Ai/Score/Evaluators/PositionEvaluator.cs +++ b/Cosette/Engine/Ai/Score/Evaluators/PositionEvaluator.cs @@ -1,5 +1,4 @@ -using System.Runtime.CompilerServices; -using Cosette.Engine.Board; +using Cosette.Engine.Board; using Cosette.Engine.Common; namespace Cosette.Engine.Ai.Score.Evaluators @@ -8,13 +7,14 @@ public static class PositionEvaluator { public static int Evaluate(BoardState board, float openingPhase, float endingPhase) { - var whitePositionScore = board.Position[(int)Color.White][(int)GamePhase.Opening] * openingPhase + - board.Position[(int)Color.White][(int)GamePhase.Ending] * endingPhase; - - var blackPositionScore = board.Position[(int)Color.Black][(int)GamePhase.Opening] * openingPhase + - board.Position[(int)Color.Black][(int)GamePhase.Ending] * endingPhase; + return Evaluate(board, Color.White, openingPhase, endingPhase) - + Evaluate(board, Color.Black, openingPhase, endingPhase); + } - return (int)whitePositionScore - (int)blackPositionScore; + public static int Evaluate(BoardState board, int color, float openingPhase, float endingPhase) + { + return (int)(board.Position[color][GamePhase.Opening] * openingPhase + + board.Position[color][GamePhase.Ending] * endingPhase); } } } diff --git a/Cosette/Engine/Ai/Score/PieceSquareTables/BishopTables.cs b/Cosette/Engine/Ai/Score/PieceSquareTables/BishopTables.cs index 6e414b3..af7644d 100644 --- a/Cosette/Engine/Ai/Score/PieceSquareTables/BishopTables.cs +++ b/Cosette/Engine/Ai/Score/PieceSquareTables/BishopTables.cs @@ -8,22 +8,22 @@ public static class BishopTables { // Opening new[] { -5, -5, -5, -5, -5, -5, -5, -5, - -5, 0, 0, 0, 0, 0, 0, -5, - -5, 0, 0, 5, 5, 0, 0, -5, - -5, 0, 5, 5, 5, 5, 0, -5, - -5, 5, 5, 10, 10, 5, 5, -5, - -5, 5, 10, 10, 10, 10, 5, -5, - -5, 10, 0, 5, 5, 0, 10, -5, - -5, -5, 0, -5, -5, 0, -5, -5 }, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 5, 5, 0, 0, -5, + -5, 0, 5, 5, 5, 5, 0, -5, + -5, 5, 5, 10, 10, 5, 5, -5, + -5, 5, 10, 10, 10, 10, 5, -5, + -5, 10, 0, 5, 5, 0, 10, -5, + -5, -5, 0, -5, -5, 0, -5, -5 }, // Ending new[] { -5, -5, -5, -5, -5, -5, -5, -5, - -5, 0, 0, 0, 0, 0, 0, -5, - -5, 0, 0, 0, 0, 0, 0, -5, - -5, 0, 0, 0, 0, 0, 0, -5, - -5, 0, 0, 0, 0, 0, 0, -5, - -5, 0, 0, 0, 0, 0, 0, -5, - -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, -5, -5, -5, -5, -5, -5, -5, -5 } }; @@ -32,15 +32,15 @@ public static class BishopTables // White new [] { - TableOperations.FlipVertically(Pattern[(int)GamePhase.Opening]), - TableOperations.FlipVertically(Pattern[(int)GamePhase.Ending]) + TableOperations.FlipVertically(Pattern[GamePhase.Opening]), + TableOperations.FlipVertically(Pattern[GamePhase.Ending]) }, // Black new [] { - TableOperations.FlipHorizontally(Pattern[(int)GamePhase.Opening]), - TableOperations.FlipHorizontally(Pattern[(int)GamePhase.Ending]) + TableOperations.FlipHorizontally(Pattern[GamePhase.Opening]), + TableOperations.FlipHorizontally(Pattern[GamePhase.Ending]) } }; } diff --git a/Cosette/Engine/Ai/Score/PieceSquareTables/KingTables.cs b/Cosette/Engine/Ai/Score/PieceSquareTables/KingTables.cs index ac95da2..9861616 100644 --- a/Cosette/Engine/Ai/Score/PieceSquareTables/KingTables.cs +++ b/Cosette/Engine/Ai/Score/PieceSquareTables/KingTables.cs @@ -13,7 +13,7 @@ public static class KingTables -30, -40, -40, -50, -50, -40, -40, -30, -20, -30, -30, -40, -40, -30, -30, -20, -10, -20, -20, -20, -20, -20, -20, -10, - -5, -5, -10, -5, -5, -5, -10, -5, + -5, -5, -20, -5, -5, -5, -20, -5, 0, 5, 20, -10, 0, -10, 20, 0 }, // Ending @@ -32,15 +32,15 @@ public static class KingTables // White new [] { - TableOperations.FlipVertically(Pattern[(int)GamePhase.Opening]), - TableOperations.FlipVertically(Pattern[(int)GamePhase.Ending]) + TableOperations.FlipVertically(Pattern[GamePhase.Opening]), + TableOperations.FlipVertically(Pattern[GamePhase.Ending]) }, // Black new [] { - TableOperations.FlipHorizontally(Pattern[(int)GamePhase.Opening]), - TableOperations.FlipHorizontally(Pattern[(int)GamePhase.Ending]) + TableOperations.FlipHorizontally(Pattern[GamePhase.Opening]), + TableOperations.FlipHorizontally(Pattern[GamePhase.Ending]) } }; } diff --git a/Cosette/Engine/Ai/Score/PieceSquareTables/KnightTables.cs b/Cosette/Engine/Ai/Score/PieceSquareTables/KnightTables.cs index 4cb1957..55710fe 100644 --- a/Cosette/Engine/Ai/Score/PieceSquareTables/KnightTables.cs +++ b/Cosette/Engine/Ai/Score/PieceSquareTables/KnightTables.cs @@ -18,12 +18,12 @@ public static class KnightTables // Ending new[] { -20, -10, -10, -10, -10, -10, -10, -20, - -10, 0, 0, 0, 0, 0, 0, -10, - -10, 0, 0, 0, 0, 0, 0, -10, - -10, 0, 0, 0, 0, 0, 0, -10, - -10, 0, 0, 0, 0, 0, 0, -10, - -10, 0, 0, 0, 0, 0, 0, -10, - -10, 0, 0, 0, 0, 0, 0, -10, + -10, 0, 0, 0, 0, 0, 0, -10, + -10, 0, 0, 0, 0, 0, 0, -10, + -10, 0, 0, 0, 0, 0, 0, -10, + -10, 0, 0, 0, 0, 0, 0, -10, + -10, 0, 0, 0, 0, 0, 0, -10, + -10, 0, 0, 0, 0, 0, 0, -10, -20, -10, -10, -10, -10, -10, -10, -20 } }; @@ -32,15 +32,15 @@ public static class KnightTables // White new [] { - TableOperations.FlipVertically(Pattern[(int)GamePhase.Opening]), - TableOperations.FlipVertically(Pattern[(int)GamePhase.Ending]) + TableOperations.FlipVertically(Pattern[GamePhase.Opening]), + TableOperations.FlipVertically(Pattern[GamePhase.Ending]) }, // Black new [] { - TableOperations.FlipHorizontally(Pattern[(int)GamePhase.Opening]), - TableOperations.FlipHorizontally(Pattern[(int)GamePhase.Ending]) + TableOperations.FlipHorizontally(Pattern[GamePhase.Opening]), + TableOperations.FlipHorizontally(Pattern[GamePhase.Ending]) } }; } diff --git a/Cosette/Engine/Ai/Score/PieceSquareTables/PawnTables.cs b/Cosette/Engine/Ai/Score/PieceSquareTables/PawnTables.cs index 4e7bd72..95e5704 100644 --- a/Cosette/Engine/Ai/Score/PieceSquareTables/PawnTables.cs +++ b/Cosette/Engine/Ai/Score/PieceSquareTables/PawnTables.cs @@ -7,24 +7,24 @@ public static class PawnTables public static int[][] Pattern = { // Opening - new[] {20, 20, 20, 20, 20, 20, 20, 20, - 15, 15, 15, 15, 15, 15, 15, 15, - 10, 10, 10, 10, 10, 10, 10, 10, - 5, 5, 5, 5, 5, 5, 5, 5, - -15, -15, -15, 10, 10, -15, -15, -15, - 10, -5, -10, 5, 5, -10, -5, 10, - 10, 0, 0, -10, -10, 10, 10, 10, - 0, 0, 0, 0, 0, 0, 0, 0 }, + new[] { 20, 20, 20, 20, 20, 20, 20, 20, + 15, 15, 15, 15, 15, 15, 15, 15, + 10, 10, 10, 10, 10, 10, 10, 10, + 5, 5, 5, 5, 5, 5, 5, 5, + -15, -15, -15, 10, 10, -15, -15, -15, + 10, 10, -10, 5, 5, -10, 10, 5, + 10, 10, 10, -10, -10, 10, 10, 10, + 0, 0, 0, 0, 0, 0, 0, 0 }, // Ending - new[] { 25, 25, 25, 25, 25, 25, 25, 25, - 20, 20, 20, 20, 20, 20, 20, 20, - 15, 15, 15, 15, 15, 15, 15, 15, - 10, 10, 10, 10, 10, 10, 10, 10, - 5, 5, 5, 5, 5, 5, 5, 5, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 } + new[] { 25, 25, 25, 25, 25, 25, 25, 25, + 20, 20, 20, 20, 20, 20, 20, 20, + 15, 15, 15, 15, 15, 15, 15, 15, + 10, 10, 10, 10, 10, 10, 10, 10, + 5, 5, 5, 5, 5, 5, 5, 5, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 } }; public static int[][][] Values = @@ -32,15 +32,15 @@ public static class PawnTables // White new [] { - TableOperations.FlipVertically(Pattern[(int)GamePhase.Opening]), - TableOperations.FlipVertically(Pattern[(int)GamePhase.Ending]) + TableOperations.FlipVertically(Pattern[GamePhase.Opening]), + TableOperations.FlipVertically(Pattern[GamePhase.Ending]) }, // Black new [] { - TableOperations.FlipHorizontally(Pattern[(int)GamePhase.Opening]), - TableOperations.FlipHorizontally(Pattern[(int)GamePhase.Ending]) + TableOperations.FlipHorizontally(Pattern[GamePhase.Opening]), + TableOperations.FlipHorizontally(Pattern[GamePhase.Ending]) } }; } diff --git a/Cosette/Engine/Ai/Score/PieceSquareTables/QueenTables.cs b/Cosette/Engine/Ai/Score/PieceSquareTables/QueenTables.cs index 93227c7..4f2b9f5 100644 --- a/Cosette/Engine/Ai/Score/PieceSquareTables/QueenTables.cs +++ b/Cosette/Engine/Ai/Score/PieceSquareTables/QueenTables.cs @@ -7,24 +7,24 @@ public static class QueenTables public static int[][] Pattern = { // Opening - new[] { -15, -10, -10, -5, -5, -10, -10, -15, - -10, 0, 0, 0, 0, 0, 0, -10, - -10, 0, 5, 5, 5, 5, 0, -10, - -5, 0, 5, 10, 10, 5, 0, -5, - -5, 0, 5, 10, 10, 5, 0, -5, - -10, 0, 5, 5, 5, 5, 0, -10, - -10, 0, 0, 0, 0, 0, 0, -10, - -15, -10, -10, -5, -5, -10, -10, -15 }, + new[] { -15, -10, -10, -5, -5, -10, -10, -15, + -10, 0, 0, 0, 0, 0, 0, -10, + -10, 0, 5, 5, 5, 5, 0, -10, + -5, 0, 5, 10, 10, 5, 0, -5, + -5, 0, 5, 10, 10, 5, 0, -5, + -10, 0, 5, 5, 5, 5, 0, -10, + -10, 0, 0, 0, 0, 0, 0, -10, + -15, -10, -10, -5, -5, -10, -10, -15 }, // Ending - new[] { 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5} + new[] { 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5 } }; public static int[][][] Values = @@ -32,15 +32,15 @@ public static class QueenTables // White new [] { - TableOperations.FlipVertically(Pattern[(int)GamePhase.Opening]), - TableOperations.FlipVertically(Pattern[(int)GamePhase.Ending]) + TableOperations.FlipVertically(Pattern[GamePhase.Opening]), + TableOperations.FlipVertically(Pattern[GamePhase.Ending]) }, // Black new [] { - TableOperations.FlipHorizontally(Pattern[(int)GamePhase.Opening]), - TableOperations.FlipHorizontally(Pattern[(int)GamePhase.Ending]) + TableOperations.FlipHorizontally(Pattern[GamePhase.Opening]), + TableOperations.FlipHorizontally(Pattern[GamePhase.Ending]) } }; } diff --git a/Cosette/Engine/Ai/Score/PieceSquareTables/RookTables.cs b/Cosette/Engine/Ai/Score/PieceSquareTables/RookTables.cs index 7757464..cb8bfd1 100644 --- a/Cosette/Engine/Ai/Score/PieceSquareTables/RookTables.cs +++ b/Cosette/Engine/Ai/Score/PieceSquareTables/RookTables.cs @@ -7,24 +7,24 @@ public static class RookTables public static int[][] Pattern = { // Opening - new[] {10, 0, 0, 0, 0, 0, 0, 10, - 10, 0, 0, 0, 0, 0, 0, 10, - 10, 0, 0, 0, 0, 0, 0, 10, - 10, 0, 0, 0, 0, 0, 0, 10, - 5, 0, 0, 0, 0, 0, 0, 5, - 5, 0, 0, 0, 0, 0, 0, 5, - 5, 0, 0, 0, 0, 0, 0, 5, - 0, 0, 0, 10, 5, 10, 0, 0 }, + new[] { 10, 0, 0, 0, 0, 0, 0, 10, + 10, 0, 0, 0, 0, 0, 0, 10, + 10, 0, 0, 0, 0, 0, 0, 10, + 10, 0, 0, 0, 0, 0, 0, 10, + 5, 0, 0, 0, 0, 0, 0, 5, + 5, 0, 0, 0, 0, 0, 0, 5, + 5, 0, 0, 0, 0, 0, 0, 5, + 0, 0, 0, 10, 5, 10, 0, 0 }, // Ending - new[] { 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5 } + new[] { 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5 } }; public static int[][][] Values = @@ -32,15 +32,15 @@ public static class RookTables // White new [] { - TableOperations.FlipVertically(Pattern[(int)GamePhase.Opening]), - TableOperations.FlipVertically(Pattern[(int)GamePhase.Ending]) + TableOperations.FlipVertically(Pattern[GamePhase.Opening]), + TableOperations.FlipVertically(Pattern[GamePhase.Ending]) }, // Black new [] { - TableOperations.FlipHorizontally(Pattern[(int)GamePhase.Opening]), - TableOperations.FlipHorizontally(Pattern[(int)GamePhase.Ending]) + TableOperations.FlipHorizontally(Pattern[GamePhase.Opening]), + TableOperations.FlipHorizontally(Pattern[GamePhase.Ending]) } }; } diff --git a/Cosette/Engine/Ai/Score/PieceSquareTables/TableOperations.cs b/Cosette/Engine/Ai/Score/PieceSquareTables/TableOperations.cs index 63497ae..c617e52 100644 --- a/Cosette/Engine/Ai/Score/PieceSquareTables/TableOperations.cs +++ b/Cosette/Engine/Ai/Score/PieceSquareTables/TableOperations.cs @@ -5,9 +5,9 @@ public static class TableOperations public static int[] FlipVertically(int[] array) { var result = new int[64]; - for (var i = 0; i < 64; i++) + for (var fieldIndex = 0; fieldIndex < 64; fieldIndex++) { - result[i] = array[63 - i]; + result[fieldIndex] = array[63 - fieldIndex]; } return result; diff --git a/Cosette/Engine/Ai/Search/IterativeDeepening.cs b/Cosette/Engine/Ai/Search/IterativeDeepening.cs index 418d026..ce2474e 100644 --- a/Cosette/Engine/Ai/Search/IterativeDeepening.cs +++ b/Cosette/Engine/Ai/Search/IterativeDeepening.cs @@ -1,10 +1,8 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; using Cosette.Engine.Ai.Ordering; using Cosette.Engine.Ai.Score; -using Cosette.Engine.Ai.Time; using Cosette.Engine.Ai.Transposition; using Cosette.Engine.Board; using Cosette.Engine.Common; @@ -14,85 +12,63 @@ namespace Cosette.Engine.Ai.Search { public static class IterativeDeepening { - public static bool AbortSearch { get; set; } - public static bool WaitForStopCommand { get; set; } - public static List MoveRestrictions { get; set; } - public static ulong MaxNodesCount { get; set; } public static event EventHandler OnSearchUpdate; - public static Move FindBestMove(BoardState board, int remainingTime, int depth, int moveNumber) + public static Move FindBestMove(SearchContext context) { - var statistics = new SearchStatistics(); - var expectedExecutionTime = 0; + HistoryHeuristic.Clear(); + var expectedExecutionTime = 0; var alpha = SearchConstants.MinValue; var beta = SearchConstants.MaxValue; - - TranspositionTable.Clear(); - HistoryHeuristic.Clear(); - - var timeLimit = TimeScheduler.CalculateTimeForMove(remainingTime, moveNumber); + var lastSearchTime = 10ul; + var bestMove = Move.Empty; var stopwatch = Stopwatch.StartNew(); - var lastTotalNodesCount = 100ul; - var bestMove = new Move(); - - AbortSearch = false; - for (var currentDepth = 1; currentDepth < SearchConstants.MaxDepth && !IsScoreCheckmate(statistics.Score); currentDepth++) + for (var depth = 1; ShouldContinueDeepening(context, depth, expectedExecutionTime); depth++) { - if (expectedExecutionTime > timeLimit) - { - break; - } - - statistics.Clear(); - - statistics.Board = board; - statistics.Depth = currentDepth; - statistics.Score = NegaMax.FindBestMove(board, currentDepth, 0, alpha, beta, true, true, statistics); - statistics.SearchTime = (ulong) stopwatch.ElapsedMilliseconds; - statistics.PrincipalVariationMovesCount = GetPrincipalVariation(board, statistics.PrincipalVariation, 0); - - bestMove = statistics.PrincipalVariation[0]; - stopwatch.Stop(); - - if (AbortSearch) - { - break; - } + context.Statistics = new SearchStatistics(); - OnSearchUpdate?.Invoke(null, statistics); - stopwatch.Start(); + context.Statistics.Board = context.BoardState; + context.Statistics.Depth = depth; + context.Statistics.Score = NegaMax.FindBestMove(context, depth, 0, alpha, beta, true); + context.Statistics.SearchTime = (ulong)stopwatch.ElapsedMilliseconds; - if (currentDepth == depth) + if (context.AbortSearch) { break; } + + context.Statistics.PrincipalVariationMovesCount = GetPrincipalVariation(context.BoardState, context.Statistics.PrincipalVariation, 0); + bestMove = context.Statistics.PrincipalVariation[0]; - var ratio = (float)statistics.TotalNodes / lastTotalNodesCount; - expectedExecutionTime = (int)(statistics.SearchTime * ratio); - lastTotalNodesCount = statistics.TotalNodes; - } + OnSearchUpdate?.Invoke(null, context.Statistics); - while (WaitForStopCommand) - { - Task.Delay(50).GetAwaiter().GetResult(); + var ratio = (float)context.Statistics.SearchTime / lastSearchTime; + expectedExecutionTime = (int)(context.Statistics.SearchTime * ratio); + lastSearchTime = context.Statistics.SearchTime; } - if (AbortSearch) + while (context.WaitForStopCommand) { - TranspositionTable.Clear(); - AbortSearch = false; + Task.Delay(1).GetAwaiter().GetResult(); } + context.AbortSearch = false; return bestMove; } - public static bool IsScoreCheckmate(int score) + public static bool ShouldContinueDeepening(SearchContext context, int depth, int expectedExecutionTime) + { + return depth < context.MaxDepth && + expectedExecutionTime <= context.MaxTime; + } + + public static bool IsScoreNearCheckmate(int score) { var scoreAbs = Math.Abs(score); - return scoreAbs > EvaluationConstants.Checkmate - SearchConstants.MaxDepth && - scoreAbs < EvaluationConstants.Checkmate + SearchConstants.MaxDepth; + return scoreAbs >= EvaluationConstants.Checkmate - SearchConstants.MaxDepth && + scoreAbs <= EvaluationConstants.Checkmate + SearchConstants.MaxDepth; } public static int GetMovesToCheckmate(int score) @@ -103,28 +79,25 @@ public static int GetMovesToCheckmate(int score) private static int GetPrincipalVariation(BoardState board, Move[] moves, int movesCount) { var entry = TranspositionTable.Get(board.Hash); - if (entry.Type != TranspositionTableEntryType.ExactScore || entry.Hash != board.Hash || movesCount >= SearchConstants.MaxDepth) + if (entry.Flags == TranspositionTableEntryFlags.ExactScore && entry.IsKeyValid(board.Hash) && movesCount < SearchConstants.MaxDepth) { - return movesCount; - } + moves[movesCount] = entry.BestMove; + board.MakeMove(entry.BestMove); - moves[movesCount] = entry.BestMove; + var enemyColor = ColorOperations.Invert(board.ColorToMove); + var king = board.Pieces[enemyColor][Piece.King]; + var kingField = BitOperations.BitScan(king); - board.MakeMove(entry.BestMove); - - var enemyColor = ColorOperations.Invert(board.ColorToMove); - var king = board.Pieces[(int)enemyColor][(int) Piece.King]; - var kingField = BitOperations.BitScan(king); + if (board.IsFieldAttacked(enemyColor, (byte)kingField)) + { + board.UndoMove(entry.BestMove); + return movesCount; + } - if (board.IsFieldAttacked(enemyColor, (byte) kingField)) - { + movesCount = GetPrincipalVariation(board, moves, movesCount + 1); board.UndoMove(entry.BestMove); - return movesCount; } - movesCount = GetPrincipalVariation(board, moves, movesCount + 1); - board.UndoMove(entry.BestMove); - return movesCount; } } diff --git a/Cosette/Engine/Ai/Search/NegaMax.cs b/Cosette/Engine/Ai/Search/NegaMax.cs index 67b496c..7b89386 100644 --- a/Cosette/Engine/Ai/Search/NegaMax.cs +++ b/Cosette/Engine/Ai/Search/NegaMax.cs @@ -1,7 +1,7 @@ using System; -using System.Runtime.CompilerServices; using Cosette.Engine.Ai.Ordering; using Cosette.Engine.Ai.Score; +using Cosette.Engine.Ai.Score.Evaluators; using Cosette.Engine.Ai.Transposition; using Cosette.Engine.Board; using Cosette.Engine.Common; @@ -11,79 +11,105 @@ namespace Cosette.Engine.Ai.Search { public static class NegaMax { - public static int FindBestMove(BoardState board, int depth, int ply, int alpha, int beta, bool allowNullMove, bool pvNode, SearchStatistics statistics) + public static int FindBestMove(SearchContext context, int depth, int ply, int alpha, int beta, bool allowNullMove) { - if (statistics.Nodes >= IterativeDeepening.MaxNodesCount) + if (context.Statistics.Nodes >= context.MaxNodesCount) { - IterativeDeepening.AbortSearch = true; + context.AbortSearch = true; return 0; } - if (IterativeDeepening.AbortSearch) + if (context.AbortSearch) { return 0; } - statistics.Nodes++; + context.Statistics.Nodes++; - if (board.Pieces[(int)board.ColorToMove][(int)Piece.King] == 0) + if (context.BoardState.Pieces[context.BoardState.ColorToMove][Piece.King] == 0) { - statistics.Leafs++; + context.Statistics.Leafs++; return -EvaluationConstants.Checkmate + ply; } - if (board.IsThreefoldRepetition()) + if (context.BoardState.IsThreefoldRepetition()) { - statistics.Leafs++; + context.Statistics.Leafs++; + return EvaluationConstants.ThreefoldRepetition; + } + + if (context.BoardState.IsFiftyMoveRuleDraw()) + { + context.Statistics.Leafs++; + + if (context.BoardState.IsKingChecked(ColorOperations.Invert(context.BoardState.ColorToMove))) + { + return EvaluationConstants.Checkmate + ply; + } + return EvaluationConstants.ThreefoldRepetition; } if (depth <= 0) { - statistics.Leafs++; - return QuiescenceSearch.FindBestMove(board, depth, ply, alpha, beta, statistics); + context.Statistics.Leafs++; + return QuiescenceSearch.FindBestMove(context, depth, ply, alpha, beta); } var originalAlpha = alpha; - var bestMove = new Move(); + var bestMove = Move.Empty; + var pvNode = beta - alpha > 1; - var entry = TranspositionTable.Get(board.Hash); - if (entry.Hash == board.Hash) + var entry = TranspositionTable.Get(context.BoardState.Hash); + if (entry.IsKeyValid(context.BoardState.Hash)) { #if DEBUG - statistics.TTHits++; + context.Statistics.TTHits++; #endif + if (entry.Flags != TranspositionTableEntryFlags.AlphaScore) + { + bestMove = entry.BestMove; + } if (entry.Depth >= depth) { - switch (entry.Type) + switch (entry.Flags) { - case TranspositionTableEntryType.ExactScore: + case TranspositionTableEntryFlags.AlphaScore: { - return entry.Score; + if (entry.Score < beta) + { + beta = entry.Score; + } + + break; } - case TranspositionTableEntryType.LowerBound: + case TranspositionTableEntryFlags.ExactScore: { - if (entry.Score > alpha) + if (!pvNode || entry.Age == context.TranspositionTableEntryAge) { - alpha = entry.Score; - bestMove = entry.BestMove; + entry.Score = (short)TranspositionTable.TTToRegularScore(entry.Score, ply); + return entry.Score; } break; } - case TranspositionTableEntryType.UpperBound: + case TranspositionTableEntryFlags.BetaScore: { - beta = Math.Min(beta, entry.Score); + if (entry.Score > alpha) + { + alpha = entry.Score; + } + break; } } if (alpha >= beta) { - statistics.Leafs++; + context.Statistics.BetaCutoffs++; return entry.Score; } } @@ -91,116 +117,163 @@ public static int FindBestMove(BoardState board, int depth, int ply, int alpha, #if DEBUG else { - statistics.TTNonHits++; + context.Statistics.TTNonHits++; } #endif -#if DEBUG - if (entry.Type != TranspositionTableEntryType.Invalid && entry.Hash != board.Hash) + if (NullWindowCanBeApplied(context.BoardState, depth, allowNullMove, pvNode)) { - statistics.TTCollisions++; - } -#endif - - if (NullWindowCanBeApplied(board, depth, allowNullMove, pvNode)) - { - board.MakeNullMove(); - var score = -FindBestMove(board, depth - 1 - SearchConstants.NullWindowDepthReduction, ply + 1, -beta, -beta + 1, false, pvNode, statistics); - board.UndoNullMove(); + context.BoardState.MakeNullMove(); + var score = -FindBestMove(context, depth - 1 - SearchConstants.NullWindowDepthReduction, ply + 1, -beta, -beta + 1, false); + context.BoardState.UndoNullMove(); if (score >= beta) { - statistics.BetaCutoffs++; + context.Statistics.BetaCutoffs++; return score; } } + + Span moves = stackalloc Move[SearchConstants.MaxMovesCount]; + Span moveValues = stackalloc short[SearchConstants.MaxMovesCount]; + var movesCount = 0; + var movesGenerated = false; - Span moves = stackalloc Move[128]; - Span moveValues = stackalloc int[128]; - - var movesCount = board.GetAvailableMoves(moves); - MoveOrdering.AssignValues(board, moves, moveValues, movesCount, depth, entry); + if (bestMove == Move.Empty) + { + movesCount = context.BoardState.GetAvailableMoves(moves); + MoveOrdering.AssignValues(context.BoardState, moves, moveValues, movesCount, depth, bestMove); + movesGenerated = true; + } + else + { + moves[0] = bestMove; + movesCount = 1; + } var pvs = true; for (var moveIndex = 0; moveIndex < movesCount; moveIndex++) { MoveOrdering.SortNextBestMove(moves, moveValues, movesCount, moveIndex); - if (IterativeDeepening.MoveRestrictions != null && ply == 0) + if (context.MoveRestrictions != null && ply == 0) { - if (!IterativeDeepening.MoveRestrictions.Contains(moves[moveIndex])) + if (!context.MoveRestrictions.Contains(moves[moveIndex])) { continue; } } - board.MakeMove(moves[moveIndex]); + context.BoardState.MakeMove(moves[moveIndex]); var score = 0; if (pvs) { - score = -FindBestMove(board, depth - 1, ply + 1, -beta, -alpha, allowNullMove, true, statistics); + score = -FindBestMove(context, depth - 1, ply + 1, -beta, -alpha, allowNullMove); pvs = false; } else { var reducedDepth = depth; - if (LMRCanBeApplied(board, depth, moveIndex, moves)) + var kingCheckedAfterMove = context.BoardState.IsKingChecked(context.BoardState.ColorToMove); + + if (LMRCanBeApplied(depth, kingCheckedAfterMove, moveIndex, moves)) { reducedDepth = LMRGetReducedDepth(depth, pvNode); } - score = -FindBestMove(board, reducedDepth - 1, ply + 1, -alpha - 1, -alpha, allowNullMove, false, statistics); + score = -FindBestMove(context, reducedDepth - 1, ply + 1, -alpha - 1, -alpha, allowNullMove); if (score > alpha) { - score = -FindBestMove(board, depth - 1, ply + 1, -beta, -alpha, allowNullMove, false, statistics); + score = -FindBestMove(context, depth - 1, ply + 1, -beta, -alpha, allowNullMove); } } + context.BoardState.UndoMove(moves[moveIndex]); + if (score > alpha) { alpha = score; bestMove = moves[moveIndex]; - } - board.UndoMove(moves[moveIndex]); - if (alpha >= beta) - { - if (moves[moveIndex].IsQuiet()) + if (alpha >= beta) { - KillerHeuristic.AddKillerMove(moves[moveIndex], board.ColorToMove, depth); - HistoryHeuristic.AddHistoryMove(board.ColorToMove, moves[moveIndex].From, moves[moveIndex].To, depth * depth); - } + if (moves[moveIndex].IsQuiet()) + { + KillerHeuristic.AddKillerMove(moves[moveIndex], context.BoardState.ColorToMove, depth); + HistoryHeuristic.AddHistoryMove(context.BoardState.ColorToMove, moves[moveIndex].From, moves[moveIndex].To, depth); + } #if DEBUG - if (moveIndex == 0) - { - statistics.BetaCutoffsAtFirstMove++; - } - else - { - statistics.BetaCutoffsNotAtFirstMove++; - } + if (moveIndex == 0) + { + context.Statistics.BetaCutoffsAtFirstMove++; + } + else + { + context.Statistics.BetaCutoffsNotAtFirstMove++; + } #endif - statistics.BetaCutoffs++; - break; + context.Statistics.BetaCutoffs++; + break; + } + } + + if (!movesGenerated) + { + movesCount = context.BoardState.GetAvailableMoves(moves); + MoveOrdering.AssignValues(context.BoardState, moves, moveValues, movesCount, depth, bestMove); + MoveOrdering.SortNextBestMove(moves, moveValues, movesCount, 0); + movesGenerated = true; } } - if (alpha == -EvaluationConstants.Checkmate + ply + 2 && !board.IsKingChecked(board.ColorToMove)) + // Don't save invalid scores to the transposition table + if (context.AbortSearch) + { + return 0; + } + + // Don't add invalid move (done after checkmate) to prevent strange behaviors + if (alpha == -(-EvaluationConstants.Checkmate + ply + 1)) { - alpha = 0; + return alpha; } - var entryType = alpha <= originalAlpha ? TranspositionTableEntryType.UpperBound : - alpha >= beta ? TranspositionTableEntryType.LowerBound : - TranspositionTableEntryType.ExactScore; - TranspositionTable.Add(board.Hash, (byte)depth, (short)alpha, bestMove, entryType); + // Set alpha to zero if the stalemate has been detected + if (alpha == -EvaluationConstants.Checkmate + ply + 2) + { + if (!context.BoardState.IsKingChecked(context.BoardState.ColorToMove)) + { + alpha = 0; + } + } + + if (entry.Age < context.TranspositionTableEntryAge || entry.Depth < depth) + { + var valueToSave = alpha; + var entryType = alpha <= originalAlpha ? TranspositionTableEntryFlags.AlphaScore : + alpha >= beta ? TranspositionTableEntryFlags.BetaScore : + TranspositionTableEntryFlags.ExactScore; + + if (entryType == TranspositionTableEntryFlags.ExactScore) + { + valueToSave = TranspositionTable.RegularToTTScore(alpha, ply); + } + + TranspositionTable.Add(context.BoardState.Hash, (byte)depth, (short)valueToSave, + (byte)context.TranspositionTableEntryAge, bestMove, entryType); #if DEBUG - statistics.TTEntries++; + if (entry.Flags != TranspositionTableEntryFlags.Invalid) + { + context.Statistics.TTReplacements++; + } + + context.Statistics.TTAddedEntries++; #endif + } return alpha; } @@ -208,13 +281,14 @@ public static int FindBestMove(BoardState board, int depth, int ply, int alpha, private static bool NullWindowCanBeApplied(BoardState board, int depth, bool allowNullMove, bool pvNode) { return !pvNode && allowNullMove && depth >= SearchConstants.NullWindowMinimalDepth && - board.GetGamePhase() == GamePhase.Opening && !board.IsKingChecked(board.ColorToMove); + board.GetGamePhase() == GamePhase.Opening && + !board.IsKingChecked(board.ColorToMove); } - private static bool LMRCanBeApplied(BoardState board, int depth, int moveIndex, Span moves) + private static bool LMRCanBeApplied(int depth, bool kingChecked, int moveIndex, Span moves) { return depth >= SearchConstants.LMRMinimalDepth && moveIndex > SearchConstants.LMRMovesWithoutReduction && - moves[moveIndex].IsQuiet() && !board.IsKingChecked(board.ColorToMove); + moves[moveIndex].IsQuiet() && !kingChecked; } private static int LMRGetReducedDepth(int depth, bool pvNode) diff --git a/Cosette/Engine/Ai/Search/QuiescenceSearch.cs b/Cosette/Engine/Ai/Search/QuiescenceSearch.cs index 10fdc41..b72ad8e 100644 --- a/Cosette/Engine/Ai/Search/QuiescenceSearch.cs +++ b/Cosette/Engine/Ai/Search/QuiescenceSearch.cs @@ -1,7 +1,7 @@ using System; +using Cosette.Engine.Ai.Ordering; using Cosette.Engine.Ai.Score; using Cosette.Engine.Ai.Transposition; -using Cosette.Engine.Board; using Cosette.Engine.Common; using Cosette.Engine.Moves; @@ -9,26 +9,52 @@ namespace Cosette.Engine.Ai.Search { public static class QuiescenceSearch { - public static int FindBestMove(BoardState board, int depth, int ply, int alpha, int beta, SearchStatistics statistics) + public static int FindBestMove(SearchContext context, int depth, int ply, int alpha, int beta) { - statistics.QNodes++; + context.Statistics.QNodes++; - if (ply > statistics.SelectiveDepth) + if (ply > context.Statistics.SelectiveDepth) { - statistics.SelectiveDepth = ply; + context.Statistics.SelectiveDepth = ply; } - if (board.Pieces[(int)board.ColorToMove][(int)Piece.King] == 0) + if (context.BoardState.Pieces[context.BoardState.ColorToMove][Piece.King] == 0) { - statistics.QLeafs++; + context.Statistics.QLeafs++; return -EvaluationConstants.Checkmate + ply; } - var standPat = Evaluation.Evaluate(board, board.ColorToMove); + var standPat = 0; + + var evaluationEntry = EvaluationHashTable.Get(context.BoardState.Hash); + if (evaluationEntry.IsKeyValid(context.BoardState.Hash)) + { + standPat = evaluationEntry.Score; + +#if DEBUG + context.Statistics.EvaluationStatistics.EHTHits++; +#endif + } + else + { + standPat = Evaluation.Evaluate(context.BoardState, context.Statistics.EvaluationStatistics); + EvaluationHashTable.Add(context.BoardState.Hash, (short)standPat); + +#if DEBUG + context.Statistics.EvaluationStatistics.EHTNonHits++; + context.Statistics.EvaluationStatistics.EHTAddedEntries++; + + if (evaluationEntry.Key != 0 || evaluationEntry.Score != 0) + { + context.Statistics.EvaluationStatistics.EHTReplacements++; + } +#endif + } + if (standPat >= beta) { - statistics.QLeafs++; - return beta; + context.Statistics.QLeafs++; + return standPat; } if (alpha < standPat) @@ -36,40 +62,40 @@ public static int FindBestMove(BoardState board, int depth, int ply, int alpha, alpha = standPat; } - Span moves = stackalloc Move[128]; - Span moveValues = stackalloc int[128]; + Span moves = stackalloc Move[SearchConstants.MaxMovesCount]; + Span moveValues = stackalloc short[SearchConstants.MaxMovesCount]; - var movesCount = board.GetAvailableQMoves(moves); - MoveOrdering.AssignQValues(board, moves, moveValues, movesCount); + var movesCount = context.BoardState.GetAvailableQMoves(moves); + MoveOrdering.AssignQValues(context.BoardState, moves, moveValues, movesCount); for (var moveIndex = 0; moveIndex < movesCount; moveIndex++) { MoveOrdering.SortNextBestMove(moves, moveValues, movesCount, moveIndex); - board.MakeMove(moves[moveIndex]); - var score = -FindBestMove(board, depth - 1, ply + 1, -beta, -alpha, statistics); - board.UndoMove(moves[moveIndex]); + context.BoardState.MakeMove(moves[moveIndex]); + var score = -FindBestMove(context, depth - 1, ply + 1, -beta, -alpha); + context.BoardState.UndoMove(moves[moveIndex]); - if (score >= beta) + if (score > alpha) { -#if DEBUG - if (moveIndex == 0) - { - statistics.QBetaCutoffsAtFirstMove++; - } - else + alpha = score; + + if (alpha >= beta) { - statistics.QBetaCutoffsNotAtFirstMove++; - } +#if DEBUG + if (moveIndex == 0) + { + context.Statistics.QBetaCutoffsAtFirstMove++; + } + else + { + context.Statistics.QBetaCutoffsNotAtFirstMove++; + } #endif - statistics.QBetaCutoffs++; - return beta; - } - - if (score > alpha) - { - alpha = score; + context.Statistics.QBetaCutoffs++; + break; + } } } diff --git a/Cosette/Engine/Ai/Search/SearchConstants.cs b/Cosette/Engine/Ai/Search/SearchConstants.cs index 6adb6c8..5a44763 100644 --- a/Cosette/Engine/Ai/Search/SearchConstants.cs +++ b/Cosette/Engine/Ai/Search/SearchConstants.cs @@ -2,12 +2,10 @@ { public static class SearchConstants { - public const int MinValue = -10000000; - public const int MaxValue = 10000000; + public const int MinValue = short.MinValue; + public const int MaxValue = short.MaxValue; public const int MaxDepth = 32; - - public const int DefaultHashTableSize = 8; - public const int DefaultPawnHashTableSize = 8; + public const int MaxMovesCount = 128; public const int NullWindowMinimalDepth = 5; public const int NullWindowDepthReduction = 3; diff --git a/Cosette/Engine/Ai/Search/SearchContext.cs b/Cosette/Engine/Ai/Search/SearchContext.cs new file mode 100644 index 0000000..69891d1 --- /dev/null +++ b/Cosette/Engine/Ai/Search/SearchContext.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Cosette.Engine.Board; +using Cosette.Engine.Moves; + +namespace Cosette.Engine.Ai.Search +{ + public class SearchContext + { + public BoardState BoardState { get; set; } + public SearchStatistics Statistics { get; set; } + + public int MaxDepth { get; set; } + public int MaxTime { get; set; } + public bool AbortSearch { get; set; } + public bool WaitForStopCommand { get; set; } + public ulong MaxNodesCount { get; set; } + public List MoveRestrictions { get; set; } + public int TranspositionTableEntryAge { get; set; } + + public SearchContext(BoardState boardState) + { + BoardState = boardState; + Statistics = new SearchStatistics(); + + MaxDepth = SearchConstants.MaxDepth; + MaxTime = int.MaxValue; + WaitForStopCommand = false; + MaxNodesCount = ulong.MaxValue; + MoveRestrictions = null; + TranspositionTableEntryAge = boardState.MovesCount; + } + } +} diff --git a/Cosette/Engine/Ai/Search/SearchStatistics.cs b/Cosette/Engine/Ai/Search/SearchStatistics.cs index 297e90f..d984280 100644 --- a/Cosette/Engine/Ai/Search/SearchStatistics.cs +++ b/Cosette/Engine/Ai/Search/SearchStatistics.cs @@ -1,4 +1,5 @@ using System; +using Cosette.Engine.Ai.Score; using Cosette.Engine.Board; using Cosette.Engine.Moves; @@ -7,6 +8,7 @@ namespace Cosette.Engine.Ai.Search public class SearchStatistics { public BoardState Board { get; set; } + public EvaluationStatistics EvaluationStatistics { get; set; } public int Depth { get; set; } public int SelectiveDepth { get; set; } @@ -32,11 +34,12 @@ public class SearchStatistics public ulong QBetaCutoffs { get; set; } public ulong TotalBetaCutoffs => BetaCutoffs + QBetaCutoffs; - public ulong TTEntries { get; set; } - public ulong TTCollisions { get; set; } + public ulong TTAddedEntries { get; set; } + public ulong TTReplacements { get; set; } public ulong TTHits { get; set; } public ulong TTNonHits { get; set; } public float TTHitsPercent => (float) TTHits * 100 / (TTHits + TTNonHits); + public float TTReplacesPercent => (float) TTReplacements * 100 / TTAddedEntries; public int BetaCutoffsAtFirstMove { get; set; } public int QBetaCutoffsAtFirstMove { get; set; } @@ -54,36 +57,8 @@ public class SearchStatistics public SearchStatistics() { + EvaluationStatistics = new EvaluationStatistics(); PrincipalVariation = new Move[SearchConstants.MaxDepth]; } - - public void Clear() - { - Board = null; - - Depth = 0; - SelectiveDepth = 0; - Score = 0; - SearchTime = 0; - - Nodes = 0; - QNodes = 0; - - Leafs = 0; - QLeafs = 0; - - BetaCutoffs = 0; - QBetaCutoffs = 0; - - TTEntries = 0; - TTCollisions = 0; - TTHits = 0; - - BetaCutoffsAtFirstMove = 0; - QBetaCutoffsAtFirstMove = 0; - - Array.Clear(PrincipalVariation, 0, PrincipalVariation.Length); - PrincipalVariationMovesCount = 0; - } } } diff --git a/Cosette/Engine/Ai/Time/TimeScheduler.cs b/Cosette/Engine/Ai/Time/TimeScheduler.cs index fd6cf8e..ce7eb86 100644 --- a/Cosette/Engine/Ai/Time/TimeScheduler.cs +++ b/Cosette/Engine/Ai/Time/TimeScheduler.cs @@ -1,11 +1,12 @@ -namespace Cosette.Engine.Ai.Time +using System; + +namespace Cosette.Engine.Ai.Time { public static class TimeScheduler { - public static int CalculateTimeForMove(int remainingTime, int moveNumber) + public static int CalculateTimeForMove(int remainingTime, int incTime, int moveNumber) { - // Improve this in the future - return remainingTime / 35; + return remainingTime / Math.Max(20, 40 - moveNumber) + (int)(incTime * 0.5); } } } diff --git a/Cosette/Engine/Ai/Transposition/EvaluationHashTable.cs b/Cosette/Engine/Ai/Transposition/EvaluationHashTable.cs new file mode 100644 index 0000000..165ef34 --- /dev/null +++ b/Cosette/Engine/Ai/Transposition/EvaluationHashTable.cs @@ -0,0 +1,47 @@ +using System; + +namespace Cosette.Engine.Ai.Transposition +{ + public class EvaluationHashTable + { + private static EvaluationHashTableEntry[] _table; + private static ulong _size; + + public static unsafe void Init(int sizeMegabytes) + { + var entrySize = sizeof(EvaluationHashTableEntry); + + _size = (ulong)sizeMegabytes * 1024ul * 1024ul / (ulong)entrySize; + _table = new EvaluationHashTableEntry[_size]; + } + + public static void Add(ulong hash, short score) + { + _table[hash % _size] = new EvaluationHashTableEntry(hash, score); + } + + public static EvaluationHashTableEntry Get(ulong hash) + { + return _table[hash % _size]; + } + + public static float GetFillLevel() + { + var filledEntries = 0; + for (var i = 0; i < 1000; i++) + { + if (_table[i].Key != 0 || _table[i].Score != 0) + { + filledEntries++; + } + } + + return (float)filledEntries / 10; + } + + public static void Clear() + { + Array.Clear(_table, 0, (int)_size); + } + } +} diff --git a/Cosette/Engine/Ai/Transposition/EvaluationHashTableEntry.cs b/Cosette/Engine/Ai/Transposition/EvaluationHashTableEntry.cs new file mode 100644 index 0000000..b5ba698 --- /dev/null +++ b/Cosette/Engine/Ai/Transposition/EvaluationHashTableEntry.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Cosette.Engine.Ai.Transposition +{ + public struct EvaluationHashTableEntry + { + public ushort Key { get; set; } + public short Score { get; set; } + + public EvaluationHashTableEntry(ulong hash, short score) + { + Key = (ushort)(hash >> 48); + Score = score; + } + + public bool IsKeyValid(ulong hash) + { + return Key == hash >> 48; + } + } +} diff --git a/Cosette/Engine/Ai/Transposition/HashTableAllocator.cs b/Cosette/Engine/Ai/Transposition/HashTableAllocator.cs new file mode 100644 index 0000000..4900963 --- /dev/null +++ b/Cosette/Engine/Ai/Transposition/HashTableAllocator.cs @@ -0,0 +1,28 @@ +using System; + +namespace Cosette.Engine.Ai.Transposition +{ + public static class HashTableAllocator + { + public static void Allocate() + { + Allocate(HashTableConstants.DefaultHashTablesSize); + } + + public static void Allocate(int megabytes) + { + var pawnHashTableSize = Math.Max(1, megabytes / HashTableConstants.PawnHashTableSizeDivider); + var evaluationHashTableSize = Math.Max(1, megabytes / HashTableConstants.EvaluationHashTableSizeDivider); + var transpositionTableSize = megabytes - pawnHashTableSize - evaluationHashTableSize; + + Allocate(transpositionTableSize, pawnHashTableSize, evaluationHashTableSize); + } + + public static void Allocate(int transpositionTableMegabytes, int evaluationHashTableMegabytes, int pawnHashTableMegabytes) + { + TranspositionTable.Init(transpositionTableMegabytes); + PawnHashTable.Init(evaluationHashTableMegabytes); + EvaluationHashTable.Init(pawnHashTableMegabytes); + } + } +} diff --git a/Cosette/Engine/Ai/Transposition/HashTableConstants.cs b/Cosette/Engine/Ai/Transposition/HashTableConstants.cs new file mode 100644 index 0000000..d0efd79 --- /dev/null +++ b/Cosette/Engine/Ai/Transposition/HashTableConstants.cs @@ -0,0 +1,16 @@ +namespace Cosette.Engine.Ai.Transposition +{ + public static class HashTableConstants + { + public const int DefaultHashTablesSize = 32; + + /* + * For every 128 megabytes of memory, there are: + * - 105 megabytes of transposition table + * - 21 megabytes of evaluation hash table + * - 2 megabyte of pawn hash table + */ + public const int PawnHashTableSizeDivider = 64; + public const int EvaluationHashTableSizeDivider = 6; + } +} diff --git a/Cosette/Engine/Ai/Transposition/PawnHashTable.cs b/Cosette/Engine/Ai/Transposition/PawnHashTable.cs index 8cd07e8..9e3ffcc 100644 --- a/Cosette/Engine/Ai/Transposition/PawnHashTable.cs +++ b/Cosette/Engine/Ai/Transposition/PawnHashTable.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.CompilerServices; namespace Cosette.Engine.Ai.Transposition { @@ -8,11 +7,11 @@ public static class PawnHashTable private static PawnHashTableEntry[] _table; private static ulong _size; - public static unsafe void Init(ulong sizeMegabytes) + public static unsafe void Init(int sizeMegabytes) { var entrySize = sizeof(PawnHashTableEntry); - _size = sizeMegabytes * 1024ul * 1024ul / (ulong)entrySize; + _size = (ulong)sizeMegabytes * 1024ul * 1024ul / (ulong)entrySize; _table = new PawnHashTableEntry[_size]; } @@ -26,6 +25,20 @@ public static PawnHashTableEntry Get(ulong hash) return _table[hash % _size]; } + public static float GetFillLevel() + { + var filledEntries = 0; + for (var i = 0; i < 1000; i++) + { + if (_table[i].Key != 0 || _table[i].Score != 0) + { + filledEntries++; + } + } + + return (float)filledEntries / 10; + } + public static void Clear() { Array.Clear(_table, 0, (int)_size); diff --git a/Cosette/Engine/Ai/Transposition/PawnHashTableEntry.cs b/Cosette/Engine/Ai/Transposition/PawnHashTableEntry.cs index 5a7fcff..54a6ff5 100644 --- a/Cosette/Engine/Ai/Transposition/PawnHashTableEntry.cs +++ b/Cosette/Engine/Ai/Transposition/PawnHashTableEntry.cs @@ -2,16 +2,20 @@ namespace Cosette.Engine.Ai.Transposition { - [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct PawnHashTableEntry { - public ulong Hash { get; set; } + public ushort Key { get; set; } public short Score { get; set; } public PawnHashTableEntry(ulong hash, short score) { - Hash = hash; + Key = (ushort)(hash >> 48); Score = score; } + + public bool IsKeyValid(ulong hash) + { + return Key == hash >> 48; + } } } diff --git a/Cosette/Engine/Ai/Transposition/TranspositionTable.cs b/Cosette/Engine/Ai/Transposition/TranspositionTable.cs index 126a44c..f5df9b5 100644 --- a/Cosette/Engine/Ai/Transposition/TranspositionTable.cs +++ b/Cosette/Engine/Ai/Transposition/TranspositionTable.cs @@ -1,5 +1,5 @@ using System; -using System.Runtime.CompilerServices; +using Cosette.Engine.Ai.Search; using Cosette.Engine.Moves; namespace Cosette.Engine.Ai.Transposition @@ -9,17 +9,17 @@ public static class TranspositionTable private static TranspositionTableEntry[] _table; private static ulong _size; - public static unsafe void Init(ulong sizeMegabytes) + public static unsafe void Init(int sizeMegabytes) { var entrySize = sizeof(TranspositionTableEntry); - _size = sizeMegabytes * 1024ul * 1024ul / (ulong)entrySize; + _size = (ulong)sizeMegabytes * 1024ul * 1024ul / (ulong)entrySize; _table = new TranspositionTableEntry[_size]; } - public static void Add(ulong hash, byte depth, short score, Move bestMove, TranspositionTableEntryType type) + public static void Add(ulong hash, byte depth, short score, byte age, Move bestMove, TranspositionTableEntryFlags flags) { - _table[hash % _size] = new TranspositionTableEntry(hash, depth, score, bestMove, type); + _table[hash % _size] = new TranspositionTableEntry(hash, depth, score, age, bestMove, flags); } public static TranspositionTableEntry Get(ulong hash) @@ -27,9 +27,57 @@ public static TranspositionTableEntry Get(ulong hash) return _table[hash % _size]; } + public static float GetFillLevel() + { + var filledEntries = 0; + for (var i = 0; i < 1000; i++) + { + if (_table[i].Key != 0 || _table[i].Score != 0) + { + filledEntries++; + } + } + + return (float)filledEntries / 10; + } + public static void Clear() { - Array.Clear(_table, 0, (int) _size); + Array.Clear(_table, 0, (int)_size); + } + + public static int RegularToTTScore(int score, int ply) + { + if (IterativeDeepening.IsScoreNearCheckmate(score)) + { + if (score > 0) + { + return score + ply; + } + else + { + return score - ply; + } + } + + return score; + } + + public static int TTToRegularScore(int score, int ply) + { + if (IterativeDeepening.IsScoreNearCheckmate(score)) + { + if (score > 0) + { + return score - ply; + } + else + { + return score + ply; + } + } + + return score; } } } diff --git a/Cosette/Engine/Ai/Transposition/TranspositionTableEntry.cs b/Cosette/Engine/Ai/Transposition/TranspositionTableEntry.cs index 5bc09e6..b4f076b 100644 --- a/Cosette/Engine/Ai/Transposition/TranspositionTableEntry.cs +++ b/Cosette/Engine/Ai/Transposition/TranspositionTableEntry.cs @@ -3,22 +3,28 @@ namespace Cosette.Engine.Ai.Transposition { - [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct TranspositionTableEntry { - public ulong Hash { get; set; } - public short Score { get; set; } + public uint Key { get; set; } public byte Depth { get; set; } - public TranspositionTableEntryType Type { get; set; } + public short Score { get; set; } + public byte Age { get; set; } + public TranspositionTableEntryFlags Flags { get; set; } public Move BestMove { get; set; } - public TranspositionTableEntry(ulong hash, byte depth, short score, Move bestMove, TranspositionTableEntryType type) + public TranspositionTableEntry(ulong hash, byte depth, short score, byte age, Move bestMove, TranspositionTableEntryFlags flags) { - Hash = hash; + Key = (uint)(hash >> 32); Depth = depth; Score = score; + Age = age; BestMove = bestMove; - Type = type; + Flags = flags; + } + + public bool IsKeyValid(ulong hash) + { + return Key == (uint)(hash >> 32); } } } diff --git a/Cosette/Engine/Ai/Transposition/TranspositionTableEntryFlags.cs b/Cosette/Engine/Ai/Transposition/TranspositionTableEntryFlags.cs new file mode 100644 index 0000000..1ceecff --- /dev/null +++ b/Cosette/Engine/Ai/Transposition/TranspositionTableEntryFlags.cs @@ -0,0 +1,12 @@ +using System; + +namespace Cosette.Engine.Ai.Transposition +{ + public enum TranspositionTableEntryFlags : byte + { + Invalid = 0, + ExactScore = 1, + BetaScore = 2, + AlphaScore = 4 + } +} diff --git a/Cosette/Engine/Ai/Transposition/TranspositionTableEntryType.cs b/Cosette/Engine/Ai/Transposition/TranspositionTableEntryType.cs deleted file mode 100644 index 4025673..0000000 --- a/Cosette/Engine/Ai/Transposition/TranspositionTableEntryType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Cosette.Engine.Ai.Transposition -{ - public enum TranspositionTableEntryType : byte - { - Invalid, - ExactScore, - LowerBound, - UpperBound - } -} diff --git a/Cosette/Engine/Board/BoardState.cs b/Cosette/Engine/Board/BoardState.cs index dd3be4e..b014752 100644 --- a/Cosette/Engine/Board/BoardState.cs +++ b/Cosette/Engine/Board/BoardState.cs @@ -1,11 +1,9 @@ using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using Cosette.Engine.Ai; using Cosette.Engine.Ai.Score; using Cosette.Engine.Ai.Score.PieceSquareTables; using Cosette.Engine.Board.Operators; using Cosette.Engine.Common; +using Cosette.Engine.Fen; using Cosette.Engine.Moves; namespace Cosette.Engine.Board @@ -17,88 +15,138 @@ public class BoardState public ulong OccupancySummary { get; set; } public ulong EnPassant { get; set; } public Castling Castling { get; set; } - public Color ColorToMove { get; set; } + public int ColorToMove { get; set; } + public int MovesCount { get; set; } + public int IrreversibleMovesCount { get; set; } + public int NullMoves { get; set; } public bool[] CastlingDone { get; set; } public int[] Material { get; set; } public int[][] Position { get; set; } + public int[] PieceTable { get; set; } + public ulong Hash { get; set; } public ulong PawnHash { get; set; } - private readonly FastStack _killedPieces; + private readonly FastStack _killedPieces; private readonly FastStack _enPassants; private readonly FastStack _castlings; - private readonly FastStack _promotedPieces; + private readonly FastStack _promotedPieces; private readonly FastStack _hashes; private readonly FastStack _pawnHashes; + private readonly FastStack _irreversibleMovesCounts; - private int _materialAtOpening; + private readonly int _materialAtOpening; public BoardState() { Pieces = new ulong[2][]; - Pieces[(int)Color.White] = new ulong[6]; - Pieces[(int)Color.Black] = new ulong[6]; + Pieces[Color.White] = new ulong[6]; + Pieces[Color.Black] = new ulong[6]; Occupancy = new ulong[2]; CastlingDone = new bool[2]; Material = new int[2]; Position = new int[2][]; - Position[(int)Color.White] = new int[2]; - Position[(int)Color.Black] = new int[2]; + Position[Color.White] = new int[2]; + Position[Color.Black] = new int[2]; + + PieceTable = new int[64]; - _killedPieces = new FastStack(512); + _killedPieces = new FastStack(512); _enPassants = new FastStack(512); _castlings = new FastStack(512); - _promotedPieces = new FastStack(512); + _promotedPieces = new FastStack(512); _hashes = new FastStack(512); _pawnHashes = new FastStack(512); + _irreversibleMovesCounts = new FastStack(512); _materialAtOpening = - EvaluationConstants.Pieces[(int) Piece.King] + - EvaluationConstants.Pieces[(int) Piece.Queen] + - EvaluationConstants.Pieces[(int) Piece.Rook] * 2 + - EvaluationConstants.Pieces[(int) Piece.Bishop] * 2+ - EvaluationConstants.Pieces[(int) Piece.Knight] * 2+ - EvaluationConstants.Pieces[(int) Piece.Pawn] * 8; - _materialAtOpening *= 2; + EvaluationConstants.Pieces[Piece.King] + + EvaluationConstants.Pieces[Piece.Queen] + + EvaluationConstants.Pieces[Piece.Rook] * 2 + + EvaluationConstants.Pieces[Piece.Bishop] * 2 + + EvaluationConstants.Pieces[Piece.Knight] * 2 + + EvaluationConstants.Pieces[Piece.Pawn] * 8; } public void SetDefaultState() { - Pieces[(int)Color.White][(int) Piece.Pawn] = 65280; - Pieces[(int)Color.White][(int) Piece.Rook] = 129; - Pieces[(int)Color.White][(int) Piece.Knight] = 66; - Pieces[(int)Color.White][(int) Piece.Bishop] = 36; - Pieces[(int)Color.White][(int) Piece.Queen] = 16; - Pieces[(int)Color.White][(int) Piece.King] = 8; - - Pieces[(int)Color.Black][(int) Piece.Pawn] = 71776119061217280; - Pieces[(int)Color.Black][(int) Piece.Rook] = 9295429630892703744; - Pieces[(int)Color.Black][(int) Piece.Knight] = 4755801206503243776; - Pieces[(int)Color.Black][(int) Piece.Bishop] = 2594073385365405696; - Pieces[(int)Color.Black][(int) Piece.Queen] = 1152921504606846976; - Pieces[(int)Color.Black][(int) Piece.King] = 576460752303423488; - - Occupancy[(int)Color.White] = 65535; - Occupancy[(int)Color.Black] = 18446462598732840960; - OccupancySummary = Occupancy[(int)Color.White] | Occupancy[(int)Color.Black]; - + Pieces[Color.White][Piece.Pawn] = 65280; + Pieces[Color.White][Piece.Rook] = 129; + Pieces[Color.White][Piece.Knight] = 66; + Pieces[Color.White][Piece.Bishop] = 36; + Pieces[Color.White][Piece.Queen] = 16; + Pieces[Color.White][Piece.King] = 8; + + Pieces[Color.Black][Piece.Pawn] = 71776119061217280; + Pieces[Color.Black][Piece.Rook] = 9295429630892703744; + Pieces[Color.Black][Piece.Knight] = 4755801206503243776; + Pieces[Color.Black][Piece.Bishop] = 2594073385365405696; + Pieces[Color.Black][Piece.Queen] = 1152921504606846976; + Pieces[Color.Black][Piece.King] = 576460752303423488; + + Occupancy[Color.White] = 65535; + Occupancy[Color.Black] = 18446462598732840960; + OccupancySummary = Occupancy[Color.White] | Occupancy[Color.Black]; + + EnPassant = 0; Castling = Castling.Everything; ColorToMove = Color.White; - - CastlingDone[(int)Color.White] = false; - CastlingDone[(int)Color.Black] = false; - - Material[(int)Color.White] = CalculateMaterial(Color.White); - Material[(int)Color.Black] = CalculateMaterial(Color.Black); - - Position[(int)Color.White][(int)GamePhase.Opening] = CalculatePosition(Color.White, GamePhase.Opening); - Position[(int)Color.White][(int)GamePhase.Ending] = CalculatePosition(Color.White, GamePhase.Ending); - Position[(int)Color.Black][(int)GamePhase.Opening] = CalculatePosition(Color.Black, GamePhase.Opening); - Position[(int)Color.Black][(int)GamePhase.Ending] = CalculatePosition(Color.Black, GamePhase.Ending); + MovesCount = 0; + IrreversibleMovesCount = 0; + NullMoves = 0; + + CastlingDone[Color.White] = false; + CastlingDone[Color.Black] = false; + + Material[Color.White] = CalculateMaterial(Color.White); + Material[Color.Black] = CalculateMaterial(Color.Black); + + Position[Color.White][GamePhase.Opening] = CalculatePosition(Color.White, GamePhase.Opening); + Position[Color.White][GamePhase.Ending] = CalculatePosition(Color.White, GamePhase.Ending); + Position[Color.Black][GamePhase.Opening] = CalculatePosition(Color.Black, GamePhase.Opening); + Position[Color.Black][GamePhase.Ending] = CalculatePosition(Color.Black, GamePhase.Ending); + + Array.Fill(PieceTable, -1); + + PieceTable[0] = Piece.Rook; + PieceTable[1] = Piece.Knight; + PieceTable[2] = Piece.Bishop; + PieceTable[3] = Piece.King; + PieceTable[4] = Piece.Queen; + PieceTable[5] = Piece.Bishop; + PieceTable[6] = Piece.Knight; + PieceTable[7] = Piece.Rook; + + PieceTable[8] = Piece.Pawn; + PieceTable[9] = Piece.Pawn; + PieceTable[10] = Piece.Pawn; + PieceTable[11] = Piece.Pawn; + PieceTable[12] = Piece.Pawn; + PieceTable[13] = Piece.Pawn; + PieceTable[14] = Piece.Pawn; + PieceTable[15] = Piece.Pawn; + + PieceTable[48] = Piece.Pawn; + PieceTable[49] = Piece.Pawn; + PieceTable[50] = Piece.Pawn; + PieceTable[51] = Piece.Pawn; + PieceTable[52] = Piece.Pawn; + PieceTable[53] = Piece.Pawn; + PieceTable[54] = Piece.Pawn; + PieceTable[55] = Piece.Pawn; + + PieceTable[56] = Piece.Rook; + PieceTable[57] = Piece.Knight; + PieceTable[58] = Piece.Bishop; + PieceTable[59] = Piece.King; + PieceTable[60] = Piece.Queen; + PieceTable[61] = Piece.Bishop; + PieceTable[62] = Piece.Knight; + PieceTable[63] = Piece.Rook; Hash = ZobristHashing.CalculateHash(this); PawnHash = ZobristHashing.CalculatePawnHash(this); @@ -109,6 +157,7 @@ public void SetDefaultState() _promotedPieces.Clear(); _hashes.Clear(); _pawnHashes.Clear(); + _irreversibleMovesCounts.Clear(); } public int GetAvailableMoves(Span moves) @@ -137,12 +186,28 @@ public int GetAvailableQMoves(Span moves) public void MakeMove(Move move) { + var pieceType = PieceTable[move.From]; var enemyColor = ColorOperations.Invert(ColorToMove); + if (ColorToMove == Color.White) + { + MovesCount++; + } + _castlings.Push(Castling); _hashes.Push(Hash); _pawnHashes.Push(PawnHash); _enPassants.Push(EnPassant); + _irreversibleMovesCounts.Push(IrreversibleMovesCount); + + if (pieceType == Piece.Pawn || ((byte)move.Flags & MoveFlagFields.Capture) != 0) + { + IrreversibleMovesCount = 0; + } + else + { + IrreversibleMovesCount++; + } if (EnPassant != 0) { @@ -151,21 +216,21 @@ public void MakeMove(Move move) EnPassant = 0; } - if (move.Flags == MoveFlags.None) + if (move.Flags == MoveFlags.Quiet) { - MovePiece(ColorToMove, move.Piece, move.From, move.To); - Hash = ZobristHashing.MovePiece(Hash, ColorToMove, move.Piece, move.From, move.To); + MovePiece(ColorToMove, pieceType, move.From, move.To); + Hash = ZobristHashing.MovePiece(Hash, ColorToMove, pieceType, move.From, move.To); - if (move.Piece == Piece.Pawn) + if (pieceType == Piece.Pawn) { - PawnHash = ZobristHashing.MovePiece(PawnHash, ColorToMove, move.Piece, move.From, move.To); + PawnHash = ZobristHashing.MovePiece(PawnHash, ColorToMove, pieceType, move.From, move.To); } } - else if ((move.Flags & MoveFlags.DoublePush) != 0) + else if (move.Flags == MoveFlags.DoublePush) { - MovePiece(ColorToMove, move.Piece, move.From, move.To); - Hash = ZobristHashing.MovePiece(Hash, ColorToMove, move.Piece, move.From, move.To); - PawnHash = ZobristHashing.MovePiece(PawnHash, ColorToMove, move.Piece, move.From, move.To); + MovePiece(ColorToMove, pieceType, move.From, move.To); + Hash = ZobristHashing.MovePiece(Hash, ColorToMove, pieceType, move.From, move.To); + PawnHash = ZobristHashing.MovePiece(PawnHash, ColorToMove, pieceType, move.From, move.To); var enPassantField = ColorToMove == Color.White ? 1ul << move.To - 8 : 1ul << move.To + 8; var enPassantFieldIndex = BitOperations.BitScan(enPassantField); @@ -173,9 +238,24 @@ public void MakeMove(Move move) EnPassant |= enPassantField; Hash = ZobristHashing.ToggleEnPassant(Hash, enPassantFieldIndex % 8); } - else if ((move.Flags & MoveFlags.Kill) != 0) + else if (move.Flags == MoveFlags.EnPassant) { - var killedPiece = GetPiece(enemyColor, move.To); + var enemyPieceField = ColorToMove == Color.White ? (byte)(move.To - 8) : (byte)(move.To + 8); + var killedPiece = PieceTable[enemyPieceField]; + + RemovePiece(enemyColor, killedPiece, enemyPieceField); + Hash = ZobristHashing.AddOrRemovePiece(Hash, enemyColor, killedPiece, enemyPieceField); + PawnHash = ZobristHashing.AddOrRemovePiece(PawnHash, enemyColor, killedPiece, enemyPieceField); + + MovePiece(ColorToMove, pieceType, move.From, move.To); + Hash = ZobristHashing.MovePiece(Hash, ColorToMove, pieceType, move.From, move.To); + PawnHash = ZobristHashing.MovePiece(PawnHash, ColorToMove, pieceType, move.From, move.To); + + _killedPieces.Push(killedPiece); + } + else if (((byte)move.Flags & MoveFlagFields.Capture) != 0) + { + var killedPiece = PieceTable[move.To]; RemovePiece(enemyColor, killedPiece, move.To); Hash = ZobristHashing.AddOrRemovePiece(Hash, enemyColor, killedPiece, move.To); @@ -216,13 +296,13 @@ public void MakeMove(Move move) } // Promotion - if ((byte)move.Flags >= 16) + if (((byte)move.Flags & MoveFlagFields.Promotion) != 0) { var promotionPiece = GetPromotionPiece(move.Flags); - RemovePiece(ColorToMove, move.Piece, move.From); - Hash = ZobristHashing.AddOrRemovePiece(Hash, ColorToMove, move.Piece, move.From); - PawnHash = ZobristHashing.AddOrRemovePiece(PawnHash, ColorToMove, move.Piece, move.From); + RemovePiece(ColorToMove, pieceType, move.From); + Hash = ZobristHashing.AddOrRemovePiece(Hash, ColorToMove, pieceType, move.From); + PawnHash = ZobristHashing.AddOrRemovePiece(PawnHash, ColorToMove, pieceType, move.From); AddPiece(ColorToMove, promotionPiece, move.To); Hash = ZobristHashing.AddOrRemovePiece(Hash, ColorToMove, promotionPiece, move.To); @@ -231,21 +311,21 @@ public void MakeMove(Move move) } else { - MovePiece(ColorToMove, move.Piece, move.From, move.To); - Hash = ZobristHashing.MovePiece(Hash, ColorToMove, move.Piece, move.From, move.To); + MovePiece(ColorToMove, pieceType, move.From, move.To); + Hash = ZobristHashing.MovePiece(Hash, ColorToMove, pieceType, move.From, move.To); - if (move.Piece == Piece.Pawn) + if (pieceType == Piece.Pawn) { - PawnHash = ZobristHashing.MovePiece(PawnHash, ColorToMove, move.Piece, move.From, move.To); + PawnHash = ZobristHashing.MovePiece(PawnHash, ColorToMove, pieceType, move.From, move.To); } } _killedPieces.Push(killedPiece); } - else if ((move.Flags & MoveFlags.Castling) != 0) + else if (move.Flags == MoveFlags.KingCastle || move.Flags == MoveFlags.QueenCastle) { // Short castling - if (move.From > move.To) + if (move.Flags == MoveFlags.KingCastle) { if (ColorToMove == Color.White) { @@ -298,30 +378,15 @@ public void MakeMove(Move move) Castling &= ~Castling.BlackCastling; } - CastlingDone[(int)ColorToMove] = true; - } - else if ((move.Flags & MoveFlags.EnPassant) != 0) - { - var enemyPieceField = ColorToMove == Color.White ? (byte)(move.To - 8) : (byte)(move.To + 8); - var killedPiece = GetPiece(enemyColor, enemyPieceField); - - RemovePiece(enemyColor, killedPiece, enemyPieceField); - Hash = ZobristHashing.AddOrRemovePiece(Hash, enemyColor, killedPiece, enemyPieceField); - PawnHash = ZobristHashing.AddOrRemovePiece(PawnHash, enemyColor, killedPiece, enemyPieceField); - - MovePiece(ColorToMove, move.Piece, move.From, move.To); - Hash = ZobristHashing.MovePiece(Hash, ColorToMove, move.Piece, move.From, move.To); - PawnHash = ZobristHashing.MovePiece(PawnHash, ColorToMove, move.Piece, move.From, move.To); - - _killedPieces.Push(killedPiece); + CastlingDone[ColorToMove] = true; } - else if ((byte)move.Flags >= 16) + else if (((byte)move.Flags & MoveFlagFields.Promotion) != 0) { var promotionPiece = GetPromotionPiece(move.Flags); - RemovePiece(ColorToMove, move.Piece, move.From); - Hash = ZobristHashing.AddOrRemovePiece(Hash, ColorToMove, move.Piece, move.From); - PawnHash = ZobristHashing.AddOrRemovePiece(PawnHash, ColorToMove, move.Piece, move.From); + RemovePiece(ColorToMove, pieceType, move.From); + Hash = ZobristHashing.AddOrRemovePiece(Hash, ColorToMove, pieceType, move.From); + PawnHash = ZobristHashing.AddOrRemovePiece(PawnHash, ColorToMove, pieceType, move.From); AddPiece(ColorToMove, promotionPiece, move.To); Hash = ZobristHashing.AddOrRemovePiece(Hash, ColorToMove, promotionPiece, move.To); @@ -329,7 +394,7 @@ public void MakeMove(Move move) _promotedPieces.Push(promotionPiece); } - if (move.Piece == Piece.King && move.Flags != MoveFlags.Castling) + if (pieceType == Piece.King && move.Flags != MoveFlags.KingCastle && move.Flags != MoveFlags.QueenCastle) { if (ColorToMove == Color.White) { @@ -344,7 +409,7 @@ public void MakeMove(Move move) Castling &= ~Castling.BlackCastling; } } - else if (move.Piece == Piece.Rook && Castling != 0) + else if (pieceType == Piece.Rook && Castling != 0) { if (move.From == 0) { @@ -374,35 +439,45 @@ public void MakeMove(Move move) public void UndoMove(Move move) { + var pieceType = PieceTable[move.To]; ColorToMove = ColorOperations.Invert(ColorToMove); - if (move.Flags == MoveFlags.None || (move.Flags & MoveFlags.DoublePush) != 0) + if (move.Flags == MoveFlags.Quiet || move.Flags == MoveFlags.DoublePush) + { + MovePiece(ColorToMove, pieceType, move.To, move.From); + } + else if (move.Flags == MoveFlags.EnPassant) { - MovePiece(ColorToMove, move.Piece, move.To, move.From); + var enemyColor = ColorOperations.Invert(ColorToMove); + var enemyPieceField = ColorToMove == Color.White ? (byte)(move.To - 8) : (byte)(move.To + 8); + var killedPiece = _killedPieces.Pop(); + + MovePiece(ColorToMove, Piece.Pawn, move.To, move.From); + AddPiece(enemyColor, killedPiece, enemyPieceField); } - else if ((move.Flags & MoveFlags.Kill) != 0) + else if (((byte)move.Flags & MoveFlagFields.Capture) != 0) { var enemyColor = ColorOperations.Invert(ColorToMove); var killedPiece = _killedPieces.Pop(); // Promotion - if ((byte)move.Flags >= 16) + if (((byte)move.Flags & MoveFlagFields.Promotion) != 0) { var promotionPiece = _promotedPieces.Pop(); RemovePiece(ColorToMove, promotionPiece, move.To); - AddPiece(ColorToMove, move.Piece, move.From); + AddPiece(ColorToMove, Piece.Pawn, move.From); } else { - MovePiece(ColorToMove, move.Piece, move.To, move.From); + MovePiece(ColorToMove, pieceType, move.To, move.From); } AddPiece(enemyColor, killedPiece, move.To); } - else if ((move.Flags & MoveFlags.Castling) != 0) + else if (move.Flags == MoveFlags.KingCastle || move.Flags == MoveFlags.QueenCastle) { // Short castling - if (move.From > move.To) + if (move.Flags == MoveFlags.KingCastle) { if (ColorToMove == Color.White) { @@ -430,32 +505,35 @@ public void UndoMove(Move move) } } - CastlingDone[(int)ColorToMove] = false; + CastlingDone[ColorToMove] = false; } - else if ((move.Flags & MoveFlags.EnPassant) != 0) - { - var enemyColor = ColorOperations.Invert(ColorToMove); - var enemyPieceField = ColorToMove == Color.White ? (byte)(move.To - 8) : (byte)(move.To + 8); - var killedPiece = _killedPieces.Pop(); - - MovePiece(ColorToMove, move.Piece, move.To, move.From); - AddPiece(enemyColor, killedPiece, enemyPieceField); - } - else if ((byte)move.Flags >= 16) + else if (((byte)move.Flags & MoveFlagFields.Promotion) != 0) { var promotionPiece = _promotedPieces.Pop(); RemovePiece(ColorToMove, promotionPiece, move.To); - AddPiece(ColorToMove, move.Piece, move.From); + AddPiece(ColorToMove, Piece.Pawn, move.From); } + IrreversibleMovesCount = _irreversibleMovesCounts.Pop(); PawnHash = _pawnHashes.Pop(); Hash = _hashes.Pop(); Castling = _castlings.Pop(); EnPassant = _enPassants.Pop(); + + if (ColorToMove == Color.White) + { + MovesCount--; + } } public void MakeNullMove() { + NullMoves++; + if (ColorToMove == Color.White) + { + MovesCount++; + } + _enPassants.Push(EnPassant); _hashes.Push(Hash); @@ -472,46 +550,52 @@ public void MakeNullMove() public void UndoNullMove() { + NullMoves--; ColorToMove = ColorOperations.Invert(ColorToMove); Hash = _hashes.Pop(); EnPassant = _enPassants.Pop(); + + if (ColorToMove == Color.White) + { + MovesCount--; + } } - public bool IsFieldAttacked(Color color, byte fieldIndex) + public bool IsFieldAttacked(int color, int fieldIndex) { var enemyColor = ColorOperations.Invert(color); - var fileRankAttacks = RookMovesGenerator.GetMoves(OccupancySummary, fieldIndex) & Occupancy[(int)enemyColor]; - var attackingRooks = fileRankAttacks & (Pieces[(int)enemyColor][(int)Piece.Rook] | Pieces[(int)enemyColor][(int)Piece.Queen]); + var fileRankAttacks = RookMovesGenerator.GetMoves(OccupancySummary, fieldIndex) & Occupancy[enemyColor]; + var attackingRooks = fileRankAttacks & (Pieces[enemyColor][Piece.Rook] | Pieces[enemyColor][Piece.Queen]); if (attackingRooks != 0) { return true; } - var diagonalAttacks = BishopMovesGenerator.GetMoves(OccupancySummary, fieldIndex) & Occupancy[(int)enemyColor]; - var attackingBishops = diagonalAttacks & (Pieces[(int)enemyColor][(int)Piece.Bishop] | Pieces[(int)enemyColor][(int)Piece.Queen]); + var diagonalAttacks = BishopMovesGenerator.GetMoves(OccupancySummary, fieldIndex) & Occupancy[enemyColor]; + var attackingBishops = diagonalAttacks & (Pieces[enemyColor][Piece.Bishop] | Pieces[enemyColor][Piece.Queen]); if (attackingBishops != 0) { return true; } var jumpAttacks = KnightMovesGenerator.GetMoves(fieldIndex); - var attackingKnights = jumpAttacks & Pieces[(int)enemyColor][(int)Piece.Knight]; + var attackingKnights = jumpAttacks & Pieces[enemyColor][Piece.Knight]; if (attackingKnights != 0) { return true; } var boxAttacks = KingMovesGenerator.GetMoves(fieldIndex); - var attackingKings = boxAttacks & Pieces[(int)enemyColor][(int)Piece.King]; + var attackingKings = boxAttacks & Pieces[enemyColor][Piece.King]; if (attackingKings != 0) { return true; } var field = 1ul << fieldIndex; - var potentialPawns = boxAttacks & Pieces[(int)enemyColor][(int)Piece.Pawn]; + var potentialPawns = boxAttacks & Pieces[enemyColor][Piece.Pawn]; var attackingPawns = color == Color.White ? field & ((potentialPawns >> 7) | (potentialPawns >> 9)) : field & ((potentialPawns << 7) | (potentialPawns << 9)); @@ -524,150 +608,146 @@ public bool IsFieldAttacked(Color color, byte fieldIndex) return false; } - public byte GetAttackingPiecesWithColor(Color color, byte fieldIndex) + public byte GetAttackingPiecesWithColor(int color, int fieldIndex) { byte result = 0; - var fileRankAttacks = RookMovesGenerator.GetMoves(OccupancySummary, fieldIndex) & Occupancy[(int)color]; - var attackingRooks = fileRankAttacks & Pieces[(int)color][(int)Piece.Rook]; + var fileRankAttacks = RookMovesGenerator.GetMoves(OccupancySummary, fieldIndex) & Occupancy[color]; + var attackingRooks = fileRankAttacks & Pieces[color][Piece.Rook]; if (attackingRooks != 0) { - result |= 1 << (int) Piece.Rook; + result |= 1 << Piece.Rook; } - var diagonalAttacks = BishopMovesGenerator.GetMoves(OccupancySummary, fieldIndex) & Occupancy[(int)color]; - var attackingBishops = diagonalAttacks & Pieces[(int)color][(int)Piece.Bishop]; + var diagonalAttacks = BishopMovesGenerator.GetMoves(OccupancySummary, fieldIndex) & Occupancy[color]; + var attackingBishops = diagonalAttacks & Pieces[color][Piece.Bishop]; if (attackingBishops != 0) { - result |= 1 << (int)Piece.Bishop; + result |= 1 << Piece.Bishop; } - var attackingQueens = (fileRankAttacks | diagonalAttacks) & Pieces[(int)color][(int)Piece.Queen]; + var attackingQueens = (fileRankAttacks | diagonalAttacks) & Pieces[color][Piece.Queen]; if (attackingQueens != 0) { - result |= 1 << (int)Piece.Queen; + result |= 1 << Piece.Queen; } var jumpAttacks = KnightMovesGenerator.GetMoves(fieldIndex); - var attackingKnights = jumpAttacks & Pieces[(int)color][(int)Piece.Knight]; + var attackingKnights = jumpAttacks & Pieces[color][Piece.Knight]; if (attackingKnights != 0) { - result |= 1 << (int)Piece.Knight; + result |= 1 << Piece.Knight; } var boxAttacks = KingMovesGenerator.GetMoves(fieldIndex); - var attackingKings = boxAttacks & Pieces[(int)color][(int)Piece.King]; + var attackingKings = boxAttacks & Pieces[color][Piece.King]; if (attackingKings != 0) { - result |= 1 << (int)Piece.King; + result |= 1 << Piece.King; } var field = 1ul << fieldIndex; - var potentialPawns = boxAttacks & Pieces[(int)color][(int)Piece.Pawn]; + var potentialPawns = boxAttacks & Pieces[color][Piece.Pawn]; var attackingPawns = color == Color.White ? field & ((potentialPawns << 7) | (potentialPawns << 9)) : field & ((potentialPawns >> 7) | (potentialPawns >> 9)); if (attackingPawns != 0) { - result |= 1 << (int)Piece.Pawn; + result |= 1 << Piece.Pawn; } return result; } - public bool IsKingChecked(Color color) + public bool IsKingChecked(int color) { - var king = Pieces[(int) color][(int) Piece.King]; - var kingField = BitOperations.BitScan(king); + var king = Pieces[color][Piece.King]; + if (king == 0) + { + return false; + } + var kingField = BitOperations.BitScan(king); return IsFieldAttacked(color, (byte)kingField); } - public void MovePiece(Color color, Piece piece, byte from, byte to) + public void MovePiece(int color, int piece, int from, int to) { var move = (1ul << from) | (1ul << to); - Pieces[(int) color][(int) piece] ^= move; - Occupancy[(int)color] ^= move; + Pieces[color][piece] ^= move; + Occupancy[color] ^= move; OccupancySummary ^= move; - Position[(int)color][(int)GamePhase.Opening] -= PieceSquareTablesData.Values[(int)piece][(int)color][(int)GamePhase.Opening][from]; - Position[(int)color][(int)GamePhase.Opening] += PieceSquareTablesData.Values[(int)piece][(int)color][(int)GamePhase.Opening][to]; - - Position[(int)color][(int)GamePhase.Ending] -= PieceSquareTablesData.Values[(int)piece][(int)color][(int)GamePhase.Ending][from]; - Position[(int)color][(int)GamePhase.Ending] += PieceSquareTablesData.Values[(int)piece][(int)color][(int)GamePhase.Ending][to]; - } - - public Piece GetPiece(Color color, byte from) - { - var field = 1ul << from; + Position[color][GamePhase.Opening] -= PieceSquareTablesData.Values[piece][color][GamePhase.Opening][from]; + Position[color][GamePhase.Opening] += PieceSquareTablesData.Values[piece][color][GamePhase.Opening][to]; - for (var i = 0; i < 6; i++) - { - if ((Pieces[(int)color][i] & field) != 0) - { - return (Piece) i; - } - } + Position[color][GamePhase.Ending] -= PieceSquareTablesData.Values[piece][color][GamePhase.Ending][from]; + Position[color][GamePhase.Ending] += PieceSquareTablesData.Values[piece][color][GamePhase.Ending][to]; - throw new InvalidOperationException(); + PieceTable[from] = -1; + PieceTable[to] = piece; } - public void AddPiece(Color color, Piece piece, byte at) + public void AddPiece(int color, int piece, int fieldIndex) { - var field = 1ul << at; + var field = 1ul << fieldIndex; - Pieces[(int)color][(int)piece] ^= field; - Occupancy[(int)color] ^= field; + Pieces[color][piece] ^= field; + Occupancy[color] ^= field; OccupancySummary ^= field; - Material[(int)color] += EvaluationConstants.Pieces[(int)piece]; + Material[color] += EvaluationConstants.Pieces[piece]; - Position[(int)color][(int)GamePhase.Opening] += PieceSquareTablesData.Values[(int)piece][(int)color][(int)GamePhase.Opening][at]; - Position[(int)color][(int)GamePhase.Ending] += PieceSquareTablesData.Values[(int)piece][(int)color][(int)GamePhase.Ending][at]; + Position[color][GamePhase.Opening] += PieceSquareTablesData.Values[piece][color][GamePhase.Opening][fieldIndex]; + Position[color][GamePhase.Ending] += PieceSquareTablesData.Values[piece][color][GamePhase.Ending][fieldIndex]; + + PieceTable[fieldIndex] = piece; } - public void RemovePiece(Color color, Piece piece, byte at) + public void RemovePiece(int color, int piece, int fieldIndex) { - var field = 1ul << at; + var field = 1ul << fieldIndex; - Pieces[(int)color][(int)piece] ^= field; - Occupancy[(int)color] ^= field; + Pieces[color][piece] ^= field; + Occupancy[color] ^= field; OccupancySummary ^= field; - Material[(int)color] -= EvaluationConstants.Pieces[(int)piece]; + Material[color] -= EvaluationConstants.Pieces[piece]; + + Position[color][GamePhase.Opening] -= PieceSquareTablesData.Values[piece][color][GamePhase.Opening][fieldIndex]; + Position[color][GamePhase.Ending] -= PieceSquareTablesData.Values[piece][color][GamePhase.Ending][fieldIndex]; - Position[(int)color][(int)GamePhase.Opening] -= PieceSquareTablesData.Values[(int)piece][(int)color][(int)GamePhase.Opening][at]; - Position[(int)color][(int)GamePhase.Ending] -= PieceSquareTablesData.Values[(int)piece][(int)color][(int)GamePhase.Ending][at]; + PieceTable[fieldIndex] = -1; } - public int CalculateMaterial(Color color) + public int CalculateMaterial(int color) { var material = 0; for (var i = 0; i < 6; i++) { - material += (int)BitOperations.Count(Pieces[(int)color][i]) * EvaluationConstants.Pieces[i]; + material += (int)BitOperations.Count(Pieces[color][i]) * EvaluationConstants.Pieces[i]; } return material; } - public int CalculatePosition(Color color, GamePhase phase) + public int CalculatePosition(int color, int phase) { var result = 0; for (var pieceIndex = 0; pieceIndex < 6; pieceIndex++) { - var pieces = Pieces[(int)color][pieceIndex]; + var pieces = Pieces[color][pieceIndex]; while (pieces != 0) { var lsb = BitOperations.GetLsb(pieces); pieces = BitOperations.PopLsb(pieces); var fieldIndex = BitOperations.BitScan(lsb); - result += PieceSquareTablesData.Values[pieceIndex][(int)color][(int)phase][fieldIndex]; + result += PieceSquareTablesData.Values[pieceIndex][color][phase][fieldIndex]; } } @@ -676,22 +756,24 @@ public int CalculatePosition(Color color, GamePhase phase) public float GetPhaseRatio() { + var materialOfWeakerSide = Math.Min(Material[Color.White], Material[Color.Black]); + var openingDelta = _materialAtOpening - EvaluationConstants.OpeningEndgameEdge; - var boardDelta = Material[(int) Color.White] + Material[(int) Color.Black] - EvaluationConstants.OpeningEndgameEdge; + var boardDelta = materialOfWeakerSide - EvaluationConstants.OpeningEndgameEdge; var ratio = (float) boardDelta / openingDelta; return Math.Max(0, ratio); } - public GamePhase GetGamePhase() + public int GetGamePhase() { - var totalMaterial = Material[(int) Color.White] + Material[(int) Color.Black]; - return totalMaterial > EvaluationConstants.OpeningEndgameEdge ? GamePhase.Opening : GamePhase.Ending; + var materialOfWeakerSide = Math.Min(Material[Color.White], Material[Color.Black]); + return materialOfWeakerSide > EvaluationConstants.OpeningEndgameEdge ? GamePhase.Opening : GamePhase.Ending; } public bool IsThreefoldRepetition() { - if (_hashes.Count() >= 8) + if (NullMoves == 0 && _hashes.Count() >= 8) { var first = _hashes.Peek(3); var second = _hashes.Peek(7); @@ -705,26 +787,48 @@ public bool IsThreefoldRepetition() return false; } - private Piece GetPromotionPiece(MoveFlags flags) + public bool IsFiftyMoveRuleDraw() { - if ((flags & MoveFlags.KnightPromotion) != 0) + if (NullMoves == 0 && IrreversibleMovesCount >= 100) { - return Piece.Knight; - } - - if ((flags & MoveFlags.BishopPromotion) != 0) - { - return Piece.Bishop; - } - - if ((flags & MoveFlags.RookPromotion) != 0) - { - return Piece.Rook; + return true; } - - if ((flags & MoveFlags.QueenPromotion) != 0) + + return false; + } + + public override string ToString() + { + return BoardToFen.Parse(this); + } + + private int GetPromotionPiece(MoveFlags flags) + { + switch (flags) { - return Piece.Queen; + case MoveFlags.QueenPromotion: + case MoveFlags.QueenPromotionCapture: + { + return Piece.Queen; + } + + case MoveFlags.RookPromotion: + case MoveFlags.RookPromotionCapture: + { + return Piece.Rook; + } + + case MoveFlags.BishopPromotion: + case MoveFlags.BishopPromotionCapture: + { + return Piece.Bishop; + } + + case MoveFlags.KnightPromotion: + case MoveFlags.KnightPromotionCapture: + { + return Piece.Knight; + } } throw new InvalidOperationException(); diff --git a/Cosette/Engine/Board/Operators/BishopOperator.cs b/Cosette/Engine/Board/Operators/BishopOperator.cs index 880b507..5b449f5 100644 --- a/Cosette/Engine/Board/Operators/BishopOperator.cs +++ b/Cosette/Engine/Board/Operators/BishopOperator.cs @@ -6,10 +6,10 @@ namespace Cosette.Engine.Board.Operators { public static class BishopOperator { - public static int GetAvailableMoves(BoardState boardState, Color color, Span moves, int offset) + public static int GetAvailableMoves(BoardState boardState, int color, Span moves, int offset) { var enemyColor = ColorOperations.Invert(color); - var bishops = boardState.Pieces[(int) color][(int) Piece.Bishop]; + var bishops = boardState.Pieces[color][Piece.Bishop]; while (bishops != 0) { @@ -17,26 +17,26 @@ public static int GetAvailableMoves(BoardState boardState, Color color, Span moves, int offset) + public static int GetAvailableQMoves(BoardState boardState, int color, Span moves, int offset) { var enemyColor = ColorOperations.Invert(color); - var bishops = boardState.Pieces[(int)color][(int)Piece.Bishop]; + var bishops = boardState.Pieces[color][Piece.Bishop]; while (bishops != 0) { @@ -44,25 +44,25 @@ public static int GetAvailableQMoves(BoardState boardState, Color color, Span moves, int offset) + public static int GetAvailableMoves(BoardState boardState, int color, Span moves, int offset) { var enemyColor = ColorOperations.Invert(color); - var piece = boardState.Pieces[(int)color][(int)Piece.King]; + var piece = boardState.Pieces[color][Piece.King]; var from = BitOperations.BitScan(piece); - var availableMoves = KingMovesGenerator.GetMoves(from) & ~boardState.Occupancy[(int)color]; + var availableMoves = KingMovesGenerator.GetMoves(from) & ~boardState.Occupancy[color]; while (availableMoves != 0) { var field = BitOperations.GetLsb(availableMoves); - availableMoves = BitOperations.PopLsb(availableMoves); var fieldIndex = BitOperations.BitScan(field); + availableMoves = BitOperations.PopLsb(availableMoves); - var flags = (field & boardState.Occupancy[(int)enemyColor]) != 0 ? MoveFlags.Kill : MoveFlags.None; - moves[offset++] = new Move(from, fieldIndex, Piece.King, flags); + var flags = (field & boardState.Occupancy[enemyColor]) != 0 ? MoveFlags.Capture : MoveFlags.Quiet; + moves[offset++] = new Move(from, fieldIndex, flags); } if (color == Color.White) @@ -30,7 +30,7 @@ public static int GetAvailableMoves(BoardState boardState, Color color, Span moves, int offset) + public static int GetAvailableQMoves(BoardState boardState, int color, Span moves, int offset) { var enemyColor = ColorOperations.Invert(color); - var piece = boardState.Pieces[(int)color][(int)Piece.King]; + var piece = boardState.Pieces[color][Piece.King]; var from = BitOperations.BitScan(piece); - var availableMoves = KingMovesGenerator.GetMoves(from) & boardState.Occupancy[(int)enemyColor]; + var availableMoves = KingMovesGenerator.GetMoves(from) & boardState.Occupancy[enemyColor]; while (availableMoves != 0) { var field = BitOperations.GetLsb(availableMoves); - availableMoves = BitOperations.PopLsb(availableMoves); var fieldIndex = BitOperations.BitScan(field); + availableMoves = BitOperations.PopLsb(availableMoves); - moves[offset++] = new Move(from, fieldIndex, Piece.King, MoveFlags.Kill); + moves[offset++] = new Move(from, fieldIndex, MoveFlags.Capture); } return offset; diff --git a/Cosette/Engine/Board/Operators/KnightOperator.cs b/Cosette/Engine/Board/Operators/KnightOperator.cs index 23f69c1..e0b1c29 100644 --- a/Cosette/Engine/Board/Operators/KnightOperator.cs +++ b/Cosette/Engine/Board/Operators/KnightOperator.cs @@ -6,10 +6,10 @@ namespace Cosette.Engine.Board.Operators { public static class KnightOperator { - public static int GetAvailableMoves(BoardState boardState, Color color, Span moves, int offset) + public static int GetAvailableMoves(BoardState boardState, int color, Span moves, int offset) { var enemyColor = ColorOperations.Invert(color); - var knights = boardState.Pieces[(int)color][(int)Piece.Knight]; + var knights = boardState.Pieces[color][Piece.Knight]; while (knights != 0) { @@ -17,26 +17,26 @@ public static int GetAvailableMoves(BoardState boardState, Color color, Span moves, int offset) + public static int GetAvailableQMoves(BoardState boardState, int color, Span moves, int offset) { var enemyColor = ColorOperations.Invert(color); - var knights = boardState.Pieces[(int)color][(int)Piece.Knight]; + var knights = boardState.Pieces[color][Piece.Knight]; while (knights != 0) { @@ -44,25 +44,25 @@ public static int GetAvailableQMoves(BoardState boardState, Color color, Span moves, int offset) + public static int GetAvailableMoves(BoardState boardState, int color, Span moves, int offset) { offset = GetSinglePush(boardState, color, moves, offset); offset = GetDoublePush(boardState, color, moves, offset); @@ -16,7 +16,7 @@ public static int GetAvailableMoves(BoardState boardState, Color color, Span moves, int offset) + public static int GetAvailableQMoves(BoardState boardState, int color, Span moves, int offset) { offset = GetDiagonalAttacks(boardState, color, color == Color.White ? 9 : 7, BoardConstants.AFile, moves, offset); offset = GetDiagonalAttacks(boardState, color, color == Color.White ? 7 : 9, BoardConstants.HFile, moves, offset); @@ -24,7 +24,7 @@ public static int GetAvailableQMoves(BoardState boardState, Color color, Span moves, int offset) + private static int GetSinglePush(BoardState boardState, int color, Span moves, int offset) { int shift; ulong promotionRank, pawns; @@ -33,14 +33,14 @@ private static int GetSinglePush(BoardState boardState, Color color, Span { shift = 8; promotionRank = BoardConstants.HRank; - pawns = boardState.Pieces[(int)Color.White][(int)Piece.Pawn]; + pawns = boardState.Pieces[Color.White][Piece.Pawn]; pawns = (pawns << 8) & ~boardState.OccupancySummary; } else { shift = -8; promotionRank = BoardConstants.ARank; - pawns = boardState.Pieces[(int)Color.Black][(int)Piece.Pawn]; + pawns = boardState.Pieces[Color.Black][Piece.Pawn]; pawns = (pawns >> 8) & ~boardState.OccupancySummary; } @@ -54,21 +54,21 @@ private static int GetSinglePush(BoardState boardState, Color color, Span if ((piece & promotionRank) != 0) { - moves[offset++] = new Move(from, to, Piece.Pawn, MoveFlags.QueenPromotion); - moves[offset++] = new Move(from, to, Piece.Pawn, MoveFlags.RookPromotion); - moves[offset++] = new Move(from, to, Piece.Pawn, MoveFlags.KnightPromotion); - moves[offset++] = new Move(from, to, Piece.Pawn, MoveFlags.BishopPromotion); + moves[offset++] = new Move(from, to, MoveFlags.QueenPromotion); + moves[offset++] = new Move(from, to, MoveFlags.RookPromotion); + moves[offset++] = new Move(from, to, MoveFlags.KnightPromotion); + moves[offset++] = new Move(from, to, MoveFlags.BishopPromotion); } else { - moves[offset++] = new Move(from, to, Piece.Pawn, 0); + moves[offset++] = new Move(from, to, 0); } } return offset; } - private static int GetDoublePush(BoardState boardState, Color color, Span moves, int offset) + private static int GetDoublePush(BoardState boardState, int color, Span moves, int offset) { int shift; ulong startRank, pawns; @@ -77,7 +77,7 @@ private static int GetDoublePush(BoardState boardState, Color color, Span { shift = 16; startRank = BoardConstants.BRank; - pawns = boardState.Pieces[(int)Color.White][(int)Piece.Pawn]; + pawns = boardState.Pieces[Color.White][Piece.Pawn]; pawns = ((pawns & startRank) << 8) & ~boardState.OccupancySummary; pawns = (pawns << 8) & ~boardState.OccupancySummary; } @@ -85,7 +85,7 @@ private static int GetDoublePush(BoardState boardState, Color color, Span { shift = -16; startRank = BoardConstants.GRank; - pawns = boardState.Pieces[(int)Color.Black][(int)Piece.Pawn]; + pawns = boardState.Pieces[Color.Black][Piece.Pawn]; pawns = ((pawns & startRank) >> 8) & ~boardState.OccupancySummary; pawns = (pawns >> 8) & ~boardState.OccupancySummary; } @@ -98,13 +98,13 @@ private static int GetDoublePush(BoardState boardState, Color color, Span var from = BitOperations.BitScan(piece) - shift; var to = BitOperations.BitScan(piece); - moves[offset++] = new Move(from, to, Piece.Pawn, MoveFlags.DoublePush); + moves[offset++] = new Move(from, to, MoveFlags.DoublePush); } return offset; } - private static int GetDiagonalAttacks(BoardState boardState, Color color, int dir, ulong prohibitedFile, Span moves, int offset) + private static int GetDiagonalAttacks(BoardState boardState, int color, int dir, ulong prohibitedFile, Span moves, int offset) { int shift; ulong promotionRank, enemyOccupancy, pawns; @@ -113,16 +113,16 @@ private static int GetDiagonalAttacks(BoardState boardState, Color color, int di { shift = dir; promotionRank = BoardConstants.HRank; - enemyOccupancy = boardState.Occupancy[(int)Color.Black] | boardState.EnPassant; - pawns = boardState.Pieces[(int)Color.White][(int)Piece.Pawn]; + enemyOccupancy = boardState.Occupancy[Color.Black] | boardState.EnPassant; + pawns = boardState.Pieces[Color.White][Piece.Pawn]; pawns = ((pawns & ~prohibitedFile) << dir) & enemyOccupancy; } else { shift = -dir; promotionRank = BoardConstants.ARank; - enemyOccupancy = boardState.Occupancy[(int)Color.White] | boardState.EnPassant; - pawns = boardState.Pieces[(int)Color.Black][(int)Piece.Pawn]; + enemyOccupancy = boardState.Occupancy[Color.White] | boardState.EnPassant; + pawns = boardState.Pieces[Color.Black][Piece.Pawn]; pawns = ((pawns & ~prohibitedFile) >> dir) & enemyOccupancy; } @@ -136,20 +136,20 @@ private static int GetDiagonalAttacks(BoardState boardState, Color color, int di if ((piece & promotionRank) != 0) { - moves[offset++] = new Move(from, to, Piece.Pawn, MoveFlags.Kill | MoveFlags.QueenPromotion); - moves[offset++] = new Move(from, to, Piece.Pawn, MoveFlags.Kill | MoveFlags.RookPromotion); - moves[offset++] = new Move(from, to, Piece.Pawn, MoveFlags.Kill | MoveFlags.KnightPromotion); - moves[offset++] = new Move(from, to, Piece.Pawn, MoveFlags.Kill | MoveFlags.BishopPromotion); + moves[offset++] = new Move(from, to, MoveFlags.QueenPromotionCapture); + moves[offset++] = new Move(from, to, MoveFlags.RookPromotionCapture); + moves[offset++] = new Move(from, to, MoveFlags.KnightPromotionCapture); + moves[offset++] = new Move(from, to, MoveFlags.BishopPromotionCapture); } else { if ((piece & boardState.EnPassant) != 0) { - moves[offset++] = new Move(from, to, Piece.Pawn, MoveFlags.EnPassant); + moves[offset++] = new Move(from, to, MoveFlags.EnPassant); } else { - moves[offset++] = new Move(from, to, Piece.Pawn, MoveFlags.Kill); + moves[offset++] = new Move(from, to, MoveFlags.Capture); } } } diff --git a/Cosette/Engine/Board/Operators/QueenOperator.cs b/Cosette/Engine/Board/Operators/QueenOperator.cs index 4d1776f..08f8bd8 100644 --- a/Cosette/Engine/Board/Operators/QueenOperator.cs +++ b/Cosette/Engine/Board/Operators/QueenOperator.cs @@ -6,10 +6,10 @@ namespace Cosette.Engine.Board.Operators { public static class QueenOperator { - public static int GetAvailableMoves(BoardState boardState, Color color, Span moves, int offset) + public static int GetAvailableMoves(BoardState boardState, int color, Span moves, int offset) { var enemyColor = ColorOperations.Invert(color); - var queens = boardState.Pieces[(int)color][(int)Piece.Queen]; + var queens = boardState.Pieces[color][Piece.Queen]; while (queens != 0) { @@ -17,26 +17,26 @@ public static int GetAvailableMoves(BoardState boardState, Color color, Span moves, int offset) + public static int GetAvailableQMoves(BoardState boardState, int color, Span moves, int offset) { var enemyColor = ColorOperations.Invert(color); - var queens = boardState.Pieces[(int)color][(int)Piece.Queen]; + var queens = boardState.Pieces[color][Piece.Queen]; while (queens != 0) { @@ -44,25 +44,25 @@ public static int GetAvailableQMoves(BoardState boardState, Color color, Span moves, int offset) + public static int GetAvailableMoves(BoardState boardState, int color, Span moves, int offset) { var enemyColor = ColorOperations.Invert(color); - var rooks = boardState.Pieces[(int)color][(int)Piece.Rook]; + var rooks = boardState.Pieces[color][Piece.Rook]; while (rooks != 0) { @@ -17,26 +17,26 @@ public static int GetAvailableMoves(BoardState boardState, Color color, Span moves, int offset) + public static int GetAvailableQMoves(BoardState boardState, int color, Span moves, int offset) { var enemyColor = ColorOperations.Invert(color); - var rooks = boardState.Pieces[(int)color][(int)Piece.Rook]; + var rooks = boardState.Pieces[color][Piece.Rook]; while (rooks != 0) { @@ -44,25 +44,25 @@ public static int GetAvailableQMoves(BoardState boardState, Color color, Span> 58]; #endif diff --git a/Cosette/Engine/Common/Castling.cs b/Cosette/Engine/Common/Castling.cs index 82887cf..11bf756 100644 --- a/Cosette/Engine/Common/Castling.cs +++ b/Cosette/Engine/Common/Castling.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Text; namespace Cosette.Engine.Common { diff --git a/Cosette/Engine/Common/Color.cs b/Cosette/Engine/Common/Color.cs index 754db64..dd26287 100644 --- a/Cosette/Engine/Common/Color.cs +++ b/Cosette/Engine/Common/Color.cs @@ -1,8 +1,8 @@ namespace Cosette.Engine.Common { - public enum Color + public static class Color { - White, - Black + public const int White = 0; + public const int Black = 1; } } diff --git a/Cosette/Engine/Common/ColorOperations.cs b/Cosette/Engine/Common/ColorOperations.cs index a1e56a6..b28aee6 100644 --- a/Cosette/Engine/Common/ColorOperations.cs +++ b/Cosette/Engine/Common/ColorOperations.cs @@ -1,10 +1,8 @@ -using System.Runtime.CompilerServices; - -namespace Cosette.Engine.Common +namespace Cosette.Engine.Common { public static class ColorOperations { - public static Color Invert(Color color) + public static int Invert(int color) { return color == Color.White ? Color.Black : Color.White; } diff --git a/Cosette/Engine/Common/Extensions/RandomExtension.cs b/Cosette/Engine/Common/Extensions/RandomExtension.cs index 0db7db2..61cec80 100644 --- a/Cosette/Engine/Common/Extensions/RandomExtension.cs +++ b/Cosette/Engine/Common/Extensions/RandomExtension.cs @@ -4,11 +4,12 @@ namespace Cosette.Engine.Common.Extensions { public static class RandomExtension { - public static ulong NextLong(this Random random) + public static unsafe ulong NextLong(this Random random) { - var bytes = new byte[8]; + Span bytes = stackalloc byte[8]; random.NextBytes(bytes); - return BitConverter.ToUInt64(bytes, 0); + + return BitConverter.ToUInt64(bytes); } } } diff --git a/Cosette/Engine/Common/FastStack.cs b/Cosette/Engine/Common/FastStack.cs index 545e06a..710a6a0 100644 --- a/Cosette/Engine/Common/FastStack.cs +++ b/Cosette/Engine/Common/FastStack.cs @@ -1,10 +1,8 @@ -using System.Runtime.CompilerServices; - -namespace Cosette.Engine.Common +namespace Cosette.Engine.Common { public class FastStack { - private T[] _stack; + private readonly T[] _stack; private int _pointer; public FastStack(int capacity) diff --git a/Cosette/Engine/Common/GamePhase.cs b/Cosette/Engine/Common/GamePhase.cs index dd1d2a1..a619d8c 100644 --- a/Cosette/Engine/Common/GamePhase.cs +++ b/Cosette/Engine/Common/GamePhase.cs @@ -1,8 +1,8 @@ namespace Cosette.Engine.Common { - public enum GamePhase + public static class GamePhase { - Opening, - Ending + public const int Opening = 0; + public const int Ending = 1; } } diff --git a/Cosette/Engine/Common/Piece.cs b/Cosette/Engine/Common/Piece.cs index f8c7748..63f8f27 100644 --- a/Cosette/Engine/Common/Piece.cs +++ b/Cosette/Engine/Common/Piece.cs @@ -1,12 +1,12 @@ namespace Cosette.Engine.Common { - public enum Piece : byte + public static class Piece { - Pawn, - Knight, - Bishop, - Rook, - Queen, - King + public const int Pawn = 0; + public const int Knight = 1; + public const int Bishop = 2; + public const int Rook = 3; + public const int Queen = 4; + public const int King = 5; } } diff --git a/Cosette/Engine/Common/Position.cs b/Cosette/Engine/Common/Position.cs index 02f231f..31805dc 100644 --- a/Cosette/Engine/Common/Position.cs +++ b/Cosette/Engine/Common/Position.cs @@ -5,6 +5,8 @@ public readonly struct Position public readonly int X; public readonly int Y; + public static Position Empty = new Position(); + public Position(int x, int y) { X = x; @@ -53,6 +55,21 @@ public static Position FromText(string move) return a.X != b.X || a.Y != b.Y; } + public override bool Equals(object obj) + { + if (ReferenceEquals(obj, null)) + { + return false; + } + + if (GetType() != obj.GetType()) + { + return false; + } + + return this == (Position)obj; + } + public override int GetHashCode() { return X * 8 + Y; diff --git a/Cosette/Engine/Fen/BoardToFen.cs b/Cosette/Engine/Fen/BoardToFen.cs new file mode 100644 index 0000000..507ef44 --- /dev/null +++ b/Cosette/Engine/Fen/BoardToFen.cs @@ -0,0 +1,157 @@ +using System; +using System.Net; +using System.Text; +using Cosette.Engine.Ai.Search; +using Cosette.Engine.Board; +using Cosette.Engine.Common; + +namespace Cosette.Engine.Fen +{ + public static class BoardToFen + { + public static string Parse(BoardState board) + { + return string.Join(' ', + ParseBoardState(board), + ParseColor(board), + ParseCastling(board), + ParseEnPassant(board), + ParseHalfmoveClock(board), + ParseFullmoveNumber(board) + ); + } + + private static string ParseBoardState(BoardState board) + { + var resultBuilder = new StringBuilder(); + var rankBuilder = new StringBuilder(); + + for (var rank = 7; rank >= 0; rank--) + { + var emptyFields = 0; + + for (var file = 7; file >= 0; file--) + { + var fieldIndex = rank * 8 + file; + + var possibleWhitePiece = board.PieceTable[fieldIndex]; + var possibleBlackPiece = board.PieceTable[fieldIndex]; + + var color = (board.Occupancy[Color.White] & (1ul << fieldIndex)) != 0 ? Color.White : + (board.Occupancy[Color.Black] & (1ul << fieldIndex)) != 0 ? Color.Black : + -1; + + var piece = color == Color.White ? possibleWhitePiece : + color == Color.Black ? possibleBlackPiece : + -1; + + if (piece != -1) + { + if (emptyFields != 0) + { + rankBuilder.Append(emptyFields); + emptyFields = 0; + } + + rankBuilder.Append(ConvertToPiece(piece, color)); + } + else + { + emptyFields++; + } + } + + if (emptyFields != 0) + { + rankBuilder.Append(emptyFields); + } + + resultBuilder.Append(rankBuilder.ToString()); + rankBuilder.Clear(); + + if (rank > 0) + { + resultBuilder.Append('/'); + } + } + + return resultBuilder.ToString(); + } + + private static string ParseColor(BoardState board) + { + return board.ColorToMove == Color.White ? "w" : "b"; + } + + private static string ParseCastling(BoardState board) + { + var resultBuilder = new StringBuilder(); + + if ((board.Castling & Castling.WhiteShort) != 0) + { + resultBuilder.Append('K'); + } + + if ((board.Castling & Castling.WhiteLong) != 0) + { + resultBuilder.Append('Q'); + } + + if ((board.Castling & Castling.BlackShort) != 0) + { + resultBuilder.Append('k'); + } + + if ((board.Castling & Castling.BlackLong) != 0) + { + resultBuilder.Append('q'); + } + + if (resultBuilder.Length == 0) + { + resultBuilder.Append('-'); + } + + return resultBuilder.ToString(); + } + + private static string ParseEnPassant(BoardState board) + { + if (board.EnPassant == 0) + { + return "-"; + } + + var enPassantField = BitOperations.BitScan(board.EnPassant); + var enPassantPosition = Position.FromFieldIndex(enPassantField); + + return enPassantPosition.ToString(); + } + + private static string ParseHalfmoveClock(BoardState board) + { + return board.IrreversibleMovesCount.ToString(); + } + + private static string ParseFullmoveNumber(BoardState board) + { + return board.MovesCount.ToString(); + } + + private static char ConvertToPiece(int piece, int color) + { + var result = piece switch + { + Piece.Pawn => 'P', + Piece.Rook => 'R', + Piece.Knight => 'N', + Piece.Bishop => 'B', + Piece.Queen => 'Q', + Piece.King => 'K', + _ => throw new InvalidOperationException() + }; + + return color == Color.Black ? char.ToLower(result) : result; + } + } +} diff --git a/Cosette/Engine/Fen/FenParser.cs b/Cosette/Engine/Fen/FenToBoard.cs similarity index 70% rename from Cosette/Engine/Fen/FenParser.cs rename to Cosette/Engine/Fen/FenToBoard.cs index 6045933..d3414fe 100644 --- a/Cosette/Engine/Fen/FenParser.cs +++ b/Cosette/Engine/Fen/FenToBoard.cs @@ -1,13 +1,12 @@ using System; -using System.Linq; using Cosette.Engine.Board; using Cosette.Engine.Common; namespace Cosette.Engine.Fen { - public static class FenParser + public static class FenToBoard { - public static BoardState Parse(string fen, out int moveNumber) + public static BoardState Parse(string fen) { var split = fen.Split(' '); var boardState = split[0]; @@ -15,24 +14,26 @@ public static BoardState Parse(string fen, out int moveNumber) var castlingState = split[2]; var enPassantState = split[3]; var halfmoveClock = int.Parse(split[4]); - moveNumber = int.Parse(split[5]); + var movesCount = int.Parse(split[5]); var result = new BoardState(); var currentColor = ParseCurrentColor(colorState); ParseBoardState(boardState, result); ParseCastlingState(castlingState, result); - ParseEnPassantState(enPassantState, currentColor, result); + ParseEnPassantState(enPassantState, result); - result.Material[(int)Color.White] = result.CalculateMaterial(Color.White); - result.Material[(int)Color.Black] = result.CalculateMaterial(Color.Black); + result.Material[Color.White] = result.CalculateMaterial(Color.White); + result.Material[Color.Black] = result.CalculateMaterial(Color.Black); - result.Position[(int)Color.White][(int)GamePhase.Opening] = result.CalculatePosition(Color.White, GamePhase.Opening); - result.Position[(int)Color.White][(int)GamePhase.Ending] = result.CalculatePosition(Color.White, GamePhase.Ending); - result.Position[(int)Color.Black][(int)GamePhase.Opening] = result.CalculatePosition(Color.Black, GamePhase.Opening); - result.Position[(int)Color.Black][(int)GamePhase.Ending] = result.CalculatePosition(Color.Black, GamePhase.Ending); + result.Position[Color.White][GamePhase.Opening] = result.CalculatePosition(Color.White, GamePhase.Opening); + result.Position[Color.White][GamePhase.Ending] = result.CalculatePosition(Color.White, GamePhase.Ending); + result.Position[Color.Black][GamePhase.Opening] = result.CalculatePosition(Color.Black, GamePhase.Opening); + result.Position[Color.Black][GamePhase.Ending] = result.CalculatePosition(Color.Black, GamePhase.Ending); - result.ColorToMove = colorState == "w" ? Color.White : Color.Black; + result.MovesCount = movesCount; + result.IrreversibleMovesCount = halfmoveClock; + result.ColorToMove = currentColor; result.Hash = ZobristHashing.CalculateHash(result); result.PawnHash = ZobristHashing.CalculatePawnHash(result); @@ -53,21 +54,20 @@ private static void ParseBoardState(string boardState, BoardState result) { var piece = ConvertToPiece(c); var color = ConvertToColor(c); - result.AddPiece(color, piece, (byte) position.ToFieldIndex()); + result.AddPiece(color, piece, (byte)position.ToFieldIndex()); position += new Position(1, 0); } else if (char.IsDigit(c)) { position += new Position(c - '0', 0); } - } position = new Position(0, position.Y - 1); } } - private static Color ParseCurrentColor(string currentColor) + private static int ParseCurrentColor(string currentColor) { return currentColor == "w" ? Color.White : Color.Black; } @@ -95,7 +95,7 @@ private static void ParseCastlingState(string castlingState, BoardState result) } } - private static void ParseEnPassantState(string enPassantState, Color color, BoardState result) + private static void ParseEnPassantState(string enPassantState, BoardState result) { if (enPassantState != "-") { @@ -104,7 +104,7 @@ private static void ParseEnPassantState(string enPassantState, Color color, Boar } } - private static Piece ConvertToPiece(char c) + private static int ConvertToPiece(char c) { switch (char.ToLower(c)) { @@ -119,7 +119,7 @@ private static Piece ConvertToPiece(char c) throw new InvalidOperationException(); } - private static Color ConvertToColor(char c) + private static int ConvertToColor(char c) { return char.IsUpper(c) ? Color.White : Color.Black; } diff --git a/Cosette/Engine/Moves/BishopMovesGenerator.cs b/Cosette/Engine/Moves/BishopMovesGenerator.cs index 89dc888..8aa5d40 100644 --- a/Cosette/Engine/Moves/BishopMovesGenerator.cs +++ b/Cosette/Engine/Moves/BishopMovesGenerator.cs @@ -1,5 +1,4 @@ -using System.Runtime.CompilerServices; -using Cosette.Engine.Moves.Magic; +using Cosette.Engine.Moves.Magic; namespace Cosette.Engine.Moves { diff --git a/Cosette/Engine/Moves/KingMovesGenerator.cs b/Cosette/Engine/Moves/KingMovesGenerator.cs index 13d5db6..074a084 100644 --- a/Cosette/Engine/Moves/KingMovesGenerator.cs +++ b/Cosette/Engine/Moves/KingMovesGenerator.cs @@ -1,5 +1,4 @@ -using System.Runtime.CompilerServices; -using Cosette.Engine.Moves.Patterns; +using Cosette.Engine.Moves.Patterns; namespace Cosette.Engine.Moves { diff --git a/Cosette/Engine/Moves/KnightMovesGenerator.cs b/Cosette/Engine/Moves/KnightMovesGenerator.cs index 40f0824..64faa8a 100644 --- a/Cosette/Engine/Moves/KnightMovesGenerator.cs +++ b/Cosette/Engine/Moves/KnightMovesGenerator.cs @@ -1,5 +1,4 @@ -using System.Runtime.CompilerServices; -using Cosette.Engine.Moves.Patterns; +using Cosette.Engine.Moves.Patterns; namespace Cosette.Engine.Moves { diff --git a/Cosette/Engine/Moves/Magic/MagicBitboards.cs b/Cosette/Engine/Moves/Magic/MagicBitboards.cs index 0fc98dd..948c11e 100644 --- a/Cosette/Engine/Moves/Magic/MagicBitboards.cs +++ b/Cosette/Engine/Moves/Magic/MagicBitboards.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.CompilerServices; using Cosette.Engine.Board; using Cosette.Engine.Common.Extensions; using Cosette.Engine.Moves.Patterns; @@ -10,7 +9,7 @@ public static class MagicBitboards { private static MagicContainer[] _rookMagicArray; private static MagicContainer[] _bishopMagicArray; - private static Random _random; + private static readonly Random _random; static MagicBitboards() { diff --git a/Cosette/Engine/Moves/Move.cs b/Cosette/Engine/Moves/Move.cs index 03c85e2..387a99d 100644 --- a/Cosette/Engine/Moves/Move.cs +++ b/Cosette/Engine/Moves/Move.cs @@ -1,4 +1,5 @@ using System; +using Cosette.Engine.Ai.Search; using Cosette.Engine.Board; using Cosette.Engine.Common; @@ -6,69 +7,82 @@ namespace Cosette.Engine.Moves { public readonly struct Move { - public readonly byte From; - public readonly byte To; - public readonly Piece Piece; - public readonly MoveFlags Flags; + public byte From => (byte)(_data & 0x3F); + public byte To => (byte)((_data >> 6) & 0x3F); + public MoveFlags Flags => (MoveFlags)(_data >> 12); + public static Move Empty = new Move(); - public Move(byte from, byte to, Piece piece, MoveFlags flags) - { - From = from; - To = to; - Piece = piece; - Flags = flags; - } + private readonly ushort _data; - public Move(int from, int to, Piece piece, MoveFlags flags) + public Move(int from, int to, MoveFlags flags) { - From = (byte) from; - To = (byte) to; - Piece = piece; - Flags = flags; + _data = (ushort)from; + _data |= (ushort)(to << 6); + _data |= (ushort)((byte)flags << 12); } public static bool operator ==(Move a, Move b) { - return a.From == b.From && a.To == b.To && a.Piece == b.Piece && a.Flags == b.Flags; + return a._data == b._data; } public static bool operator !=(Move a, Move b) { - return a.From != b.From || a.To != b.To || a.Piece != b.Piece || a.Flags != b.Flags; + return a._data != b._data; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(obj, null)) + { + return false; + } + + if (GetType() != obj.GetType()) + { + return false; + } + + return this == (Move)obj; + } + + public override int GetHashCode() + { + return _data; } public static Move FromTextNotation(BoardState board, string textNotation) { var from = Position.FromText(textNotation.Substring(0, 2)); var to = Position.FromText(textNotation.Substring(2, 2)); - var flags = textNotation.Length == 5 ? GetMoveFlags(textNotation[4]) : MoveFlags.None; + var flags = textNotation.Length == 5 ? GetMoveFlags(textNotation[4]) : MoveFlags.Quiet; - Span moves = stackalloc Move[128]; + Span moves = stackalloc Move[SearchConstants.MaxMovesCount]; var movesCount = board.GetAvailableMoves(moves); for (var i = 0; i < movesCount; i++) { if (Position.FromFieldIndex(moves[i].From) == from && Position.FromFieldIndex(moves[i].To) == to) { - if (flags == MoveFlags.None || (moves[i].Flags & flags) != 0) + if (flags == MoveFlags.Quiet || ((byte)moves[i].Flags & ~MoveFlagFields.Capture) == (byte)flags) { return moves[i]; } } } - return new Move(); + return Empty; } public bool IsQuiet() { - return Flags == MoveFlags.None || Flags == MoveFlags.DoublePush; + return Flags == MoveFlags.Quiet || Flags == MoveFlags.DoublePush; } public override string ToString() { var baseMove = $"{Position.FromFieldIndex(From)}{Position.FromFieldIndex(To)}"; - if ((int) Flags >= 16) + if (((byte)Flags & MoveFlagFields.Promotion) != 0) { return baseMove + GetPromotionSymbol(Flags); } @@ -80,24 +94,34 @@ public override string ToString() private string GetPromotionSymbol(MoveFlags flags) { - if ((flags & MoveFlags.KnightPromotion) != 0) - { - return "n"; - } - else if ((flags & MoveFlags.BishopPromotion) != 0) + switch (flags) { - return "b"; - } - else if((flags & MoveFlags.RookPromotion) != 0) - { - return "r"; - } - else if((flags & MoveFlags.QueenPromotion) != 0) - { - return "q"; + case MoveFlags.QueenPromotion: + case MoveFlags.QueenPromotionCapture: + { + return "q"; + } + + case MoveFlags.RookPromotion: + case MoveFlags.RookPromotionCapture: + { + return "r"; + } + + case MoveFlags.BishopPromotion: + case MoveFlags.BishopPromotionCapture: + { + return "b"; + } + + case MoveFlags.KnightPromotion: + case MoveFlags.KnightPromotionCapture: + { + return "n"; + } } - return null; + throw new InvalidOperationException(); } private static MoveFlags GetMoveFlags(char promotionPiece) diff --git a/Cosette/Engine/Moves/MoveFlags.cs b/Cosette/Engine/Moves/MoveFlags.cs index 574c4c5..7fcf436 100644 --- a/Cosette/Engine/Moves/MoveFlags.cs +++ b/Cosette/Engine/Moves/MoveFlags.cs @@ -1,18 +1,28 @@ -using System; - -namespace Cosette.Engine.Moves +namespace Cosette.Engine.Moves { - [Flags] public enum MoveFlags : byte { - None = 0, - Kill = 1, - Castling = 2, - DoublePush = 4, - EnPassant = 8, - KnightPromotion = 16, - BishopPromotion = 32, - RookPromotion = 64, - QueenPromotion = 128 + Quiet = 0, + DoublePush = 1, + KingCastle = 2, + QueenCastle = 3, + Capture = 4, + EnPassant = 5, + KnightPromotion = 8, + BishopPromotion = 9, + RookPromotion = 10, + QueenPromotion = 11, + KnightPromotionCapture = 12, + BishopPromotionCapture = 13, + RookPromotionCapture = 14, + QueenPromotionCapture = 15 + } + + public static class MoveFlagFields + { + public const int Special0 = 1; + public const int Special1 = 2; + public const int Capture = 4; + public const int Promotion = 8; } } diff --git a/Cosette/Engine/Moves/Patterns/BoxPatternGenerator.cs b/Cosette/Engine/Moves/Patterns/BoxPatternGenerator.cs index 374bf54..9a00bbc 100644 --- a/Cosette/Engine/Moves/Patterns/BoxPatternGenerator.cs +++ b/Cosette/Engine/Moves/Patterns/BoxPatternGenerator.cs @@ -1,23 +1,22 @@ -using System.Runtime.CompilerServices; -using Cosette.Engine.Board; +using Cosette.Engine.Board; namespace Cosette.Engine.Moves.Patterns { public static class BoxPatternGenerator { - private static readonly ulong[] Patterns = new ulong[64]; + private static readonly ulong[] _patterns = new ulong[64]; static BoxPatternGenerator() { - for (var i = 0; i < Patterns.Length; i++) + for (var i = 0; i < _patterns.Length; i++) { - Patterns[i] = GetPatternForField(i); + _patterns[i] = GetPatternForField(i); } } public static ulong GetPattern(int fieldIndex) { - return Patterns[fieldIndex]; + return _patterns[fieldIndex]; } private static ulong GetPatternForField(int fieldIndex) diff --git a/Cosette/Engine/Moves/Patterns/DiagonalPatternGenerator.cs b/Cosette/Engine/Moves/Patterns/DiagonalPatternGenerator.cs index 2fee5b3..42653fc 100644 --- a/Cosette/Engine/Moves/Patterns/DiagonalPatternGenerator.cs +++ b/Cosette/Engine/Moves/Patterns/DiagonalPatternGenerator.cs @@ -1,28 +1,27 @@ -using System.Runtime.CompilerServices; -using Cosette.Engine.Common; +using Cosette.Engine.Common; namespace Cosette.Engine.Moves.Patterns { public static class DiagonalPatternGenerator { - private static readonly ulong[] Patterns = new ulong[64]; + private static readonly ulong[] _patterns = new ulong[64]; static DiagonalPatternGenerator() { - for (var i = 0; i < Patterns.Length; i++) + for (var i = 0; i < _patterns.Length; i++) { var rightTopPattern = GetPatternForField(i, new Position(-1, 1)); var leftTopPattern = GetPatternForField(i, new Position(1, 1)); var rightBottomPattern = GetPatternForField(i, new Position(-1, -1)); var leftBottomPattern = GetPatternForField(i, new Position(1, -1)); - Patterns[i] = rightTopPattern | leftTopPattern | rightBottomPattern | leftBottomPattern; + _patterns[i] = rightTopPattern | leftTopPattern | rightBottomPattern | leftBottomPattern; } } public static ulong GetPattern(int fieldIndex) { - return Patterns[fieldIndex]; + return _patterns[fieldIndex]; } private static ulong GetPatternForField(int fieldIndex, Position shift) diff --git a/Cosette/Engine/Moves/Patterns/FilePatternGenerator.cs b/Cosette/Engine/Moves/Patterns/FilePatternGenerator.cs index c9ba4e0..a81df5b 100644 --- a/Cosette/Engine/Moves/Patterns/FilePatternGenerator.cs +++ b/Cosette/Engine/Moves/Patterns/FilePatternGenerator.cs @@ -1,24 +1,23 @@ -using System.Runtime.CompilerServices; -using Cosette.Engine.Board; +using Cosette.Engine.Board; using Cosette.Engine.Common; namespace Cosette.Engine.Moves.Patterns { public static class FilePatternGenerator { - private static readonly ulong[] Patterns = new ulong[64]; + private static readonly ulong[] _patterns = new ulong[64]; static FilePatternGenerator() { - for (var i = 0; i < Patterns.Length; i++) + for (var i = 0; i < _patterns.Length; i++) { - Patterns[i] = GetPatternForField(i) & ~(1ul << i); + _patterns[i] = GetPatternForField(i) & ~(1ul << i); } } public static ulong GetPattern(int fieldIndex) { - return Patterns[fieldIndex]; + return _patterns[fieldIndex]; } private static ulong GetPatternForField(int fieldIndex) diff --git a/Cosette/Engine/Moves/Patterns/JumpPatternGenerator.cs b/Cosette/Engine/Moves/Patterns/JumpPatternGenerator.cs index d177dbe..a5e7792 100644 --- a/Cosette/Engine/Moves/Patterns/JumpPatternGenerator.cs +++ b/Cosette/Engine/Moves/Patterns/JumpPatternGenerator.cs @@ -1,23 +1,22 @@ -using System.Runtime.CompilerServices; -using Cosette.Engine.Board; +using Cosette.Engine.Board; namespace Cosette.Engine.Moves.Patterns { public static class JumpPatternGenerator { - private static readonly ulong[] Patterns = new ulong[64]; + private static readonly ulong[] _patterns = new ulong[64]; static JumpPatternGenerator() { - for (var i = 0; i < Patterns.Length; i++) + for (var i = 0; i < _patterns.Length; i++) { - Patterns[i] = GetPatternForField(i); + _patterns[i] = GetPatternForField(i); } } public static ulong GetPattern(int fieldIndex) { - return Patterns[fieldIndex]; + return _patterns[fieldIndex]; } private static ulong GetPatternForField(int fieldIndex) diff --git a/Cosette/Engine/Moves/Patterns/RankPatternGenerator.cs b/Cosette/Engine/Moves/Patterns/RankPatternGenerator.cs index 9540180..ad12bf7 100644 --- a/Cosette/Engine/Moves/Patterns/RankPatternGenerator.cs +++ b/Cosette/Engine/Moves/Patterns/RankPatternGenerator.cs @@ -1,24 +1,23 @@ -using System.Runtime.CompilerServices; -using Cosette.Engine.Board; +using Cosette.Engine.Board; using Cosette.Engine.Common; namespace Cosette.Engine.Moves.Patterns { public static class RankPatternGenerator { - private static readonly ulong[] Patterns = new ulong[64]; + private static readonly ulong[] _patterns = new ulong[64]; static RankPatternGenerator() { - for (var i = 0; i < Patterns.Length; i++) + for (var i = 0; i < _patterns.Length; i++) { - Patterns[i] = GetPatternForField(i) & ~(1ul << i); + _patterns[i] = GetPatternForField(i) & ~(1ul << i); } } public static ulong GetPattern(int fieldIndex) { - return Patterns[fieldIndex]; + return _patterns[fieldIndex]; } private static ulong GetPatternForField(int fieldIndex) diff --git a/Cosette/Engine/Moves/QueenMovesGenerator.cs b/Cosette/Engine/Moves/QueenMovesGenerator.cs index 13b09ed..b45b1a8 100644 --- a/Cosette/Engine/Moves/QueenMovesGenerator.cs +++ b/Cosette/Engine/Moves/QueenMovesGenerator.cs @@ -1,5 +1,4 @@ -using System.Runtime.CompilerServices; -using Cosette.Engine.Moves.Magic; +using Cosette.Engine.Moves.Magic; namespace Cosette.Engine.Moves { diff --git a/Cosette/Engine/Moves/RookMovesGenerator.cs b/Cosette/Engine/Moves/RookMovesGenerator.cs index 55d42a0..2864dc6 100644 --- a/Cosette/Engine/Moves/RookMovesGenerator.cs +++ b/Cosette/Engine/Moves/RookMovesGenerator.cs @@ -1,5 +1,4 @@ -using System.Runtime.CompilerServices; -using Cosette.Engine.Moves.Magic; +using Cosette.Engine.Moves.Magic; namespace Cosette.Engine.Moves { diff --git a/Cosette/Engine/Perft/AdvancedPerft.cs b/Cosette/Engine/Perft/AdvancedPerft.cs index 9354e6d..f0f3728 100644 --- a/Cosette/Engine/Perft/AdvancedPerft.cs +++ b/Cosette/Engine/Perft/AdvancedPerft.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using Cosette.Engine.Ai.Search; using Cosette.Engine.Board; using Cosette.Engine.Common; using Cosette.Engine.Moves; @@ -21,7 +22,7 @@ public static AdvancedPerftResult Run(BoardState boardState, int depth) private static void Perft(BoardState boardState, int depth, AdvancedPerftResult result) { - Span moves = stackalloc Move[128]; + Span moves = stackalloc Move[SearchConstants.MaxMovesCount]; var movesCount = boardState.GetAvailableMoves(moves); if (depth <= 0) @@ -56,17 +57,17 @@ private static void UpdateResult(BoardState boardState, Span moves, int mo if (!boardState.IsKingChecked(ColorOperations.Invert(boardState.ColorToMove))) { - if ((moves[i].Flags & MoveFlags.Kill) != 0) + if (((byte)moves[i].Flags & MoveFlagFields.Capture) != 0) { result.Captures++; } - if ((moves[i].Flags & MoveFlags.Castling) != 0) + if (moves[i].Flags == MoveFlags.KingCastle || moves[i].Flags == MoveFlags.QueenCastle) { result.Castles++; } - if ((moves[i].Flags & MoveFlags.EnPassant) != 0) + if (moves[i].Flags == MoveFlags.EnPassant) { result.EnPassants++; result.Captures++; diff --git a/Cosette/Engine/Perft/DividedPerft.cs b/Cosette/Engine/Perft/DividedPerft.cs index a66dd85..cc93682 100644 --- a/Cosette/Engine/Perft/DividedPerft.cs +++ b/Cosette/Engine/Perft/DividedPerft.cs @@ -1,4 +1,5 @@ using System; +using Cosette.Engine.Ai.Search; using Cosette.Engine.Board; using Cosette.Engine.Common; using Cosette.Engine.Moves; @@ -10,7 +11,7 @@ public static class DividedPerft { public static DividedPerftResult Run(BoardState boardState, int depth) { - Span moves = stackalloc Move[128]; + Span moves = stackalloc Move[SearchConstants.MaxMovesCount]; var movesCount = boardState.GetAvailableMoves(moves); var result = new DividedPerftResult(); @@ -42,7 +43,7 @@ private static ulong Perft(BoardState boardState, int depth) return 1; } - Span moves = stackalloc Move[128]; + Span moves = stackalloc Move[SearchConstants.MaxMovesCount]; var movesCount = boardState.GetAvailableMoves(moves); ulong nodes = 0; diff --git a/Cosette/Engine/Perft/SimplePerft.cs b/Cosette/Engine/Perft/SimplePerft.cs index 127de6f..84e97d1 100644 --- a/Cosette/Engine/Perft/SimplePerft.cs +++ b/Cosette/Engine/Perft/SimplePerft.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using Cosette.Engine.Ai.Search; using Cosette.Engine.Board; using Cosette.Engine.Common; using Cosette.Engine.Moves; @@ -32,7 +33,7 @@ private static ulong Perft(BoardState boardState, int depth) return 1; } - Span moves = stackalloc Move[128]; + Span moves = stackalloc Move[SearchConstants.MaxMovesCount]; var movesCount = boardState.GetAvailableMoves(moves); ulong nodes = 0; diff --git a/Cosette/Engine/Perft/VerificationPerft.cs b/Cosette/Engine/Perft/VerificationPerft.cs index ff9bfd1..f9fbadf 100644 --- a/Cosette/Engine/Perft/VerificationPerft.cs +++ b/Cosette/Engine/Perft/VerificationPerft.cs @@ -1,5 +1,5 @@ using System; -using System.Diagnostics; +using Cosette.Engine.Ai.Search; using Cosette.Engine.Board; using Cosette.Engine.Common; using Cosette.Engine.Moves; @@ -33,7 +33,7 @@ private static ulong Perft(BoardState boardState, int depth, ref bool verificati return 1; } - Span moves = stackalloc Move[128]; + Span moves = stackalloc Move[SearchConstants.MaxMovesCount]; var movesCount = boardState.GetAvailableMoves(moves); ulong nodes = 0; @@ -55,8 +55,8 @@ private static ulong Perft(BoardState boardState, int depth, ref bool verificati private static bool VerifyBoard(BoardState board) { - if (board.Material[(int)Color.White] != board.CalculateMaterial(Color.White) || - board.Material[(int)Color.Black] != board.CalculateMaterial(Color.Black)) + if (board.Material[Color.White] != board.CalculateMaterial(Color.White) || + board.Material[Color.Black] != board.CalculateMaterial(Color.Black)) { return false; } @@ -71,10 +71,10 @@ private static bool VerifyBoard(BoardState board) return false; } - if (board.Position[(int) Color.White][(int) GamePhase.Opening] != board.CalculatePosition(Color.White, GamePhase.Opening) || - board.Position[(int) Color.White][(int) GamePhase.Ending] != board.CalculatePosition(Color.White, GamePhase.Ending) || - board.Position[(int) Color.Black][(int) GamePhase.Opening] != board.CalculatePosition(Color.Black, GamePhase.Opening) || - board.Position[(int) Color.Black][(int) GamePhase.Ending] != board.CalculatePosition(Color.Black, GamePhase.Ending)) + if (board.Position[Color.White][GamePhase.Opening] != board.CalculatePosition(Color.White, GamePhase.Opening) || + board.Position[Color.White][GamePhase.Ending] != board.CalculatePosition(Color.White, GamePhase.Ending) || + board.Position[Color.Black][GamePhase.Opening] != board.CalculatePosition(Color.Black, GamePhase.Opening) || + board.Position[Color.Black][GamePhase.Ending] != board.CalculatePosition(Color.Black, GamePhase.Ending)) { return false; } diff --git a/Cosette/Interactive/Commands/AdvancedPerftCommand.cs b/Cosette/Interactive/Commands/AdvancedPerftCommand.cs index 9853a4a..267a9b2 100644 --- a/Cosette/Interactive/Commands/AdvancedPerftCommand.cs +++ b/Cosette/Interactive/Commands/AdvancedPerftCommand.cs @@ -1,8 +1,4 @@ -using System; -using System.Diagnostics; -using Cosette.Engine.Board; -using Cosette.Engine.Common; -using Cosette.Engine.Moves; +using Cosette.Engine.Board; using Cosette.Engine.Perft; namespace Cosette.Interactive.Commands @@ -10,7 +6,7 @@ namespace Cosette.Interactive.Commands public class AdvancedPerftCommand : ICommand { public string Description { get; } - private InteractiveConsole _interactiveConsole; + private readonly InteractiveConsole _interactiveConsole; public AdvancedPerftCommand(InteractiveConsole interactiveConsole) { diff --git a/Cosette/Interactive/Commands/BenchmarkCommand.cs b/Cosette/Interactive/Commands/BenchmarkCommand.cs index 6714ac7..a3d6d8a 100644 --- a/Cosette/Interactive/Commands/BenchmarkCommand.cs +++ b/Cosette/Interactive/Commands/BenchmarkCommand.cs @@ -1,26 +1,16 @@ -using System; -using System.Diagnostics; -using System.Linq; -using System.Net; -using System.Xml.XPath; -using Cosette.Engine.Ai; +using System.Diagnostics; using Cosette.Engine.Ai.Ordering; using Cosette.Engine.Ai.Search; using Cosette.Engine.Ai.Transposition; using Cosette.Engine.Board; -using Cosette.Engine.Common; using Cosette.Engine.Fen; -using Cosette.Engine.Moves; -using Cosette.Engine.Moves.Magic; -using Cosette.Engine.Perft; -using Cosette.Engine.Perft.Results; namespace Cosette.Interactive.Commands { public class BenchmarkCommand : ICommand { public string Description { get; } - private InteractiveConsole _interactiveConsole; + private readonly InteractiveConsole _interactiveConsole; public BenchmarkCommand(InteractiveConsole interactiveConsole) { @@ -30,8 +20,7 @@ public BenchmarkCommand(InteractiveConsole interactiveConsole) public void Run(params string[] parameters) { - TranspositionTable.Init(512); - PawnHashTable.Init(8); + HashTableAllocator.Allocate(512); var stopwatch = Stopwatch.StartNew(); TestOpening(); @@ -47,19 +36,19 @@ private void TestOpening() var boardState = new BoardState(); boardState.SetDefaultState(); - Test(boardState, "Opening", 11); + Test(boardState, "Opening", 12); } private void TestMidGame() { - var boardState = FenParser.Parse("r2qr1k1/p2n1p2/1pb3pp/2ppN1P1/1R1PpP2/BQP1n1PB/P4N1P/1R4K1 w - - 0 21", out _); - Test(boardState, "Midgame", 11); + var boardState = FenToBoard.Parse("r2qr1k1/p2n1p2/1pb3pp/2ppN1P1/1R1PpP2/BQP1n1PB/P4N1P/1R4K1 w - - 0 21"); + Test(boardState, "Midgame", 12); } private void TestEndGame() { - var boardState = FenParser.Parse("7r/8/2k3P1/1p1p2Kp/1P6/2P5/7r/Q7 w - - 0 1", out _); - Test(boardState, "Endgame", 16); + var boardState = FenToBoard.Parse("7r/8/2k3P1/1p1p2Kp/1P6/2P5/7r/Q7 w - - 0 1"); + Test(boardState, "Endgame", 17); } private void Test(BoardState boardState, string name, int depth) @@ -68,22 +57,23 @@ private void Test(BoardState boardState, string name, int depth) TranspositionTable.Clear(); PawnHashTable.Clear(); + EvaluationHashTable.Clear(); KillerHeuristic.Clear(); HistoryHeuristic.Clear(); - IterativeDeepening.AbortSearch = false; - IterativeDeepening.WaitForStopCommand = false; - IterativeDeepening.MoveRestrictions = null; - IterativeDeepening.MaxNodesCount = ulong.MaxValue; + var context = new SearchContext(boardState) + { + MaxDepth = depth + }; - IterativeDeepening.OnSearchUpdate += IterativeDeepening_OnOnSearchUpdate; - IterativeDeepening.FindBestMove(boardState, int.MaxValue, depth, 1); - IterativeDeepening.OnSearchUpdate -= IterativeDeepening_OnOnSearchUpdate; + IterativeDeepening.OnSearchUpdate += IterativeDeepening_OnSearchUpdate; + IterativeDeepening.FindBestMove(context); + IterativeDeepening.OnSearchUpdate -= IterativeDeepening_OnSearchUpdate; _interactiveConsole.WriteLine(); } - private void IterativeDeepening_OnOnSearchUpdate(object? sender, SearchStatistics statistics) + private void IterativeDeepening_OnSearchUpdate(object sender, SearchStatistics statistics) { // Main search result _interactiveConsole.WriteLine($" === Depth: {statistics.Depth}, Score: {statistics.Score}, Best: {statistics.PrincipalVariation[0]}, " + @@ -108,8 +98,28 @@ private void IterativeDeepening_OnOnSearchUpdate(object? sender, SearchStatistic $"Q Beta cutoffs at first move: {statistics.QBetaCutoffsAtFirstMove} ({statistics.QBetaCutoffsAtFirstMovePercent:F} %)"); // Transposition statistics - _interactiveConsole.WriteLine($" Transposition: Entries: {statistics.TTEntries}, Hits: {statistics.TTHits} ({statistics.TTHitsPercent:F} %), " + - $"NonHits: {statistics.TTNonHits}, Collisions: {statistics.TTCollisions}"); + _interactiveConsole.WriteLine($" TT: " + + $"Added: {statistics.TTAddedEntries}, " + + $"Replacements: {statistics.TTReplacements} ({statistics.TTReplacesPercent:F} %), " + + $"Hits: {statistics.TTHits} ({statistics.TTHitsPercent:F} %), " + + $"Missed: {statistics.TTNonHits}, " + + $"Filled: {TranspositionTable.GetFillLevel():F} %"); + + // Pawn hash table statistics + _interactiveConsole.WriteLine($" PHT: " + + $"Added: {statistics.EvaluationStatistics.PHTAddedEntries}, " + + $"Replacements: {statistics.EvaluationStatistics.PHTReplacements} ({statistics.EvaluationStatistics.PHTReplacesPercent:F} %), " + + $"Hits: {statistics.EvaluationStatistics.PHTHits} ({statistics.EvaluationStatistics.PHTHitsPercent:F} %), " + + $"Missed: {statistics.EvaluationStatistics.PHTNonHits}, " + + $"Filled: {PawnHashTable.GetFillLevel():F} %"); + + // Evaluation hash table statistics + _interactiveConsole.WriteLine($" EHT: " + + $"Added: {statistics.EvaluationStatistics.EHTAddedEntries}, " + + $"Replacements: {statistics.EvaluationStatistics.EHTReplacements} ({statistics.EvaluationStatistics.EHTReplacesPercent:F} %), " + + $"Hits: {statistics.EvaluationStatistics.EHTHits} ({statistics.EvaluationStatistics.EHTHitsPercent:F} %), " + + $"Missed: {statistics.EvaluationStatistics.EHTNonHits}, " + + $"Filled: {EvaluationHashTable.GetFillLevel():F} %"); #endif diff --git a/Cosette/Interactive/Commands/DividedPerftCommand.cs b/Cosette/Interactive/Commands/DividedPerftCommand.cs index 4cda1df..84dce69 100644 --- a/Cosette/Interactive/Commands/DividedPerftCommand.cs +++ b/Cosette/Interactive/Commands/DividedPerftCommand.cs @@ -1,10 +1,4 @@ -using System; -using System.Diagnostics; -using System.Linq; -using Cosette.Engine.Board; -using Cosette.Engine.Common; -using Cosette.Engine.Moves; -using Cosette.Engine.Moves.Magic; +using Cosette.Engine.Board; using Cosette.Engine.Perft; namespace Cosette.Interactive.Commands @@ -12,7 +6,7 @@ namespace Cosette.Interactive.Commands public class DividedPerftCommand : ICommand { public string Description { get; } - private InteractiveConsole _interactiveConsole; + private readonly InteractiveConsole _interactiveConsole; public DividedPerftCommand(InteractiveConsole interactiveConsole) { diff --git a/Cosette/Interactive/Commands/EvaluateCommand.cs b/Cosette/Interactive/Commands/EvaluateCommand.cs new file mode 100644 index 0000000..38b2277 --- /dev/null +++ b/Cosette/Interactive/Commands/EvaluateCommand.cs @@ -0,0 +1,53 @@ +using System; +using Cosette.Engine.Ai.Score; +using Cosette.Engine.Ai.Score.Evaluators; +using Cosette.Engine.Fen; + +namespace Cosette.Interactive.Commands +{ + public class EvaluateCommand : ICommand + { + public string Description { get; } + private InteractiveConsole _interactiveConsole; + + public EvaluateCommand(InteractiveConsole interactiveConsole) + { + _interactiveConsole = interactiveConsole; + Description = "Evaluate the specified position"; + } + + public void Run(params string[] parameters) + { + var fen = string.Join(' ', parameters); + var boardState = FenToBoard.Parse(fen); + var evaluationStatistics = new EvaluationStatistics(); + + var openingPhase = boardState.GetPhaseRatio(); + var endingPhase = 1 - openingPhase; + + var materialEvaluation = MaterialEvaluator.Evaluate(boardState); + var castlingEvaluation = CastlingEvaluator.Evaluate(boardState, openingPhase, endingPhase); + var positionEvaluation = PositionEvaluator.Evaluate(boardState, openingPhase, endingPhase); + var pawnStructureEvaluation = PawnStructureEvaluator.Evaluate(boardState, evaluationStatistics, openingPhase, endingPhase); + var mobility = MobilityEvaluator.Evaluate(boardState, openingPhase, endingPhase); + var kingSafety = KingSafetyEvaluator.Evaluate(boardState, openingPhase, endingPhase); + var pieces = PiecesEvaluator.Evaluate(boardState, openingPhase, endingPhase); + + var total = materialEvaluation + castlingEvaluation + positionEvaluation + pawnStructureEvaluation + + mobility + kingSafety + pieces; + + _interactiveConsole.WriteLine($"Evaluation for board with hash {boardState.Hash} (phase {openingPhase:F}, " + + $"{boardState.IrreversibleMovesCount} irreversible moves)"); + + _interactiveConsole.WriteLine($" = Material: {materialEvaluation}"); + _interactiveConsole.WriteLine($" = Castling: {castlingEvaluation}"); + _interactiveConsole.WriteLine($" = Position: {positionEvaluation}"); + _interactiveConsole.WriteLine($" = Pawns: {pawnStructureEvaluation}"); + _interactiveConsole.WriteLine($" = Mobility: {mobility}"); + _interactiveConsole.WriteLine($" = King safety: {kingSafety}"); + _interactiveConsole.WriteLine($" = Pieces evaluation: {pieces}"); + _interactiveConsole.WriteLine(); + _interactiveConsole.WriteLine($" = Total: {total}"); + } + } +} \ No newline at end of file diff --git a/Cosette/Interactive/Commands/HelpCommand.cs b/Cosette/Interactive/Commands/HelpCommand.cs index 2f60672..aebd202 100644 --- a/Cosette/Interactive/Commands/HelpCommand.cs +++ b/Cosette/Interactive/Commands/HelpCommand.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace Cosette.Interactive.Commands { @@ -7,8 +6,8 @@ public class HelpCommand : ICommand { public string Description { get; } - private InteractiveConsole _interactiveConsole; - private Dictionary _commands; + private readonly InteractiveConsole _interactiveConsole; + private readonly Dictionary _commands; public HelpCommand(InteractiveConsole interactiveConsole, Dictionary commands) { diff --git a/Cosette/Interactive/Commands/MagicCommand.cs b/Cosette/Interactive/Commands/MagicCommand.cs index 8530af8..be4f0bc 100644 --- a/Cosette/Interactive/Commands/MagicCommand.cs +++ b/Cosette/Interactive/Commands/MagicCommand.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using Cosette.Engine.Moves.Magic; namespace Cosette.Interactive.Commands @@ -7,7 +6,7 @@ namespace Cosette.Interactive.Commands public class MagicCommand : ICommand { public string Description { get; } - private InteractiveConsole _interactiveConsole; + private readonly InteractiveConsole _interactiveConsole; public MagicCommand(InteractiveConsole interactiveConsole) { diff --git a/Cosette/Interactive/Commands/SimplePerftCommand.cs b/Cosette/Interactive/Commands/SimplePerftCommand.cs index a8f5f00..4c4c94d 100644 --- a/Cosette/Interactive/Commands/SimplePerftCommand.cs +++ b/Cosette/Interactive/Commands/SimplePerftCommand.cs @@ -1,10 +1,4 @@ -using System; -using System.Diagnostics; -using System.Linq; -using Cosette.Engine.Board; -using Cosette.Engine.Common; -using Cosette.Engine.Moves; -using Cosette.Engine.Moves.Magic; +using Cosette.Engine.Board; using Cosette.Engine.Perft; namespace Cosette.Interactive.Commands @@ -12,7 +6,7 @@ namespace Cosette.Interactive.Commands public class SimplePerftCommand : ICommand { public string Description { get; } - private InteractiveConsole _interactiveConsole; + private readonly InteractiveConsole _interactiveConsole; public SimplePerftCommand(InteractiveConsole interactiveConsole) { diff --git a/Cosette/Interactive/Commands/UciCommand.cs b/Cosette/Interactive/Commands/UciCommand.cs index a4f23fd..88fa04b 100644 --- a/Cosette/Interactive/Commands/UciCommand.cs +++ b/Cosette/Interactive/Commands/UciCommand.cs @@ -1,19 +1,11 @@ -using System; -using System.Diagnostics; -using System.Linq; -using Cosette.Engine.Board; -using Cosette.Engine.Common; -using Cosette.Engine.Moves; -using Cosette.Engine.Moves.Magic; -using Cosette.Engine.Perft; -using Cosette.Uci; +using Cosette.Uci; namespace Cosette.Interactive.Commands { public class UciCommand : ICommand { public string Description { get; } - private InteractiveConsole _interactiveConsole; + private readonly InteractiveConsole _interactiveConsole; public UciCommand(InteractiveConsole interactiveConsole) { diff --git a/Cosette/Interactive/Commands/VerifyCommand.cs b/Cosette/Interactive/Commands/VerifyCommand.cs index d16b619..745b5df 100644 --- a/Cosette/Interactive/Commands/VerifyCommand.cs +++ b/Cosette/Interactive/Commands/VerifyCommand.cs @@ -1,22 +1,13 @@ -using System; -using System.Diagnostics; -using System.Linq; -using System.Net; -using System.Xml.XPath; -using Cosette.Engine.Board; -using Cosette.Engine.Common; +using Cosette.Engine.Board; using Cosette.Engine.Fen; -using Cosette.Engine.Moves; -using Cosette.Engine.Moves.Magic; using Cosette.Engine.Perft; -using Cosette.Engine.Perft.Results; namespace Cosette.Interactive.Commands { public class VerifyCommand : ICommand { public string Description { get; } - private InteractiveConsole _interactiveConsole; + private readonly InteractiveConsole _interactiveConsole; public VerifyCommand(InteractiveConsole interactiveConsole) { @@ -41,13 +32,13 @@ private void TestOpening() private void TestMidGame() { - var boardState = FenParser.Parse("r2qr1k1/p2n1p2/1pb3pp/2ppN1P1/1R1PpP2/BQP1n1PB/P4N1P/1R4K1 w - - 0 21", out _); + var boardState = FenToBoard.Parse("r2qr1k1/p2n1p2/1pb3pp/2ppN1P1/1R1PpP2/BQP1n1PB/P4N1P/1R4K1 w - - 0 21"); Test(boardState, "Midgame", 5); } private void TestEndGame() { - var boardState = FenParser.Parse("7r/8/2k3P1/1p1p2Kp/1P6/2P5/7r/Q7 w - - 0 1", out _); + var boardState = FenToBoard.Parse("7r/8/2k3P1/1p1p2Kp/1P6/2P5/7r/Q7 w - - 0 1"); Test(boardState, "Endgame", 6); } diff --git a/Cosette/Interactive/InteractiveConsole.cs b/Cosette/Interactive/InteractiveConsole.cs index 099b970..8ddadac 100644 --- a/Cosette/Interactive/InteractiveConsole.cs +++ b/Cosette/Interactive/InteractiveConsole.cs @@ -9,16 +9,16 @@ namespace Cosette.Interactive { public class InteractiveConsole { - private Dictionary _commands; - private List _symbols; + private readonly Dictionary _commands; + private readonly List _symbols; - private ConsoleColor _keywordColor; - private ConsoleColor _moveColor; - private ConsoleColor _numberColor; - private ConsoleColor _symbolColor; + private readonly ConsoleColor _keywordColor; + private readonly ConsoleColor _moveColor; + private readonly ConsoleColor _numberColor; + private readonly ConsoleColor _symbolColor; - private Regex _splitRegex = new Regex(@"(\s|\,|\:|\(|\))", RegexOptions.Compiled); - private Regex _moveRegex = new Regex(@"([a-h][1-8]){2}[nbrq]?", RegexOptions.Compiled); + private readonly Regex _splitRegex = new Regex(@"(\s|\,|\:|\(|\))", RegexOptions.Compiled); + private readonly Regex _moveRegex = new Regex(@"([a-h][1-8]){2}[nbrq]?", RegexOptions.Compiled); public InteractiveConsole() { @@ -30,6 +30,7 @@ public InteractiveConsole() _commands["perft"] = new SimplePerftCommand(this); _commands["benchmark"] = new BenchmarkCommand(this); _commands["verify"] = new VerifyCommand(this); + _commands["evaluate"] = new EvaluateCommand(this); _commands["uci"] = new UciCommand(this); _commands["quit"] = new QuitCommand(this); @@ -73,39 +74,48 @@ public void WriteLine() public void WriteLine(string message) { - var splitMessage = _splitRegex.Split(message); - foreach (var chunk in splitMessage.Where(p => !string.IsNullOrEmpty(p))) + if (Console.IsOutputRedirected) { - if(_moveRegex.IsMatch(chunk)) - { - Console.ForegroundColor = _moveColor; - } - else if (_symbols.Contains(chunk)) - { - Console.ForegroundColor = _symbolColor; - } - else if (float.TryParse(chunk, NumberStyles.Any, CultureInfo.InvariantCulture, out _)) - { - Console.ForegroundColor = _numberColor; - } - else + Console.WriteLine(message); + } + else + { + var splitMessage = _splitRegex.Split(message); + foreach (var chunk in splitMessage.Where(p => !string.IsNullOrEmpty(p))) { - Console.ForegroundColor = _keywordColor; + if (_moveRegex.IsMatch(chunk)) + { + Console.ForegroundColor = _moveColor; + } + else if (_symbols.Contains(chunk)) + { + Console.ForegroundColor = _symbolColor; + } + else if (float.TryParse(chunk, NumberStyles.Any, CultureInfo.InvariantCulture, out _)) + { + Console.ForegroundColor = _numberColor; + } + else + { + Console.ForegroundColor = _keywordColor; + } + + Console.Write(chunk); + Console.ResetColor(); } - Console.Write(chunk); - Console.ResetColor(); + Console.WriteLine(); } - - Console.WriteLine(); } private void DisplayIntro() { - Console.WriteLine($"Cosette v1.0 (Aqua), 19.09.2020 @ {Environment.OSVersion}"); - Console.WriteLine("Homepage and source code: https://github.com/Tearth/Cosette"); + var runtimeVersion = $"{Environment.Version.Major}.{Environment.Version.Minor}.{Environment.Version.Build}"; + + Console.WriteLine($"Cosette v2.0 (Darkness), 19.10.2020 @ {Environment.OSVersion} (.NET Core {runtimeVersion})"); + Console.WriteLine("Distributed under AGPL license, homepage and source code: https://github.com/Tearth/Cosette"); Console.WriteLine(); - Console.WriteLine("\"There are two types of sacrifices: correct ones, and mine.\" ~ Mikhail Tal"); + Console.WriteLine("\"The blunders are all there on the board, waiting to be made.\" ~ Savielly Tartakower"); Console.WriteLine(); Console.WriteLine("Type \"help\" to display all available commands"); } diff --git a/Cosette/Logs/LogManager.cs b/Cosette/Logs/LogManager.cs index c749b32..39dd063 100644 --- a/Cosette/Logs/LogManager.cs +++ b/Cosette/Logs/LogManager.cs @@ -5,12 +5,13 @@ namespace Cosette.Logs { public static class LogManager { - private static string _logFile; - private static string _basePath; + private static readonly string _logFile; + private static readonly string _basePath; + + private static StreamWriter _infoLogStreamWriter; static LogManager() { -#if LOGGER _basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"); _logFile = Path.Combine(_basePath, $"info-{DateTime.Now.Ticks}.log"); @@ -18,26 +19,22 @@ static LogManager() { Directory.CreateDirectory(_basePath); } -#endif - } - public static void LogInfo(string message) - { #if LOGGER - Log(Path.Combine(_basePath, _logFile), message); + _infoLogStreamWriter = new StreamWriter(_logFile); #endif } - public static void LogError(string message) + public static void LogInfo(string message) { #if LOGGER - Log(Path.Combine(_basePath, $"error-{DateTime.Now.Ticks}.log"), message); + _infoLogStreamWriter.WriteLine(message); #endif } - private static void Log(string filePath, string message) + public static void LogError(string message) { - using (var streamWriter = new StreamWriter(filePath, true)) + using (var streamWriter = new StreamWriter(Path.Combine(_basePath, $"error-{DateTime.Now.Ticks}.log"), true)) { streamWriter.WriteLine(message); } diff --git a/Cosette/Program.cs b/Cosette/Program.cs index 00c9ef8..89fb36f 100644 --- a/Cosette/Program.cs +++ b/Cosette/Program.cs @@ -1,26 +1,28 @@ -using System.Diagnostics; -using System.Threading; -using Cosette.Engine.Ai; +using System; using Cosette.Engine.Ai.Ordering; -using Cosette.Engine.Ai.Score.PieceSquareTables; -using Cosette.Engine.Ai.Search; using Cosette.Engine.Ai.Transposition; -using Cosette.Engine.Common; -using Cosette.Engine.Moves; using Cosette.Engine.Moves.Magic; using Cosette.Interactive; +using Cosette.Logs; namespace Cosette { public class Program { - static void Main(string[] args) + private static void Main() { - TranspositionTable.Init(SearchConstants.DefaultHashTableSize); - PawnHashTable.Init(SearchConstants.DefaultPawnHashTableSize); + AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; + + HashTableAllocator.Allocate(); MagicBitboards.InitWithInternalKeys(); + StaticExchangeEvaluation.Init(); new InteractiveConsole().Run(); } + + public static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) + { + LogManager.LogError(e.ExceptionObject.ToString()); + } } } diff --git a/Cosette/Uci/Commands/DebugCommand.cs b/Cosette/Uci/Commands/DebugCommand.cs index 431c2af..d734a58 100644 --- a/Cosette/Uci/Commands/DebugCommand.cs +++ b/Cosette/Uci/Commands/DebugCommand.cs @@ -1,16 +1,12 @@ -using System; - -namespace Cosette.Uci.Commands +namespace Cosette.Uci.Commands { public class DebugCommand : IUciCommand { - private UciClient _uciClient; - private UciGame _uciGame; + private readonly UciClient _uciClient; - public DebugCommand(UciClient uciClient, UciGame uciGame) + public DebugCommand(UciClient uciClient) { _uciClient = uciClient; - _uciGame = uciGame; } public void Run(params string[] parameters) diff --git a/Cosette/Uci/Commands/GoCommand.cs b/Cosette/Uci/Commands/GoCommand.cs index 2b1b34d..9c82848 100644 --- a/Cosette/Uci/Commands/GoCommand.cs +++ b/Cosette/Uci/Commands/GoCommand.cs @@ -1,12 +1,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; -using Cosette.Engine.Ai; using Cosette.Engine.Ai.Search; +using Cosette.Engine.Ai.Time; using Cosette.Engine.Common; using Cosette.Engine.Moves; using Cosette.Logs; @@ -15,67 +13,77 @@ namespace Cosette.Uci.Commands { public class GoCommand : IUciCommand { - private UciClient _uciClient; - private UciGame _uciGame; + private readonly UciClient _uciClient; - public GoCommand(UciClient uciClient, UciGame uciGame) + public GoCommand(UciClient uciClient) { _uciClient = uciClient; - _uciGame = uciGame; } public void Run(params string[] parameters) { var whiteTime = GetParameter(parameters, "wtime", int.MaxValue); var blackTime = GetParameter(parameters, "btime", int.MaxValue); - var depth = GetParameter(parameters, "depth", SearchConstants.MaxDepth); + var colorTime = _uciClient.BoardState.ColorToMove == Color.White ? whiteTime : blackTime; + + var whiteIncTime = GetParameter(parameters, "winc", 0); + var blackIncTime = GetParameter(parameters, "binc", 0); + var incTime = _uciClient.BoardState.ColorToMove == Color.White ? whiteIncTime : blackIncTime; + + var maxColorTime = TimeScheduler.CalculateTimeForMove(colorTime, incTime, _uciClient.BoardState.MovesCount); + + var depth = GetParameter(parameters, "depth", SearchConstants.MaxDepth - 1); var moveTime = GetParameter(parameters, "movetime", 0); var nodesCount = GetParameter(parameters, "nodes", ulong.MaxValue); var searchMoves = GetParameterWithMoves(parameters, "searchmoves"); var infiniteFlag = GetFlag(parameters, "infinite"); + _uciClient.SearchContext = new SearchContext(_uciClient.BoardState) + { + MaxDepth = depth + 1, + MaxNodesCount = nodesCount, + MoveRestrictions = searchMoves + }; + if (moveTime != 0) { - whiteTime = int.MaxValue; - blackTime = int.MaxValue; - IterativeDeepening.WaitForStopCommand = true; + maxColorTime = int.MaxValue; + _uciClient.SearchContext.WaitForStopCommand = true; Task.Run(() => { var stopwatch = Stopwatch.StartNew(); while (stopwatch.ElapsedMilliseconds < moveTime) { - Task.Delay(20).GetAwaiter().GetResult(); + Task.Delay(1).GetAwaiter().GetResult(); } - IterativeDeepening.AbortSearch = true; - IterativeDeepening.WaitForStopCommand = false; + _uciClient.SearchContext.AbortSearch = true; + _uciClient.SearchContext.WaitForStopCommand = false; }); } if (infiniteFlag) { - whiteTime = int.MaxValue; - blackTime = int.MaxValue; - IterativeDeepening.WaitForStopCommand = true; + maxColorTime = int.MaxValue; + _uciClient.SearchContext.WaitForStopCommand = true; } - IterativeDeepening.MoveRestrictions = searchMoves; - IterativeDeepening.MaxNodesCount = nodesCount; - Task.Run(() => SearchEntryPoint(whiteTime, blackTime, depth)); + _uciClient.SearchContext.MaxTime = maxColorTime; + Task.Run(SearchEntryPoint); } - private void SearchEntryPoint(int whiteTime, int blackTime, int depth) + private void SearchEntryPoint() { try { - var bestMove = _uciGame.SearchBestMove(whiteTime, blackTime, depth); + var bestMove = IterativeDeepening.FindBestMove(_uciClient.SearchContext); _uciClient.Send($"bestmove {bestMove}"); } - catch (Exception ex) + catch (Exception e) { - LogManager.LogError(ex.ToString()); - throw; + Program.OnUnhandledException(this, new UnhandledExceptionEventArgs(e, true)); + Environment.Exit(-1); } } @@ -85,7 +93,7 @@ private T GetParameter(string[] parameters, string name, T defaultValue) { if (parameters[i] == name) { - return (T) Convert.ChangeType(parameters[i + 1], typeof(T)); + return (T)Convert.ChangeType(parameters[i + 1], typeof(T)); } } @@ -102,7 +110,7 @@ private List GetParameterWithMoves(string[] parameters, string name) for (var moveIndex = i + 1; moveIndex < parameters.Length; moveIndex++) { var moveTextNotation = parameters[moveIndex]; - var parsedMove = Move.FromTextNotation(_uciGame.BoardState, moveTextNotation); + var parsedMove = Move.FromTextNotation(_uciClient.BoardState, moveTextNotation); movesList.Add(parsedMove); } } diff --git a/Cosette/Uci/Commands/IsReadyCommand.cs b/Cosette/Uci/Commands/IsReadyCommand.cs index 9af4f1c..262c7e7 100644 --- a/Cosette/Uci/Commands/IsReadyCommand.cs +++ b/Cosette/Uci/Commands/IsReadyCommand.cs @@ -1,16 +1,12 @@ -using System; - -namespace Cosette.Uci.Commands +namespace Cosette.Uci.Commands { public class IsReadyCommand : IUciCommand { - private UciClient _uciClient; - private UciGame _uciGame; + private readonly UciClient _uciClient; - public IsReadyCommand(UciClient uciClient, UciGame uciGame) + public IsReadyCommand(UciClient uciClient) { _uciClient = uciClient; - _uciGame = uciGame; } public void Run(params string[] parameters) diff --git a/Cosette/Uci/Commands/PositionCommand.cs b/Cosette/Uci/Commands/PositionCommand.cs index b83dd37..887cb9f 100644 --- a/Cosette/Uci/Commands/PositionCommand.cs +++ b/Cosette/Uci/Commands/PositionCommand.cs @@ -2,19 +2,18 @@ using System.Collections.Generic; using System.Linq; using Cosette.Engine.Common; +using Cosette.Engine.Fen; using Cosette.Engine.Moves; namespace Cosette.Uci.Commands { public class PositionCommand : IUciCommand { - private UciClient _uciClient; - private UciGame _uciGame; + private readonly UciClient _uciClient; - public PositionCommand(UciClient uciClient, UciGame uciGame) + public PositionCommand(UciClient uciClient) { _uciClient = uciClient; - _uciGame = uciGame; } public void Run(params string[] parameters) @@ -43,13 +42,13 @@ public void Run(params string[] parameters) private void ParseStartPos(List moves) { - _uciGame.SetDefaultState(); + _uciClient.BoardState.SetDefaultState(); ParseMoves(moves); } private void ParseFen(string fen, List moves) { - _uciGame.SetFen(fen); + _uciClient.BoardState = FenToBoard.Parse(fen); ParseMoves(moves); } @@ -58,17 +57,15 @@ private void ParseMoves(List moves) var color = Color.White; foreach (var move in moves) { - var parsedMove = Move.FromTextNotation(_uciGame.BoardState, move); - if (parsedMove == new Move()) + var parsedMove = Move.FromTextNotation(_uciClient.BoardState, move); + if (parsedMove == Move.Empty) { throw new InvalidOperationException(); } - _uciGame.MakeMove(parsedMove); + _uciClient.BoardState.MakeMove(parsedMove); color = ColorOperations.Invert(color); } - - _uciGame.SetCurrentColor(color); } } } \ No newline at end of file diff --git a/Cosette/Uci/Commands/QuitCommand.cs b/Cosette/Uci/Commands/QuitCommand.cs index f33a4e4..fdd2b51 100644 --- a/Cosette/Uci/Commands/QuitCommand.cs +++ b/Cosette/Uci/Commands/QuitCommand.cs @@ -4,13 +4,11 @@ namespace Cosette.Uci.Commands { public class QuitCommand : IUciCommand { - private UciClient _uciClient; - private UciGame _uciGame; + private readonly UciClient _uciClient; - public QuitCommand(UciClient uciClient, UciGame uciGame) + public QuitCommand(UciClient uciClient) { _uciClient = uciClient; - _uciGame = uciGame; } public void Run(params string[] parameters) diff --git a/Cosette/Uci/Commands/SetOptionCommand.cs b/Cosette/Uci/Commands/SetOptionCommand.cs index 8ece3fc..5ab1dc3 100644 --- a/Cosette/Uci/Commands/SetOptionCommand.cs +++ b/Cosette/Uci/Commands/SetOptionCommand.cs @@ -1,18 +1,15 @@ -using System; -using Cosette.Engine.Ai.Search; +using Cosette.Engine.Ai.Search; using Cosette.Engine.Ai.Transposition; namespace Cosette.Uci.Commands { public class SetOptionCommand : IUciCommand { - private UciClient _uciClient; - private UciGame _uciGame; + private readonly UciClient _uciClient; - public SetOptionCommand(UciClient uciClient, UciGame uciGame) + public SetOptionCommand(UciClient uciClient) { _uciClient = uciClient; - _uciGame = uciGame; } public void Run(params string[] parameters) @@ -21,8 +18,9 @@ public void Run(params string[] parameters) { case "Hash": { - TranspositionTable.Init(ulong.Parse(parameters[3]) - SearchConstants.DefaultPawnHashTableSize); - PawnHashTable.Init(SearchConstants.DefaultPawnHashTableSize); + var hashTablesSize = int.Parse(parameters[3]); + HashTableAllocator.Allocate(hashTablesSize); + break; } } diff --git a/Cosette/Uci/Commands/StopCommand.cs b/Cosette/Uci/Commands/StopCommand.cs index 233933d..affafd6 100644 --- a/Cosette/Uci/Commands/StopCommand.cs +++ b/Cosette/Uci/Commands/StopCommand.cs @@ -1,23 +1,18 @@ -using System; -using Cosette.Engine.Ai.Search; - -namespace Cosette.Uci.Commands +namespace Cosette.Uci.Commands { public class StopCommand : IUciCommand { - private UciClient _uciClient; - private UciGame _uciGame; + private readonly UciClient _uciClient; - public StopCommand(UciClient uciClient, UciGame uciGame) + public StopCommand(UciClient uciClient) { _uciClient = uciClient; - _uciGame = uciGame; } public void Run(params string[] parameters) { - IterativeDeepening.AbortSearch = true; - IterativeDeepening.WaitForStopCommand = false; + _uciClient.SearchContext.AbortSearch = true; + _uciClient.SearchContext.WaitForStopCommand = false; } } } \ No newline at end of file diff --git a/Cosette/Uci/Commands/UciNewGameCommand.cs b/Cosette/Uci/Commands/UciNewGameCommand.cs index 5396ab7..f7c5084 100644 --- a/Cosette/Uci/Commands/UciNewGameCommand.cs +++ b/Cosette/Uci/Commands/UciNewGameCommand.cs @@ -1,21 +1,26 @@ -using System; +using Cosette.Engine.Ai.Ordering; +using Cosette.Engine.Ai.Transposition; namespace Cosette.Uci.Commands { public class UciNewGameCommand : IUciCommand { - private UciClient _uciClient; - private UciGame _uciGame; + private readonly UciClient _uciClient; - public UciNewGameCommand(UciClient uciClient, UciGame uciGame) + public UciNewGameCommand(UciClient uciClient) { _uciClient = uciClient; - _uciGame = uciGame; } public void Run(params string[] parameters) { - _uciGame.SetDefaultState(); + TranspositionTable.Clear(); + PawnHashTable.Clear(); + EvaluationHashTable.Clear(); + KillerHeuristic.Clear(); + HistoryHeuristic.Clear(); + + _uciClient.BoardState.SetDefaultState(); } } } \ No newline at end of file diff --git a/Cosette/Uci/UciClient.cs b/Cosette/Uci/UciClient.cs index 802cce2..96b6de3 100644 --- a/Cosette/Uci/UciClient.cs +++ b/Cosette/Uci/UciClient.cs @@ -2,10 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using Cosette.Engine.Ai; using Cosette.Engine.Ai.Score; using Cosette.Engine.Ai.Score.Evaluators; using Cosette.Engine.Ai.Search; +using Cosette.Engine.Ai.Transposition; +using Cosette.Engine.Board; using Cosette.Engine.Common; using Cosette.Engine.Moves; using Cosette.Interactive; @@ -16,31 +17,34 @@ namespace Cosette.Uci { public class UciClient { - private UciGame _uciGame; + public BoardState BoardState; + public SearchContext SearchContext; + private bool _debugMode; - private InteractiveConsole _interactiveConsole; - private Dictionary _commands; + private readonly InteractiveConsole _interactiveConsole; + private readonly Dictionary _commands; public UciClient(InteractiveConsole interactiveConsole) { - _interactiveConsole = interactiveConsole; + BoardState = new BoardState(); + BoardState.SetDefaultState(); - _uciGame = new UciGame(); + _interactiveConsole = interactiveConsole; #if UCI_DEBUG_OUTPUT _debugMode = true; #endif _commands = new Dictionary(); - _commands["quit"] = new QuitCommand(this, _uciGame); - _commands["setoption"] = new SetOptionCommand(this, _uciGame); - _commands["isready"] = new IsReadyCommand(this, _uciGame); - _commands["ucinewgame"] = new UciNewGameCommand(this, _uciGame); - _commands["position"] = new PositionCommand(this, _uciGame); - _commands["debug"] = new DebugCommand(this, _uciGame); - _commands["go"] = new GoCommand(this, _uciGame); - _commands["stop"] = new StopCommand(this, _uciGame); + _commands["quit"] = new QuitCommand(this); + _commands["setoption"] = new SetOptionCommand(this); + _commands["isready"] = new IsReadyCommand(this); + _commands["ucinewgame"] = new UciNewGameCommand(this); + _commands["position"] = new PositionCommand(this); + _commands["debug"] = new DebugCommand(this); + _commands["go"] = new GoCommand(this); + _commands["stop"] = new StopCommand(this); IterativeDeepening.OnSearchUpdate += OnSearchUpdate; } @@ -61,13 +65,21 @@ public void Send(string command) public (string Command, string[] parameters) Receive() { - var input = Console.ReadLine(); - var splitInput = input.Split(' '); - var command = splitInput[0].ToLower(); - var parameters = splitInput.Skip(1).ToArray(); + while (true) + { + var input = Console.ReadLine(); + if (input == null) + { + Environment.Exit(0); + } + + var splitInput = input.Split(' '); + var command = splitInput[0].ToLower(); + var parameters = splitInput.Skip(1).ToArray(); - LogManager.LogInfo("[RECV] " + input); - return (command, parameters); + LogManager.LogInfo("[RECV] " + input); + return (command, parameters); + } } public void SetDebugMode(bool state) @@ -87,9 +99,7 @@ private void SendAuthor() private void SendOptions() { - var defaultHashTablesSize = SearchConstants.DefaultHashTableSize + SearchConstants.DefaultPawnHashTableSize; - - Send($"option name Hash type spin default {defaultHashTablesSize} min 1 max 2048"); + Send($"option name Hash type spin default {HashTableConstants.DefaultHashTablesSize} min 3 max 65536"); Send("uciok"); } @@ -115,25 +125,30 @@ private void OnSearchUpdate(object sender, SearchStatistics stats) if (_debugMode) { + var evaluationStatistics = new EvaluationStatistics(); var openingPhase = stats.Board.GetPhaseRatio(); var endingPhase = 1 - openingPhase; - var materialEvaluation = MaterialEvaluator.Evaluate(stats.Board, openingPhase, endingPhase); + var materialEvaluation = MaterialEvaluator.Evaluate(stats.Board); var castlingEvaluation = CastlingEvaluator.Evaluate(stats.Board, openingPhase, endingPhase); var positionEvaluation = PositionEvaluator.Evaluate(stats.Board, openingPhase, endingPhase); - var pawnStructureEvaluation = PawnStructureEvaluator.Evaluate(stats.Board, openingPhase, endingPhase); + var pawnStructureEvaluation = PawnStructureEvaluator.Evaluate(stats.Board, evaluationStatistics, openingPhase, endingPhase); var mobility = MobilityEvaluator.Evaluate(stats.Board, openingPhase, endingPhase); var kingSafety = KingSafetyEvaluator.Evaluate(stats.Board, openingPhase, endingPhase); - var total = materialEvaluation + castlingEvaluation + positionEvaluation + pawnStructureEvaluation + mobility + kingSafety; + var pieces = PiecesEvaluator.Evaluate(stats.Board, openingPhase, endingPhase); + + var total = materialEvaluation + castlingEvaluation + positionEvaluation + pawnStructureEvaluation + + mobility + kingSafety + pieces; Send($"info string evaluation {total} phase {openingPhase:F} material {materialEvaluation} castling {castlingEvaluation} " + - $"position {positionEvaluation} pawns {pawnStructureEvaluation} mobility {mobility} ksafety {kingSafety}"); + $"position {positionEvaluation} pawns {pawnStructureEvaluation} mobility {mobility} ksafety {kingSafety} " + + $"pieces {pieces} irrmoves {stats.Board.IrreversibleMovesCount}"); } } private string FormatScore(int score) { - if (IterativeDeepening.IsScoreCheckmate(score)) + if (IterativeDeepening.IsScoreNearCheckmate(score)) { var movesToCheckmate = IterativeDeepening.GetMovesToCheckmate(score); if (score < 0) diff --git a/Cosette/Uci/UciGame.cs b/Cosette/Uci/UciGame.cs deleted file mode 100644 index 490acba..0000000 --- a/Cosette/Uci/UciGame.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Threading.Tasks; -using Cosette.Engine.Ai; -using Cosette.Engine.Ai.Search; -using Cosette.Engine.Board; -using Cosette.Engine.Common; -using Cosette.Engine.Fen; -using Cosette.Engine.Moves; - -namespace Cosette.Uci -{ - public class UciGame - { - public BoardState BoardState; - private Color _currentColor; - private int _currentMoveNumber; - - public UciGame() - { - BoardState = new BoardState(); - } - - public void SetDefaultState() - { - BoardState.SetDefaultState(); - _currentColor = Color.White; - _currentMoveNumber = 1; - } - - public void SetFen(string fen) - { - BoardState = FenParser.Parse(fen, out _currentMoveNumber); - } - - public void MakeMove(Move move) - { - BoardState.MakeMove(move); - if (BoardState.ColorToMove == Color.White) - { - _currentMoveNumber++; - } - } - - public Move SearchBestMove(int whiteTime, int blackTime, int depth) - { - var remainingTime = _currentColor == Color.White ? whiteTime : blackTime; - var bestMove = IterativeDeepening.FindBestMove(BoardState, remainingTime, depth, _currentMoveNumber); - - return bestMove; - } - - public void SetCurrentColor(Color color) - { - _currentColor = color; - } - } -} diff --git a/README.md b/README.md index 97c4733..78fa0cd 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ # Cosette -**Current version: v1.0 (Aqua), 19.09.2020** +**Current version: v2.0 (Darkness), 19.10.2020** -A brand new UCI-compliant chess engine written in C# for .NET Core platform. The project is still in the early stage of development and lacks a few major performance improvements like more advanced pruning, extensions, or better evaluation. The current strength is estimated at 1900 ELO and hopefully will significantly improve in the future. +A brand new UCI-compliant chess engine written in C# for .NET Core platform. The project is still in the early stage of development and lacks a few major performance improvements like more advanced pruning, extensions, or better evaluation. The current strength is estimated at 1950 ELO and hopefully will significantly improve in the future. -![asd](https://i.imgur.com/ck4GbbF.png) +The engine has **[an official profile on CCRL](http://ccrl.chessdom.com/ccrl/404/cgi/engine_details.cgi?match_length=30&print=Details&each_game=1&eng=Cosette%201.0%2064-bit#Cosette_1_0_64-bit)** (Computer Chess Rating Lists) where you can check the best strength estimation. Also, feel free to visit **[a dedicated forum thread](http://kirill-kryukov.com/chess/discussion-board/viewtopic.php?f=7&t=12402)** for Cosette releases and discussions! + +![Cosette interactive console example](https://i.imgur.com/hIcaAmz.png) ## How to play? -The simplest way is to download the newest version from the [Releases page](https://github.com/Tearth/Cosette/releases) and use it with a graphical interface like Arena or WinBoard. The engine has been tested extensively on the first one, but should work with every UCI-compliant GUI. +The simplest way is to download the newest version from the **[Releases page](https://github.com/Tearth/Cosette/releases)** and use it with a graphical interface like Arena or WinBoard. The engine has been tested extensively on the first one, but should work with every UCI-compliant GUI. -Cosette has an official account on [lichess.org](https://lichess.org/) platform, which allows everybody to challenge her - feel free to do it! +Cosette has an official account on **[lichess.org](https://lichess.org/)** platform, which allows everybody to challenge her - feel free to do it! **https://lichess.org/@/CosetteBot** @@ -26,6 +28,9 @@ Cosette has an official account on [lichess.org](https://lichess.org/) platform, - mobility - king safety - pawn structure (cached in pawn hash table) + - doubled rooks + - rooks on open files + - bishop pair **Search:** - negamax