// the public interface to other classes, kicks off threaded // task, then returns to caller. caller polls AI.Done to see // if threaded task is done public void SelectMove(GameBoard board) { m_Done = false; m_Move = 0; m_Status = 0; m_ThreadParam.Board = board; m_ThreadParam.Depth = Depth; Thread task = new Thread(SelectMoveTask); task.Start(); }
// examine every valid move, trying to maximize Player.One's score private int Max(GameBoard board, int depth, int alpha, int beta, ref int move) { // have we recursed far enough? if (depth <= 0) { return Evaluate(board); } for (int index = 0; index < board.ValidMoves.Count; index++) { // report status if we're at top-most recursion depth if (depth == Depth) { m_Status = (double)index / (double)board.ValidMoves.Count; } // copy current board GameBoard board2 = new GameBoard(board); // make move based on ValidMoves[index] board2.MakeMove(index); // get best move that opponent can make int val = Min(board2, depth - 1, alpha, beta, ref move); if (val >= beta) { // already found a better branch than this can possibly be return beta; } if (val > alpha) { // found a better score! alpha = val; if (depth == Depth) { // udpate move if this is top-level move = index; } } } // return best score found return alpha; }
// called by base class from seperate thread protected override int SelectMoveRecursive(GameBoard board, int depth) { // assume first move is the best int move = 0; // best score, based on player if (board.CurrentPlayer == Player.One) { move = FindBestMove(board, true, int.MinValue); } else if (board.CurrentPlayer == Player.Two) { move = FindBestMove(board, false, int.MaxValue); } // return selected move return move; }
// called by base class from seperate thread protected override int SelectMoveRecursive(GameBoard board, int depth) { // default to the first valid move int move = 0; // select min or max method based on player if (board.CurrentPlayer == Player.One) { int score = Max(board, Depth, ref move); } else if (board.CurrentPlayer == Player.Two) { int score = Min(board, Depth, ref move); } // return selected move (may have been updated by Min or Max) return move; }
// examine every valid move, trying to maximize Player.One's score private int Max(GameBoard board, int depth, ref int move) { // have we recursed far enough? if (depth <= 0) { return Evaluate(board); } // start with rediculously low score so that first // inspection will result in a match int score = int.MinValue; // for each ValidMove ... for (int index = 0; index < board.ValidMoves.Count; index++) { // report status if we're at top-most recursion depth if (depth == Depth) { m_Status = (double)index / (double)board.ValidMoves.Count; } // copy current board GameBoard board2 = new GameBoard(board); // make move based on ValidMoves[index] board2.MakeMove(index); // get best move that opponent can make int val = Min(board2, depth - 1, ref move); if (val > score) { // found a better score! score = val; if (depth == Depth) { // udpate move if this is top-level move = index; } } } // return best score found return score; }
protected int FindBestMove(GameBoard board, bool isPlayerOne, int score) { // assume first move is the best int move = 0; // scan valid moves, looking for best score for (int index = 0; index < board.ValidMoves.Count; index++) { // create copy of current board to play with GameBoard board2 = new GameBoard(board); // make the next valid move board2.MakeMove(index); // what's the score? int val = Evaluate(board2); // best score for player one is positive, // best score for player two is negative if (isPlayerOne) { if (val > score) { score = val; move = index; } } else { if (val < score) { score = val; move = index; } } } // report findings to the caller return move; }
// called by base class from seperate thread protected override int SelectMoveRecursive(GameBoard board, int depth) { // default to the first valid move int move = 0; // select min or max method based on player if (board.CurrentPlayer == Player.One) { // start with rediculously low alpha and high beta so that first // inspection will result in a match int score = Max(board, Depth, int.MinValue, int.MaxValue, ref move); } else if (board.CurrentPlayer == Player.Two) { // start with rediculously low alpha and high beta so that first // inspection will result in a match int score = Min(board, Depth, int.MinValue, int.MaxValue, ref move); } // return selected move (may have been updated by Min or Max) return move; }
// copy another board's state data to this board public void Copy(GameBoard board) { InitGrid(board.GridWidth, board.GridHeight); for (int y = 0; y < GridHeight; y++) { for (int x = 0; x < GridWidth; x++) { Grid[x, y] = board.Grid[x, y]; } } CurrentPlayer = board.CurrentPlayer; UpdateListOfValidMoves(); }
// copy constructor public GameBoard(GameBoard board) { Copy(board); }
// as simple as AI gets -- random selection protected override int SelectMoveRecursive(GameBoard board, int depth) { // randomly select a move from the list of valid moves return m_rand.Next(board.ValidMoves.Count); }
// actually process the input private void ProcessInput(GamePadState pad, KeyboardState kbd) { bool pressed; // move to previous valid move pressed = pad.Triggers.Left > 0; pressed |= pad.DPad.Left == ButtonState.Pressed; pressed |= pad.ThumbSticks.Left.X < 0; pressed |= kbd.IsKeyDown(Keys.Left); if (pressed && m_PressDelay <= 0) { m_PressDelay = PRESS_DELAY; m_GameBoard.PreviousValidMove(); } // move to next valid move pressed = pad.Triggers.Right > 0; pressed |= pad.DPad.Right == ButtonState.Pressed; pressed |= pad.ThumbSticks.Left.X > 0; pressed |= kbd.IsKeyDown(Keys.Right); if (pressed && m_PressDelay <= 0) { m_PressDelay = PRESS_DELAY; m_GameBoard.NextValidMove(); } // commit selection as your move pressed = pad.Buttons.A == ButtonState.Pressed; pressed |= kbd.IsKeyDown(Keys.Space); if (pressed && m_PressDelay <= 0) { m_PressDelay = PRESS_DELAY; m_GameBoard.MakeMove(); } // start a new game // only works when it's a human's turn so that AI threads // (if any) have time to complete pressed = pad.Buttons.Start == ButtonState.Pressed; pressed |= kbd.IsKeyDown(Keys.Enter); if (pressed && m_PressDelay <= 0) { m_PressDelay = PRESS_DELAY; m_GameBoard = new GameBoard(); } // pick previous player two type pressed = pad.Buttons.LeftShoulder == ButtonState.Pressed; pressed |= kbd.IsKeyDown(Keys.PageUp); if (pressed && m_PressDelay <= 0) { m_PressDelay = PRESS_DELAY; m_GameBoard.PreviousPlayerType(); } // pick next player two type pressed = pad.Buttons.RightShoulder == ButtonState.Pressed; pressed |= kbd.IsKeyDown(Keys.PageDown); if (pressed && m_PressDelay <= 0) { m_PressDelay = PRESS_DELAY; m_GameBoard.NextPlayerType(); } }
// helper method to generate a simple heuristic for a given GameBoard // add 1 for each piece owned by Player.One, subtract 1 for each piece // owned by Player.Two. Larger sums favor Player.One, smaller sums // favor Player.Two. protected int Evaluate(GameBoard board) { return board.Score(Player.One) - board.Score(Player.Two); }
// this method must be implemented by any actual AIs that derive // from this base class. the threaded method calls this method, // which only exists in derived classes. that way, this base // class can handle the nitty-gritty threading and synchronization // tasks, and leave the actual AI processing to the derived classes. protected abstract int SelectMoveRecursive(GameBoard board, int depth);