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