From 90e707949e1c99c19036372d0d704afc33a2544c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20C=C3=A1ceres?= Date: Tue, 2 Jan 2024 19:23:54 +0100 Subject: [PATCH] Cleanup and use switch method --- src/Lynx.Benchmark/CastleHash.cs | 74 --------- src/Lynx.Benchmark/ZobristHash_Castle.cs | 147 ++++++++++++++++++ ...assantHash.cs => ZobristHash_EnPassant.cs} | 2 +- src/Lynx/ZobristTable.cs | 88 ++++------- tests/Lynx.Test/ZobristTableTest.cs | 29 ++++ 5 files changed, 207 insertions(+), 133 deletions(-) delete mode 100644 src/Lynx.Benchmark/CastleHash.cs create mode 100644 src/Lynx.Benchmark/ZobristHash_Castle.cs rename src/Lynx.Benchmark/{EnPassantHash.cs => ZobristHash_EnPassant.cs} (98%) diff --git a/src/Lynx.Benchmark/CastleHash.cs b/src/Lynx.Benchmark/CastleHash.cs deleted file mode 100644 index 8064e87bc..000000000 --- a/src/Lynx.Benchmark/CastleHash.cs +++ /dev/null @@ -1,74 +0,0 @@ -/* - * - - */ - -using BenchmarkDotNet.Attributes; -using Lynx.Model; -using System.Runtime.CompilerServices; -using static Lynx.ZobristTable; - -namespace Lynx.Benchmark; -public class CastleHash : BaseBenchmark -{ - public static IEnumerable Data => new[] { - new Position(Constants.InitialPositionFEN), - new Position(Constants.TrickyTestPositionFEN), - new Position(Constants.TrickyTestPositionReversedFEN), - new Position(Constants.CmkTestPositionFEN), - new Position(Constants.ComplexPositionFEN), - new Position(Constants.KillerTestPositionFEN), - }; - - [Benchmark(Baseline = true)] - [ArgumentsSource(nameof(Data))] - public long Naive(Position position) - { - return CalculateCastleHash(position.Castle); - } - - [Benchmark] - [ArgumentsSource(nameof(Data))] - public long Dictionary(Position position) - { - return ZobristTable.CastleHash(position.Castle); - } - - [Benchmark] - [ArgumentsSource(nameof(Data))] - public long Switch(Position position) - { - return SwitchMethod(position); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static long SwitchMethod(Position position) - { - return position.Castle switch - { - 0 => 0, // - | - - - (byte)CastlingRights.WK => WK_Hash, // K | - - (byte)CastlingRights.WQ => WQ_Hash, // Q | - - (byte)CastlingRights.BK => BK_Hash, // - | k - (byte)CastlingRights.BQ => BQ_Hash, // - | q - - (byte)CastlingRights.WK | (byte)CastlingRights.WQ => WK_Hash ^ WQ_Hash, // KQ | - - (byte)CastlingRights.WK | (byte)CastlingRights.BK => WK_Hash ^ BK_Hash, // K | k - (byte)CastlingRights.WK | (byte)CastlingRights.BQ => WK_Hash ^ BQ_Hash, // K | q - (byte)CastlingRights.WQ | (byte)CastlingRights.BK => WQ_Hash ^ BK_Hash, // Q | k - (byte)CastlingRights.WQ | (byte)CastlingRights.BQ => WQ_Hash ^ BQ_Hash, // Q | q - (byte)CastlingRights.BK | (byte)CastlingRights.BQ => BK_Hash ^ BQ_Hash, // - | kq - - (byte)CastlingRights.WK | (byte)CastlingRights.WQ | (byte)CastlingRights.BK => WK_Hash ^ WQ_Hash ^ BK_Hash, // KQ | k - (byte)CastlingRights.WK | (byte)CastlingRights.WQ | (byte)CastlingRights.BQ => WK_Hash ^ WQ_Hash ^ BQ_Hash, // KQ | q - (byte)CastlingRights.WK | (byte)CastlingRights.BK | (byte)CastlingRights.BQ => WK_Hash ^ BK_Hash ^ BQ_Hash, // K | kq - (byte)CastlingRights.WQ | (byte)CastlingRights.BK | (byte)CastlingRights.BQ => WQ_Hash ^ BK_Hash ^ BQ_Hash, // Q | kq - - (byte)CastlingRights.WK | (byte)CastlingRights.WQ | (byte)CastlingRights.BK | (byte)CastlingRights.BQ => // KQ | kq - WK_Hash ^ WQ_Hash ^ BK_Hash ^ BQ_Hash, - - _ => new() - }; - } -} diff --git a/src/Lynx.Benchmark/ZobristHash_Castle.cs b/src/Lynx.Benchmark/ZobristHash_Castle.cs new file mode 100644 index 000000000..1773f523f --- /dev/null +++ b/src/Lynx.Benchmark/ZobristHash_Castle.cs @@ -0,0 +1,147 @@ +/* + * + + */ + +using BenchmarkDotNet.Attributes; +using Lynx.Model; +using System.Runtime.CompilerServices; + +namespace Lynx.Benchmark; +public class ZobristHash_Castle : BaseBenchmark +{ + public static IEnumerable Data => new[] { + new Position(Constants.InitialPositionFEN), + new Position(Constants.TrickyTestPositionFEN), + new Position(Constants.TrickyTestPositionReversedFEN), + new Position(Constants.CmkTestPositionFEN), + new Position(Constants.ComplexPositionFEN), + new Position(Constants.KillerTestPositionFEN), + }; + + [Benchmark(Baseline = true)] + [ArgumentsSource(nameof(Data))] + public long Naive(Position position) => CalculateMethod(position.Castle); + + [Benchmark] + [ArgumentsSource(nameof(Data))] + public long Dictionary(Position position) => DictionaryMethod(position.Castle); + + [Benchmark] + [ArgumentsSource(nameof(Data))] + public long Switch(Position position) => SwitchMethod(position); + + private static readonly long[,] _table = Initialize(); + + private static readonly long WK_Hash = _table[(int)BoardSquare.a8, (int)Piece.p]; + private static readonly long WQ_Hash = _table[(int)BoardSquare.b8, (int)Piece.p]; + private static readonly long BK_Hash = _table[(int)BoardSquare.c8, (int)Piece.p]; + private static readonly long BQ_Hash = _table[(int)BoardSquare.d8, (int)Piece.p]; + + private static readonly Dictionary _castleHashDictionary = new() + { + [0] = 0, // - | - + [(byte)CastlingRights.WK] = WK_Hash, // K | - + [(byte)CastlingRights.WQ] = WQ_Hash, // Q | - + [(byte)CastlingRights.BK] = BK_Hash, // - | k + [(byte)CastlingRights.BQ] = BQ_Hash, // - | q + + [(byte)CastlingRights.WK | (byte)CastlingRights.WQ] = WK_Hash ^ WQ_Hash, // KQ | - + [(byte)CastlingRights.WK | (byte)CastlingRights.BK] = WK_Hash ^ BK_Hash, // K | k + [(byte)CastlingRights.WK | (byte)CastlingRights.BQ] = WK_Hash ^ BQ_Hash, // K | q + [(byte)CastlingRights.WQ | (byte)CastlingRights.BK] = WQ_Hash ^ BK_Hash, // Q | k + [(byte)CastlingRights.WQ | (byte)CastlingRights.BQ] = WQ_Hash ^ BQ_Hash, // Q | q + [(byte)CastlingRights.BK | (byte)CastlingRights.BQ] = BK_Hash ^ BQ_Hash, // - | kq + + [(byte)CastlingRights.WK | (byte)CastlingRights.WQ | (byte)CastlingRights.BK] = WK_Hash ^ WQ_Hash ^ BK_Hash, // KQ | k + [(byte)CastlingRights.WK | (byte)CastlingRights.WQ | (byte)CastlingRights.BQ] = WK_Hash ^ WQ_Hash ^ BQ_Hash, // KQ | q + [(byte)CastlingRights.WK | (byte)CastlingRights.BK | (byte)CastlingRights.BQ] = WK_Hash ^ BK_Hash ^ BQ_Hash, // K | kq + [(byte)CastlingRights.WQ | (byte)CastlingRights.BK | (byte)CastlingRights.BQ] = WQ_Hash ^ BK_Hash ^ BQ_Hash, // Q | kq + + [(byte)CastlingRights.WK | (byte)CastlingRights.WQ | (byte)CastlingRights.BK | (byte)CastlingRights.BQ] = // KQ | kq + WK_Hash ^ WQ_Hash ^ BK_Hash ^ BQ_Hash + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static long CalculateMethod(byte castle) + { + long combinedHash = 0; + + if ((castle & (int)CastlingRights.WK) != default) + { + combinedHash ^= _table[(int)BoardSquare.a8, (int)Piece.p]; // a8 + } + + if ((castle & (int)CastlingRights.WQ) != default) + { + combinedHash ^= _table[(int)BoardSquare.b8, (int)Piece.p]; // b8 + } + + if ((castle & (int)CastlingRights.BK) != default) + { + combinedHash ^= _table[(int)BoardSquare.c8, (int)Piece.p]; // c8 + } + + if ((castle & (int)CastlingRights.BQ) != default) + { + combinedHash ^= _table[(int)BoardSquare.d8, (int)Piece.p]; // d8 + } + + return combinedHash; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static long SwitchMethod(Position position) + { + return position.Castle switch + { + 0 => 0, // - | - + + (byte)CastlingRights.WK => WK_Hash, // K | - + (byte)CastlingRights.WQ => WQ_Hash, // Q | - + (byte)CastlingRights.BK => BK_Hash, // - | k + (byte)CastlingRights.BQ => BQ_Hash, // - | q + + (byte)CastlingRights.WK | (byte)CastlingRights.WQ => WK_Hash ^ WQ_Hash, // KQ | - + (byte)CastlingRights.WK | (byte)CastlingRights.BK => WK_Hash ^ BK_Hash, // K | k + (byte)CastlingRights.WK | (byte)CastlingRights.BQ => WK_Hash ^ BQ_Hash, // K | q + (byte)CastlingRights.WQ | (byte)CastlingRights.BK => WQ_Hash ^ BK_Hash, // Q | k + (byte)CastlingRights.WQ | (byte)CastlingRights.BQ => WQ_Hash ^ BQ_Hash, // Q | q + (byte)CastlingRights.BK | (byte)CastlingRights.BQ => BK_Hash ^ BQ_Hash, // - | kq + + (byte)CastlingRights.WK | (byte)CastlingRights.WQ | (byte)CastlingRights.BK => WK_Hash ^ WQ_Hash ^ BK_Hash, // KQ | k + (byte)CastlingRights.WK | (byte)CastlingRights.WQ | (byte)CastlingRights.BQ => WK_Hash ^ WQ_Hash ^ BQ_Hash, // KQ | q + (byte)CastlingRights.WK | (byte)CastlingRights.BK | (byte)CastlingRights.BQ => WK_Hash ^ BK_Hash ^ BQ_Hash, // K | kq + (byte)CastlingRights.WQ | (byte)CastlingRights.BK | (byte)CastlingRights.BQ => WQ_Hash ^ BK_Hash ^ BQ_Hash, // Q | kq + + (byte)CastlingRights.WK | (byte)CastlingRights.WQ | (byte)CastlingRights.BK | (byte)CastlingRights.BQ => // KQ | kq + WK_Hash ^ WQ_Hash ^ BK_Hash ^ BQ_Hash, + + _ => new() + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long DictionaryMethod(byte castle) => _castleHashDictionary[castle]; + + /// + /// Initializes Zobrist table (long[64, 12]) + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static long[,] Initialize() + { + var zobristTable = new long[64, 12]; + var randomInstance = new Random(int.MaxValue); + + for (int squareIndex = 0; squareIndex < 64; ++squareIndex) + { + for (int pieceIndex = 0; pieceIndex < 12; ++pieceIndex) + { + zobristTable[squareIndex, pieceIndex] = randomInstance.NextInt64(); + } + } + + return zobristTable; + } +} diff --git a/src/Lynx.Benchmark/EnPassantHash.cs b/src/Lynx.Benchmark/ZobristHash_EnPassant.cs similarity index 98% rename from src/Lynx.Benchmark/EnPassantHash.cs rename to src/Lynx.Benchmark/ZobristHash_EnPassant.cs index 468f4d100..74951933a 100644 --- a/src/Lynx.Benchmark/EnPassantHash.cs +++ b/src/Lynx.Benchmark/ZobristHash_EnPassant.cs @@ -42,7 +42,7 @@ using System.Runtime.CompilerServices; namespace Lynx.Benchmark; -public class PieceHash : BaseBenchmark +public class ZobristHash_EnPassant : BaseBenchmark { private static readonly long[,] _table = Initialize(); diff --git a/src/Lynx/ZobristTable.cs b/src/Lynx/ZobristTable.cs index a27023fb5..cf5eda2e7 100644 --- a/src/Lynx/ZobristTable.cs +++ b/src/Lynx/ZobristTable.cs @@ -11,6 +11,11 @@ public static class ZobristTable { private static readonly long[,] _table = Initialize(); + private static readonly long WK_Hash = _table[(int)BoardSquare.a8, (int)Piece.p]; + private static readonly long WQ_Hash = _table[(int)BoardSquare.b8, (int)Piece.p]; + private static readonly long BK_Hash = _table[(int)BoardSquare.c8, (int)Piece.p]; + private static readonly long BQ_Hash = _table[(int)BoardSquare.d8, (int)Piece.p]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long PieceHash(int boardSquare, int piece) => _table[boardSquare, piece]; @@ -49,35 +54,6 @@ public static long SideHash() return _table[(int)BoardSquare.h8, (int)Piece.p]; } - internal static readonly long WK_Hash = _table[(int)BoardSquare.a8, (int)Piece.p]; - internal static readonly long WQ_Hash = _table[(int)BoardSquare.b8, (int)Piece.p]; - internal static readonly long BK_Hash = _table[(int)BoardSquare.c8, (int)Piece.p]; - internal static readonly long BQ_Hash = _table[(int)BoardSquare.d8, (int)Piece.p]; - - private static readonly Dictionary _castleHashDictionary = new() - { - [0] = 0, // - | - - [(byte)CastlingRights.WK] = WK_Hash, // K | - - [(byte)CastlingRights.WQ] = WQ_Hash, // Q | - - [(byte)CastlingRights.BK] = BK_Hash, // - | k - [(byte)CastlingRights.BQ] = BQ_Hash, // - | q - - [(byte)CastlingRights.WK | (byte)CastlingRights.WQ] = WK_Hash ^ WQ_Hash, // KQ | - - [(byte)CastlingRights.WK | (byte)CastlingRights.BK] = WK_Hash ^ BK_Hash, // K | k - [(byte)CastlingRights.WK | (byte)CastlingRights.BQ] = WK_Hash ^ BQ_Hash, // K | q - [(byte)CastlingRights.WQ | (byte)CastlingRights.BK] = WQ_Hash ^ BK_Hash, // Q | k - [(byte)CastlingRights.WQ | (byte)CastlingRights.BQ] = WQ_Hash ^ BQ_Hash, // Q | q - [(byte)CastlingRights.BK | (byte)CastlingRights.BQ] = BK_Hash ^ BQ_Hash, // - | kq - - [(byte)CastlingRights.WK | (byte)CastlingRights.WQ | (byte)CastlingRights.BK] = WK_Hash ^ WQ_Hash ^ BK_Hash, // KQ | k - [(byte)CastlingRights.WK | (byte)CastlingRights.WQ | (byte)CastlingRights.BQ] = WK_Hash ^ WQ_Hash ^ BQ_Hash, // KQ | q - [(byte)CastlingRights.WK | (byte)CastlingRights.BK | (byte)CastlingRights.BQ] = WK_Hash ^ BK_Hash ^ BQ_Hash, // K | kq - [(byte)CastlingRights.WQ | (byte)CastlingRights.BK | (byte)CastlingRights.BQ] = WQ_Hash ^ BK_Hash ^ BQ_Hash, // Q | kq - - [(byte)CastlingRights.WK | (byte)CastlingRights.WQ | (byte)CastlingRights.BK | (byte)CastlingRights.BQ] = // KQ | kq - WK_Hash ^ WQ_Hash ^ BK_Hash ^ BQ_Hash - }; - /// /// Uses and /// for , for @@ -88,36 +64,32 @@ public static long SideHash() [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long CastleHash(byte castle) { - System.Diagnostics.Debug.Assert(_castleHashDictionary[castle] == CalculateCastleHash(castle)); - - return _castleHashDictionary[castle]; - } - - internal static long CalculateCastleHash(byte castle) - { - long combinedHash = 0; - - if ((castle & (int)CastlingRights.WK) != default) - { - combinedHash ^= _table[(int)BoardSquare.a8, (int)Piece.p]; // a8 - } - - if ((castle & (int)CastlingRights.WQ) != default) - { - combinedHash ^= _table[(int)BoardSquare.b8, (int)Piece.p]; // b8 - } - - if ((castle & (int)CastlingRights.BK) != default) + return castle switch { - combinedHash ^= _table[(int)BoardSquare.c8, (int)Piece.p]; // c8 - } - - if ((castle & (int)CastlingRights.BQ) != default) - { - combinedHash ^= _table[(int)BoardSquare.d8, (int)Piece.p]; // d8 - } - - return combinedHash; + 0 => 0, // - | - + + (byte)CastlingRights.WK => WK_Hash, // K | - + (byte)CastlingRights.WQ => WQ_Hash, // Q | - + (byte)CastlingRights.BK => BK_Hash, // - | k + (byte)CastlingRights.BQ => BQ_Hash, // - | q + + (byte)CastlingRights.WK | (byte)CastlingRights.WQ => WK_Hash ^ WQ_Hash, // KQ | - + (byte)CastlingRights.WK | (byte)CastlingRights.BK => WK_Hash ^ BK_Hash, // K | k + (byte)CastlingRights.WK | (byte)CastlingRights.BQ => WK_Hash ^ BQ_Hash, // K | q + (byte)CastlingRights.WQ | (byte)CastlingRights.BK => WQ_Hash ^ BK_Hash, // Q | k + (byte)CastlingRights.WQ | (byte)CastlingRights.BQ => WQ_Hash ^ BQ_Hash, // Q | q + (byte)CastlingRights.BK | (byte)CastlingRights.BQ => BK_Hash ^ BQ_Hash, // - | kq + + (byte)CastlingRights.WK | (byte)CastlingRights.WQ | (byte)CastlingRights.BK => WK_Hash ^ WQ_Hash ^ BK_Hash, // KQ | k + (byte)CastlingRights.WK | (byte)CastlingRights.WQ | (byte)CastlingRights.BQ => WK_Hash ^ WQ_Hash ^ BQ_Hash, // KQ | q + (byte)CastlingRights.WK | (byte)CastlingRights.BK | (byte)CastlingRights.BQ => WK_Hash ^ BK_Hash ^ BQ_Hash, // K | kq + (byte)CastlingRights.WQ | (byte)CastlingRights.BK | (byte)CastlingRights.BQ => WQ_Hash ^ BK_Hash ^ BQ_Hash, // Q | kq + + (byte)CastlingRights.WK | (byte)CastlingRights.WQ | (byte)CastlingRights.BK | (byte)CastlingRights.BQ => // KQ | kq + WK_Hash ^ WQ_Hash ^ BK_Hash ^ BQ_Hash, + + _ => throw new($"Unexpected castle encoded number: {castle}") + }; } /// diff --git a/tests/Lynx.Test/ZobristTableTest.cs b/tests/Lynx.Test/ZobristTableTest.cs index 38463b0cc..3fe96e9a9 100644 --- a/tests/Lynx.Test/ZobristTableTest.cs +++ b/tests/Lynx.Test/ZobristTableTest.cs @@ -96,6 +96,35 @@ public void CastleHash(string fen) var castleHash = ZobristTable.CastleHash(position.Castle); + Assert.AreEqual(CalculateCastleHash(position.Castle)castleHash); + Assert.AreEqual(positionWithoutCastlingRightsHash, positionHash ^ castleHash); } + + private static long CalculateCastleHash(byte castle) + { + long combinedHash = 0; + + if ((castle & (int)CastlingRights.WK) != default) + { + combinedHash ^= _zobristTable[(int)BoardSquare.a8, (int)Piece.p]; // a8 + } + + if ((castle & (int)CastlingRights.WQ) != default) + { + combinedHash ^= _zobristTable[(int)BoardSquare.b8, (int)Piece.p]; // b8 + } + + if ((castle & (int)CastlingRights.BK) != default) + { + combinedHash ^= _zobristTable[(int)BoardSquare.c8, (int)Piece.p]; // c8 + } + + if ((castle & (int)CastlingRights.BQ) != default) + { + combinedHash ^= _zobristTable[(int)BoardSquare.d8, (int)Piece.p]; // d8 + } + + return combinedHash; + } }