/// <summary> /// Asks the game engine next (valid) move given a game position /// The board assumes following standard move notation: /// v /// A B C D E F G H I /// [0 1 2 3 4 5 6 7 8] (first index) /// 1[0] /// 2[1] /// 3[2] w K /// 4[3] K w /// 5[4] /// 6[5] /// >7[6] x /// /// {Column;Line} /// E.g.: 'w' on D3 in game notation will map to {3,2}, and 'x' on I7 to {8,6} /// </summary> /// <param name="game">a 2D board with integer values: 0 for white 1 for black and -1 for empty tiles. First index for the column, second index for the line</param> /// <param name="level">an integer value to set the level of the IA, 5 normally</param> /// <param name="whiteTurn">true if white players turn, false otherwise</param> /// <returns>The column and line indices. Will return {-1,-1} as PASS if no possible move </returns> public Tuple <int, int> GetNextMove(int[,] game, int level, bool whiteTurn) { int[,] gameState = new int[9, 7]; for (int i = 0; i < 9; i++) { for (int j = 0; j < 7; j++) { gameState[i, j] = game[i, j]; } } BoardIA logic = new BoardIA { GameBoard = gameState, PlayerTurn = whiteTurn ? Player.White : Player.Black }; List <IntPosition> possibleMoves = logic.GetAllPossibleMoves(); if (possibleMoves.Count == 0) { return(new Tuple <int, int>(-1, -1)); } var nextMove = AlphaBeta(logic, level, int.MinValue, true); // Always minimum value (int.MinValue) and we maximize (true), whatever the color... return(new Tuple <int, int>(nextMove.Item2.Column, nextMove.Item2.Row)); }
/// <summary> /// Implementation of negamax algorithm with alpha beta pruning. A node is represented by a game board. /// </summary> /// <param name="nodeBoard">A particular game state</param> /// <param name="depth">The maximum tree depth</param> /// <param name="parentValue">The parent node value</param> /// <param name="maximizingPlayer">Whether node is a maximizimer or minimizer. True if maximizer. </param> /// <returns>A tuple containing the value as it's first item and the position corresponding to the best /// predicted play as it's second item. </returns> private Tuple <int, IntPosition> AlphaBeta(BoardIA nodeBoard, int depth, int parentValue, bool maximizingPlayer) { if (depth == 0 || nodeBoard.IsTerminal()) { var heuristicValue = nodeBoard.GetHeuristicValue(); return(new Tuple <int, IntPosition>(heuristicValue, new IntPosition(-1, -1))); } else { int bestValue = maximizingPlayer ? -int.MaxValue : int.MaxValue; IntPosition bestMove = new IntPosition(-1, -1); var childPositions = nodeBoard.GetAllPossibleMoves(); foreach (var child in childPositions) { var childValue = AlphaBeta(PosToBoard(child, nodeBoard), depth - 1, bestValue, !maximizingPlayer); int minOrMax = maximizingPlayer ? 1 : -1; if (childValue.Item1 * minOrMax > bestValue * minOrMax) { bestValue = childValue.Item1; bestMove = child; if (bestValue * minOrMax > parentValue * minOrMax) { break; } } } return(new Tuple <int, IntPosition>(bestValue, bestMove)); } }
/// <summary> /// Creates an AIBoard class given the position of a move to play /// and the board where the move has to be played. /// </summary> /// <param name="position">The position of the pawn to play on the board</param> /// <param name="sourceBoard">The board where the move takes place</param> /// <returns>A new AIBoard with the updated game state</returns> private BoardIA PosToBoard(IntPosition position, BoardIA sourceBoard) { int[,] gameState = new int[9, 7]; for (int i = 0; i < 9; i++) { for (int j = 0; j < 7; j++) { gameState[i, j] = sourceBoard.GameBoard[i, j]; } } BoardIA newBoard = new BoardIA { GameBoard = gameState, PlayerTurn = sourceBoard.PlayerTurn }; newBoard.PlayMove(position.Column, position.Row, sourceBoard.PlayerTurn == Player.White); newBoard.SwitchPlayer(); return(newBoard); }