public override FutureMove Think(Board board, CancellationToken ct) { Winner winner; PColor colorOfOurAI = board.Turn; possibleMoves.Clear(); nonLosingMoves.Clear(); for (int col = 0; col < Cols; col++) { if (board.IsColumnFull(col)) { continue; } for (int shp = 0; shp < 2; shp++) { PShape shape = (PShape)shp; if (board.PieceCount(colorOfOurAI, shape) == 0) { continue; } possibleMoves.Add(new FutureMove(col, shape)); board.DoMove(shape, col); winner = board.CheckWinner(); // immediately board.UndoMove(); if (winner.ToPColor() == colorOfOurAI) { return(new FutureMove(col, shape)); } else if (winner.ToPColor() != colorOfOurAI.Other()) { nonLosingMoves.Add(new FutureMove(col, shape)); } } } if (nonLosingMoves.Count > 0) { return(nonLosingMoves[random.Next(nonLosingMoves.Count)]); } return(possibleMoves[random.Next(possibleMoves.Count)]); }
// Raise and invalid play event and return the match winner (which is // the opponent of the thinker that made an invalid play) private Winner OnInvalidPlay( PColor color, IThinker thinker, string reason) { // Set the other thinker as the winner of the match Winner winner = color.Other().ToWinner(); // Set solution to null solution = null; // Notify listeners that thinker made an invalid play InvalidPlay?.Invoke(color, thinker.ToString(), reason); // Return the winner return(winner); }
// Negamax with alpha beta pruning. private (FutureMove move, float score) Negamax( Board board, CancellationToken ct, PColor turn, int depth, float alpha, float beta) { (FutureMove move, float score)currentMove; Winner winner; // In case of cancellation request, skips rest of algorithm. if (ct.IsCancellationRequested) { // Makes no move. currentMove = (FutureMove.NoMove, float.NaN); } // If game has ended, returns final score. else if ((winner = board.CheckWinner()) != Winner.None) { // Oizys wins, returns maximum score. if (winner.ToPColor() == turn) { currentMove = (FutureMove.NoMove, float.PositiveInfinity); } // Opponent wins, returns minimum score. else if (winner.ToPColor() == turn.Other()) { currentMove = (FutureMove.NoMove, float.NegativeInfinity); } // A draw board, return 0. else { currentMove = (FutureMove.NoMove, 0f); } } // If maximum depth has been reached, get heuristic value. else if (depth == maxDepth) { currentMove = (FutureMove.NoMove, Heuristic(board, turn)); } // Board isn't final and maximum depth hasn't been reached. else { // Set up currentMove for future maximizing. currentMove = (FutureMove.NoMove, float.NegativeInfinity); // Iterate each column. for (int c = 0; c < Cols; c++) { // If column is full, skip to next column. if (board.IsColumnFull(c)) { continue; } // Try both shapes. for (int s = 0; s < 2; s++) { // Store current shape. PShape shape = (PShape)s; // If player doesn't have this piece, skip. if (board.PieceCount(turn, shape) == 0) { continue; } // Test move. board.DoMove(shape, c); // Call Negamax and register board's score. float score = -Negamax( board, ct, turn.Other(), depth + 1, -beta, -alpha).score; // Undo move. board.UndoMove(); // If this move has the best score yet, keep it. if (score > currentMove.score) { currentMove = (new FutureMove(c, shape), score); } // Update alpha value. if (score > alpha) { alpha = score; } // Beta pruning. if (alpha >= beta) { return(currentMove); } } } } // Returns move and its value. return(currentMove); }
// Our minimax implementation private (FutureMove move, float score) Minimax( Board board, CancellationToken ct, PColor player, PColor turn, int depth) { // Move to return and its heuristic value (FutureMove move, float score)selectedMove; // Current board state Winner winner; // If a cancellation request was made... if (ct.IsCancellationRequested) { // ...set a "no move" and skip the remaining part of the algorithm selectedMove = (FutureMove.NoMove, float.NaN); } // Otherwise, if it's a final board, return the appropriate evaluation else if ((winner = board.CheckWinner()) != Winner.None) { if (winner.ToPColor() == player) { // AI player wins, return highest possible score selectedMove = (FutureMove.NoMove, float.PositiveInfinity); } else if (winner.ToPColor() == player.Other()) { // Opponent wins, return lowest possible score selectedMove = (FutureMove.NoMove, float.NegativeInfinity); } else { // A draw, return zero selectedMove = (FutureMove.NoMove, 0f); } } // If we're at maximum depth and don't have a final board, use // the heuristic else if (depth == maxDepth) { // Where did this Heuristic() function come from? // We'll return to it in a moment selectedMove = (FutureMove.NoMove, Heuristic(board, player)); } else // Board not final and depth not at max... { //...so let's test all possible moves and recursively call Minimax() // for each one of them, maximizing or minimizing depending on who's // turn it is // Initialize the selected move... selectedMove = turn == player // ...with negative infinity if it's the AI's turn and we're // maximizing (so anything except defeat will be better than this) ? (FutureMove.NoMove, float.NegativeInfinity) // ...or with positive infinity if it's the opponent's turn and we're // minimizing (so anything except victory will be worse than this) : (FutureMove.NoMove, float.PositiveInfinity); // Test each column for (int i = 0; i < Cols; i++) { // Skip full columns if (board.IsColumnFull(i)) { continue; } // Test shapes for (int j = 0; j < 2; j++) { // Get current shape PShape shape = (PShape)j; // Use this variable to keep the current board's score float eval; // Skip unavailable shapes if (board.PieceCount(turn, shape) == 0) { continue; } // Test move, call minimax and undo move board.DoMove(shape, i); eval = Minimax(board, ct, player, turn.Other(), depth + 1).score; board.UndoMove(); // If we're maximizing, is this the best move so far? if (turn == player && eval > selectedMove.score) { // If so, keep it selectedMove = (new FutureMove(i, shape), eval); } // Otherwise, if we're minimizing, is this the worst move so far? else if (turn == player.Other() && eval < selectedMove.score) { // If so, keep it selectedMove = (new FutureMove(i, shape), eval); } } } } // Return selected move and its heuristic value return(selectedMove); }