Skip to content

Commit 24d774f

Browse files
committed
Added a 2-bucket TT that supports extraction of the PV which allows me to get rid of the PrincipalVariation class entirely.
Also temporarily added some stats that get logged during CompareBestMove in the MinimalChessBoard application.
1 parent a24585b commit 24d774f

File tree

5 files changed

+51
-112
lines changed

5 files changed

+51
-112
lines changed

MinimalChess/IterativeSearch.cs

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,18 @@ public class IterativeSearch
1212
public long NodesVisited { get; private set; }
1313
public int Depth { get; private set; }
1414
public int Score { get; private set; }
15-
public Move[] PrincipalVariation => _pv.GetLine(Depth);
15+
public Move[] PrincipalVariation => Transpositions.ExtractPV(new Board(_root), new List<Move>());
1616
public bool Aborted => NodesVisited >= _maxNodes || _killSwitch.Get(NodesVisited % QUERY_TC_FREQUENCY == 0);
1717
public bool GameOver => Evaluation.IsCheckmate(Score);
1818

1919
Board _root = null;
20-
PrincipalVariation _pv;
2120
KillerMoves _killers;
2221
KillSwitch _killSwitch;
2322
long _maxNodes;
2423

2524
public IterativeSearch(Board board, long maxNodes = long.MaxValue)
2625
{
2726
_root = new Board(board);
28-
_pv = new PrincipalVariation();
2927
_killers = new KillerMoves(4);
3028
_maxNodes = maxNodes;
3129
}
@@ -42,32 +40,15 @@ public void SearchDeeper(Func<bool> killSwitch = null)
4240
return;
4341

4442
Depth++;
45-
_pv.Grow(Depth);
4643
_killers.Grow(Depth);
4744
_killSwitch = new KillSwitch(killSwitch);
48-
PrepareTranspositions(Depth);
49-
var window = SearchWindow.Infinite;
50-
Score = EvalPosition(_root, Depth, window);
51-
}
52-
53-
private void PrepareTranspositions(int depth)
54-
{
55-
//make sure the PV is in the TT
56-
Board position = new Board(_root);
57-
foreach (Move move in _pv.GetLine(depth))
58-
{
59-
Transpositions.Store(position.ZobristHash, --depth, SearchWindow.Infinite, Score, move);
60-
position.Play(move);
61-
}
45+
Score = EvalPosition(_root, Depth, SearchWindow.Infinite);
6246
}
6347

6448
private int EvalPositionTT(Board position, int depth, SearchWindow window, bool isNullMove = false)
6549
{
6650
if (Transpositions.GetScore(position, depth, window, out int score))
67-
{
68-
_pv.Truncate(depth);
6951
return score;
70-
}
7152

7253
score = EvalPosition(position, depth, window, isNullMove);
7354
Transpositions.Store(position.ZobristHash, depth, window, score, default);
@@ -122,7 +103,6 @@ private int EvalPosition(Board position, int depth, SearchWindow window, bool is
122103
if (window.Inside(score, color))
123104
{
124105
Transpositions.Store(position.ZobristHash, depth, window, score, move);
125-
_pv[depth] = move;
126106
//...and maybe get a beta cutoff
127107
if (window.Cut(score, color))
128108
{
@@ -139,8 +119,6 @@ private int EvalPosition(Board position, int depth, SearchWindow window, bool is
139119
if (expandedNodes == 0)
140120
{
141121
//clear PV because the game is over
142-
_pv[depth] = default;
143-
144122
if (position.IsChecked(color))
145123
return Evaluation.Checkmate(color); //lost!
146124
else

MinimalChess/PrincipalVariation.cs

Lines changed: 0 additions & 75 deletions
This file was deleted.

MinimalChess/Transpositions.cs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using System.Text;
45

56
namespace MinimalChess
67
{
78
public static class Transpositions
89
{
9-
1010
public static long HashOverwrites = 0;
1111
public static long HashWrites = 0;
12+
public static int Count => _table.Length;
13+
public static int Empty => _table.Count(entry => entry.Hash == default);
14+
1215

1316
public enum ScoreType : byte
1417
{
@@ -33,7 +36,22 @@ public struct HashEntry
3336
const int ENTRY_SIZE = 16; //BYTES
3437
static HashEntry[] _table;
3538

36-
static int Index(in ulong hash) => (int)(hash % (ulong)_table.Length);
39+
static int Index(in ulong hash)
40+
{
41+
int i0 = (int)(hash % (ulong)_table.Length);
42+
return i0;
43+
44+
if (_table[i0].Hash == hash)
45+
return i0;
46+
47+
//try other entry in 'bucket' if not in first one
48+
int i1 = i0 ^ 1;
49+
if (_table[i1].Hash == hash)
50+
return i1;
51+
52+
//return the 'bucket' with less depth
53+
return (_table[i0].Depth < _table[i1].Depth) ? i0 : i1;
54+
}
3755

3856
static Transpositions()
3957
{
@@ -51,13 +69,27 @@ public static void Clear()
5169
Array.Clear(_table, 0, _table.Length);
5270
}
5371

72+
public static Move[] ExtractPV(Board position, List<Move> pv)
73+
{
74+
ulong zobristHash = position.ZobristHash;
75+
ref HashEntry entry = ref _table[Index(zobristHash)];
76+
if (entry.Hash != zobristHash || entry.BestMove == default)
77+
return pv.Count > 0 ? pv.ToArray() : new Move[1] { default };
78+
79+
pv.Add(entry.BestMove);
80+
position.Play(entry.BestMove);
81+
return ExtractPV(position, pv);
82+
}
83+
5484
public static void Store(ulong zobristHash, int depth, SearchWindow window, int score, Move bestMove)
5585
{
5686
int index = Index(zobristHash);
5787
ref HashEntry entry = ref _table[index];
58-
if (entry.Depth == PERSISTENT)
59-
return;
60-
88+
89+
HashWrites++;
90+
if (entry.Hash != default && entry.Hash != zobristHash)
91+
HashOverwrites++;
92+
6193
//don't overwrite a bestmove unless it's a new position OR the new bestMove is explored to a greater depth
6294
if (entry.Hash != zobristHash || (depth >= entry.Depth && bestMove != default))
6395
_table[index].BestMove = bestMove;

MinimalChessBoard/Program.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ static void Main()
4141
string command = tokens[0];
4242

4343
long t0 = Stopwatch.GetTimestamp();
44-
try
44+
//try
4545
{
4646
if (command == "reset")
4747
{
@@ -103,10 +103,10 @@ static void Main()
103103
if (dt > 0.01)
104104
Console.WriteLine($" Operation took {dt:0.####}s");
105105
}
106-
catch (Exception error)
107-
{
108-
Console.WriteLine("ERROR: " + error.Message);
109-
}
106+
//catch (Exception error)
107+
//{
108+
// Console.WriteLine("ERROR: " + error.Message);
109+
//}
110110
}
111111
}
112112

@@ -128,8 +128,9 @@ private static void Print(Board board, Move move = default)
128128
Console.WriteLine($"|{rank + 1}"); //ranks aren't zero-indexed
129129
}
130130
Console.WriteLine(" '----------------'");
131-
int score = Evaluation.Evaluate(board) + Evaluation.ComputeMobility(board);
132-
Console.WriteLine($" A B C D E F G H {score:+0.00;-0.00}");
131+
int pstScore = Evaluation.Evaluate(board);
132+
int mobScore = Evaluation.ComputeMobility(board);
133+
Console.WriteLine($" A B C D E F G H {(pstScore + mobScore):+0.00;-0.00} (PST:{pstScore:+0.00;-0.00}, Mobility:{mobScore})");
133134
}
134135

135136
private static void PrintMobility(Board board)
@@ -317,6 +318,9 @@ private static void CompareBestMove(int depth, string filePath, int maxCount)
317318
if (foundBestMove)
318319
foundBest++;
319320
Console.WriteLine($"{count,4}. {(foundBestMove ? "[X]" : "[ ]")} {pvString} = {search.Score:+0.00;-0.00}, {search.NodesVisited / 1000}K nodes, { 1000 * dt / freq}ms");
321+
Console.WriteLine($" TT Density: {(100 * (Transpositions.Count - Transpositions.Empty)) / Transpositions.Count}% TT Overwrites: {(100 * Transpositions.HashOverwrites) / Transpositions.HashWrites}%");
322+
Transpositions.HashOverwrites = 0;
323+
Transpositions.HashWrites = 0;
320324
}
321325
Console.WriteLine();
322326
Console.WriteLine($"Searched {count} positions to depth {depth}. {totalNodes/1000}K nodes visited. Took {totalTime/freq:0.###} seconds!");

MinimalChessEngine/Engine.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ private void StartSearch(int maxDepth, int maxNodes)
9292
//do the first iteration. it's cheap, no time check, no thread
9393
Uci.Log($"Search scheduled to take {_time.TimePerMoveWithMargin}ms!");
9494

95-
Transpositions.PERSISTENT++;//unfreeze old repetitions
95+
Transpositions.Clear();
9696
//add all history positions with a score of 0 (Draw through 3-fold repetition) and freeze them by setting a depth that is never going to be overwritten
9797
foreach (var position in _history)
9898
Transpositions.Store(position.ZobristHash, Transpositions.PERSISTENT, SearchWindow.Infinite, 0, default);

0 commit comments

Comments
 (0)