コード例 #1
0
ファイル: search.py プロジェクト: rrrane/connect_four
def min_value(board, player, alpha, beta, depth):

    #if terminal state then return utility of leaf node
    if is_terminal(board):
        u, alp, bet = utility(board, player)
        return u, alp, bet, -1

    #if reached depth limit then return utility 0
    #that is, consider it a draw
    if depth == 0:
        u = 0
        return u, -1, -1, -1

    v = math.inf
    ret_mov = -1
    a, b = alpha, beta

    #get list of possible moves and shuffle it
    legal_moves = shuffle(board.generate_moves())

    #find a move with minimum utility
    for mov in legal_moves:
        board.make_move(mov)
        min_val, a1, b1, m = max_value(board, player, a, b, depth - 1)
        if min_val < v:
            v, ret_mov = min_val, mov

        board.unmake_last_move()
        if v <= a:
            b = v
            return v, a, b, ret_mov

        b = min(b, v)

    return v, a, b, ret_mov
コード例 #2
0
ファイル: search.py プロジェクト: rrrane/connect_four
def perft(board, depth):

    #Get all possible moves
    legal_moves = board.generate_moves()

    #Check if last move caused win
    lmv = board.last_move_won()

    #if last move won the game or no valid move available
    if lmv or len(legal_moves) <= 0:
        #Unmake last move made by calling function
        board.unmake_last_move()
        return 1

    #if last move did not win the game and valid moves are available,
    #but depth limit is reached
    if depth == 1:
        board.unmake_last_move()
        return len(legal_moves)

    #initialize variable to count leaf nodes visited so far
    count = 0

    #For all legal moves from current state, call perft recursively
    for i in legal_moves:
        board.make_move(i)
        count += perft(board, depth - 1)

    #Unmake last move made by calling function before returning count
    board.unmake_last_move()

    #return number of leaf nodes visited
    return count

    pass
コード例 #3
0
ファイル: Player.py プロジェクト: rrrane/connect_four
	def min_value(self, board, player, alpha, beta, depth):
		
		#check if termial state
		if self.is_terminal(board):
			u, alp, bet = self.utility(board, player)
			return u, alp, bet, -1
		
		#check if depth limit is reached
		if depth == 0:
			u = 0
			return u, -1, -1, -1
		
		v = math.inf
		ret_mov = -1
		a, b = alpha, beta
		
		#Get list of all possible moves
		legal_moves = self.shuffle(board.generate_moves())
		
		#Search for a move with minimum utility
		for mov in legal_moves:
			board.make_move(mov)
			min_val, a1, b1, m = self.max_value(board, player, a, b, depth - 1)
			if min_val < v:
				v, ret_mov = min_val, mov
			
			board.unmake_last_move()
			if v <= a:
				b = v
				return v, a, b, ret_mov
			
			b = min(b, v)
			
		return v, a, b, ret_mov
コード例 #4
0
 def iter(board, depth):
     nonlocal total
     total += 1
     if depth > 0 and not board.is_finished:
         for move in board.legal_moves:
             board.make_move(move)
             iter(board, depth - 1)
             board.unmake_move(move)
コード例 #5
0
ファイル: chess.py プロジェクト: willschwarzer/Chess
def play_game(agent1, agent2, surface, variant, wait_between):
    ''' Play chess '''
    global last_move
    chessboard = board.set_board(variant=variant)
    result = None
    pos_counts = defaultdict(int)
    if surface:
        surface.fill([0, 0, 0])
        draw_board(chessboard, surface)
        pygame.display.flip()
    pos_counts[chessboard] += 1
    while True:
        move = agent1.get_move(chessboard, pos_counts)
        last_move = move
        chessboard = board.make_move(chessboard, *move)
        if type(agent1) == MCTS:
            agent1.record_move(move)
        if type(agent2) == MCTS:
            agent2.record_move(move)
        if surface:
            surface.fill([0, 0, 0])
            draw_board(chessboard, surface)
            pygame.display.flip()
        # checkmate checks, etc
        pos_counts[chessboard] += 1
        result = board.get_result(chessboard, pos_counts, variant, 1)
        if result is not None:
            if wait_between:
                print("Click to continue...")
                wait_for_click()
            return result

        move = agent2.get_move(chessboard, pos_counts)
        last_move = move
        chessboard = board.make_move(chessboard, *move)
        if type(agent1) == MCTS:
            agent1.record_move(move)
        if type(agent2) == MCTS:
            agent2.record_move(move)
        if surface:
            surface.fill([0, 0, 0])
            draw_board(chessboard, surface)
            pygame.display.flip()
        pos_counts[chessboard] += 1
        result = board.get_result(chessboard, pos_counts, variant, -1)
        if result is not None:
            if wait_between:
                print("Click to continue...")
                wait_for_click()
            return result
        if surface:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    sys.exit()
コード例 #6
0
 def random_to_end(self, chessboard, pos_counts, side, depth):
     ''' side: which player's move it is in position chessboard''' 
     self.random_moves_list = [] if depth == 0 else self.random_moves_list
     result = board.get_result(chessboard, pos_counts, self.variant, -side, False)
     if result is not None:
         # Return a large number since we might be stopping early and doing
         # a heuristic evaluation, which might be bigger than 1 or -1
         return result*100000
     elif self.max_depth != 0 and depth == self.max_depth:
         return heuristic.evaluate(chessboard)
     elif self.use_heuristic:
         move = self.order_moves_naive(chessboard, side)[0] 
     else:
         moves = board.get_all_moves(chessboard, side)
         random.shuffle(moves)
         move = moves[0]
     # if board.piece_at_square(chessboard, *move[0]) == 11:
         # board.print_board(chessboard)
         # print(board.get_castling_rights(chessboard, 1))
         # print()
     new_board = board.make_move(chessboard, *move)
     self.random_moves_list.append(move)
     piece_counts = defaultdict(int)
     for row in range(8):
         for col in range(8):
             piece_counts[board.piece_at_square(new_board, row, col)] += 1
     if piece_counts[11] != 1 or piece_counts[12] != 1:
         breakpoint()
     pos_counts[new_board] += 1
     outcome = self.random_to_end(new_board, pos_counts, -side, depth+1)
     pos_counts[new_board] -= 1
     return outcome
コード例 #7
0
ファイル: search.py プロジェクト: kiliczsh/scaling-eureka
def dfs_rand(cur_node,point_table,time_limit):
    NODE_COUNT = 1
    print("Depth First Search with Random Selection")
    print("Time Limit is ",time_limit," seconds.")
    TIME_LIMIT = time_limit
    FRONTIER_LIST = [("flag",cur_node)]
    WHILE_COUNT = 0
    start = time()
    SUB_OPTIMAL = cur_node
    while FRONTIER_LIST:
        IS_SUB = (cur_node.depth_level > SUB_OPTIMAL.depth_level) and (count_pegs(cur_node) < count_pegs(SUB_OPTIMAL))
        if(IS_SUB):
            SUB_OPTIMAL = cur_node
        IS_TIME_OUT = int(time()) > int(TIME_LIMIT +  start)   
        if(IS_TIME_OUT):
            print("TIME IS OUT")
            print("Sub optimal solution found.")
            print("Total Run Time: ",time()-start)
            print("Frontier Length: ",len(FRONTIER_LIST))
            print("Node Visited: ",NODE_COUNT)
            print_parents(SUB_OPTIMAL)
            break
        if FRONTIER_LIST[0][0] =="flag":
            FRONTIER_LIST.pop(0)
        move_list = list_possible_moves(cur_node.board)
        move_list.sort(reverse=True)
        NODE_COUNT += int(len(move_list))
        SUB_FRONT_LIST = []
        for new_states in move_list:
            peg_x,peg_y, way = int(new_states[0]),int(new_states[1]),new_states[2]
            free_x,free_y = get_extra(peg_x,peg_y,way)
            move_value = int(point_table[peg_x,peg_y]) + int(point_table[free_x,free_y])
            new_board = board.make_move(np.copy(cur_node.board),peg_x,peg_y,way)
            new_node = MyNode(new_board,cur_node,(count_depth(cur_node)+1),count_pegs(cur_node))
            SUB_FRONT_LIST.append((move_value,new_node))
        shuffle(SUB_FRONT_LIST)
        for everything in SUB_FRONT_LIST:
            FRONTIER_LIST.append(everything)
        if FRONTIER_LIST:
            FRONTIER_LIST_LEN = len(FRONTIER_LIST)
            list_element = FRONTIER_LIST.pop(FRONTIER_LIST_LEN-1)
            cur_node = list_element[1]
            IS_FOUND = is_equal(cur_node.board,SOLUTION)
            if(IS_FOUND):
                print("“Optimum solution found.”")
                print("Total Run Time: ",time()-start)
                print("Frontier Length: ",len(FRONTIER_LIST))
                print("Node Visited: ",NODE_COUNT)
                print_parents(cur_node)
                return True
        else:
            print("NOTHING LEFT BEHIND")
            print("Total Run Time: ",time()-start)
            print("Frontier Length: ",len(FRONTIER_LIST))
            print("Node Visited: ",NODE_COUNT)
            cur_node = None
            return True
        WHILE_COUNT += 1
    pass
コード例 #8
0
 def add_move(self, move):
     """
     Adds a new node for the child resulting from move if one doesn't already exist.
     Returns true if a new node was added, false otherwise.
     """
     if move not in self.children:
         chessboard = board.make_move(self.chessboard, move[0], move[1])
         self.children[move] = Node(chessboard, self, self.ucb_const)
         return True
     return False
コード例 #9
0
ファイル: minimax.py プロジェクト: willschwarzer/Chess
    def order_moves_naive(self, chessboard, side):
        '''Given a board and a side, naively orders the set of all possible moves based solely on whether or not they involve the capture of a piece, and if so, how much the piece is worth.'''
        moves = board.get_all_moves(chessboard, side)

        moves_and_values = [
            (move,
             heuristic.evaluate(board.make_move(chessboard, move[0], move[1])))
            for move in moves
        ]
        moves_and_values.sort(reverse=(side == 1), key=lambda x: x[1])
        return [tup[0] for tup in moves_and_values]
コード例 #10
0
ファイル: minmax.py プロジェクト: Moogen/CS440
def minmax(white_workers, black_workers, depth, is_white_turn):
    '''[summary]
	
	[description]
	
	Arguments:
		white_workers {[type]} -- [description]
		black_workers {[type]} -- [description]
		depth {[type]} -- [description]
	
	Returns:
		float -- utility value
	'''
    global best_move, total_nodes, black_nodes, white_nodes, white_heuristic, black_heuristic, is_white_turn_global

    total_nodes += 1
    if is_white_turn_global:
        white_nodes += 1
    else:
        black_nodes += 1
    winner, utility, name = winner_exists(white_workers, black_workers)
    #check for winner first
    if winner:
        if (is_white_turn_global
                and name == "white") or (not is_white_turn_global
                                         and name == "black"):
            return utility
        else:
            return -utility
    #then do a depth check
    if depth >= max_depth:
        if is_white_turn_global:
            utility = white_heuristic(white_workers, black_workers,
                                      is_white_turn_global)
        else:
            utility = black_heuristic(white_workers, black_workers,
                                      is_white_turn_global)
        return utility

    moves = get_moves(white_workers, black_workers, is_white_turn)
    utility = -99999
    for move in moves:
        new_white_workers, new_black_workers = make_move(
            white_workers, black_workers, is_white_turn, move)
        result = minmax(new_white_workers, new_black_workers, depth + 1,
                        not is_white_turn)
        if utility < result:
            utility = result
            if (depth == 0):
                best_move = np.copy(move)
    return utility
コード例 #11
0
ファイル: minmax.py プロジェクト: Moogen/CS440
def alpha_beta(white_workers, black_workers, depth, is_white_turn, alpha,
               beta):
    global best_move, total_nodes, black_nodes, white_nodes, white_heuristic, black_heuristic, is_white_turn_global

    total_nodes += 1
    if is_white_turn_global:
        white_nodes += 1
    else:
        black_nodes += 1
    winner, utility, name = winner_exists(white_workers, black_workers)
    #check for winner first
    if winner:
        if (is_white_turn_global
                and name == "white") or (not is_white_turn_global
                                         and name == "black"):
            return utility
        else:
            return -utility
    #then do a depth check
    if depth >= max_depth:
        if is_white_turn_global:
            utility = white_heuristic(white_workers, black_workers,
                                      is_white_turn_global)
        else:
            utility = black_heuristic(white_workers, black_workers,
                                      is_white_turn_global)
        return utility

    moves = get_moves(white_workers, black_workers, is_white_turn)
    utility = -99999
    for move in moves:
        new_white_workers, new_black_workers = make_move(
            white_workers, black_workers, is_white_turn, move)
        result = alpha_beta(new_white_workers, new_black_workers, depth + 1,
                            not is_white_turn, alpha, beta)
        if utility < result:
            utility = result
            if (depth == 0):
                best_move = np.copy(move)
        if is_white_turn:
            if result < beta:
                return utility
            alpha = max(alpha, utility)
        else:
            if result < alpha:
                return utility
            beta = max(beta, utility)

    return utility
コード例 #12
0
ファイル: search.py プロジェクト: kiliczsh/scaling-eureka
def ids_bfs(cur_node,point_table,depth_level_param,time_limit): 
    NODE_COUNT = 1
    print("IDS For Depth: ",depth_level_param)
    print("Time Limit is ",time_limit," seconds.")
    TIME_LIMIT = time_limit
    FRONTIER_LIST = [("flag",cur_node)]
    WHILE_COUNT = 0
    start = time()
    SUB_OPTIMAL = cur_node
    while FRONTIER_LIST:
        IS_DEPTH_GOAL = cur_node.depth_level > depth_level_param
        if(IS_DEPTH_GOAL):
            print("Depth limit is over.")
            print("Depth ",cur_node.depth_level - 1 ," completed.")
            print("Total Run Time: ",time()-start)
            print("Frontier Length: ",len(FRONTIER_LIST))
            print("Node Visited: ",NODE_COUNT)
            print()
            return SUB_OPTIMAL
        IS_SUB = (cur_node.depth_level > SUB_OPTIMAL.depth_level) and (count_pegs(cur_node) < count_pegs(SUB_OPTIMAL))
        if(IS_SUB):
            SUB_OPTIMAL = cur_node
        IS_TIME_OUT = int(time()) > int(TIME_LIMIT) +  int(start)      
        if(IS_TIME_OUT):
            print("TIME IS OUT")
            print("Sub optimal solution found.")
            print("Sub optimal solution located at depth: ",SUB_OPTIMAL.depth_level)
            print("Total Run Time: ",time()-start)
            print("Frontier Length: ",len(FRONTIER_LIST))
            print("Node Visited: ",NODE_COUNT)
            #print_parents(SUB_OPTIMAL)
            print()
            return SUB_OPTIMAL
        if (FRONTIER_LIST[0][0] == "flag"):
            FRONTIER_LIST.pop(0)
        move_list = list_possible_moves(cur_node.board)
        move_list.sort(reverse=True)
        NODE_COUNT += int(len(move_list))
        SUB_FRONT_LIST = []
        for new_states in move_list:
            peg_x,peg_y, way = int(new_states[0]),int(new_states[1]),new_states[2]
            free_x,free_y = get_extra(peg_x,peg_y,way)
            move_value = int(point_table[peg_x,peg_y]) + int(point_table[free_x,free_y])
            new_board = board.make_move(np.copy(cur_node.board),peg_x,peg_y,way)
            new_node = MyNode(new_board,cur_node,(count_depth(cur_node)+1),count_pegs(cur_node))
            SUB_FRONT_LIST.append((move_value,new_node))
        SUB_FRONT_LIST.sort(key=itemgetter(0))
        for everything in SUB_FRONT_LIST:
            FRONTIER_LIST.append(everything)
        if FRONTIER_LIST:
            FRONTIER_LIST_LEN = len(FRONTIER_LIST)
            list_element = FRONTIER_LIST.pop(0)
            cur_node = list_element[1]
            IS_FOUND = is_equal(cur_node.board,SOLUTION) and (cur_node.depth_level >= 31)
            if(IS_FOUND):
                print("Optimum solution found for depth: ",cur_node.depth_level - 1)
                print("Total Run Time: ",time()-start)
                print("Frontier Length: ",len(FRONTIER_LIST))
                print("Node Visited: ",NODE_COUNT)
                print_parents(cur_node)
                print()
                return cur_node
        else:
            print("NOTHING LEFT BEHIND")
            print("Total Run Time: ",time()-start)
            print("Frontier Length: ",len(FRONTIER_LIST))
            print("Node Visited: ",NODE_COUNT)
            cur_node = None
            return SUB_OPTIMAL
        WHILE_COUNT += 1
コード例 #13
0
ファイル: search.py プロジェクト: kiliczsh/scaling-eureka
def bfs(cur_node,point_table,time_limit):
    context = zmq.Context()
    socket = context.socket(zmq.REP)
    socket.bind("tcp://*:5558")
    try:  
        NODE_COUNT = 1
        print("Breadth First Search")
        print("Time Limit is ",time_limit," seconds.")
        TIME_LIMIT = time_limit
        FRONTIER_LIST = [("flag",cur_node)]
        WHILE_COUNT = 0
        start = time()
        SUB_OPTIMAL = cur_node
        while FRONTIER_LIST:
            if(WHILE_COUNT%100 ==0 ):
                print("Tur: ", WHILE_COUNT)
            IS_SUB = not((cur_node.depth_level > SUB_OPTIMAL.depth_level) and (count_pegs(cur_node) < count_pegs(SUB_OPTIMAL)))
            if(IS_SUB):
                SUB_OPTIMAL = cur_node
            IS_TIME_OUT = int(time()) > int(TIME_LIMIT) +  int(start)
            if(psutil.virtual_memory().free < 125000000): 
                print("MEMORY OUT")
                print("Sub optimal solution found.")
                print("Total Run Time: ",time() - start)
                print("Frontier Length: ",len(FRONTIER_LIST))
                print("Node Visited: ",NODE_COUNT)    
                print_parents(SUB_OPTIMAL)
                break
            if(IS_TIME_OUT):
                print("TIME IS OUT")
                print("Sub optimal solution found.")
                print("Total Run Time: ",time() - start)
                print("Frontier Length: ",len(FRONTIER_LIST))
                print("Node Visited: ",NODE_COUNT)    
                print_parents(SUB_OPTIMAL)
                break
            if FRONTIER_LIST[0][0] =="flag":
                FRONTIER_LIST.pop(0)
            move_list = list_possible_moves(cur_node.board)
            move_list.sort(reverse=True)
            NODE_COUNT += int(len(move_list))
            # create new boards from move_list and add to SUB_FRONT_LIST
            SUB_FRONT_LIST = []
            for new_states in move_list:
                peg_x,peg_y, way = int(new_states[0]),int(new_states[1]),new_states[2]
                free_x,free_y = get_extra(peg_x,peg_y,way)
                move_value = int(point_table[peg_x,peg_y]) + int(point_table[free_x,free_y])
                new_board = board.make_move(np.copy(cur_node.board),peg_x,peg_y,way)
                new_node = MyNode(new_board,cur_node,(count_depth(cur_node)+1),count_pegs(cur_node))
                SUB_FRONT_LIST.append((move_value,new_node))
            SUB_FRONT_LIST.sort(key=itemgetter(0))
            # add all new boards in an order to FRONTIER_LIST
            for everything in SUB_FRONT_LIST:
                FRONTIER_LIST.append(everything)
            if FRONTIER_LIST:
                # choose new board node to continue searching and check if is it a solution
                FRONTIER_LIST_LEN = len(FRONTIER_LIST)
                list_element = FRONTIER_LIST.pop(0)
                cur_node = list_element[1]
                IS_FOUND = is_equal(cur_node.board,SOLUTION)
                if(IS_FOUND):
                    print("Optimum solution found.")
                    print("VM Used: ",psutil.virtual_memory().used)
                    print("VM Free: ",psutil.virtual_memory().free)
                    print("Total Run Time: ",time()-start)
                    print("Frontier Length: ",len(FRONTIER_LIST))
                    print("Node Visited: ",NODE_COUNT)
                    print_parents(cur_node)
                    return True
            else:
                print("NOTHING LEFT BEHIND")
                print("Total Run Time: ",time()-start)
                cur_node = None
                return True
            WHILE_COUNT += 1
    except KeyboardInterrupt:
        print("Sub optimal solution found.")
        print("VM Used: ",psutil.virtual_memory().used)
        print("VM Free: ",psutil.virtual_memory().free)
        print("Total Run Time: ",time() - start)
        print("Frontier Length: ",len(FRONTIER_LIST))
        print("Node Visited: ",NODE_COUNT)    
        print_parents(SUB_OPTIMAL)
    finally:
        socket.close()
        context.term()    
    return SUB_OPTIMAL
コード例 #14
0
ファイル: minmax.py プロジェクト: Moogen/CS440
def run_alphabeta():
    global best_move, white_heuristic, black_heuristic, is_white_turn_global, average_total_nodes, average_time_per_move
    global num_wins_white, num_wins_black

    start_time = time.time()

    #change these heuristics depending on which problem you're doing
    white_heuristic = custom_heuristic_offensive
    black_heuristic = custom_heuristic_defensive

    #run boardgame matches a certain number of times
    for i in range(num_simulations):
        reset()

        white_workers, black_workers = setup()
        winner = False
        is_white_turn_global = True
        moves = np.array(np.zeros((0, 2, 2)))

        game_time = time.time()

        while (not winner):
            best_move = None
            #print(utility, is_white_turn_global)
            utility = alpha_beta(white_workers, black_workers, 0,
                                 is_white_turn_global, -99999, -99999)

            #make the move
            white_workers, black_workers = make_move(white_workers,
                                                     black_workers,
                                                     is_white_turn_global,
                                                     best_move)
            winner, utility, winner_name = winner_exists(
                white_workers, black_workers)
            moves = np.insert(moves, len(moves), best_move, 0)

            is_white_turn_global = not is_white_turn_global
            #print_board(white_workers, black_workers)

        average_time_per_move += (time.time() - game_time) / len(moves)
        #The final state of the board (who owns each square) and the winning player.
        print_board(white_workers, black_workers)
        print("Winner: " + winner_name)
        print()

        #The number of opponent workers captured by each player, as well as the total number of moves required till the win.
        print("Pieces captured by white: " + str(16 - len(black_workers)))
        print("Pieces captured by black: " + str(16 - len(white_workers)))
        print("Number of moves: " + str(len(moves)))

        #update simulation variables
        if winner_name == 'white':
            num_wins_white += 1
        elif winner_name == 'black':
            num_wins_black += 1
        average_total_nodes += total_nodes / len(moves)

    print()
    print("=== SIMULATION RESULTS ===")
    print("Number of runs: " + str(num_simulations))
    print("White wins: " + str(num_wins_white),
          "Black wins: " + str(num_wins_black))
    print()
    #The total number of game tree nodes expanded by each player in the course of the game.
    print("Average white nodes expanded per game: " +
          str(white_nodes / num_simulations))
    print("Average black nodes expanded per game: " +
          str(black_nodes / num_simulations))
    print()
    #The average number of nodes expanded per move and the average amount of time to make a move.
    print("Average nodes expanded per move: " +
          str(average_total_nodes / num_simulations))
    print("Average time per move: " +
          str(average_time_per_move / num_simulations))
    print()

    end_time = time.time()
    print("Time: " + str(end_time - start_time))
    print("Average time per game: " +
          str((end_time - start_time) / num_simulations))
コード例 #15
0
ファイル: minimax.py プロジェクト: willschwarzer/Chess
 def alpha_beta(self, chessboard, pos_counts, depth, alpha, beta, side):
     '''Given a board and a move, returns an evaluation for that move by recursing over every possible move in each state until the depth limit is reached, then using the evaluate() function and passing the values back up through minimax with alpha-beta pruning.'''
     # if not board.find_king(chessboard, 1) and self.variant != 'horde':
     #     return (None, -10000)
     # elif not board.find_king(chessboard, -1):
     #     return (None, 10000)
     # elif pos_counts[chessboard] == 3:
     #     return (None, 0)
     # elif side == 1:
     #     if board.has_no_moves(chessboard, 1):
     #         if board.test_check(chessboard, 1) or self.variant == 'horde':
     #             return (None, -10000)
     #         else:
     #             return (None, 0)
     # elif side == -1:
     #     if board.has_no_moves(chessboard, -1):
     #         if board.test_check(chessboard, -1):
     #             return (None, 10000)
     #         else:
     #             return (None, 0)
     result = board.get_result(chessboard, pos_counts, self.variant, side,
                               False)
     if result is not None:
         # Return a large number since we might be stopping early and doing
         # a heuristic evaluation, which might be bigger than 1 or -1
         return (None, result * 100000)
     elif depth == self.depth:
         value = heuristic.evaluate(chessboard)
         return (None, value)
     ordered_moves = self.order_moves_naive(chessboard, side)
     if side == 1:
         best_move = (None, -100000)
         for move in ordered_moves:
             new_board = board.make_move(chessboard, move[0], move[1])
             pos_counts[new_board] += 1
             _, move_value = self.alpha_beta(new_board, pos_counts,
                                             depth + 1, alpha, beta, -1)
             noise = np.random.normal(scale=0.05)
             move_value += noise
             pos_counts[new_board] -= 1
             if move_value > best_move[1]:
                 best_move = (move, move_value)
             alpha = max(alpha, best_move[1])
             if beta <= best_move[1]:
                 return best_move
         return best_move
     else:
         best_move = (None, 100000)
         for move in ordered_moves:
             new_board = board.make_move(chessboard, move[0], move[1])
             pos_counts[new_board] += 1
             _, move_value = self.alpha_beta(new_board, pos_counts,
                                             depth + 1, alpha, beta, 1)
             noise = np.random.normal(scale=0.05)
             move_value += noise
             pos_counts[new_board] -= 1
             if move_value < best_move[1]:
                 best_move = (move, move_value)
             beta = min(beta, best_move[1])
             if alpha >= best_move[1]:
                 return best_move
         return best_move
     return total