/// <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 }