From d409412ac3ca0272a31d5c026df3d0eb8265ae16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20C=C3=A1ceres?= Date: Thu, 4 Jan 2024 03:58:43 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Replace=20`EnPassantCaptureSquares`?= =?UTF-8?q?=20dictionary=20with=20equivalent=20array=20(#578)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Lynx.Benchmark/EnPassantCaptureSquares.cs | 143 ++++++++++++++++++ src/Lynx.Benchmark/FENGeneration.cs | 10 -- .../MakeUnmakeMove_implementation.cs | 10 -- .../MakeUnmakeMove_integration.cs | 11 -- src/Lynx/Constants.cs | 50 +++--- src/Lynx/Model/Position.cs | 4 +- src/Lynx/ZobristTable.cs | 2 +- tests/Lynx.Test/ConstantsTest.cs | 24 +++ tests/Lynx.Test/ZobristTableTest.cs | 6 +- 9 files changed, 205 insertions(+), 55 deletions(-) create mode 100644 src/Lynx.Benchmark/EnPassantCaptureSquares.cs diff --git a/src/Lynx.Benchmark/EnPassantCaptureSquares.cs b/src/Lynx.Benchmark/EnPassantCaptureSquares.cs new file mode 100644 index 000000000..2845bf503 --- /dev/null +++ b/src/Lynx.Benchmark/EnPassantCaptureSquares.cs @@ -0,0 +1,143 @@ +/* + * + * BenchmarkDotNet v0.13.11, Ubuntu 22.04.3 LTS (Jammy Jellyfish) + * AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores + * .NET SDK 8.0.100 + * [Host] : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2 + * DefaultJob : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2 + * + * | Method | square | Mean | Error | StdDev | Ratio | Allocated | Alloc Ratio | + * |----------- |------- |----------:|----------:|----------:|------:|----------:|------------:| + * | Dictionary | b6 | 2.5037 ns | 0.0047 ns | 0.0042 ns | 1.00 | - | NA | + * | Array | b6 | 0.3192 ns | 0.0244 ns | 0.0228 ns | 0.13 | - | NA | + * | | | | | | | | | + * | Dictionary | d6 | 2.1117 ns | 0.0079 ns | 0.0062 ns | 1.00 | - | NA | + * | Array | d6 | 0.3081 ns | 0.0012 ns | 0.0011 ns | 0.15 | - | NA | + * | | | | | | | | | + * | Dictionary | f6 | 2.1361 ns | 0.0075 ns | 0.0063 ns | 1.00 | - | NA | + * | Array | f6 | 0.3251 ns | 0.0244 ns | 0.0228 ns | 0.15 | - | NA | + * | | | | | | | | | + * | Dictionary | h6 | 2.1471 ns | 0.0407 ns | 0.0381 ns | 1.00 | - | NA | + * | Array | h6 | 0.3160 ns | 0.0188 ns | 0.0176 ns | 0.15 | - | NA | + * | | | | | | | | | + * | Dictionary | a3 | 2.1403 ns | 0.0458 ns | 0.0428 ns | 1.00 | - | NA | + * | Array | a3 | 0.3102 ns | 0.0029 ns | 0.0025 ns | 0.14 | - | NA | + * | | | | | | | | | + * | Dictionary | c3 | 2.4238 ns | 0.0417 ns | 0.0390 ns | 1.00 | - | NA | + * | Array | c3 | 0.3191 ns | 0.0233 ns | 0.0194 ns | 0.13 | - | NA | + * | | | | | | | | | + * | Dictionary | e3 | 2.1081 ns | 0.0042 ns | 0.0035 ns | 1.00 | - | NA | + * | Array | e3 | 0.3221 ns | 0.0231 ns | 0.0216 ns | 0.15 | - | NA | + * | | | | | | | | | + * | Dictionary | g3 | 2.1282 ns | 0.0385 ns | 0.0341 ns | 1.00 | - | NA | + * | Array | g3 | 0.3095 ns | 0.0013 ns | 0.0011 ns | 0.15 | - | NA | + * + * + * BenchmarkDotNet v0.13.11, Windows 10 (10.0.20348.2159) (Hyper-V) + * AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores + * .NET SDK 8.0.100 + * [Host] : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2 + * DefaultJob : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2 + * + * | Method | square | Mean | Error | StdDev | Median | Ratio | Allocated | Alloc Ratio | + * |----------- |------- |----------:|----------:|----------:|----------:|------:|----------:|------------:| + * | Dictionary | b6 | 2.2527 ns | 0.0111 ns | 0.0092 ns | 2.2528 ns | 1.00 | - | NA | + * | Array | b6 | 0.2910 ns | 0.0036 ns | 0.0033 ns | 0.2894 ns | 0.13 | - | NA | + * | | | | | | | | | | + * | Dictionary | d6 | 2.2405 ns | 0.0057 ns | 0.0047 ns | 2.2393 ns | 1.000 | - | NA | + * | Array | d6 | 0.0011 ns | 0.0015 ns | 0.0014 ns | 0.0000 ns | 0.000 | - | NA | + * | | | | | | | | | | + * | Dictionary | f6 | 2.2561 ns | 0.0089 ns | 0.0083 ns | 2.2539 ns | 1.000 | - | NA | + * | Array | f6 | 0.0032 ns | 0.0031 ns | 0.0028 ns | 0.0027 ns | 0.001 | - | NA | + * | | | | | | | | | | + * | Dictionary | h6 | 2.2558 ns | 0.0067 ns | 0.0060 ns | 2.2551 ns | 1.000 | - | NA | + * | Array | h6 | 0.0002 ns | 0.0004 ns | 0.0003 ns | 0.0000 ns | 0.000 | - | NA | + * | | | | | | | | | | + * | Dictionary | a3 | 1.9623 ns | 0.0046 ns | 0.0043 ns | 1.9635 ns | 1.00 | - | NA | + * | Array | a3 | 0.2893 ns | 0.0011 ns | 0.0009 ns | 0.2893 ns | 0.15 | - | NA | + * | | | | | | | | | | + * | Dictionary | c3 | 1.9713 ns | 0.0074 ns | 0.0062 ns | 1.9743 ns | 1.000 | - | NA | + * | Array | c3 | 0.0014 ns | 0.0017 ns | 0.0014 ns | 0.0009 ns | 0.001 | - | NA | + * | | | | | | | | | | + * | Dictionary | e3 | 1.9725 ns | 0.0064 ns | 0.0056 ns | 1.9719 ns | 1.000 | - | NA | + * | Array | e3 | 0.0009 ns | 0.0012 ns | 0.0010 ns | 0.0006 ns | 0.000 | - | NA | + * | | | | | | | | | | + * | Dictionary | g3 | 2.2266 ns | 0.0242 ns | 0.0226 ns | 2.2245 ns | 1.000 | - | NA | + * | Array | g3 | 0.0008 ns | 0.0011 ns | 0.0009 ns | 0.0005 ns | 0.000 | - | NA | + * + * + * BenchmarkDotNet v0.13.11, macOS Monterey 12.7.2 (21G1974) [Darwin 21.6.0] + * Intel Xeon CPU E5-1650 v2 3.50GHz (Max: 3.34GHz), 1 CPU, 3 logical and 3 physical cores + * .NET SDK 8.0.100 + * [Host] : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX + * DefaultJob : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX + * + * | Method | square | Mean | Error | StdDev | Ratio | Allocated | Alloc Ratio | + * |----------- |------- |----------:|----------:|----------:|------:|----------:|------------:| + * | Dictionary | b6 | 3.7197 ns | 0.1121 ns | 0.1903 ns | 1.00 | - | NA | + * | Array | b6 | 0.3118 ns | 0.0068 ns | 0.0057 ns | 0.08 | - | NA | + * | | | | | | | | | + * | Dictionary | d6 | 3.6341 ns | 0.0158 ns | 0.0132 ns | 1.00 | - | NA | + * | Array | d6 | 0.4346 ns | 0.0474 ns | 0.0582 ns | 0.11 | - | NA | + * | | | | | | | | | + * | Dictionary | f6 | 3.6134 ns | 0.0479 ns | 0.0400 ns | 1.00 | - | NA | + * | Array | f6 | 0.3405 ns | 0.0089 ns | 0.0083 ns | 0.09 | - | NA | + * | | | | | | | | | + * | Dictionary | h6 | 3.8879 ns | 0.1147 ns | 0.1127 ns | 1.00 | - | NA | + * | Array | h6 | 0.3174 ns | 0.0467 ns | 0.0459 ns | 0.08 | - | NA | + * | | | | | | | | | + * | Dictionary | a3 | 3.9865 ns | 0.0769 ns | 0.0681 ns | 1.00 | - | NA | + * | Array | a3 | 0.2815 ns | 0.0325 ns | 0.0288 ns | 0.07 | - | NA | + * | | | | | | | | | + * | Dictionary | c3 | 3.5451 ns | 0.0580 ns | 0.0542 ns | 1.00 | - | NA | + * | Array | c3 | 0.3042 ns | 0.0303 ns | 0.0268 ns | 0.09 | - | NA | + * | | | | | | | | | + * | Dictionary | e3 | 3.4793 ns | 0.1037 ns | 0.0970 ns | 1.00 | - | NA | + * | Array | e3 | 0.3493 ns | 0.0305 ns | 0.0255 ns | 0.10 | - | NA | + * | | | | | | | | | + * | Dictionary | g3 | 3.5165 ns | 0.0173 ns | 0.0162 ns | 1.00 | - | NA | + * | Array | g3 | 0.3083 ns | 0.0085 ns | 0.0079 ns | 0.09 | - | NA | + * +*/ + +using BenchmarkDotNet.Attributes; +using Lynx.Model; +using System.Collections.Frozen; + +namespace Lynx.Benchmark; +public class EnPassantCaptureSquares : BaseBenchmark +{ + public static IEnumerable Data => new[] { + BoardSquare.a3, BoardSquare.c3, BoardSquare.e3, BoardSquare.g3, + BoardSquare.b6, BoardSquare.d6, BoardSquare.f6, BoardSquare.h6 + }; + + [Benchmark(Baseline = true)] + [ArgumentsSource(nameof(Data))] + public int Dictionary(BoardSquare square) => EnPassantCaptureSquaresDictionary[(int)square]; + + [Benchmark] + [ArgumentsSource(nameof(Data))] + public int Array(BoardSquare square) => Constants.EnPassantCaptureSquares[(int)square]; + + private static readonly FrozenDictionary EnPassantCaptureSquaresDictionary = new Dictionary(16) + { + [(int)BoardSquare.a6] = (int)BoardSquare.a6 + 8, + [(int)BoardSquare.b6] = (int)BoardSquare.b6 + 8, + [(int)BoardSquare.c6] = (int)BoardSquare.c6 + 8, + [(int)BoardSquare.d6] = (int)BoardSquare.d6 + 8, + [(int)BoardSquare.e6] = (int)BoardSquare.e6 + 8, + [(int)BoardSquare.f6] = (int)BoardSquare.f6 + 8, + [(int)BoardSquare.g6] = (int)BoardSquare.g6 + 8, + [(int)BoardSquare.h6] = (int)BoardSquare.h6 + 8, + + [(int)BoardSquare.a3] = (int)BoardSquare.a3 - 8, + [(int)BoardSquare.b3] = (int)BoardSquare.b3 - 8, + [(int)BoardSquare.c3] = (int)BoardSquare.c3 - 8, + [(int)BoardSquare.d3] = (int)BoardSquare.d3 - 8, + [(int)BoardSquare.e3] = (int)BoardSquare.e3 - 8, + [(int)BoardSquare.f3] = (int)BoardSquare.f3 - 8, + [(int)BoardSquare.g3] = (int)BoardSquare.g3 - 8, + [(int)BoardSquare.h3] = (int)BoardSquare.h3 - 8, + }.ToFrozenDictionary(); +} diff --git a/src/Lynx.Benchmark/FENGeneration.cs b/src/Lynx.Benchmark/FENGeneration.cs index 7bfbf51ce..391798f60 100644 --- a/src/Lynx.Benchmark/FENGeneration.cs +++ b/src/Lynx.Benchmark/FENGeneration.cs @@ -455,7 +455,6 @@ public StructCustomPosition(StructCustomPosition position, Move move) : this(pos { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; } @@ -558,7 +557,6 @@ public StructCustomPosition(StructCustomPosition position, Move move, bool calcu { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; } @@ -803,7 +801,6 @@ public ReadonlyStructCustomPosition(ReadonlyStructCustomPosition position, Move { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; } @@ -907,7 +904,6 @@ public ReadonlyStructCustomPosition(ReadonlyStructCustomPosition position, Move { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; } @@ -1159,7 +1155,6 @@ public ClassCustomPosition(ClassCustomPosition position, Move move) : this(posit { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; } @@ -1261,7 +1256,6 @@ public ClassCustomPosition(ClassCustomPosition position, Move move, bool calcula { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; } @@ -1513,7 +1507,6 @@ public RecordClassCustomPosition(RecordClassCustomPosition position, Move move) { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; } @@ -1615,7 +1608,6 @@ public RecordClassCustomPosition(RecordClassCustomPosition position, Move move, { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; } @@ -1867,7 +1859,6 @@ public RecordStructCustomPosition(RecordStructCustomPosition position, Move move { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; } @@ -1969,7 +1960,6 @@ public RecordStructCustomPosition(RecordStructCustomPosition position, Move move { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; } diff --git a/src/Lynx.Benchmark/MakeUnmakeMove_implementation.cs b/src/Lynx.Benchmark/MakeUnmakeMove_implementation.cs index 6f85bc0ea..124f28f51 100644 --- a/src/Lynx.Benchmark/MakeUnmakeMove_implementation.cs +++ b/src/Lynx.Benchmark/MakeUnmakeMove_implementation.cs @@ -274,7 +274,6 @@ public MakeMovePosition(MakeMovePosition position, Move move) : this(position) { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; UniqueIdentifier ^= ZobristTable.EnPassantHash(enPassantSquare); @@ -394,7 +393,6 @@ public MakeMoveGameState MakeMove_Original(Move move) { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; UniqueIdentifier ^= ZobristTable.EnPassantHash(enPassantSquare); @@ -618,7 +616,6 @@ public MakeMoveGameStateWithZobristKey MakeMove_WithZobristKey(Move move) { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {(BoardSquare)enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; UniqueIdentifier ^= ZobristTable.EnPassantHash(enPassantSquare); @@ -867,13 +864,6 @@ public static long EnPassantHash(int enPassantSquare) return default; } -#if DEBUG - if (!Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare)) - { - throw new ArgumentException($"{Constants.Coordinates[enPassantSquare]} is not a valid en-passant square"); - } -#endif - var file = enPassantSquare % 8; return _table[file, (int)Piece.P]; diff --git a/src/Lynx.Benchmark/MakeUnmakeMove_integration.cs b/src/Lynx.Benchmark/MakeUnmakeMove_integration.cs index 6bb516910..224367254 100644 --- a/src/Lynx.Benchmark/MakeUnmakeMove_integration.cs +++ b/src/Lynx.Benchmark/MakeUnmakeMove_integration.cs @@ -432,7 +432,6 @@ public MakeMovePosition(MakeMovePosition position, Move move) : this(position) { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; UniqueIdentifier ^= ZobristTable.EnPassantHash(enPassantSquare); @@ -552,7 +551,6 @@ public MakeMoveGameState MakeMove_Original(Move move) { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; UniqueIdentifier ^= ZobristTable.EnPassantHash(enPassantSquare); @@ -674,7 +672,6 @@ public void MakeMove_PassOut(Move move, out MakeMoveGameState_PassOut gameState) { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; UniqueIdentifier ^= ZobristTable.EnPassantHash(enPassantSquare); @@ -796,7 +793,6 @@ public void MakeMove_PassRef(Move move, ref MakeMoveGameState_PassRef gameState) { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; UniqueIdentifier ^= ZobristTable.EnPassantHash(enPassantSquare); @@ -1275,13 +1271,6 @@ public static long EnPassantHash(int enPassantSquare) return default; } -#if DEBUG - if (!Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare)) - { - throw new ArgumentException($"{Constants.Coordinates[enPassantSquare]} is not a valid en-passant square"); - } -#endif - var file = enPassantSquare % 8; return _table[file, (int)Piece.P]; diff --git a/src/Lynx/Constants.cs b/src/Lynx/Constants.cs index a827b42a5..b86f00aab 100644 --- a/src/Lynx/Constants.cs +++ b/src/Lynx/Constants.cs @@ -505,26 +505,36 @@ public static class Constants public const string WhiteLongCastle = "e1c1"; public const string BlackLongCastle = "e8c8"; - public static readonly FrozenDictionary EnPassantCaptureSquares = new Dictionary(16) - { - [(int)BoardSquare.a6] = (int)BoardSquare.a6 + 8, - [(int)BoardSquare.b6] = (int)BoardSquare.b6 + 8, - [(int)BoardSquare.c6] = (int)BoardSquare.c6 + 8, - [(int)BoardSquare.d6] = (int)BoardSquare.d6 + 8, - [(int)BoardSquare.e6] = (int)BoardSquare.e6 + 8, - [(int)BoardSquare.f6] = (int)BoardSquare.f6 + 8, - [(int)BoardSquare.g6] = (int)BoardSquare.g6 + 8, - [(int)BoardSquare.h6] = (int)BoardSquare.h6 + 8, - - [(int)BoardSquare.a3] = (int)BoardSquare.a3 - 8, - [(int)BoardSquare.b3] = (int)BoardSquare.b3 - 8, - [(int)BoardSquare.c3] = (int)BoardSquare.c3 - 8, - [(int)BoardSquare.d3] = (int)BoardSquare.d3 - 8, - [(int)BoardSquare.e3] = (int)BoardSquare.e3 - 8, - [(int)BoardSquare.f3] = (int)BoardSquare.f3 - 8, - [(int)BoardSquare.g3] = (int)BoardSquare.g3 - 8, - [(int)BoardSquare.h3] = (int)BoardSquare.h3 - 8, - }.ToFrozenDictionary(); + #pragma warning disable IDE0055 // Discard formatting in this region + + public static readonly int[] EnPassantCaptureSquares = + [ + 0, 0, 0, 0, 0, 0, 0, 0, // 0-7 + 0, 0, 0, 0, 0, 0, 0, 0, // 8-15 + + (int)BoardSquare.a6 + 8, // 16 = a6 + (int)BoardSquare.b6 + 8, + (int)BoardSquare.c6 + 8, + (int)BoardSquare.d6 + 8, + (int)BoardSquare.e6 + 8, + (int)BoardSquare.f6 + 8, + (int)BoardSquare.g6 + 8, + (int)BoardSquare.h6 + 8, // 23 = h6 + + 0, 0, 0, 0, 0, 0, 0, 0, // 24-31 + 0, 0, 0, 0, 0, 0, 0, 0, // 32-39 + + (int)BoardSquare.a3 - 8, // 40 = a3 + (int)BoardSquare.b3 - 8, + (int)BoardSquare.c3 - 8, + (int)BoardSquare.d3 - 8, + (int)BoardSquare.e3 - 8, + (int)BoardSquare.f3 - 8, + (int)BoardSquare.g3 - 8, + (int)BoardSquare.h3 - 8 //47 = h3 + ]; + + #pragma warning restore IDE0055 /// /// https://github.com/maksimKorzh/chess_programming/blob/master/src/bbc/make_move_castling_rights/bbc.c#L1474 diff --git a/src/Lynx/Model/Position.cs b/src/Lynx/Model/Position.cs index 7dedabe06..28101f4be 100644 --- a/src/Lynx/Model/Position.cs +++ b/src/Lynx/Model/Position.cs @@ -171,7 +171,7 @@ public Position(Position position, Move move) : this(position) { var pawnPush = +8 - ((int)oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {(BoardSquare)enPassantSquare}"); + Utils.Assert(Constants.EnPassantCaptureSquares.Length > enPassantSquare && Constants.EnPassantCaptureSquares[enPassantSquare] != 0, $"Unexpected en passant square : {(BoardSquare)enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; UniqueIdentifier ^= ZobristTable.EnPassantHash(enPassantSquare); @@ -291,7 +291,7 @@ public GameState MakeMove(Move move) { var pawnPush = +8 - (oldSide * 16); var enPassantSquare = sourceSquare + pawnPush; - Utils.Assert(Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare), $"Unexpected en passant square : {(BoardSquare)enPassantSquare}"); + Utils.Assert(Constants.EnPassantCaptureSquares.Length > enPassantSquare && Constants.EnPassantCaptureSquares[enPassantSquare] != 0, $"Unexpected en passant square : {(BoardSquare)enPassantSquare}"); EnPassant = (BoardSquare)enPassantSquare; UniqueIdentifier ^= ZobristTable.EnPassantHash(enPassantSquare); diff --git a/src/Lynx/ZobristTable.cs b/src/Lynx/ZobristTable.cs index 0dfc259d6..ba648b853 100644 --- a/src/Lynx/ZobristTable.cs +++ b/src/Lynx/ZobristTable.cs @@ -33,7 +33,7 @@ public static long EnPassantHash(int enPassantSquare) } #if DEBUG - if (!Constants.EnPassantCaptureSquares.ContainsKey(enPassantSquare)) + if (Constants.EnPassantCaptureSquares.Length <= enPassantSquare || Constants.EnPassantCaptureSquares[enPassantSquare] == 0) { throw new ArgumentException($"{Constants.Coordinates[enPassantSquare]} is not a valid en-passant square"); } diff --git a/tests/Lynx.Test/ConstantsTest.cs b/tests/Lynx.Test/ConstantsTest.cs index 61fd81e33..d6c907d9e 100644 --- a/tests/Lynx.Test/ConstantsTest.cs +++ b/tests/Lynx.Test/ConstantsTest.cs @@ -1,5 +1,6 @@ using Lynx.Model; using NUnit.Framework; +using System.Collections.Frozen; namespace Lynx.Test; @@ -58,12 +59,35 @@ public void EnPassantCaptureSquares() for (int square = (int)BoardSquare.a6; square <= (int)BoardSquare.h6; ++square) { Assert.AreEqual(square + 8, Constants.EnPassantCaptureSquares[square]); + Assert.AreEqual(EnPassantCaptureSquaresDictionary[square], Constants.EnPassantCaptureSquares[square]); } Assert.AreEqual((int)BoardSquare.d4, Constants.EnPassantCaptureSquares[(int)BoardSquare.d3]); for (int square = (int)BoardSquare.a3; square <= (int)BoardSquare.h3; ++square) { Assert.AreEqual(square - 8, Constants.EnPassantCaptureSquares[square]); + Assert.AreEqual(EnPassantCaptureSquaresDictionary[square], Constants.EnPassantCaptureSquares[square]); } } + + private static readonly FrozenDictionary EnPassantCaptureSquaresDictionary = new Dictionary(16) + { + [(int)BoardSquare.a6] = (int)BoardSquare.a6 + 8, + [(int)BoardSquare.b6] = (int)BoardSquare.b6 + 8, + [(int)BoardSquare.c6] = (int)BoardSquare.c6 + 8, + [(int)BoardSquare.d6] = (int)BoardSquare.d6 + 8, + [(int)BoardSquare.e6] = (int)BoardSquare.e6 + 8, + [(int)BoardSquare.f6] = (int)BoardSquare.f6 + 8, + [(int)BoardSquare.g6] = (int)BoardSquare.g6 + 8, + [(int)BoardSquare.h6] = (int)BoardSquare.h6 + 8, + + [(int)BoardSquare.a3] = (int)BoardSquare.a3 - 8, + [(int)BoardSquare.b3] = (int)BoardSquare.b3 - 8, + [(int)BoardSquare.c3] = (int)BoardSquare.c3 - 8, + [(int)BoardSquare.d3] = (int)BoardSquare.d3 - 8, + [(int)BoardSquare.e3] = (int)BoardSquare.e3 - 8, + [(int)BoardSquare.f3] = (int)BoardSquare.f3 - 8, + [(int)BoardSquare.g3] = (int)BoardSquare.g3 - 8, + [(int)BoardSquare.h3] = (int)BoardSquare.h3 - 8, + }.ToFrozenDictionary(); } diff --git a/tests/Lynx.Test/ZobristTableTest.cs b/tests/Lynx.Test/ZobristTableTest.cs index cb2749472..65cb30d4b 100644 --- a/tests/Lynx.Test/ZobristTableTest.cs +++ b/tests/Lynx.Test/ZobristTableTest.cs @@ -51,12 +51,16 @@ public void PieceHash() [Test] public void EnPassantHash() { - foreach (var enPassantSquare in Constants.EnPassantCaptureSquares.Keys) + var enPassantSquares = Constants.EnPassantCaptureSquares.Select((item, index) => (item, index)).Where(pair => pair.item != 0).Select(pair => pair.index); + + foreach (var enPassantSquare in enPassantSquares) { var file = enPassantSquare % 8; Assert.AreEqual(_zobristTable[file, (int)Piece.P], ZobristTable.EnPassantHash(enPassantSquare)); } + Assert.AreEqual(16, enPassantSquares.Count()); + #if DEBUG for (int square = 0; square < 64; ++square) {