def _boardSearch(self, board, color, depth, alpha, beta, isMaxNode,
                     stopSrchTime):
        """Performs a board via alpha-beta pruning/quiescence search

        NOTE: Not guaranteed to stop promptly at stopSrchTime - may take some
        time to terminate. Leave a time buffer.

        Selectively explores past the final depth if many pieces have
        been captured recently, by calling quiescent search

        @param board: The starting board state to evaluate
        @param color: The color of the player to generate a move for
        @param depth: The number of plies forward that should be explored
        @param alpha: Nodes with a likability below this will be ignored
        @param beta: Nodes with a likability above this will be ignored
        @param isMaxNode: Is this a beta node? (Is this node seeking to
                        maximize the value of child nodes?)
        @param stopSrchTime: Time at which the search should begin to terminate
        @param nodesVisited: The number of nodes already searched

        @return: A tuple with the following elements:
                1. None, or a move of the form (fromChessPosn, toChessPosn)
                    representing the next move that should be made by the given
                    player
                2. The likability of this move's path in the tree, as followed
                    by the search and as determined by the likability of the
                    leaf node terminating the path
                3. The number of nodes visited in the search

        Implementation based on information found here: http://bit.ly/t1dHKA"""
        ## TODO (James): Check timeout less than once per iteration

        ## TODO (James): Make logging conditional - temporarily disabled

        ## TODO (James): Set this to True before handing in!
        # Whether to limit based on wall clock time or number of nodes seen
        USE_WALL_CLOCK = False

        # Number of nodes to visit (for when wall clock time is not used)
        NUM_NODES_TO_VISIT = 1200

        #logStrF = "Performing minimax search to depth {0}.".format(depth)
        #QLAI._logger.debug(logStrF)

        # Note that we've visited a node
        nodesVisited = 1

        otherColor = ChessBoard.getOtherColor(color)

        # Check if we are at a leaf node
        if (depth == 0):
            (a, b) = self._quiescentSearch(board, color, alpha, beta,
                                           isMaxNode)
            return (a, b, 1)

        # Check if we should otherwise terminate
        elif (time() > stopSrchTime or board.isKingCheckmated(color)
              or board.isKingCheckmated(otherColor)):
            return (None,
                    evaluateBoardLikability(color, board,
                                            self.heuristicWgts), nodesVisited)

        else:
            moveChoices = enumMoves(board, color)
            #logStrF = "Considering {0} poss. moves".format(len(moveChoices))
            #QLAI._logger.debug(logStrF)

            # Check whether seeking to find minimum or maximum value
            if isMaxNode:
                newMin = alpha
                newMoveChoice = None
                for move in moveChoices:

                    # Rather than calling getPlyResult, use THIS board. Much
                    # faster. REMEMBER TO UNDO THIS HYPOTHETICAL MOVE

                    # Save the old flag sets so they can be restored
                    boardMoveUndoDict = board.getPlyResult(move[0], move[1])

                    # Find the next move for this node, and how likable the
                    # enemy will consider this child node
                    (_, nodeEnemyLikability,
                     nVisit) = self._boardSearch(board, otherColor, depth - 1,
                                                 newMin, beta, not isMaxNode,
                                                 stopSrchTime)
                    # RESTORE THE OLD BOARD STATE - VERY IMPORTANT
                    board.undoPlyResult(boardMoveUndoDict)

                    # Note how many more nodes we've visited
                    nodesVisited += nVisit

                    # Make note of the least likable branches that it still
                    # makes sense to pursue, given how likable this one is
                    if nodeEnemyLikability > newMin:
                        newMin = nodeEnemyLikability
                        newMoveChoice = move

                    # Don't search outside of the target range
                    elif nodeEnemyLikability > beta:
                        #QLAI._logger.debug("Pruning because new value > beta")
                        return (move, beta, nodesVisited)

                    # Check to see if we've evaluated the max number of nodes
                    if ((not USE_WALL_CLOCK)
                            and (nodesVisited > NUM_NODES_TO_VISIT)):
                        return (newMoveChoice, newMin, nodesVisited)

                return (newMoveChoice, newMin, nodesVisited)
            else:
                newMax = beta
                newMoveChoice = None
                for move in moveChoices:

                    # Rather than calling getPlyResult, use THIS board. Much
                    # faster. REMEMBER TO UNDO THIS HYPOTHETICAL MOVE

                    # Save the old flag sets so they can be restored
                    boardMoveUndoDict = board.getPlyResult(move[0], move[1])

                    # Find how likable the enemy will consider this child node
                    (_, nodeEnemyLikability,
                     nVisit) = self._boardSearch(board, otherColor, depth - 1,
                                                 alpha, newMax, not isMaxNode,
                                                 stopSrchTime)

                    # RESTORE THE OLD BOARD STATE - VERY IMPORTANT:
                    board.undoPlyResult(boardMoveUndoDict)

                    # Note how many more nodes we've visited
                    nodesVisited += nVisit

                    # Make note of the most likable branches that it still
                    # makes sense to pursue, given how likable this one is
                    if nodeEnemyLikability < newMax:
                        newMax = nodeEnemyLikability
                        newMoveChoice = move

                    # Don't bother searching outside of our target range
                    elif nodeEnemyLikability < alpha:
                        #QLAI._logger.debug("pruning because new val < alpha")
                        return (move, alpha, nodesVisited)

                    # Check to see if we've evaluated the max number of nodes
                    if ((not USE_WALL_CLOCK)
                            and (nodesVisited > NUM_NODES_TO_VISIT)):
                        return (newMoveChoice, newMin, nodesVisited)
                return (newMoveChoice, newMax, nodesVisited)
    def _boardSearch(self, board, color, depth, alpha, beta,
                     isMaxNode, stopSrchTime):
        """Performs a board via alpha-beta pruning/quiescence search

        NOTE: Not guaranteed to stop promptly at stopSrchTime - may take some
        time to terminate. Leave a time buffer.

        Selectively explores past the final depth if many pieces have
        been captured recently, by calling quiescent search

        @param board: The starting board state to evaluate
        @param color: The color of the player to generate a move for
        @param depth: The number of plies forward that should be explored
        @param alpha: Nodes with a likability below this will be ignored
        @param beta: Nodes with a likability above this will be ignored
        @param isMaxNode: Is this a beta node? (Is this node seeking to
                        maximize the value of child nodes?)
        @param stopSrchTime: Time at which the search should begin to terminate
        @param nodesVisited: The number of nodes already searched

        @return: A tuple with the following elements:
                1. None, or a move of the form (fromChessPosn, toChessPosn)
                    representing the next move that should be made by the given
                    player
                2. The likability of this move's path in the tree, as followed
                    by the search and as determined by the likability of the
                    leaf node terminating the path
                3. The number of nodes visited in the search

        Implementation based on information found here: http://bit.ly/t1dHKA"""
        ## TODO (James): Check timeout less than once per iteration

        ## TODO (James): Make logging conditional - temporarily disabled

        ## TODO (James): Set this to True before handing in!
        # Whether to limit based on wall clock time or number of nodes seen
        USE_WALL_CLOCK = False

        # Number of nodes to visit (for when wall clock time is not used)
        NUM_NODES_TO_VISIT = 1200

        #logStrF = "Performing minimax search to depth {0}.".format(depth)
        #QLAI._logger.debug(logStrF)

        # Note that we've visited a node
        nodesVisited = 1

        otherColor = ChessBoard.getOtherColor(color)

        # Check if we are at a leaf node
        if (depth == 0):
            (a, b) = self._quiescentSearch(board, color, alpha, beta,
                                           isMaxNode)
            return (a, b, 1)

        # Check if we should otherwise terminate
        elif (time() > stopSrchTime or
              board.isKingCheckmated(color) or
              board.isKingCheckmated(otherColor)):
            return (None, evaluateBoardLikability(color, board,
                                                  self.heuristicWgts),
                    nodesVisited)

        else:
            moveChoices = enumMoves(board, color)
            #logStrF = "Considering {0} poss. moves".format(len(moveChoices))
            #QLAI._logger.debug(logStrF)

            # Check whether seeking to find minimum or maximum value
            if isMaxNode:
                newMin = alpha
                newMoveChoice = None
                for move in moveChoices:

                    # Rather than calling getPlyResult, use THIS board. Much
                    # faster. REMEMBER TO UNDO THIS HYPOTHETICAL MOVE

                    # Save the old flag sets so they can be restored
                    boardMoveUndoDict = board.getPlyResult(move[0], move[1])

                    # Find the next move for this node, and how likable the
                    # enemy will consider this child node
                    (_, nodeEnemyLikability, nVisit) = self._boardSearch(board,
                                                                 otherColor,
                                                                 depth - 1,
                                                                 newMin, beta,
                                                                 not isMaxNode,
                                                                 stopSrchTime)
                    # RESTORE THE OLD BOARD STATE - VERY IMPORTANT
                    board.undoPlyResult(boardMoveUndoDict)

                    # Note how many more nodes we've visited
                    nodesVisited += nVisit

                    # Make note of the least likable branches that it still
                    # makes sense to pursue, given how likable this one is
                    if nodeEnemyLikability > newMin:
                        newMin = nodeEnemyLikability
                        newMoveChoice = move

                    # Don't search outside of the target range
                    elif nodeEnemyLikability > beta:
                        #QLAI._logger.debug("Pruning because new value > beta")
                        return (move, beta, nodesVisited)

                    # Check to see if we've evaluated the max number of nodes
                    if ((not USE_WALL_CLOCK) and
                        (nodesVisited > NUM_NODES_TO_VISIT)):
                        return (newMoveChoice, newMin, nodesVisited)

                return (newMoveChoice, newMin, nodesVisited)
            else:
                newMax = beta
                newMoveChoice = None
                for move in moveChoices:

                    # Rather than calling getPlyResult, use THIS board. Much
                    # faster. REMEMBER TO UNDO THIS HYPOTHETICAL MOVE

                    # Save the old flag sets so they can be restored
                    boardMoveUndoDict = board.getPlyResult(move[0], move[1])

                    # Find how likable the enemy will consider this child node
                    (_, nodeEnemyLikability, nVisit) = self._boardSearch(board,
                                                                 otherColor,
                                                                 depth - 1,
                                                                 alpha, newMax,
                                                                 not isMaxNode,
                                                                 stopSrchTime)

                    # RESTORE THE OLD BOARD STATE - VERY IMPORTANT:
                    board.undoPlyResult(boardMoveUndoDict)

                    # Note how many more nodes we've visited
                    nodesVisited += nVisit

                    # Make note of the most likable branches that it still
                    # makes sense to pursue, given how likable this one is
                    if nodeEnemyLikability < newMax:
                        newMax = nodeEnemyLikability
                        newMoveChoice = move

                    # Don't bother searching outside of our target range
                    elif nodeEnemyLikability < alpha:
                        #QLAI._logger.debug("pruning because new val < alpha")
                        return (move, alpha, nodesVisited)

                    # Check to see if we've evaluated the max number of nodes
                    if ((not USE_WALL_CLOCK) and
                        (nodesVisited > NUM_NODES_TO_VISIT)):
                        return (newMoveChoice, newMin, nodesVisited)
                return (newMoveChoice, newMax, nodesVisited)
    def _quiescentSearch(self, board, color, alpha, beta, isMaxNode):
        """Perform a quiescent search on the given board, examining captures

        Enumerates captures, and evaluates them to see if they alter results

        @param board: The starting board state to evaluate
        @param color: The color of the player to generate a move for
        @param alpha: Nodes with a likability below this will be ignored
        @param beta: Nodes with a likability above this will be ignored
        @param isMaxNode: Is this a beta node? (Is this node seeking to
                        maximize the value of child nodes?)

        @return: A tuple with the following elements:
                1. None, or a move of the form (fromChessPosn, toChessPosn)
                    representing the next move that should be made by the given
                    player
                2. The likability of this move's path in the tree, as followed
                    by the search and as determined by the likability of the
                    leaf node terminating the path

        No timeout is allowed. This shouldn't take long, anyway. If it does,
        then we're doing good things.

        Note: this was influenced by information here: http://bit.ly/VYlJVC """

        otherColor = ChessBoard.getOtherColor(color)

        # Note the appeal of this board, with no captures
        standPatVal = evaluateBoardLikability(color, board, self.heuristicWgts)

        # Build up a list of capture moves

        moveChoices = enumMoves(board, color)
        moveFilterFunct = lambda m: (
            (board[m[1]] is not None) and (board[m[1]].color == otherColor))
        captureMoves = filter(moveFilterFunct, moveChoices)

        # Determine whether captures are a good or a bad thing
        if isMaxNode:

            # Check whether it is even worth proceeding with evaluation
            if (standPatVal > beta):
                return (None, beta)
            elif (alpha < standPatVal):
                alpha = standPatVal

            # Evaluate all captures
            for capMv in captureMoves:
                boardMoveUndoDict = board.getPlyResult(capMv[0], capMv[1])
                moveResultScore = evaluateBoardLikability(
                    otherColor, board, self.heuristicWgts)
                board.undoPlyResult(boardMoveUndoDict)

                # Don't bother searching outside of target range
                if (moveResultScore > beta):
                    return (None, beta)

                # Check whether we've found something superior to our best
                elif (moveResultScore > alpha):
                    alpha = moveResultScore

            # All captures for this node have been evaluated - return best
            return (None, alpha)
        else:
            # Check whether it is even worth proceeding with evaluation
            if (standPatVal < alpha):
                return (None, alpha)
            elif (beta > standPatVal):
                beta = standPatVal

            # Evaluate all captures
            for capMv in captureMoves:
                boardMoveUndoDict = board.getPlyResult(capMv[0], capMv[1])
                moveResultScore = evaluateBoardLikability(
                    otherColor, board, self.heuristicWgts)
                board.undoPlyResult(boardMoveUndoDict)

                # Don't bother searching outside of target range
                if (moveResultScore > alpha):
                    return (None, alpha)

                # Check whether we've found something superior to our best
                elif (moveResultScore < beta):
                    beta = moveResultScore

            # All captures for this node have been evaluated - return best
            return (None, beta)
    def _quiescentSearch(self, board, color, alpha, beta, isMaxNode):
        """Perform a quiescent search on the given board, examining captures

        Enumerates captures, and evaluates them to see if they alter results

        @param board: The starting board state to evaluate
        @param color: The color of the player to generate a move for
        @param alpha: Nodes with a likability below this will be ignored
        @param beta: Nodes with a likability above this will be ignored
        @param isMaxNode: Is this a beta node? (Is this node seeking to
                        maximize the value of child nodes?)

        @return: A tuple with the following elements:
                1. None, or a move of the form (fromChessPosn, toChessPosn)
                    representing the next move that should be made by the given
                    player
                2. The likability of this move's path in the tree, as followed
                    by the search and as determined by the likability of the
                    leaf node terminating the path

        No timeout is allowed. This shouldn't take long, anyway. If it does,
        then we're doing good things.

        Note: this was influenced by information here: http://bit.ly/VYlJVC """

        otherColor = ChessBoard.getOtherColor(color)

        # Note the appeal of this board, with no captures
        standPatVal = evaluateBoardLikability(color, board,
                                              self.heuristicWgts)

        # Build up a list of capture moves

        moveChoices = enumMoves(board, color)
        moveFilterFunct = lambda m: ((board[m[1]] is not None) and
                                    (board[m[1]].color == otherColor))
        captureMoves = filter(moveFilterFunct, moveChoices)

        # Determine whether captures are a good or a bad thing
        if isMaxNode:

            # Check whether it is even worth proceeding with evaluation
            if (standPatVal > beta):
                return (None, beta)
            elif (alpha < standPatVal):
                alpha = standPatVal

            # Evaluate all captures
            for capMv in captureMoves:
                boardMoveUndoDict = board.getPlyResult(capMv[0], capMv[1])
                moveResultScore = evaluateBoardLikability(otherColor, board,
                                                          self.heuristicWgts)
                board.undoPlyResult(boardMoveUndoDict)

                # Don't bother searching outside of target range
                if (moveResultScore > beta):
                    return (None, beta)

                # Check whether we've found something superior to our best
                elif (moveResultScore > alpha):
                    alpha = moveResultScore

            # All captures for this node have been evaluated - return best
            return (None, alpha)
        else:
            # Check whether it is even worth proceeding with evaluation
            if (standPatVal < alpha):
                return (None, alpha)
            elif (beta > standPatVal):
                beta = standPatVal

            # Evaluate all captures
            for capMv in captureMoves:
                boardMoveUndoDict = board.getPlyResult(capMv[0], capMv[1])
                moveResultScore = evaluateBoardLikability(otherColor, board,
                                                          self.heuristicWgts)
                board.undoPlyResult(boardMoveUndoDict)

                # Don't bother searching outside of target range
                if (moveResultScore > alpha):
                    return (None, alpha)

                # Check whether we've found something superior to our best
                elif (moveResultScore < beta):
                    beta = moveResultScore

            # All captures for this node have been evaluated - return best
            return (None, beta)