diff --git a/Alligator.Benchmark/MeasurableHeuristicTables.cs b/Alligator.Benchmark/MeasurableHeuristicTables.cs index b21250e..8668d71 100644 --- a/Alligator.Benchmark/MeasurableHeuristicTables.cs +++ b/Alligator.Benchmark/MeasurableHeuristicTables.cs @@ -27,6 +27,11 @@ public void StoreBetaCutOff(Step move, int depth) heuristicTables.StoreBetaCutOff(move, depth); } + public int GetHistoryScore(Step move) + { + return heuristicTables.GetHistoryScore(move); + } + public void ClearCounters() { GetKillerStepsCallCount = 0; diff --git a/Alligator.Solver/Algorithms/AlphaBetaPruning.cs b/Alligator.Solver/Algorithms/AlphaBetaPruning.cs index f4949f1..8460a02 100644 --- a/Alligator.Solver/Algorithms/AlphaBetaPruning.cs +++ b/Alligator.Solver/Algorithms/AlphaBetaPruning.cs @@ -8,6 +8,9 @@ internal class AlphaBetaPruning : IAlphaBetaPruning private readonly IHeuristicTables heuristicTables; private readonly ISearchManager searchManager; + private const int MaxSearchDepth = 16; + private readonly List[] orderedStepBuffers; + public AlphaBetaPruning( IRules rules, ICacheTables cacheTables, @@ -18,6 +21,12 @@ public AlphaBetaPruning( this.cacheTables = cacheTables ?? throw new ArgumentNullException(nameof(cacheTables)); this.heuristicTables = heuristicTables ?? throw new ArgumentNullException(nameof(heuristicTables)); this.searchManager = searchManager ?? throw new ArgumentNullException(nameof(searchManager)); + + orderedStepBuffers = new List[MaxSearchDepth]; + for (int i = 0; i < MaxSearchDepth; i++) + { + orderedStepBuffers[i] = new List(); + } } public int Search(TPosition position, int alpha, int beta) @@ -27,15 +36,20 @@ public int Search(TPosition position, int alpha, int beta) private int SearchRecursively(TPosition position, int depth, int alpha, int beta) { - if (IsLeaf(position, depth)) + if (depth <= 0) { + if (rules.IsGoal(position)) + { + return -(sbyte.MaxValue + depth); + } return -HeuristicValue(position, depth); } var originalAlpha = alpha; - if (cacheTables.TryGetTransposition(position, out Transposition transposition) - && depth <= transposition.Depth) + bool hasTransposition = cacheTables.TryGetTransposition(position, out Transposition transposition); + + if (hasTransposition && depth <= transposition.Depth) { switch (transposition.EvaluationMode) { @@ -55,11 +69,19 @@ private int SearchRecursively(TPosition position, int depth, int alpha, int beta } } + var orderedSteps = GetOrderedLegalSteps(position, depth, hasTransposition, transposition.OptimalStep); + + if (orderedSteps.Count == 0) + { + return -(rules.IsGoal(position) ? sbyte.MaxValue + depth : 0); + } + var bestValue = -int.MaxValue; TStep bestStep = default; - foreach (var step in OrderedLegalStepsAt(position, depth, transposition)) + for (int i = 0; i < orderedSteps.Count; i++) { + var step = orderedSteps[i]; position.Take(step); var value = -SearchRecursively(position, depth - 1, -beta, -alpha); position.TakeBack(); @@ -78,40 +100,45 @@ private int SearchRecursively(TPosition position, int depth, int alpha, int beta } if (depth > 1) { - EvaluationMode evaluationMode = GetEvaluationMode(bestValue, originalAlpha, beta); - transposition = new Transposition(evaluationMode, bestValue, depth, bestStep); - cacheTables.AddTransposition(position, transposition); + var newTransposition = new Transposition(GetEvaluationMode(bestValue, originalAlpha, beta), bestValue, depth, bestStep); + cacheTables.AddTransposition(position, newTransposition); } return bestValue; } - private IEnumerable OrderedLegalStepsAt(TPosition position, int depth, Transposition transposition) + private List GetOrderedLegalSteps(TPosition position, int depth, bool hasTransposition, TStep transpositionStep) { - if (transposition != null) - { - yield return transposition.OptimalStep; - } - var prevKillerSteps = heuristicTables.GetKillerSteps(depth); - var otherSteps = new List(); + var result = orderedStepBuffers[depth]; + result.Clear(); + + var killers = heuristicTables.GetKillerSteps(depth); + int killerCount = 0; + bool transpositionStepIsLegal = false; + foreach (var move in rules.LegalStepsAt(position)) { - if (transposition != null && move.Equals(transposition.OptimalStep)) + if (hasTransposition && move.Equals(transpositionStep)) { + transpositionStepIsLegal = true; continue; } - if (prevKillerSteps.Contains(move)) + if (killers.Contains(move)) { - yield return move; + result.Insert(killerCount, move); + killerCount++; } else { - otherSteps.Add(move); + result.Add(move); } } - foreach (var move in otherSteps) + + if (transpositionStepIsLegal) { - yield return move; + result.Insert(0, transpositionStep); } + + return result; } private bool IsBetaCutOff(int alpha, int beta) @@ -124,11 +151,6 @@ private void HandleBetaCutOff(TStep step, int depth) heuristicTables.StoreBetaCutOff(step, depth); } - private bool IsLeaf(TPosition position, int depth) - { - return depth <= 0 || !rules.LegalStepsAt(position).Any(); - } - private EvaluationMode GetEvaluationMode(int value, int alpha, int beta) { if (value <= alpha) @@ -147,16 +169,12 @@ private EvaluationMode GetEvaluationMode(int value, int alpha, int beta) private int HeuristicValue(TPosition position, int depth) { - if (rules.LegalStepsAt(position).Any()) + if (!cacheTables.TryGetValue(position, out var value)) { - if (!cacheTables.TryGetValue(position, out var value)) - { - value = position.Value; - cacheTables.AddValue(position, value); - } - return IsOpponentsTurn(depth) ? -value : value; // TODO: check if the sign is required! + value = position.Value; + cacheTables.AddValue(position, value); } - return rules.IsGoal(position) ? sbyte.MaxValue + depth : 0; + return IsOpponentsTurn(depth) ? -value : value; } private bool IsOpponentsTurn(int depth) diff --git a/Alligator.Solver/Algorithms/AlphaBetaSolver.cs b/Alligator.Solver/Algorithms/AlphaBetaSolver.cs index 5a8fc60..bd36be5 100644 --- a/Alligator.Solver/Algorithms/AlphaBetaSolver.cs +++ b/Alligator.Solver/Algorithms/AlphaBetaSolver.cs @@ -52,7 +52,7 @@ public TStep OptimizeNextStep(IList history) IList candidates = rules.LegalStepsAt(position).ToList(); - var optimalValue = 0; + var optimalValue = guess; while (alpha + 1 < beta && candidates.Count > 1) { diff --git a/Alligator.Solver/Algorithms/CacheTables.cs b/Alligator.Solver/Algorithms/CacheTables.cs index 3abd9a5..35776a9 100644 --- a/Alligator.Solver/Algorithms/CacheTables.cs +++ b/Alligator.Solver/Algorithms/CacheTables.cs @@ -1,35 +1,66 @@ -namespace Alligator.Solver.Algorithms +namespace Alligator.Solver.Algorithms { internal class CacheTables : ICacheTables where TPosition : IPosition { - private readonly IDictionary evaluationTable; - private readonly IDictionary> transpositionTable; + private const int TranspositionTableSize = 1 << 21; + private const int TranspositionTableMask = TranspositionTableSize - 1; + + private const int EvaluationTableSize = 1 << 23; + private const int EvaluationTableMask = EvaluationTableSize - 1; + + private const ulong KeyMarker = 0xBF58476D1CE4E5B9UL; + + private readonly ulong[] transpositionKeys; + private readonly Transposition[] transpositionValues; + + private readonly ulong[] evaluationKeys; + private readonly int[] evaluationValues; public CacheTables() { - evaluationTable = new Dictionary(10000000); - transpositionTable = new Dictionary>(10000000); + transpositionKeys = new ulong[TranspositionTableSize]; + transpositionValues = new Transposition[TranspositionTableSize]; + evaluationKeys = new ulong[EvaluationTableSize]; + evaluationValues = new int[EvaluationTableSize]; } public void AddTransposition(TPosition position, Transposition transposition) { - transpositionTable[position.Identifier] = transposition; + int index = (int)(position.Identifier & TranspositionTableMask); + transpositionKeys[index] = position.Identifier ^ KeyMarker; + transpositionValues[index] = transposition; } public void AddValue(TPosition position, int value) { - evaluationTable[position.Identifier] = value; + int index = (int)(position.Identifier & EvaluationTableMask); + evaluationKeys[index] = position.Identifier ^ KeyMarker; + evaluationValues[index] = value; } public bool TryGetTransposition(TPosition position, out Transposition transposition) { - return transpositionTable.TryGetValue(position.Identifier, out transposition); + int index = (int)(position.Identifier & TranspositionTableMask); + if (transpositionKeys[index] == (position.Identifier ^ KeyMarker)) + { + transposition = transpositionValues[index]; + return true; + } + transposition = default; + return false; } public bool TryGetValue(TPosition position, out int value) { - return evaluationTable.TryGetValue(position.Identifier, out value); + int index = (int)(position.Identifier & EvaluationTableMask); + if (evaluationKeys[index] == (position.Identifier ^ KeyMarker)) + { + value = evaluationValues[index]; + return true; + } + value = 0; + return false; } } -} \ No newline at end of file +} diff --git a/Alligator.Solver/Algorithms/HeuristicTables.cs b/Alligator.Solver/Algorithms/HeuristicTables.cs index a8e5ed7..c3ad6d6 100644 --- a/Alligator.Solver/Algorithms/HeuristicTables.cs +++ b/Alligator.Solver/Algorithms/HeuristicTables.cs @@ -3,17 +3,20 @@ internal class HeuristicTables : IHeuristicTables { private readonly IDictionary> killerSteps; + private readonly Dictionary historyScores; private const int StoredKillerStepsLimitPerDepth = 2; public HeuristicTables() { killerSteps = new Dictionary>(); + historyScores = new Dictionary(); } public void StoreBetaCutOff(TMove move, int depth) { UpdateKillerSteps(move, depth); + RecordHistorySuccess(move, depth); } public IEnumerable GetKillerSteps(int depth) @@ -26,6 +29,24 @@ public IEnumerable GetKillerSteps(int depth) return Enumerable.Empty(); } + public int GetHistoryScore(TMove move) + { + return historyScores.TryGetValue(move, out int score) ? score : 0; + } + + private void RecordHistorySuccess(TMove move, int depth) + { + int bonus = depth * depth; + if (historyScores.TryGetValue(move, out int current)) + { + historyScores[move] = current + bonus; + } + else + { + historyScores[move] = bonus; + } + } + private void UpdateKillerSteps(TMove move, int depth) { IList killers; diff --git a/Alligator.Solver/Algorithms/IHeuristicTables.cs b/Alligator.Solver/Algorithms/IHeuristicTables.cs index 4f486a6..b9be602 100644 --- a/Alligator.Solver/Algorithms/IHeuristicTables.cs +++ b/Alligator.Solver/Algorithms/IHeuristicTables.cs @@ -4,5 +4,6 @@ internal interface IHeuristicTables { IEnumerable GetKillerSteps(int depth); void StoreBetaCutOff(TMove move, int depth); + int GetHistoryScore(TMove move); } } \ No newline at end of file diff --git a/Alligator.Solver/Algorithms/Transposition.cs b/Alligator.Solver/Algorithms/Transposition.cs index dce7451..9ef88a5 100644 --- a/Alligator.Solver/Algorithms/Transposition.cs +++ b/Alligator.Solver/Algorithms/Transposition.cs @@ -1,6 +1,6 @@ namespace Alligator.Solver.Algorithms { - internal class Transposition + internal struct Transposition { public EvaluationMode EvaluationMode; public int Value;