/// <summary> /// Perform the CPU's Move /// </summary> /// <param name="random">Make a random move?</param> private void makeCPUsMove(int OsRow, int OsCol, bool random = false) { int row = 0, col = 0; // If to make a random move if (random) { Random rand = new Random(); row = rand.Next(0, 4); col = rand.Next(0, 5); // pictureBox_click(Controls["pic" + row.ToString() + col.ToString()], new EventArgs()); } // Else make a move based on Alpha-Beta Search else { GameTreeNode gtn = new GameTreeNode(gameBoard, OsRow, OsCol); gtn.NextTurn = (State)Turn; SearchTree gt = new SearchTree(); gtn.Text = "Root"; GameTreeNode newMove; if (gt.insertNode(gtn)) { newMove = gt.immResult; } else { newMove = gt.getNextBestMove(); //gt.ShowDialog(); } row = newMove.Row; col = newMove.Column; } // Perform a click operation at on the PictureBox pictureBox_click(Controls["pic" + row.ToString() + col.ToString()], new EventArgs()); }
/// <summary> /// Recursively expand nodex till the depth is met /// </summary> /// <param name="tn">TreeNode to expand</param> /// <param name="depth">Depth till which to expand</param> public void ExpandNode(TreeNode tn, int depth) { // If depth is reached return if (depth <= 0) { return; } GameTreeNode gtn = (GameTreeNode)tn; // Expand the node only if the Board of the node is Incomplete, otherwise no need to expand if (gtn.NodeBoard.getBoardStatus() == Status.Incomplete) { // Clear previous nodes, if any gtn.Nodes.Clear(); // Generate the child nodes for the given node gtn.Nodes.AddRange(generateSubTree(gtn)); } foreach (TreeNode child in gtn.Nodes) { // Expand node for each of the child nodes in the current node ExpandNode(child, depth - 1); } }
/// <summary> /// Generates child nodes for the given node /// </summary> /// <param name="selectedNode">Node for which the child nodes are to be generated</param> /// <returns>Returns the list of generated child nodes</returns> private GameTreeNode[] generateSubTree(GameTreeNode selectedNode) { List <GameTreeNode> gtns = new List <GameTreeNode>(); // Get the current board Board currentBoard = new Board(selectedNode.NodeBoard); // For each possible moves available on the board, generate a child node // and add it to the list of child nodes for (int i = 0; i < Globals.ROWS; ++i) { for (int j = 0; j < Globals.COLS; ++j) { if (currentBoard[i, j] == State.N) { Board temp = new Board(currentBoard); // Create a possible move upon the current board temp[i, j] = selectedNode.NextTurn; // Generate a child node with the possible board and mark down the // position at which the new move is made GameTreeNode childNode = new GameTreeNode(temp, i, j); childNode.NextTurn = (State)((int)selectedNode.NextTurn % 2 + 1); // Enlist the child node gtns.Add(childNode); } } } // Return the list of nodes return(gtns.ToArray()); }
/// <summary> /// Performs the maximum search at the given depth i.e. Maximizes the chance for X /// </summary> /// <param name="state">The current state</param> /// <param name="alpha">The alpha value for the state</param> /// <param name="beta">The beta value for the state</param> /// <returns>Estimated value for the give state</returns> private static int MaxValue(GameTreeNode state, int alpha, int beta) { // if Terminal-test(state) then return utitly(state) if (state.Nodes.Count == 0) { return(utility(state)); } // v = -Inf state.Value = Globals.MININT; // Foreach action in state.Actions do foreach (GameTreeNode action in state.Nodes) { // v <- Max(v, MinValue(Result(s, a), alpha, beta)) state.Value = Math.Max(state.Value, MinValue(action, alpha, beta)); // Pruning steps if (state.Value >= beta) { return(state.Value); } alpha = Math.Max(alpha, state.Value); } // Return state value return(state.Value); }
private static int MinValue(GameTreeNode state, int alpha, int beta) { // if Terminal-test(state) then return utitly(state) if (state.Nodes.Count == 0) { return(utility(state)); } // v = +Inf; state.Value = Globals.MAXINT; // Foreach action in state.Actions do foreach (GameTreeNode child in state.Nodes) { // v <- Max(v, MinValue(Result(s, a), alpha, beta)) state.Value = Math.Min(state.Value, MaxValue(child, alpha, beta)); // Pruning steps if (state.Value <= alpha) { return(state.Value); } beta = Math.Min(beta, state.Value); } // Return state value return(state.Value); }
/// <summary> /// Initializes the Minimax Search i.e. the Alpha Beta Search Algorithm /// </summary> /// <param name="state">The current state of the tree i.e the root node of the minimax tree</param> /// <returns>The next possible best move</returns> public static GameTreeNode MinMax(GameTreeNode state) { // Gets the maximum value for the root node int bestValue = MaxValue(state, Globals.MININT, Globals.MAXINT); // Find the node with maximum value in the list of childs of the root node foreach (GameTreeNode action in state.Nodes) { if (action.Value == bestValue) { return(action); } } // By default return the first child of the root return((GameTreeNode)state.Nodes[0]); }
/// <summary> /// Initializes the Minimax Search i.e. the Alpha Beta Search Algorithm /// </summary> /// <param name="state">The current state of the tree i.e the root node of the minimax tree</param> /// <returns>The next possible best move</returns> public static GameTreeNode MinMax(GameTreeNode state) { // Gets the maximum value for the root node int bestValue = MaxValue(state, Globals.MININT, Globals.MAXINT); // Find the node with maximum value in the list of childs of the root node foreach (GameTreeNode action in state.Nodes) { if (action.Value == bestValue) { return action; } } // By default return the first child of the root return (GameTreeNode)state.Nodes[0]; }
/// <summary> /// Create the first root node expands the root node /// </summary> /// <param name="gtn">The root game node</param> /// <returns>Returns true if immediate move is possible, otherwise false</returns> public bool insertNode(GameTreeNode gtn) { // Clear the previous nodes, if any GameTree.Nodes.Clear(); // Add the root node to tree GameTree.Nodes.Add(gtn); // Immediate move is explained below // If immediate move is available return true if (immediateResult(new GameTreeNode(gtn.NodeBoard, gtn.Row, gtn.Column))) return true; // Else start expanding the nodes from the root to the given depth ExpandNode(gtn, getDepth(gtn.NodeBoard.numberOfEmptyStates())); // And return false for not immediate moves return false; }
/// <summary> /// The utility function calculates the value of the give node in the tree /// </summary> /// <param name="state">The node to be evaluated</param> /// <returns>Either +200 for X, -200 for O or appropriate evaluated value</returns> private static int utility(GameTreeNode state) { switch (state.NodeBoard.getBoardStatus()) { // Success for X, return +200 case Status.Success: return(200); // Success for O i.e. Failure for X, return -200 case Status.Failure: return(-200); // Incomplete do Eval for the state case Status.Incomplete: return(state.evaluate()); } // Draw return 0 return(0); }
/// <summary> /// Immediate move allows the CPU to mark down the Board with a winning move without expanding and working /// on Alpha Beta Search tree. This move facilitates quicker performance and more accuracy and more winning /// chance for X /// </summary> /// <param name="gtn">Node for which the immediate move is to be calculated</param> /// <returns></returns> private bool immediateResult(GameTreeNode gtn) { // Mark down the node for X's move gtn.NextTurn = State.X; // Generate list of immediate moves gtn.Nodes.AddRange(generateSubTree(gtn)); foreach (TreeNode child in gtn.Nodes) { // If the immediate move results in success, return true if (((GameTreeNode)child).NodeBoard.getBoardStatus() == Status.Success) { immResult = (GameTreeNode)child; return(true); } } // Return false otherwise return(false); }
/// <summary> /// Create the first root node expands the root node /// </summary> /// <param name="gtn">The root game node</param> /// <returns>Returns true if immediate move is possible, otherwise false</returns> public bool insertNode(GameTreeNode gtn) { // Clear the previous nodes, if any GameTree.Nodes.Clear(); // Add the root node to tree GameTree.Nodes.Add(gtn); // Immediate move is explained below // If immediate move is available return true if (immediateResult(new GameTreeNode(gtn.NodeBoard, gtn.Row, gtn.Column))) { return(true); } // Else start expanding the nodes from the root to the given depth ExpandNode(gtn, getDepth(gtn.NodeBoard.numberOfEmptyStates())); // And return false for not immediate moves return(false); }
/// <summary> /// Performs the maximum search at the given depth i.e. Maximizes the chance for X /// </summary> /// <param name="state">The current state</param> /// <param name="alpha">The alpha value for the state</param> /// <param name="beta">The beta value for the state</param> /// <returns>Estimated value for the give state</returns> private static int MaxValue(GameTreeNode state, int alpha, int beta) { // if Terminal-test(state) then return utitly(state) if (state.Nodes.Count == 0) return utility(state); // v = -Inf state.Value = Globals.MININT; // Foreach action in state.Actions do foreach (GameTreeNode action in state.Nodes) { // v <- Max(v, MinValue(Result(s, a), alpha, beta)) state.Value = Math.Max(state.Value, MinValue(action, alpha, beta)); // Pruning steps if (state.Value >= beta) return state.Value; alpha = Math.Max(alpha, state.Value); } // Return state value return state.Value; }
private static int MinValue(GameTreeNode state, int alpha, int beta) { // if Terminal-test(state) then return utitly(state) if (state.Nodes.Count == 0) return utility(state); // v = +Inf; state.Value = Globals.MAXINT; // Foreach action in state.Actions do foreach (GameTreeNode child in state.Nodes) { // v <- Max(v, MinValue(Result(s, a), alpha, beta)) state.Value = Math.Min(state.Value, MaxValue(child, alpha, beta)); // Pruning steps if (state.Value <= alpha) return state.Value; beta = Math.Min(beta, state.Value); } // Return state value return state.Value; }
/// <summary> /// Perform the CPU's Move /// </summary> /// <param name="random">Make a random move?</param> private void makeCPUsMove(int OsRow, int OsCol, bool random = false) { int row = 0, col = 0; // If to make a random move if (random) { Random rand = new Random(); row = rand.Next(0, 4); col = rand.Next(0, 5); // pictureBox_click(Controls["pic" + row.ToString() + col.ToString()], new EventArgs()); } // Else make a move based on Alpha-Beta Search else { GameTreeNode gtn = new GameTreeNode(gameBoard, OsRow, OsCol); gtn.NextTurn = (State)Turn; SearchTree gt = new SearchTree(); gtn.Text = "Root"; GameTreeNode newMove; if (gt.insertNode(gtn)) newMove = gt.immResult; else { newMove = gt.getNextBestMove(); //gt.ShowDialog(); } row = newMove.Row; col = newMove.Column; } // Perform a click operation at on the PictureBox pictureBox_click(Controls["pic" + row.ToString() + col.ToString()], new EventArgs()); }
/// <summary> /// The utility function calculates the value of the give node in the tree /// </summary> /// <param name="state">The node to be evaluated</param> /// <returns>Either +200 for X, -200 for O or appropriate evaluated value</returns> private static int utility(GameTreeNode state) { switch (state.NodeBoard.getBoardStatus()) { // Success for X, return +200 case Status.Success: return 200; // Success for O i.e. Failure for X, return -200 case Status.Failure: return -200; // Incomplete do Eval for the state case Status.Incomplete: return state.evaluate(); } // Draw return 0 return 0; }
/// <summary> /// Constructor: Initializes the variables /// </summary> public SearchTree() { immResult = new GameTreeNode(); GameTree = new GameTreeNode(); }
/// <summary> /// Constructor: Initializes the variables /// </summary> public SearchTree() { immResult = new GameTreeNode(); GameTree = new GameTreeNode(); }
/// <summary> /// Immediate move allows the CPU to mark down the Board with a winning move without expanding and working /// on Alpha Beta Search tree. This move facilitates quicker performance and more accuracy and more winning /// chance for X /// </summary> /// <param name="gtn">Node for which the immediate move is to be calculated</param> /// <returns></returns> private bool immediateResult(GameTreeNode gtn) { // Mark down the node for X's move gtn.NextTurn = State.X; // Generate list of immediate moves gtn.Nodes.AddRange(generateSubTree(gtn)); foreach (TreeNode child in gtn.Nodes) { // If the immediate move results in success, return true if (((GameTreeNode)child).NodeBoard.getBoardStatus() == Status.Success) { immResult = (GameTreeNode)child; return true; } } // Return false otherwise return false; }
/// <summary> /// Generates child nodes for the given node /// </summary> /// <param name="selectedNode">Node for which the child nodes are to be generated</param> /// <returns>Returns the list of generated child nodes</returns> private GameTreeNode[] generateSubTree(GameTreeNode selectedNode) { List<GameTreeNode> gtns = new List<GameTreeNode>(); // Get the current board Board currentBoard = new Board(selectedNode.NodeBoard); // For each possible moves available on the board, generate a child node // and add it to the list of child nodes for (int i = 0; i < Globals.ROWS; ++i) for (int j = 0; j < Globals.COLS; ++j) if (currentBoard[i, j] == State.N) { Board temp = new Board(currentBoard); // Create a possible move upon the current board temp[i, j] = selectedNode.NextTurn; // Generate a child node with the possible board and mark down the // position at which the new move is made GameTreeNode childNode = new GameTreeNode(temp, i, j); childNode.NextTurn = (State)((int)selectedNode.NextTurn % 2 + 1); // Enlist the child node gtns.Add(childNode); } // Return the list of nodes return gtns.ToArray(); }