Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Alligator.Benchmark/MeasurableHeuristicTables.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
84 changes: 51 additions & 33 deletions Alligator.Solver/Algorithms/AlphaBetaPruning.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
private readonly IHeuristicTables<TStep> heuristicTables;
private readonly ISearchManager searchManager;

private const int MaxSearchDepth = 16;
private readonly List<TStep>[] orderedStepBuffers;

public AlphaBetaPruning(
IRules<TPosition, TStep> rules,
ICacheTables<TPosition, TStep> cacheTables,
Expand All @@ -18,6 +21,12 @@
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<TStep>[MaxSearchDepth];
for (int i = 0; i < MaxSearchDepth; i++)
{
orderedStepBuffers[i] = new List<TStep>();
}
}

public int Search(TPosition position, int alpha, int beta)
Expand All @@ -27,15 +36,20 @@

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<TStep> transposition)
&& depth <= transposition.Depth)
bool hasTransposition = cacheTables.TryGetTransposition(position, out Transposition<TStep> transposition);

if (hasTransposition && depth <= transposition.Depth)
{
switch (transposition.EvaluationMode)
{
Expand All @@ -55,11 +69,19 @@
}
}

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;

Check warning on line 80 in Alligator.Solver/Algorithms/AlphaBetaPruning.cs

View workflow job for this annotation

GitHub Actions / build

Converting null literal or possible null value to non-nullable type.

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();
Expand All @@ -78,40 +100,45 @@
}
if (depth > 1)
{
EvaluationMode evaluationMode = GetEvaluationMode(bestValue, originalAlpha, beta);
transposition = new Transposition<TStep>(evaluationMode, bestValue, depth, bestStep);
cacheTables.AddTransposition(position, transposition);
var newTransposition = new Transposition<TStep>(GetEvaluationMode(bestValue, originalAlpha, beta), bestValue, depth, bestStep);

Check warning on line 103 in Alligator.Solver/Algorithms/AlphaBetaPruning.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'optimalStep' in 'Transposition<TStep>.Transposition(EvaluationMode evaluationMode, int value, int depth, TStep optimalStep)'.
cacheTables.AddTransposition(position, newTransposition);
}
return bestValue;
}

private IEnumerable<TStep> OrderedLegalStepsAt(TPosition position, int depth, Transposition<TStep> transposition)
private List<TStep> 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<TStep>();
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))

Check warning on line 120 in Alligator.Solver/Algorithms/AlphaBetaPruning.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
{
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)
Expand All @@ -124,11 +151,6 @@
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)
Expand All @@ -147,16 +169,12 @@

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)
Expand Down
2 changes: 1 addition & 1 deletion Alligator.Solver/Algorithms/AlphaBetaSolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
logger($"Iteration has been completed in {sw.ElapsedMilliseconds} ms (value: {Value}, step: {bestStep}, depth: {i})");
}

return bestStep;

Check warning on line 45 in Alligator.Solver/Algorithms/AlphaBetaSolver.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference return.
}

private (ICollection<TStep> OptimalSteps, int Value) BestNodeSearch(TPosition position, int guess)
Expand All @@ -52,7 +52,7 @@

IList<TStep> candidates = rules.LegalStepsAt(position).ToList();

var optimalValue = 0;
var optimalValue = guess;

while (alpha + 1 < beta && candidates.Count > 1)
{
Expand Down
51 changes: 41 additions & 10 deletions Alligator.Solver/Algorithms/CacheTables.cs
Original file line number Diff line number Diff line change
@@ -1,35 +1,66 @@
namespace Alligator.Solver.Algorithms
namespace Alligator.Solver.Algorithms
{
internal class CacheTables<TPosition, TStep> : ICacheTables<TPosition, TStep>
where TPosition : IPosition<TStep>
{
private readonly IDictionary<ulong, int> evaluationTable;
private readonly IDictionary<ulong, Transposition<TStep>> 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<TStep>[] transpositionValues;

private readonly ulong[] evaluationKeys;
private readonly int[] evaluationValues;

public CacheTables()
{
evaluationTable = new Dictionary<ulong, int>(10000000);
transpositionTable = new Dictionary<ulong, Transposition<TStep>>(10000000);
transpositionKeys = new ulong[TranspositionTableSize];
transpositionValues = new Transposition<TStep>[TranspositionTableSize];
evaluationKeys = new ulong[EvaluationTableSize];
evaluationValues = new int[EvaluationTableSize];
}

public void AddTransposition(TPosition position, Transposition<TStep> 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<TStep> 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;
}
}
}
}
21 changes: 21 additions & 0 deletions Alligator.Solver/Algorithms/HeuristicTables.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,56 @@
internal class HeuristicTables<TMove> : IHeuristicTables<TMove>
{
private readonly IDictionary<int, IList<TMove>> killerSteps;
private readonly Dictionary<TMove, int> historyScores;

Check warning on line 6 in Alligator.Solver/Algorithms/HeuristicTables.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TMove' cannot be used as type parameter 'TKey' in the generic type or method 'Dictionary<TKey, TValue>'. Nullability of type argument 'TMove' doesn't match 'notnull' constraint.

private const int StoredKillerStepsLimitPerDepth = 2;

public HeuristicTables()
{
killerSteps = new Dictionary<int, IList<TMove>>();
historyScores = new Dictionary<TMove, int>();

Check warning on line 13 in Alligator.Solver/Algorithms/HeuristicTables.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TMove' cannot be used as type parameter 'TKey' in the generic type or method 'Dictionary<TKey, TValue>'. Nullability of type argument 'TMove' doesn't match 'notnull' constraint.
}

public void StoreBetaCutOff(TMove move, int depth)
{
UpdateKillerSteps(move, depth);
RecordHistorySuccess(move, depth);
}

public IEnumerable<TMove> GetKillerSteps(int depth)
{
IList<TMove> killers;
if (killerSteps.TryGetValue(depth, out killers))

Check warning on line 25 in Alligator.Solver/Algorithms/HeuristicTables.cs

View workflow job for this annotation

GitHub Actions / build

Converting null literal or possible null value to non-nullable type.
{
return killers;
}
return Enumerable.Empty<TMove>();
}

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<TMove> killers;
if (killerSteps.TryGetValue(depth, out killers))

Check warning on line 53 in Alligator.Solver/Algorithms/HeuristicTables.cs

View workflow job for this annotation

GitHub Actions / build

Converting null literal or possible null value to non-nullable type.
{
if (killers[0].Equals(move))

Check warning on line 55 in Alligator.Solver/Algorithms/HeuristicTables.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
{
return;
}
Expand Down
1 change: 1 addition & 0 deletions Alligator.Solver/Algorithms/IHeuristicTables.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ internal interface IHeuristicTables<TMove>
{
IEnumerable<TMove> GetKillerSteps(int depth);
void StoreBetaCutOff(TMove move, int depth);
int GetHistoryScore(TMove move);
}
}
2 changes: 1 addition & 1 deletion Alligator.Solver/Algorithms/Transposition.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Alligator.Solver.Algorithms
{
internal class Transposition<TStep>
internal struct Transposition<TStep>
{
public EvaluationMode EvaluationMode;
public int Value;
Expand Down
Loading