private ScoredMove minimax(int depth, int alpha, int beta, Board board, char player) { ScoredMove bestMove = resetBestScore(player); if (board.hasFinished() || depth == 0) { return(new ScoredMove(score(board, depth), bestMove.score, this)); } foreach (int position in board.availablePositions()) { Board newBoard = board.update(position, player); ScoredMove score = minimax(depth - 1, alpha, beta, newBoard, player == 'x' ? 'o' : 'x'); bestMove = updateScore(player, bestMove, position, score); if (player == mark) { alpha = Math.Max(alpha, bestMove.score); } else { beta = Math.Min(beta, bestMove.score); } if (alpha >= beta) { break; } } return(bestMove); }
private ScoredMove updateScore(char player, ScoredMove currentBestMove, int position, ScoredMove score) { if (score.isBetter(currentBestMove, player)) { currentBestMove = new ScoredMove(score.score, position, this); } return(currentBestMove); }
public static Vector2 GetBestMove(bool player1, GameState gs, StrategyHandler sh, ScoreFunction sf) { // First let the strategy handler determine all possible move options for this round List <Vector2> options = sh.ComputeOptions(gs, player1); // Convert the options to scored moves List <ScoredMove> scoredMoves = options.ConvertAll(o => new ScoredMove(o)); foreach (ScoredMove scoredMove in scoredMoves) { // The depth specified is the number of moves after the first next move float value = DoMiniMax(scoredMove.Move, scoredMove, 1, !player1, gs, sh, sf); // Debug.Log("==============================> score: " + value); // Debug.Log("-----> Move: " + scoredMove.Move); scoredMove.SetScore(value); } float bestScore; if (player1) { bestScore = float.MinValue; } else { bestScore = float.MaxValue; } ScoredMove bestMove = scoredMoves[0]; foreach (ScoredMove scoredMove in scoredMoves) { if (player1) { // Maximize the score if minimax is used for player 1 if (scoredMove.Score > bestScore) { bestScore = scoredMove.Score; bestMove = scoredMove; } } else { // Minimize the score if minimax is used for player 2 if (scoredMove.Score < bestScore) { bestScore = scoredMove.Score; bestMove = scoredMove; } } } Vector2 move = bestMove.Move; storedOptions = options; return(move); }
private ScoredMove getStrongestMove(Piece[,] squares, Piece turn, int depth = 0) { List <Point> legalMoves = getLegalMoves(squares); bool gameOver = _gameIsOver(squares); if (legalMoves.Count == 0 || gameOver) { int score = evaluateScore(squares) - depth; return(new ScoredMove() { Score = score }); } ScoredMove bestMove = new ScoredMove() { Score = (turn == _computerPiece ? -10000 : 10000) }; foreach (Point move in legalMoves) { squares[move.y, move.x] = turn; ScoredMove nextMove = getStrongestMove(squares, nextTurn(turn), depth + 1); if ( (turn == _computerPiece && nextMove.Score > bestMove.Score) || (turn == _humanPiece && nextMove.Score < bestMove.Score) ) { bestMove = new ScoredMove() { Score = nextMove.Score, Square = move }; } squares[move.y, move.x] = Piece.Empty; } return(bestMove); }
private static float DoMiniMax(Vector2 nextMove, ScoredMove scoredMove, int depth, bool maximizingPlayer, GameState gs, StrategyHandler sh, ScoreFunction sf) { // Create a copy of the gamestate and apply the move option GameState gsTemp = gs.Copy(); gsTemp.AddPoint(nextMove, !maximizingPlayer); if (depth <= 0) { return(sf.ComputeScore(nextMove, gsTemp)); } List <Vector2> options = sh.ComputeOptions(gsTemp, maximizingPlayer); if (maximizingPlayer) { float max = float.MinValue; foreach (Vector2 option in options) { // Execute minimax on the new game state float value = DoMiniMax(option, scoredMove, depth - 1, false, gsTemp, sh, sf); max = Mathf.Max(max, value); } return(max); } else { float min = float.MaxValue; foreach (Vector2 option in options) { // Execute minimax on the new game state float value = DoMiniMax(option, scoredMove, depth - 1, true, gsTemp, sh, sf); min = Mathf.Min(min, value); } return(min); } }
public bool isBetter(ScoredMove scoredMove, char player) { return((player == computerPlayer.mark && score > scoredMove.score) || (player != computerPlayer.mark && score < scoredMove.score)); }
public (Move move, Statistics statistics) Search(Position position, ITimeStrategy timeStrategy, Statistics.PrintInfoDelegate printInfoDelegate) { // setup _timeStrategy = timeStrategy; _enteredCount = 0; _statistics = new Statistics(); _statistics.Timer.Start(); _statistics.SideCalculating = position.SideToMove; _pvTable = new TriangularPVTable(); // TODO: should we be passing this in instead? _qSearch.StartSearch(_timeStrategy, _pvTable, _statistics); Move bestMove = Move.Null; var moveList = new List <Move>(); _moveGenerator.Generate(moveList, position); var cachedPositionObject = new Position(); if (!_moveGenerator.OnlyLegalMoves) { // remove all illegal moves for (int i = moveList.Count - 1; i >= 0; i--) { var move = moveList[i]; var testingPosition = Position.MakeMove(cachedPositionObject, move, position); if (testingPosition.MovedIntoCheck()) { moveList.QuickRemoveAt(i); } } } var scoredMoveList = new List <ScoredMove>(); foreach (var move in moveList) { scoredMoveList.Add(new ScoredMove(move, 0)); } // TODO: instead of looping here, why don't we only loop in InnerSearch and get the best value from the PV table? // That would simplify things a lot. // However, if we have aspiration windows and we get a beta cutoff, how do we retrieve the best move? Or is that even required? // The PV table would probably need to handle that case. for (int depth = 1;; depth++) { _statistics.NormalNonLeafNodes++; Score alpha = Score.MinValue; Score beta = Score.MaxValue; for (int i = 0; i < scoredMoveList.Count; i++) { var move = scoredMoveList[i].Move; var nextPosition = Position.MakeMove(cachedPositionObject, move, position); _pvTable.Add(move, 0); _statistics.CurrentDepth = depth; var nextEval = -InnerSearch(nextPosition, depth - 1, false, -beta, -alpha, 1); if (nextEval == alpha) { nextEval -= 1; } scoredMoveList[i] = new ScoredMove(move, nextEval); if (timeStrategy.ShouldStop(_statistics)) { _statistics.Timer.Stop(); return(bestMove, _statistics); } if (nextEval > alpha) { alpha = nextEval; bestMove = move; // this is safe, because we search the best move from last pass first in the next pass _pvTable.Commit(0); _statistics.BestLine = _pvTable.GetBestLine(); if (position.SideToMove == Color.White) { _statistics.CurrentScore = alpha; } else { _statistics.CurrentScore = -alpha; } printInfoDelegate(_statistics); } } // if we don't do this, we'll never return from a terminal position (with no moves) if (timeStrategy.ShouldStop(_statistics)) { _statistics.Timer.Stop(); return(bestMove, _statistics); } // sort the moves for next pass scoredMoveList.Sort((m1, m2) => (int)(m2.Score - m1.Score)); } }