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
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
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
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)
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()
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
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
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
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]
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
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
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
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
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))
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