diff --git a/.github/workflows/release-pipeline.yaml b/.github/workflows/release-pipeline.yaml index 1fffd26..3b567b7 100644 --- a/.github/workflows/release-pipeline.yaml +++ b/.github/workflows/release-pipeline.yaml @@ -25,7 +25,7 @@ jobs: working-directory: src/Sapling id: get_version run: | - VERSION=1.2.1 + VERSION=1.2.2 echo "Application version: $VERSION" echo "::set-output name=version::$VERSION" diff --git a/src/Sapling.Engine/DataGen/Chess960.cs b/src/Sapling.Engine/DataGen/Chess960.cs new file mode 100644 index 0000000..4fb161b --- /dev/null +++ b/src/Sapling.Engine/DataGen/Chess960.cs @@ -0,0 +1,135 @@ +namespace Sapling.Engine.DataGen; + +using System.Collections.Generic; +using System.Linq; + +public static class Chess960 +{ + public static string[] Fens; + + static Chess960() + { + char[] board = new char[8]; + var fens = new HashSet(); + GenerateBishops(board, fens); + Fens = fens.ToArray(); + } + + // Step 1: Place bishops on opposite-colored squares + private static void GenerateBishops(char[] board, HashSet fenList) + { + for (int b1 = 0; b1 < 8; b1 += 2) // Dark square bishop + { + for (int b2 = 1; b2 < 8; b2 += 2) // Light square bishop + { + board[b1] = 'B'; + board[b2] = 'B'; + GenerateRooksAndKing(board, fenList); // Proceed to place rooks and king + board[b1] = '\0'; + board[b2] = '\0'; + } + } + } + + // Step 2: Place the king between the two rooks + private static void GenerateRooksAndKing(char[] board, HashSet fenList) + { + for (int r1 = 0; r1 < 8; r1++) + { + if (board[r1] != '\0') continue; // Skip occupied squares (bishops) + + for (int r2 = r1 + 1; r2 < 8; r2++) + { + if (board[r2] != '\0') continue; + + // Ensure the king is placed between the two rooks + for (int k = r1 + 1; k < r2; k++) + { + if (board[k] == '\0') // The square must be free for the king + { + board[r1] = 'R'; + board[r2] = 'R'; + board[k] = 'K'; + GenerateKnightsAndQueen(board, fenList); // Proceed to place knights and queen + board[r1] = '\0'; + board[r2] = '\0'; + board[k] = '\0'; + } + } + } + } + } + + // Step 3: Place knights and queen in remaining empty squares + private static void GenerateKnightsAndQueen(char[] board, HashSet fenList) + { + List emptySquares = new List(); + for (int i = 0; i < 8; i++) + { + if (board[i] == '\0') + { + emptySquares.Add(i); + } + } + + // There are 3 empty squares remaining, so permute 'N', 'N', 'Q' + char[] remainingPieces = { 'N', 'N', 'Q' }; + Permute(remainingPieces, 0, board, emptySquares, fenList); + } + + // Step 4: Permute remaining knights and queen, placing them on the board + private static void Permute(char[] pieces, int index, char[] board, List emptySquares, HashSet fenList) + { + if (index == pieces.Length) + { + // Fill the empty squares with the current permutation + for (int i = 0; i < emptySquares.Count; i++) + { + board[emptySquares[i]] = pieces[i]; + } + + // Generate the FEN string with Shredder-style castling rights + var castlingRights = GetShredderCastlingRights(board); + var fen = string.Join("", board).ToLower() + "/pppppppp/8/8/8/8/PPPPPPPP/" + string.Join("", board).ToUpper() + " w " + castlingRights.ToUpper()+ castlingRights.ToLower() + " - 0 1"; + fenList.Add(fen); + + // Clear the board for the next permutation + foreach (var indexToClear in emptySquares) + { + board[indexToClear] = '\0'; + } + + return; + } + + for (int i = index; i < pieces.Length; i++) + { + Swap(ref pieces[index], ref pieces[i]); + Permute(pieces, index + 1, board, emptySquares, fenList); + Swap(ref pieces[index], ref pieces[i]); // Backtrack + } + } + + // Helper function to swap pieces in the permutation + private static void Swap(ref char a, ref char b) + { + (a, b) = (b, a); + } + + // Generate castling rights in Shredder FEN format by checking rook positions + private static string GetShredderCastlingRights(char[] board) + { + string castlingRights = ""; + + // Find the positions of the rooks (R) and convert them to file letters (a-h) + for (int i = 0; i < 8; i++) + { + if (board[i] == 'R') + { + castlingRights += (char)('a' + i); // Convert index to file letter + } + } + + return castlingRights; + } +} diff --git a/src/Sapling.Engine/DataGen/DataGenerator.cs b/src/Sapling.Engine/DataGen/DataGenerator.cs index d9cbb3a..87385da 100644 --- a/src/Sapling.Engine/DataGen/DataGenerator.cs +++ b/src/Sapling.Engine/DataGen/DataGenerator.cs @@ -6,6 +6,8 @@ namespace Sapling.Engine.DataGen; +using System; + public class DataGeneratorStats { public BoardStateData InitialBoard = default; @@ -73,6 +75,9 @@ public void Output() } public class DataGenerator { + public const bool Is960 = true; + public const int RandomMoves = 8; + public const int MaxTurnCount = 500; public bool Cancelled = false; @@ -140,8 +145,14 @@ private unsafe void RunWorker(DataGeneratorStats stats) } stats.Output(dataGenPositions, positions, result); - - gameState.ResetTo(ref stats.InitialBoard, initialLegalMoves); + if (Is960) + { + gameState.ResetToFen(Chess960.Fens[Random.Shared.Next(0, 960)]); + } + else + { + gameState.ResetTo(ref stats.InitialBoard, initialLegalMoves); + } } catch (Exception ex) { @@ -161,7 +172,7 @@ private unsafe void RunWorker(DataGeneratorStats stats) while (!gameState.GameOver() && gameState.Board.TurnCount < MaxTurnCount && !IsAdjudicatedDraw(gameState, drawScoreCount)) { uint move; - if (randomMoveCount <= 8) + if (randomMoveCount <= RandomMoves) { move = gameState.LegalMoves[Random.Shared.Next(0, gameState.LegalMoves.Count)]; randomMoveCount++; diff --git a/src/Sapling.Engine/MoveGen/MoveGenerator.cs b/src/Sapling.Engine/MoveGen/MoveGenerator.cs index e4d4d02..d37b243 100644 --- a/src/Sapling.Engine/MoveGen/MoveGenerator.cs +++ b/src/Sapling.Engine/MoveGen/MoveGenerator.cs @@ -653,13 +653,13 @@ public static unsafe void GenerateWhiteKingPseudoLegalMoves(this ref BoardStateD var startSquare = Math.Min(board.WhiteKingSquare, Math.Min(rookSquare, 5)); var endSquare = Math.Max(board.WhiteKingSquare, Math.Max(rookSquare, 6)); - ulong path = (*(AttackTables.LineBitBoardsInclusive + startSquare * 64 + endSquare) & board.Occupancy[Constants.Occupancy]) & ~((1UL << rookSquare) | (1UL << board.WhiteKingSquare)); + ulong path = (*(AttackTables.LineBitBoardsInclusive + (startSquare << 6) + endSquare) & board.Occupancy[Constants.Occupancy]) & ~((1UL << rookSquare) | (1UL << board.WhiteKingSquare)); if (path == 0) { var canCastle = true; var kingStart = Math.Min((int)board.WhiteKingSquare, 6); var kingEnd = Math.Max((int)board.WhiteKingSquare, 6); - for (var i = kingStart; i <= kingEnd; i++) + for (var i = kingEnd; i >= kingStart; i--) { if (board.IsAttackedByBlack(i)) { @@ -682,14 +682,14 @@ public static unsafe void GenerateWhiteKingPseudoLegalMoves(this ref BoardStateD var rookSquare = board.Is960 ? board.WhiteQueenSideTargetSquare : 0; var startSquare = Math.Min(board.WhiteKingSquare, Math.Min(rookSquare, 2)); var endSquare = Math.Max(board.WhiteKingSquare, Math.Max(rookSquare, 3)); - ulong path = (*(AttackTables.LineBitBoardsInclusive + startSquare * 64 + endSquare) & board.Occupancy[Constants.Occupancy]) & ~((1UL << rookSquare) | (1UL << board.WhiteKingSquare)); + ulong path = (*(AttackTables.LineBitBoardsInclusive + (startSquare << 6) + endSquare) & board.Occupancy[Constants.Occupancy]) & ~((1UL << rookSquare) | (1UL << board.WhiteKingSquare)); if (path == 0) { bool canCastle = true; var kingStart = Math.Min((int)board.WhiteKingSquare, 2); var kingEnd = Math.Max((int)board.WhiteKingSquare, 2); - for (var i = kingStart; i <= kingEnd; i++) + for (var i = kingEnd; i >= kingStart; i--) { if (board.IsAttackedByBlack(i)) { @@ -747,7 +747,7 @@ public static unsafe void GetBlackKingPseudoLegalMoves(this ref BoardStateData b var startSquare = Math.Min(board.BlackKingSquare, Math.Min(rookSquare, 61)); var endSquare = Math.Max(board.BlackKingSquare, Math.Max(rookSquare, 62)); - ulong path = (*(AttackTables.LineBitBoardsInclusive + startSquare * 64 + endSquare) & board.Occupancy[Constants.Occupancy]) & ~((1UL << rookSquare) | (1UL << board.BlackKingSquare)); + ulong path = (*(AttackTables.LineBitBoardsInclusive + (startSquare << 6) + endSquare) & board.Occupancy[Constants.Occupancy]) & ~((1UL << rookSquare) | (1UL << board.BlackKingSquare)); if (path == 0) { @@ -755,7 +755,7 @@ public static unsafe void GetBlackKingPseudoLegalMoves(this ref BoardStateData b var kingStart = Math.Min((int)board.BlackKingSquare, 62); var kingEnd = Math.Max((int)board.BlackKingSquare, 62); - for (var i = kingStart; i <= kingEnd; i++) + for (var i = kingEnd; i >= kingStart; i--) { if (board.IsAttackedByWhite(i)) { @@ -779,14 +779,14 @@ public static unsafe void GetBlackKingPseudoLegalMoves(this ref BoardStateData b var startSquare = Math.Min(board.BlackKingSquare, Math.Min(rookSquare, 58)); var endSquare = Math.Max(board.BlackKingSquare, Math.Max(rookSquare, 59)); - ulong path = (*(AttackTables.LineBitBoardsInclusive + startSquare * 64 + endSquare) & board.Occupancy[Constants.Occupancy]) & ~((1UL << rookSquare) | (1UL << board.BlackKingSquare)); + ulong path = (*(AttackTables.LineBitBoardsInclusive + (startSquare << 6) + endSquare) & board.Occupancy[Constants.Occupancy]) & ~((1UL << rookSquare) | (1UL << board.BlackKingSquare)); if (path == 0) { bool canCastle = true; var kingStart = Math.Min((int)board.BlackKingSquare, 58); var kingEnd = Math.Max((int)board.BlackKingSquare, 58); - for (var i = kingStart; i <= kingEnd; i++) + for (var i = kingEnd; i >= kingStart; i--) { if (board.IsAttackedByWhite(i)) { diff --git a/src/Sapling.Engine/Resources/WeightsHistory/20_(768x4-1024)x2-8.bin b/src/Sapling.Engine/Resources/WeightsHistory/20_(768x4-1024)x2-8.bin new file mode 100644 index 0000000..7964075 Binary files /dev/null and b/src/Sapling.Engine/Resources/WeightsHistory/20_(768x4-1024)x2-8.bin differ diff --git a/src/Sapling.Engine/Resources/WeightsHistory/log.txt b/src/Sapling.Engine/Resources/WeightsHistory/log.txt index bf85dbf..8b99e5a 100644 --- a/src/Sapling.Engine/Resources/WeightsHistory/log.txt +++ b/src/Sapling.Engine/Resources/WeightsHistory/log.txt @@ -166,3 +166,11 @@ Data: 1.7bn positions WDL: 0.3 LR: CosineDecayLR 0.001 * 0.3 * 0.3 * 0.3 SuperBatches: 260 + +---------------------------------- +20_(768x4-1024)x2-8 +---------------------------------- +Data: 2bn positions (400m FRC) +WDL: 0.3 +LR: CosineDecayLR 0.001 * 0.3 * 0.3 * 0.3 +SuperBatches: 300 \ No newline at end of file diff --git a/src/Sapling.Engine/Resources/sapling.nnue b/src/Sapling.Engine/Resources/sapling.nnue index 6d2cc6c..7964075 100644 Binary files a/src/Sapling.Engine/Resources/sapling.nnue and b/src/Sapling.Engine/Resources/sapling.nnue differ diff --git a/src/Sapling.Engine/Tuning/SpsaOptions.cs b/src/Sapling.Engine/Tuning/SpsaOptions.cs index 4fa6355..bca686d 100644 --- a/src/Sapling.Engine/Tuning/SpsaOptions.cs +++ b/src/Sapling.Engine/Tuning/SpsaOptions.cs @@ -152,14 +152,14 @@ public static unsafe class SpsaOptions [SpsaMinValue("1400"), SpsaMaxValue("3000")] public static int AsperationWindowE = 2662; #else - public const int ReverseFutilityPruningMargin = 68; + public const int ReverseFutilityPruningMargin = 67; public const int ReverseFutilityPruningDepth = 7; public const int NullMovePruningDepth = 2; public const float NullMovePruningReductionA = 3; public const float NullMovePruningReductionB = 4; public const float NullMovePruningReductionC = 3; - public const int RazorMarginA = 57; - public const int RazorMarginB = 370; + public const int RazorMarginA = 58; + public const int RazorMarginB = 378; public const int InternalIterativeDeepeningDepth = 2; public const int LateMovePruningConstant = 8; public const int LateMoveReductionMinDepth = 3; @@ -168,26 +168,26 @@ public static unsafe class SpsaOptions public const float LateMoveReductionInterestingB = 2.4956496f; public const float LateMoveReductionA = 1.315961f; public const float LateMoveReductionB = 2.7831153f; - public const int HistoryHeuristicMaxHistory = 9599; - public const int HistoryHeuristicBonusMax = 425; - public const int HistoryHeuristicBonusCoeff = 86; - public const int MoveOrderingBestMoveBias = 240615; - public const int MoveOrderingEnPassantMoveBias = 77989; - public const int MoveOrderingWinningCaptureBias = 142295; - public const int MoveOrderingLosingCaptureBias = 15778; - public const int MoveOrderingPromoteBias = 45184; - public const int MoveOrderingCapturePromoteBias = 30512; - public const int MoveOrderingKillerABias = 61238; - public const int MoveOrderingCounterMoveBias = 87526; - public const int InterestingNegaMaxMoveScore = 40731; - public const int InterestingQuiescenceMoveScore = 40988; + public const int HistoryHeuristicMaxHistory = 9532; + public const int HistoryHeuristicBonusMax = 406; + public const int HistoryHeuristicBonusCoeff = 83; + public const int MoveOrderingBestMoveBias = 239130; + public const int MoveOrderingEnPassantMoveBias = 78671; + public const int MoveOrderingWinningCaptureBias = 134191; + public const int MoveOrderingLosingCaptureBias = 16845; + public const int MoveOrderingPromoteBias = 46577; + public const int MoveOrderingCapturePromoteBias = 35558; + public const int MoveOrderingKillerABias = 66845; + public const int MoveOrderingCounterMoveBias = 87003; + public const int InterestingNegaMaxMoveScore = 40775; + public const int InterestingQuiescenceMoveScore = 35432; public const int ProbCutBetaMargin = 220; public const int ProbCutMinDepth = 3; - public const int AsperationWindowA = 38; - public const int AsperationWindowB = 60; - public const int AsperationWindowC = 275; - public const int AsperationWindowD = 837; - public const int AsperationWindowE = 2662; + public const int AsperationWindowA = 37; + public const int AsperationWindowB = 59; + public const int AsperationWindowC = 279; + public const int AsperationWindowD = 847; + public const int AsperationWindowE = 2785; #endif public static int* LateMovePruningInterestingReductionTable; diff --git a/src/Sapling/Program.cs b/src/Sapling/Program.cs index 54fb863..ed0c3fb 100644 --- a/src/Sapling/Program.cs +++ b/src/Sapling/Program.cs @@ -25,7 +25,7 @@ internal class Program private static StreamWriter _logWriter; private static void Main(string[] args) - { +{ Console.SetIn(new StreamReader(Console.OpenStandardInput(), Encoding.UTF8, false, 2048 * 4)); if (args.Length > 0 && args[0] == "--version") diff --git a/src/Sapling/Sapling.csproj b/src/Sapling/Sapling.csproj index 974c3e9..ff50f31 100644 --- a/src/Sapling/Sapling.csproj +++ b/src/Sapling/Sapling.csproj @@ -7,9 +7,9 @@ enable logo.ico Sapling - 1.2.1.0 - 1.2.1.0 - 1.2.1.0 + 1.2.2.0 + 1.2.2.0 + 1.2.2.0 true