/// <summary> /// Recursively search for the best move for the given board /// </summary> /// <param name="depth">How many recursive iterations you want to take(each iteration is 1 turn of 1 of the players, so steps ahead is depth/2</param> /// <param name="tiles">The Tile to iterate over</param> /// <param name="initialSequence">Initial sequence of moves, which is used as state to know what sequence </param> /// <param name="priority"></param> /// <param name="max"></param> /// <param name="currentPlayer"></param> /// <param name="enemyPlayer"></param> /// <returns></returns> public MinimaxResult GetBestTurn(BoardTileCollection tiles, IPlayer currentPlayer) { #if DEBUG this._amountOfCheckups = 0; #endif IPlayer enemyPlayer = this.Game.GetEnemyPlayer(currentPlayer); var results = new List <MinimaxResult>(); //get all possible initial moves for the player var checkerMovePairs = currentPlayer.GetAllPossibleMoves(Game, enemyPlayer, tiles); Parallel.ForEach(checkerMovePairs, (pair, loopState) => { List <MoveSequence> possibleSequences = null; Turn turn = pair.Value; foreach (var move in turn.Moves) //for each initial move of the checker { if (move is AttackMove attMove) { //all possible followup move sequences possibleSequences = GetPossibleMoveSequences(turn, attMove); } else if (move is WalkMove) { possibleSequences = new List <MoveSequence> { new MoveSequence(turn, new List <Move> { move }) }; } //in case theres only 1 possible move, we dont need to calculate anything, so cut of. if (checkerMovePairs.Count == 1 && possibleSequences.Count == 1) { var seq = possibleSequences.First(); results.Add(new MinimaxResult(seq.Moves, seq.Turn, 0)); loopState.Break(); } foreach (var sequence in possibleSequences) { BoardTileCollection clonedTiles = (BoardTileCollection)tiles.Clone(); float prio = sequence.Moves[0].Priority; foreach (var mov in sequence.Moves) { Board.TakeMove(clonedTiles, mov); } //now we need to call search the move tree for the value of the sequence. var sequenceValue = GetValueOfMoveSequence(this.MaxDepth - 1, clonedTiles, prio, enemyPlayer, currentPlayer, false); var result = new MinimaxResult(sequence.Moves, sequence.Turn, sequenceValue); lock (_resultAddLock) results.Add(result); } } }); #if DEBUG Console.WriteLine($"amount of checkups: {this._amountOfCheckups}"); #endif return(ChooseBestResult(results)); }
public void WinEndsTheSearchForPlayerY() { HexBoard board = new HexBoard(5); PlayToWinInOneMove(board, false); Minimax minimax = MakeMinimaxForBoard(board); const int SearchDepth = 4; MinimaxResult bestMove = minimax.DoMinimax(SearchDepth, false); Location win = new Location(0, 3); Assert.AreEqual(win, bestMove.Move, "wrong win location"); // do: test that locations after 0, 4 aren't even looked at. A win ends the search IList <Location> locationsExamined = minimax.DebugDataItems .Where(d => d.Lookahead == SearchDepth) .Select(d => d.Location).ToList(); Assert.IsTrue(locationsExamined.Count > 0, "No locations examined"); Assert.IsTrue(locationsExamined.Contains(win), "Locations examined does not contain win"); Location unexpected = new Location(4, 4); Assert.IsFalse(locationsExamined.Contains(unexpected), "Should not have examined location " + unexpected + " after win"); }
private void checkMoves(MouseState mouseState) { MouseState currentMouseState = mouseState; Point mousePosition = new Point(currentMouseState.X, currentMouseState.Y); if (board.CurrentPlayer() == human) { if (lastMouseState.LeftButton == ButtonState.Pressed && currentMouseState.LeftButton == ButtonState.Released) { foreach (KeyValuePair <Point, Rectangle> boundingBox in boundingBoxes) { if (boundingBox.Value.Contains(mousePosition)) { board.MakeMove(new Move(boundingBox.Key), human); } } } } else { MinimaxResult result = Minimax.Do(board, cpu, GameSettings.Difficulty, 0); Move move = result.GetMove(); board.MakeMove(move, cpu); } currentTurn = board.CurrentPlayer(); }
/// <summary> /// public wrapper for recursion /// try each possible move, find the one with the best score /// </summary> /// <param name="lookahead">how far to look ahead</param> /// <param name="playerX">is this for player x</param> /// <returns>The data on the best move location and score</returns> public MinimaxResult DoMinimax(int lookahead, bool playerX) { // set up inital state DateTime startTime = DateTime.Now; if (lookahead < 1) { throw new Exception("Invalid lookahead of " + lookahead); } this.debugDataItems.Clear(); Occupied player = playerX.ToPlayer(); int alpha = MoveScoreConverter.ConvertWin(player.Opponent(), 0); int beta = MoveScoreConverter.ConvertWin(player, 0); MinimaxResult bestMove = this.ScoreBoard(lookahead, this.ActualBoard, playerX, alpha, beta); if (bestMove.Move != Location.Null) { GoodMoves.AddGoodMove(0, bestMove.Move); } DateTime endTime = DateTime.Now; this.MoveTime = endTime - startTime; return(bestMove); }
private void DoMove() { Minimax hexPlayer = new Minimax(this.hexGame.Board, this.hexGame.GoodMoves, this.MakeCandiateMovesFinder()); MinimaxResult minimaxResult = hexPlayer.DoMinimax(4, this.MoveIsPlayerX()); this.PlayLocation = this.GetBestMove(minimaxResult); this.MoveTime = hexPlayer.MoveTime; this.CallCompletedAction(); }
private static void DoTestTestNoLingering2Win(Minimax minimax, int depth) { MinimaxResult bestMove = minimax.DoMinimax(depth, true); // play here to win Location expectedMove = new Location(2, 2); Assert.AreEqual(expectedMove, bestMove.Move, "Wrong best move at depth " + depth); AssertWinner(bestMove.Score, Occupied.PlayerX); }
private MinimaxResult MinimaxStep(BoardData p_board, Players p_player, int p_currentDepth) { GameResults t_gameResult = p_board.GetGameResult(); if (t_gameResult != GameResults.Unfinished) { int t_score = t_gameResult == GameResults.Win ? MAX_SCORE : -MAX_SCORE; return(new MinimaxResult(t_score, null)); } else if (p_currentDepth > MAX_DEPTH) { return(m_boardEvaluator.EvaluateBoard(p_board, p_player)); } Move t_bestMove = null; int t_bestScore = 0; if (p_board.CurrentPlayer == Players.Human) { t_bestScore = -INFINITE; } else { t_bestScore = INFINITE; } List <Move> t_moves = new List <Move>(); p_board.GetMoves(t_moves); foreach (Move t_move in t_moves) { BoardData t_newBoard = p_board.GetNewBoard(t_move); MinimaxResult t_result = MinimaxStep(t_newBoard, p_player, p_currentDepth + 1); if (p_board.CurrentPlayer == Players.Human) { if (t_bestScore < t_result.Score) { t_bestScore = t_result.Score; t_bestMove = t_move; } } else { if (t_result.Score < t_bestScore) { t_bestScore = t_result.Score; t_bestMove = t_move; } } } return(new MinimaxResult(t_bestScore, t_bestMove)); }
public void GoodMoveAtLevel3() { HexBoard board = new HexBoard(6); PlayToWinInThreeMoves(board); Minimax minimax = MakeMinimaxForBoard(board); MinimaxResult bestMove = minimax.DoMinimax(3, true); Location win = new Location(1, 3); Assert.AreEqual(win, bestMove.Move, "Wrong play location"); }
/// <summary> /// Recursively search for the best move for the given board /// </summary> /// <param name="depth">How many recursive iterations you want to take(each iteration is 1 turn of 1 of the players, so steps ahead is depth/2</param> /// <param name="tiles">The Tile to iterate over</param> /// <param name="initialSequence">Initial sequence of moves, which is used as state to know what sequence </param> /// <param name="priority"></param> /// <param name="max"></param> /// <param name="currentPlayer"></param> /// <param name="enemyPlayer"></param> /// <returns></returns> public MinimaxResult GetBestTurn(BoardTileCollection tiles, IPlayer currentPlayer) { IPlayer enemyPlayer = this.Game.GetEnemyPlayer(currentPlayer); var results = new List <MinimaxResult>(); //get all possible initial moves for the player var checkerMovePairs = currentPlayer.GetAllPossibleMoves(Game, enemyPlayer, tiles); //loop variables Parallel.ForEach(checkerMovePairs, (pair) => { //foreach(var pair in checkerMovePairs) { //for each checker List <MoveSequence> possibleSequences = null; Turn turn = pair.Value; foreach (var move in turn.Moves) //for each initial move of the checker { if (move is AttackMove attMove) { //all possible followup move sequences possibleSequences = GetPossibleMoveSequences(turn, attMove); } else if (move is WalkMove) { possibleSequences = new List <MoveSequence> { new MoveSequence(turn, new List <Move> { move }) }; } //Parallel.ForEach(possibleSequences, (sequence) => { // Not useful, its max 2 moves foreach (var sequence in possibleSequences) { BoardTileCollection clonedTiles = (BoardTileCollection)tiles.Clone(); float prio = sequence.Moves[0].Priority; foreach (var mov in sequence.Moves) { Board.TakeMove(clonedTiles, mov); } //now we need to call the same method from the enemy perception again var sequenceValue = GetBestMoveSequence(this.MaxDepth, clonedTiles, prio, enemyPlayer, currentPlayer); var result = new MinimaxResult(sequence.Moves, sequence.Turn, sequenceValue); results.Add(result); } //}); } }); //} return(ChooseBestResult(results)); }
public void TestCalculateMove2MinimaxPlayerY() { HexBoard board = new HexBoard(3); PlayTwoMoves(board); Minimax minimax = MakeMinimaxForBoard(board); MinimaxResult secondPlayerResult = minimax.DoMinimax(2, false); Location expectedPlay = new Location(0, 2); Assert.AreEqual(expectedPlay, secondPlayerResult.Move, "Wrong play location"); Assert.IsFalse(MoveScoreConverter.IsWin(secondPlayerResult.Score)); }
public void TestCalculateMove3PlayerX() { HexBoard board = new HexBoard(5); PlayFourMoves(board); Minimax minimax = MakeMinimaxForBoard(board); MinimaxResult firstPlayerResult = minimax.DoMinimax(4, true); Location firstPlayerExpectedMove = new Location(2, 1); Assert.AreEqual(firstPlayerExpectedMove, firstPlayerResult.Move, "Wrong first player location"); }
public void TestCalculateMoveLookahead1Player1() { HexBoard board = new HexBoard(3); PlayFourMoves(board); Minimax minimax = MakeMinimaxForBoard(board); MinimaxResult firstPlayerResult = minimax.DoMinimax(1, true); Location expectedMove = new Location(1, 1); Assert.AreEqual(expectedMove, firstPlayerResult.Move, "playerOneBestMove"); AssertWinner(firstPlayerResult.Score, Occupied.PlayerX); }
private static void TestBestMove(HexGame game, int level, Location expectedBestMove) { Minimax hexPlayer = new Minimax(game.Board, game.GoodMoves, new CandidateMovesAll()); MinimaxResult bestMove = hexPlayer.DoMinimax(level, true); // test the location of the move Assert.AreEqual(expectedBestMove, bestMove.Move, "Wrong move at level " + level); // test the expected score if (level >= 3) { Assert.AreEqual(Occupied.PlayerX, MoveScoreConverter.Winner(bestMove.Score)); } }
public void TestCalculateMoveLookahead1Player2() { HexBoard board = new HexBoard(3); PlayFourMoves(board); Minimax minimax = MakeMinimaxForBoard(board); MinimaxResult secondPlayerResult = minimax.DoMinimax(1, false); Location expectedMove = new Location(1, 1); Assert.AreEqual(expectedMove, secondPlayerResult.Move, "playerTwoBestMove"); AssertWinner(secondPlayerResult.Score, Occupied.PlayerY); }
public void TestCalculateMove3MinimaxPlayerX() { HexBoard board = new HexBoard(3); PlayTwoMoves(board); Minimax minimax = MakeMinimaxForBoard(board); MinimaxResult firstPlayerResult = minimax.DoMinimax(3, true); Location bestMoveLocation = firstPlayerResult.Move; int moveScore = firstPlayerResult.Score; Location exectedWin = new Location(0, 2); Assert.AreEqual(exectedWin, bestMoveLocation, "Wrong win location"); AssertWinner(moveScore, Occupied.PlayerX); }
public void TestCalculateMove3MinimaxPlayerY() { HexBoard board = new HexBoard(3); PlayTwoMoves(board); Minimax minimax = MakeMinimaxForBoard(board); MinimaxResult secondPlayerResult = minimax.DoMinimax(3, false); Location secondPlayerMoveLocation = secondPlayerResult.Move; int moveScore = secondPlayerResult.Score; Location exectedWin = new Location(0, 2); Assert.AreEqual(exectedWin, secondPlayerMoveLocation, "Wrong second player location"); AssertWinner(moveScore, Occupied.PlayerY); }
public void RightMoveAtLevel5PlayerY() { HexBoard board = new HexBoard(6); PlayToWinInThreeMoves(board); Minimax minimax = MakeMinimaxForBoard(board); MinimaxResult bestMove = minimax.DoMinimax(5, false); List <Location> playerYWinningLocations = new List <Location> { new Location(1, 3), new Location(2, 2) }; Assert.IsTrue(playerYWinningLocations.Contains(bestMove.Move), "Wrong play location"); }
public void TestMinimax3() { HexBoard board = new HexBoard(5); Minimax minimax = MakeMinimaxForBoard(board); /* * on a 5 * 5 board, red(playerx) has 3, 0 and 1, 4 * needs to play 2,2 to win - should know this at look ahead 5 */ board.PlayMove(3, 0, true); board.PlayMove(1, 4, true); MinimaxResult bestMove = minimax.DoMinimax(5, true); Location expectedMove = new Location(2, 2); Assert.IsTrue(MoveScoreConverter.IsWin(bestMove.Score), "No win " + bestMove.Score); Assert.AreEqual(expectedMove, bestMove.Move, "Wrong expected move"); }
public void TestMinimax4() { HexBoard board = new HexBoard(5); Minimax minimax = MakeMinimaxForBoard(board); /* * on a 5 * 5 board, red(playerx) has 3, 0 and 1, 4 and 2,2 * PlayerX has won, even if PlayerY goes next * should know this at look ahead 5 */ board.PlayMove(3, 0, true); board.PlayMove(1, 4, true); board.PlayMove(2, 2, true); MinimaxResult bestMove = minimax.DoMinimax(4, false); AssertWinner(bestMove.Score, Occupied.PlayerX); }
private Location GetBestMove(MinimaxResult playResult) { Location result = playResult.Move; int moveScore = playResult.Score; this.IsGameWon = MoveScoreConverter.IsWin(moveScore) && MoveScoreConverter.WinDepth(moveScore) == 1; Occupied opponent = (!this.hexGame.PlayerX).ToPlayer(); bool losingMove = MoveScoreConverter.Winner(playResult.Score) == opponent; if (losingMove) { Location losingLocation = this.MakeLosingMove(); if (losingLocation != Location.Null) { result = losingLocation; } } return(result); }
public void TestCalculateMove3PlayerY() { HexBoard board = new HexBoard(5); PlayFourMoves(board); Minimax minimax = MakeMinimaxForBoard(board); // test score at this point PathLengthLoop pathLength = new PathLengthLoop(board); int playerScore = pathLength.PlayerScore(true); Assert.AreEqual(3, playerScore); playerScore = pathLength.PlayerScore(false); Assert.AreEqual(3, playerScore); MinimaxResult secondPlayerResult = minimax.DoMinimax(4, false); Location secondPlayerExpectedMove = new Location(1, 2); Assert.AreEqual(secondPlayerExpectedMove, secondPlayerResult.Move, "Wrong second player location"); }
/// <summary> /// Recursively search for the best move for the given board /// </summary> /// <param name="depth">How many recursive iterations you want to take(each iteration is 1 turn of 1 of the players, so steps ahead is depth/2</param> /// <param name="tiles">The Tile to iterate over</param> /// <param name="initialSequence">Initial sequence of moves, which is used as state to know what sequence </param> /// <param name="priority"></param> /// <param name="max"></param> /// <param name="currentPlayer"></param> /// <param name="enemyPlayer"></param> /// <returns></returns> public MinimaxResult GetBestTurn(BoardTileCollection tiles, IPlayer currentPlayer) { IPlayer enemyPlayer = this.Game.GetEnemyPlayer(currentPlayer); var results = new List <MinimaxResult>(); //get all possible initial moves for the player var checkerMovePairs = currentPlayer.GetAllPossibleMoves(Game, enemyPlayer, tiles); //loop variables List <MoveSequence> possibleSequences = null; foreach (var pair in checkerMovePairs) //for each checker { Turn turn = pair.Value; foreach (var move in turn.Moves) //for each initial move of the checker { if (move is AttackMove attMove) { //all possible followup move sequences possibleSequences = GetPossibleMoveSequences(turn, attMove); } else if (move is WalkMove) { possibleSequences = new List <MoveSequence> { new MoveSequence(turn, new List <Move> { move }) }; } foreach (var sequence in possibleSequences) { BoardTileCollection clonedTiles = (BoardTileCollection)tiles.Clone(); float prio = sequence.Moves[0].Priority; foreach (var mov in sequence.Moves) { Board.TakeMove(clonedTiles, mov); } //now we need to call the same method from the enemy perception again var sequenceValue = GetBestMoveSequence(this.MaxDepth, clonedTiles, prio, enemyPlayer, currentPlayer); var result = new MinimaxResult(sequence.Moves, sequence.Turn, sequenceValue); results.Add(result); } } } #if DEBUG var str = ""; foreach (var result in results) { str += $"move: {result.Moves.First().StartLocation} => {result.Moves.Last().EndLocation}, value:{result.TurnValue}\n"; } Console.WriteLine($"Turn: {this.Game.Turn}"); Console.WriteLine(str); #endif if (results.Count() == 0) { return(null); } IEnumerable <MinimaxResult> bestResults = ((this.MaxDepth & 0b1) == 0b1) ? results.Where(res => res.TurnValue == results.Max(r => r.TurnValue)) : results.Where(res => res.TurnValue == results.Min(r => r.TurnValue)); var random = new Random(); var ind = random.Next(0, bestResults.Count()); var chosenResult = bestResults.ElementAtOrDefault(ind); return(chosenResult); }
/// <summary> /// private recursive worker - does the minimax algorithm /// </summary> /// <param name="lookahead">the current ply, counts down to zero</param> /// <param name="stateBoard">the current board</param> /// <param name="isPlayerX">player X or player Y</param> /// <param name="alpha">alpha value used in alpha-beta pruning</param> /// <param name="beta">beta value used in alpha-beta pruning</param> /// <returns>the score of the board and best move location</returns> private MinimaxResult ScoreBoard( int lookahead, HexBoard stateBoard, bool isPlayerX, int alpha, int beta) { this.CountBoards++; MinimaxResult bestResult = null; Location cutoffMove = Location.Null; var possibleMoves = this.candidateMovesFinder.CandidateMoves(stateBoard, lookahead); foreach (Location move in possibleMoves) { // end on null loc if (move.IsNull()) { break; } if (this.GenerateDebugData) { this.AddDebugDataItem(lookahead, move, isPlayerX, alpha, beta); } // make a speculative board, like the current, but with this cell played HexBoard testBoard = this.boardCache.GetBoard(); testBoard.CopyStateFrom(stateBoard); testBoard.PlayMove(move, isPlayerX); MinimaxResult moveScore; PathLengthBase staticAnalysis = this.pathLengthFactory.CreatePathLength(testBoard); int situationScore = staticAnalysis.SituationScore(); if (lookahead <= 1) { // we have reached the limits of lookahead - return the situation score moveScore = new MinimaxResult(situationScore); } else if (MoveScoreConverter.IsWin(situationScore)) { // stop - someone has won moveScore = new MinimaxResult(situationScore); } else { // recurse moveScore = this.ScoreBoard(lookahead - 1, testBoard, !isPlayerX, beta, alpha); moveScore.MoveWins(); } moveScore.Move = move; this.boardCache.Release(testBoard); // higher scores are good for player x, lower scores for player y if (bestResult == null || MoveScoreConverter.IsBetterFor(moveScore.Score, bestResult.Score, isPlayerX)) { bestResult = new MinimaxResult(move, moveScore); } // do the alpha-beta pruning alpha = CheckAlpha(alpha, moveScore.Score, isPlayerX); if (IsAlphaBetaCutoff(isPlayerX, alpha, beta)) { cutoffMove = move; bestResult.Score = alpha; break; } // end a-b pruning } if (bestResult != null) { GoodMoves.AddGoodMove(lookahead, bestResult.Move); } if (cutoffMove != Location.Null) { GoodMoves.AddGoodMove(lookahead, cutoffMove); } return(bestResult); }