public void TestTranspositionTableBucketConstruction() { var entry1 = new TranspositionTableEntry( 0x1234567890A, GameMove.FromStringNotation("a1b1"), EvaluationScore.Mate, EvaluationScore.Zero, ScoreBound.Exact, 3); var entry2 = new TranspositionTableEntry( 0xABCDEF9876, GameMove.FromStringNotation("g7g8q"), new EvaluationScore(1001), new EvaluationScore(997), ScoreBound.Lower, 11); Assert.That(entry1.Key, Is.Not.EqualTo(entry2.Key)); Assert.That(entry1.BestMove, Is.Not.EqualTo(entry2.BestMove)); Assert.That(entry1.Score, Is.Not.EqualTo(entry2.Score)); Assert.That(entry1.LocalScore, Is.Not.EqualTo(entry2.LocalScore)); Assert.That(entry1.Bound, Is.Not.EqualTo(entry2.Bound)); Assert.That(entry1.Depth, Is.Not.EqualTo(entry2.Depth)); var bucket = new TranspositionTableBucket { Entry1 = entry1, Entry2 = entry2 }; Assert.That(bucket.Entry1, Is.EqualTo(entry1)); Assert.That(bucket.Entry2, Is.EqualTo(entry2)); }
public void TranspositionTable_FillAndReplacePerfTest() { TimeSpan sum = TimeSpan.Zero; int iterations = 1000; for (int i = 0; i < iterations; i++) { TranspositionTable tt = new TranspositionTable(TranspositionTable.DefaultSizeInBytes / 1024); Assert.IsNotNull(tt); Stopwatch sw = Stopwatch.StartNew(); // Fill for (int j = 0; j < tt.Capacity; j++) { ulong key = (ulong)j; tt.Store(key, CreateMaxEntry(j)); } // Replace for (int j = tt.Capacity - 1; j >= 0; j--) { TranspositionTableEntry newEntry = CreateMaxEntry(j); newEntry.Depth++; ulong key = (ulong)j; tt.Store(key, newEntry); } sw.Stop(); sum += sw.Elapsed; } Trace.WriteLine(string.Format("Average Ticks: {0}", sum.Ticks / iterations)); }
public void SettingDepthReturnsDepthAgeWithDepth() { var test = new TranspositionTableEntry(); test.Depth = 5; test.Age = 3; Assert.Equal(0xC5, test.DepthAge); //1100 0101// } //age^ ^depth
} //age^ ^depth [Fact] void SettingAgeReturnsDepthAgeWithAge() { var test = new TranspositionTableEntry(); test.Age = 3; var b = test.DepthAge; Assert.True((b & 0xC0) == 0xC0, "Age bits incorrect"); }
private TranspositionTableEntry CreateMaxEntry(int id) { TranspositionTableEntry te = new TranspositionTableEntry { Depth = 0, Type = TranspositionTableEntryType.Exact, Value = 0, BestMove = new Move(PieceName.WhiteSoldierAnt1, new Position(0, 0, 0, 0)) }; return(te); }
public void TestTranspositionTable() { var transpositionTable = new TranspositionTable(FakeLogger.Instance, TranspositionTableHelper.SizeInMegaBytesRange.Lower); Assert.That(transpositionTable.Version, Is.Not.EqualTo(0)); const long Key = 0x12345678ABCDEF01L; const long OtherKey = 0x987654321L; const ScoreBound Bound = ScoreBound.Exact; const int Depth = CommonEngineConstants.MaxPlyDepthUpperLimit; var bestMove = GameMove.FromStringNotation("b2b1q"); var score = EvaluationScore.Mate; var localScore = new EvaluationScore(-789); var entry = new TranspositionTableEntry(Key, bestMove, score, localScore, Bound, Depth); transpositionTable.Save(ref entry); Assert.That(entry.Version, Is.EqualTo(transpositionTable.Version)); Assert.That(transpositionTable.ProbeCount, Is.EqualTo(0)); Assert.That(transpositionTable.HitCount, Is.EqualTo(0)); var foundEntry1 = transpositionTable.Probe(Key); Assert.That(transpositionTable.ProbeCount, Is.EqualTo(1)); Assert.That(transpositionTable.HitCount, Is.EqualTo(1)); Assert.That(foundEntry1.HasValue, Is.True); Assert.That(foundEntry1.Value.Key, Is.EqualTo(Key)); Assert.That(foundEntry1.Value.BestMove, Is.EqualTo(bestMove)); Assert.That(foundEntry1.Value.Score, Is.EqualTo(score)); Assert.That(foundEntry1.Value.LocalScore, Is.EqualTo(localScore)); Assert.That(foundEntry1.Value.Bound, Is.EqualTo(Bound)); Assert.That(foundEntry1.Value.Depth, Is.EqualTo(Depth)); Assert.That(foundEntry1.Value.Version, Is.EqualTo(transpositionTable.Version)); var foundEntry2 = transpositionTable.Probe(OtherKey); Assert.That(transpositionTable.ProbeCount, Is.EqualTo(2)); Assert.That(transpositionTable.HitCount, Is.EqualTo(1)); Assert.That(foundEntry2.HasValue, Is.False); }
public void TestTranspositionTableEntryConstruction() { const long Key = 0x12345678ABCDEF01L; const ScoreBound Bound = ScoreBound.Exact; const int Depth = CommonEngineConstants.MaxPlyDepthUpperLimit; var bestMove = GameMove.FromStringNotation("b2b1q"); var score = EvaluationScore.Mate; var localScore = new EvaluationScore(-789); var entry = new TranspositionTableEntry(Key, bestMove, score, localScore, Bound, Depth); Assert.That(entry.Key, Is.EqualTo(Key)); Assert.That(entry.BestMove, Is.EqualTo(bestMove)); Assert.That(entry.Score, Is.EqualTo(score)); Assert.That(entry.LocalScore, Is.EqualTo(localScore)); Assert.That(entry.Bound, Is.EqualTo(Bound)); Assert.That(entry.Depth, Is.EqualTo(Depth)); Assert.That(entry.Version, Is.EqualTo(0)); }
/// <summary> /// Using NegaMax with Alpha-Beta Pruning and Transposition Table to determine the best possible move. /// </summary> /// <param name="gameState"> Current GameState. </param> /// <param name="depth"> How many layers of branches to foresee. </param> /// <param name="alpha"> Maximum to prune out. </param> /// <param name="beta"> Minimum to prune out. </param> /// <param name="maximizingPlayer"> Current Player. True = caller. </param> /// <returns> The evaluated score. </returns> private float NegaMax(GameStateReader gameState, int depth, float alpha = float.NegativeInfinity, float beta = float.PositiveInfinity, bool maximizingPlayer = true /*, bool allowNullMove = true*/) { #region Transposition Table retrieval var origAlpha = alpha; long zobristHash = gameState.zobristHash; if (transpositionTable.ContainsKey(zobristHash)) { var ttEntry = transpositionTable[zobristHash]; var value = ttEntry.value; if (!maximizingPlayer) { value *= -1; } if (ttEntry.depth >= depth) { switch (ttEntry.flag) { case TranspositionTableEntry.Flag.EXACT: return(value); case TranspositionTableEntry.Flag.UPPERBOUND: alpha = Mathf.Max(alpha, value); break; case TranspositionTableEntry.Flag.LOWERBOUND: beta = Mathf.Min(beta, value); break; } if (alpha >= beta) { return(value); } } } #endregion #region End Node if (gameState.GameOver) { return(EvaluateBoardState(gameState) * (maximizingPlayer ? 1 : -1)); } if (depth == 0) { if (gameState.Quiet()) { return(EvaluateBoardState(gameState) * (maximizingPlayer ? 1 : -1)); } else { return(Quiesce(gameState, quiesceDepth, !maximizingPlayer, alpha, beta)); } } #endregion #region Search Tree var bestEval = float.NegativeInfinity; var bestMove = new Vector2Int[2]; var orderedMoves = gameState.PossibleMovesQuick(maximizingPlayer); orderedMoves = OrderMoves(orderedMoves, gameState, depth); foreach (var child in orderedMoves) { gameState.MakeMove(child); var eval = -NegaMax(gameState, depth - 1, -beta, -alpha, !maximizingPlayer); gameState.UndoMove(); if (eval > bestEval) { bestEval = eval; bestMove = child; alpha = Mathf.Max(alpha, bestEval); if (alpha >= beta) { if (gameState.IsntCapture(child[1])) { int ply = this.depth - depth; killerMoves[ply, 1] = killerMoves[ply, 0]; killerMoves[ply, 0] = child; historyMoves[child[0].x, child[0].y, child[1].x, child[1].y] += depth * depth; } break; } } } #endregion #region Add to Transposition Table. var newEntry = new TranspositionTableEntry { depth = depth, value = bestEval * (maximizingPlayer ? 1 : -1), pvMove = bestMove }; switch (bestEval) { case float value when value <= origAlpha: newEntry.flag = TranspositionTableEntry.Flag.UPPERBOUND; break; case float value when value >= beta: newEntry.flag = TranspositionTableEntry.Flag.LOWERBOUND; break; default: newEntry.flag = TranspositionTableEntry.Flag.EXACT; break; } transpositionTable[zobristHash] = newEntry; #endregion #region Return result return(bestEval); #endregion }
/// <summary> /// Using (Root) NegaMax with Alpha-Beta Pruning and Transposition Table to determine the best possible move. /// </summary> /// <param name="gameState"> Current GameState. </param> /// <param name="depth"> How many layers of branches to foresee. </param> /// <param name="alpha"> Maximum to prune out. </param> /// <param name="beta"> Minimum to prune out. </param> /// <param name="maximizingPlayer"> Current Player. True = caller. </param> /// <returns> The best move. </returns> private Vector2Int[] RootNegaMax(GameStateReader gameState, int depth, float alpha = float.NegativeInfinity, float beta = float.PositiveInfinity, bool maximizingPlayer = true /*, bool allowNullMove = true*/) { #region Inititalize Variables var origAlpha = alpha; long zobristHash = gameState.zobristHash; var bestEval = float.NegativeInfinity; var orderedMoves = gameState.PossibleMovesQuick(maximizingPlayer); orderedMoves = OrderMoves(orderedMoves, gameState, depth); var bestMove = orderedMoves[0]; #endregion #region Search Tree foreach (var child in orderedMoves) { gameState.MakeMove(child); var eval = -NegaMax(gameState, depth - 1, -beta, -alpha, !maximizingPlayer); gameState.UndoMove(); if (eval > bestEval) { bestEval = eval; bestMove = child; alpha = Mathf.Max(alpha, bestEval); if (alpha >= beta) { if (gameState.IsntCapture(child[1])) { int ply = this.depth - depth; killerMoves[ply, 1] = killerMoves[ply, 0]; killerMoves[ply, 0] = child; historyMoves[child[0].x, child[0].y, child[1].x, child[1].y] += depth * depth; } break; } } } #endregion #region Add to Transposition Table. var newEntry = new TranspositionTableEntry { depth = depth, value = bestEval, pvMove = bestMove }; switch (bestEval) { case float value when value <= origAlpha: newEntry.flag = TranspositionTableEntry.Flag.UPPERBOUND; break; case float value when value >= beta: newEntry.flag = TranspositionTableEntry.Flag.LOWERBOUND; break; default: newEntry.flag = TranspositionTableEntry.Flag.EXACT; break; } transpositionTable[zobristHash] = newEntry; #endregion #region Return result return(bestMove); #endregion }