class StudentAI(): def __init__(self,col,row,p): self.col = col self.row = row self.p = p self.board = Board(col,row,p) self.board.initialize_game() self.color = '' self.opponent = {1:2,2:1} self.color = 2 def get_move(self, move): #print(self.color) if len(move) != 0: self.board.make_move(move,self.opponent[self.color]) else: self.color = 1 moves = self.board.get_all_possible_moves(self.color) #self.train() self.simulate_lr(self.color) #index = randint(0,len(moves)-1) #inner_index = randint(0,len(moves[index])-1) #move = moves[index][inner_index] ql = QLearning() move = ql.make_action(self.board, moves) self.board.make_move(move, self.color) self.movecount += 1 return move
class StudentAI(): def __init__(self,col,row,p): self.col = col self.row = row self.p = p self.board = Board(col,row,p) self.board.initialize_game() self.color = '' self.opponent = {1:2,2:1} self.color = 2 self.count = 0 def get_move(self,move): if len(move) != 0: self.board.make_move(move,self.opponent[self.color]) else: self.color = 1 moves = self.board.get_all_possible_moves(self.color) # index = randint(0,len(moves)-1) # inner_index = randint(0,len(moves[index])-1) # move = moves[index][inner_index] if len(moves) == 1 and len(moves[0]) == 1: move = moves[0][0] if self.count < 15: mct = MonteCarloTree(self.board, self.color, self.opponent, (10, 0, -10)) move = mct.get_action(10, 0) self.board.make_move(move, self.color) else: mct = MonteCarloTree(self.board, self.color, self.opponent, (10, 0, -10)) move = mct.get_action(10, 0) self.board.make_move(move, self.color) return move
class StudentAI(): def __init__(self,row,col,p): self.row = row self.col = col self.p = p self.board = Board(row,col,p) self.board.initialize_game() self.color = '' self.opponent = {1:2,2:1} self.color = 2 def get_move(self, move): if len(move) != 0: self.board.make_move(move, self.opponent[self.color]) else: self.color = 1 moves = self.board.get_all_possible_moves(self.color) tree_depth = 4 # for beta alpha pruning alpha = -math.inf beta = math.inf # best moves list best_moves = [] # Get best moves for row in moves:
class ManualAI(): """ This class describes the ManualAI. """ def __init__(self, col, row, p): """ Intializes manualAI @param row: no of rows in the board @param col: no of columns in the board @param k: no of rows to be filled with checker pieces at the start @return : @raise : """ self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = 2 self.opponent = {1: 2, 2: 1} # to switch turns after each turn def get_move(self, move): """ get_move function for manualAI called from the gameloop in the main module. @param move: A Move object describing the move. @return res_move: A Move object describing the move manualAI wants to make. This move is basically console input. @raise : """ if move.seq: # if move.seq is not an empty list self.board.make_move(move, self.opponent[self.color]) else: self.color = 1 moves = self.board.get_all_possible_moves(self.color) #print(moves) while True: try: for i, checker_moves in enumerate(moves): print(i, ':[', end="") for j, move in enumerate(checker_moves): print(j, ":", move, end=", ") print("]") index, inner_index = map( lambda x: int(x), input("Select Move {int} {int}: ").split( )) # input is from console is handled here. res_move = moves[index][inner_index] except KeyboardInterrupt: raise KeyboardInterrupt except: print('invalid move') continue else: break self.board.make_move(res_move, self.color) return res_move
def simulate_lr(self, color): # simulate one time # record all X features to feature_matrix # update the y value accordingly print("entering simulations") newboard = Board(self.col, self.row, self.p) newboard.initialize_game() feature_list_b = [] feature_list_w = [] win = 0 ### TODO: Fixing Current move in a new board curr_turn = self.opponent[color] for turn in range(50): if newboard.is_win(color) == color: win = 1 break elif newboard.is_win(self.opponent[color]) == self.opponent[color]: break move = self.minimax_move(newboard.get_all_possible_moves(curr_turn)) newboard.make_move(move, curr_turn) b, w = self.get_X(self.board) feature_list_b.append(b) feature_list_w.append(w) self.feature_matrix = np.append(self.feature_matrix, np.array([b, w]), axis=0) print(self.feature_matrix) curr_turn = self.opponent[curr_turn] else: win = 0.5 # matrix = np.array([feature_list_b, feature_list_w]) # feature_matrix = np.hstack((matrix, np.zeros((matrix.shape[0], 1)))) # TODO: Fixing y value update if win == 1 and color == 1: for fb in feature_list_b: index = np.where(fb in self.feature_matrix[:, 0:self.feature_size]) if index == []: self.feature_matrix = np.append(self.feature_matrix, np.array([b, w]), axis=0) self.feature_matrix[index, self.feature_size] += 1 elif win == 0 and color == 1: for fw in feature_list_w: index = np.where(fw in self.feature_matrix[:, 0:self.feature_size]) if index == []: self.feature_matrix = np.append(self.feature_matrix, np.array([b, w]), axis=0) self.feature_matrix[index, self.feature_size] += 1 return win
class StudentAI(): """ This class describes randomAI """ def __init__(self, col, row, p): """ Intializes randomAI @param row: no of rows in the board @param col: no of columns in the board @param p: no of rows to be filled with checker pieces at the start @return : @raise : """ self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} # to switch turns after each turn self.color = 2 def get_move(self, move): """ get_move function for randomAI called from the gameloop in the main module. @param move: A Move object describing the move. @return res_move: A Move object describing the move manualAI wants to make. This move is a random move from the set of valid moves. @raise : """ if len(move) != 0: self.board.make_move(move, self.opponent[self.color]) else: self.color = 1 moves = self.board.get_all_possible_moves(self.color) index = randint(0, len(moves) - 1) inner_index = randint(0, len(moves[index]) - 1) move = moves[index][inner_index] self.board.make_move(move, self.color) return move
class StudentAI(): def __init__(self,col,row,p): self.col = col self.row = row self.p = p self.board = Board(col,row,p) self.board.initialize_game() self.color = '' self.opponent = {1:2,2:1} self.color = 2 def get_move(self,move): if len(move) != 0: self.board.make_move(move,self.opponent[self.color]) else: self.color = 1 moves = self.board.get_all_possible_moves(self.color) index = randint(0,len(moves)-1) inner_index = randint(0,len(moves[index])-1) move = moves[index][inner_index] self.board.make_move(move,self.color) return move
def simulate(self, player): win = 0 counter = 0 fake_board = Board(self.col, self.row, self.p) self.copy_board(fake_board) # print("DIT ME DIEN") # fake_board.show_board() # totaltime = 0 while win == 0: moves = fake_board.get_all_possible_moves(player) if len(moves) == 1: index = 0 elif len(moves) == 0: win = self.opponent[player] break else: index = randint(0, len(moves) - 1) if len(moves[index]) == 1: inner_index = 0 else: inner_index = randint(0, len(moves[index]) - 1) move = moves[index][inner_index] fake_board.make_move(move, player) counter += 1 # bt = time.time() if fake_board.tie_counter >= fake_board.tie_max: win = -1 # totaltime += time.time() - bt # print("self.board.is_win():", time.time() - bt) player = self.opponent[player] # #print("total time is_win:", totaltime) # #bt = time.time() # for i in range(counter): # self.board.undo() # #rint("total time undo:", time.time() - bt) # fake_board.show_board() return win
class StudentAI(): def __init__(self,col,row,p): self.col = col self.row = row self.p = p self.board = Board(col,row,p) self.board.initialize_game() self.color = '' self.opponent = {1:2,2:1} self.color = 2 def get_move(self,move): if len(move) != 0: self.board.make_move(move,self.opponent[self.color]) else: self.color = 1 bestVal = -999 bestMove = None # if there is only one move to make, just make the move without evaluating possible_moves = self.board.get_all_possible_moves(self.color) if len(possible_moves) == 1 and len(possible_moves[0]) == 1: self.board.make_move(possible_moves[0][0], self.color) return possible_moves[0][0] for moves in possible_moves: for move in moves: self.board.make_move(move, self.color) val = self.search(1, StudentAI.switchColors(self.color), MIN, MAX) self.board.undo() if val > bestVal: bestVal = val bestMove = move self.board.make_move(bestMove, self.color) return bestMove def search(self, depth, currentColor, alpha, beta): if depth == 4 or self.board.is_win('B') or self.board.is_win('W'): return self.evaluate(currentColor) best = MIN if currentColor == self.color else MAX for moves in self.board.get_all_possible_moves(currentColor): for move in moves: self.board.make_move(move, currentColor) val = self.search(depth+1, StudentAI.switchColors(currentColor), alpha, beta) self.board.undo() if currentColor == self.color: best = max(best, val) alpha = max(alpha, best) elif currentColor != self.color: best = min(best, val) beta = min(beta, best) if beta <= alpha: return best return best def piece_differential(self, currentColor): if currentColor == 'B': return self.board.black_count - self.board.white_count return self.board.white_count - self.board.black_count def evaluate(self, currentColor): currentColor = 'B' if currentColor == 1 else 'W' oppColor = 'W' if currentColor == 'B' else 'B' # if we win in this game state, prefer to choose this path # if the opponent wins in this game state, stay away from this path if self.board.is_win(currentColor): return 500 elif self.board.is_win(oppColor): return -500 piece_location, kings = 0, 0 for i in range(self.board.row): for j in range(self.board.col): if (self.board.board[i][j].color == currentColor): if self.board.board[i][j].is_king: kings += 1 # we prefer the king to be in the middle of the board if i <= self.row / 2: piece_location += 7 + i else: piece_location += 7 + (self.board.row - i - 1) else: # we prefer the pawns to go to the opponent's side of the board if self.board.board[i][j].color == 'B': piece_location += 5 + i else: piece_location += 5 + (self.board.row - i - 1) elif (self.board.board[i][j].color == oppColor): if self.board.board[i][j].is_king: kings -= 1 # we prefer the opponent's king to not be in the middle of the board if i <= self.row / 2: piece_location -= 7 + i else: piece_location -= 7 + (self.board.row - i - 1) else: # we prefer the opponent's pawns to not be on our side of the board if self.board.board[i][j].color == 'B': piece_location -= 5 + i else: piece_location -= 5 + (self.board.row - i - 1) # if we have more kings, we prefer to play more aggressive if kings > 0: return piece_location + self.board.row * self.piece_differential(currentColor) else: return piece_location + self.piece_differential(currentColor) @staticmethod def switchColors(color): if color == 1: return 2 return 1
class StudentAI(): def __init__(self,col,row,p): self.col = col self.row = row self.p = p self.board = Board(col,row,p) self.board.initialize_game() self.color = '' self.opponent = {1:2,2:1} self.color = 2 def get_move(self,move): if len(move) != 0: self.board.make_move(move,self.opponent[self.color]) else: self.color = 1 moves = self.board.get_all_possible_moves(self.color) index = randint(0,len(moves)-1) inner_index = randint(0,len(moves[index])-1) # move = moves[index][inner_index] move = self.min_max_recursion(4, True)[0] self.board.make_move(move,self.color) return move def min_max_recursion(self, depth, maximizingPlayer): if depth == 0 and self.color == 1: return self.board.black_count - self.board.white_count elif depth == 0 and self.color == 2: return self.board.white_count - self.board.black_count maximum = -100 max_move = "" minimum = 100 min_move = "" if maximizingPlayer: selfmoves = self.board.get_all_possible_moves(self.color) #maximum = -100 for s_checker_moves in selfmoves: for sm in s_checker_moves: self.board.make_move(sm, self.color) Recurs = self.min_max_recursion(depth - 1, False) # print("Recurs: ",Recurs) temp = maximum if type(Recurs) == type(tuple()): maximum = max(maximum, Recurs[1]) else: maximum = max(maximum, Recurs) # print("maximum: ",maximum) if temp != maximum: max_move = sm #alpha = max(alpha, Recurs) # print("alpha",alpha) self.board.undo() #if beta <= alpha: # break return (max_move, maximum) else: #minimum = 100 oppmoves = self.board.get_all_possible_moves(self.opponent[self.color]) for o_checker_moves in oppmoves: for om in o_checker_moves: self.board.make_move(om, self.opponent[self.color]) Recurs = self.min_max_recursion(depth - 1, True) # print("Recurs: ",Recurs) temp = minimum if type(Recurs) == type(tuple()): minimum = min(minimum, Recurs[1]) else: minimum = min(minimum, Recurs) # print("minimum: ",minimum) if temp != minimum: min_move = om #beta = min(beta, Recurs) # print("beta: ", beta) self.board.undo() #if beta <= alpha: # break return (min_move, minimum)
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 self.root = Node(self.color, -1) self.start = None def flatten(self, ini_list) -> list: return sum(ini_list, []) def isTimeLeft(self): time = datetime.datetime.now() if (time - self.start).seconds < turnTimer: return True return False def select( self ) -> Node: #REMINDER: moves is the flattened list of all available moves maxNode = self.root maxUct = -1 ptr = self.root uct = None found = False while len(ptr.children) != 0: #Node is not a leaf node moves = self.flatten(self.board.get_all_possible_moves(ptr.color)) for m in moves: found = False for c in ptr.children: if not found and m == c.move: uct = c.UCT() if uct > maxUct: maxUct = uct maxNode = c found = True if not found: return ptr #Node is a leaf node, return parent to expand later if maxNode.move != -1: self.board.make_move(maxNode.move, ptr.color) ptr = maxNode # Node is leaf node return ptr #Same thing as line 135 def expand(self, node) -> Node: moves = self.flatten(self.board.get_all_possible_moves(node.color)) toMove = moves[0] childrenMoves = [] for c in node.children: childrenMoves.append(c.move.seq) for m in moves: if childrenMoves.count( m.seq ) == 0: #Get all available moves for node, then find the leaf node to expand toMove = m break child = Node(self.opponent[node.color], toMove, node) node.children.append(child) return child def simulate(self, child): players = {1: "B", 2: "W"} winner = None counter = 0 color = child.color while self.board.is_win(players[color]) == 0: moves = self.flatten(self.board.get_all_possible_moves(color)) if len(moves) != 0: #player has moves i = randint(0, len(moves) - 1) self.board.make_move(moves[i], color) color = self.opponent[color] counter += 1 else: #player doesnt have moves, but game hasn't ended yet color = self.opponent[color] winner = self.board.is_win(players[color]) while counter != 0: self.board.undo() counter -= 1 return winner def backProp(self, result, child): while child is not None: child.upSims() if result != child.color: child.upWins() child = child.parent def MCTS(self, moves) -> Move: while (self.isTimeLeft()): parent = self.select() expand = self.expand(parent) #TODO check if expand() returns None result = self.simulate(expand) self.backProp(result, expand) bestMove = None # self.root.children[i].move if len(self.root.children) == 0: index = randint(0, len(moves) - 1) bestMove = moves[index] else: bestWR = -1 i = 0 while i != len(self.root.children): if self.root.children[i].getWinRate() > bestWR: bestWR = self.root.children[i].getWinRate() bestMove = self.root.children[i].move i += 1 return bestMove def get_move(self, move): if len(move) != 0: self.board.make_move(move, self.opponent[self.color]) if self.root.parent is None: # len(self.root.children) == 0: #what if the root.children doesnt contain the one move we wanted? # FIX: checking len of self.root.children to moves of self.root self.root.move = move else: i = 0 while i != len(self.root.children): if self.root.children[i].move == move: break i += 1 if i != len(self.root.children): self.root = self.root.children[i] else: #no child node: add it new_root = Node(self.color, move, self.root) self.root.children.append(new_root) self.root = new_root else: self.color = 1 self.root.color = 1 self.start = datetime.datetime.now() moves = self.flatten(self.board.get_all_possible_moves( self.root.color)) move = self.MCTS(moves) self.board.make_move(move, self.root.color) # PROBLEM LINE: color mismatch # update root to move just picked from MCTS i = 0 while i != len(self.root.children): if self.root.children[i].move == move: break i += 1 self.root = self.root.children[i] return move
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 self.ct = 0 #self.dif_val = False self.size = self.col * self.row if self.size < 40: #6x6 #print(8) self.search_depth = 8 elif self.size < 50: #7x7 #print(7) self.search_depth = 5 elif self.size < 80: #8x8 #print(6) self.search_depth = 4 else: self.search_depth = 4 def get_move(self, move): if len(move) != 0: self.board.make_move(move, self.opponent[ self.color]) # Run opponent's move for self.board else: self.color = 1 try: self.search_depth except NameError: print("ERROR") search_depth = 5 if self.size < 40: #6x6 if self.ct == 5: self.search_depth += 1 #9 elif self.ct == 10: self.search_depth += 1 #10 elif self.size < 50: #7x7 if self.ct == 2: self.search_depth += 1 #6 elif self.ct == 5: self.search_depth += 1 #7 if self.ct == 10: self.search_depth += 1 #8 elif self.ct == 15: self.search_depth += 1 #9 elif self.ct == 20: self.search_depth += 1 #10 elif self.size < 80: #8x8 if self.ct == 3: self.search_depth += 1 #5 elif self.ct == 5: self.search_depth += 1 #6 elif self.ct == 7: self.search_depth += 1 #7 elif self.ct == 11: self.search_depth += 1 #8 else: if self.ct == 10: self.search_depth += 1 elif self.ct == 20: self.search_depth += 2 root = Tree(self.opponent[self.color]) # Tree root #print('Detph', self.search_depth, self.ct) self.rec_tree(root, self.search_depth) # Set up tree self.rec_min_max_heuristic(root) #self.rec_abp_heuristic(root) #self.rec_abp_v2(root) avail_moves = root.value[list(root.value)[0]] #cur_move = avail_moves[randint(0,len(avail_moves)-1)] cur_move = avail_moves[0] ''' print("ALL MOVES") moves = self.board.get_all_possible_moves(self.color) for i, checker_moves in enumerate(moves): print(i, ':[', end="") for j, move in enumerate(checker_moves): print(j, ":", move, end=", ") print("]") print("AVAIL MOVES") #print(avail_moves) for i, checker_moves in enumerate(avail_moves): print(i, ':[', end="") for j, move in enumerate(checker_moves): print(j, ":", move, end=", ") print("]") ''' #if self.dif_val: if debug: print("##########TREE##########") self.print_tree(root) if debug: print("##########TREE##########") # self.dif_val = False self.board.make_move(cur_move, self.color) # Make the optimal move move = cur_move return move # Board Heuristic def board_points( self): # 5 + row number for pawns, 5 + row number + 2 for kings king_pts_value = 5 + ( self.row - 1 ) + 5 #5 pts for piece, self.row -1 pts for pts at end of board, + 1 for being king pts = 0 b_pawns = set() b_kings = set() w_pawns = set() w_kings = set() for i in range(self.row): for j in range(self.col): checker = self.board.board[i][j] if checker.color == "B": #Black if checker.is_king: b_kings.add((i, j)) else: b_pawns.add((i, j)) elif checker.color == "W": #White if checker.is_king: w_kings.add((i, j)) else: w_pawns.add((i, j)) # if b_pawns == set(): # print("-" * 20) # self.board.show_board() # b_pawns = set() # b_kings = set() # w_pawns = set() # w_kings = set() # for i in range(self.row): # for j in range(self.col): # checker = self.board.board[i][j] # if checker.color == "B": #Black # if checker.is_king: # b_kings.add((i,j)) # else: # b_pawns.add((i,j)) # elif checker.color == "W": #White # if checker.is_king: # w_kings.add((i,j)) # else: # w_pawns.add((i,j)) for pawn in b_pawns: pts += 5 + pawn[0] for pawn in w_pawns: pts -= (5 + (self.row - pawn[0] - 1)) for king in b_kings: pts += king_pts_value dist = 0 for w in w_kings: dist += sqrt((king[0] - w[0])**2 + (king[1] - w[1])**2) for w in w_pawns: dist += sqrt((king[0] - w[0])**2 + (king[1] - w[1])**2) if len(w_kings) + len(w_pawns) != 0: pts -= dist / (len(w_kings) + len(w_pawns)) for king in w_kings: pts -= king_pts_value dist = 0 for b in b_kings: dist += sqrt((king[0] - b[0])**2 + (king[1] - b[1])**2) for b in b_pawns: dist += sqrt((king[0] - b[0])**2 + (king[1] - b[1])**2) if len(b_kings) + len(b_pawns) != 0: pts += dist / (len(b_kings) + len(b_pawns)) #if abs(pts) > 2: # self.dif_val = True #if debug: print(color(root.color), pts, -pts) return pts if self.color == 2 else -pts #BLACK(1) GOES FIRST, so positive points, if self.color == white(2), then return white pieces as positive points def print_tree(self, root, level=0): if not debug: return print("\t" * level, color(root.color), root.value, "->", root.move) if len(root.children) != 0: # Not Leaf node for child in root.children: self.print_tree(child, level + 1) def rec_tree(self, root: Tree, level=1): # Create tree up to depth level if level == 0: pass else: if root.move is not None: # Not root of tree self.board.make_move(root.move, root.color) # Check if win here maybe? avail_moves = self.board.get_all_possible_moves( self.opponent[root.color]) for i in range(len(avail_moves)): for j in range(len(avail_moves[i])): # print(root) root.children.append( Tree(self.opponent[root.color], avail_moves[i][j])) for child in root.children: self.rec_tree(child, level - 1) if root.move is not None: self.board.undo() # MinMax Functions def ftu(self, color): # Function to use (min vs max by color) if color == self.color: # Calculate Max return max else: # Calculate Min return min def min_max(self, children, color): # Returns dict -> {Max/min value: Moves to get here} ftu = self.ftu( color) # Use corresponding min or max depending on color value_map = {} for child in children: for v in child.value.keys(): value_map.setdefault(v, []).append( child.move ) # D: {heuristic value: Move to make to get here} # print(value_map) return {ftu(value_map): value_map[ftu(value_map)]} def rec_min_max_heuristic(self, root: Tree): # Apply min_max heuristic to tree if root.move is not None: # AKA this is root, the move is what opponent made to get here (none so we don't have to redo move on our board) self.board.make_move(root.move, root.color) if len(root.children) == 0: # Passed node has no children # Evaluate heuristic for board(and return?) root.value = { self.board_points(): [] } # Value will be dict with key = heuristic points and value = all the moves that result in that many points else: # Evaluate rec_heuristic for children, then retrieve values and apply min/max as appropriate for child in root.children: self.rec_min_max_heuristic(child) root.value = self.min_max(root.children, root.color) if root.move is not None: self.board.undo( ) # Undo move to revert action (done for searching) and return to parent # AlphaBeta Functions def set_alpha_beta(self, root, child, color): ftu = self.ftu(color) if child.value is None: print(child) if root.value is None: root.value = {} if color == self.color: # Max aka update alpha (This ai's turn) # return ftu(alpha, ftu(child.value)), beta if root.alpha < ftu(child.value): root.alpha = ftu(child.value) root.value.setdefault(root.alpha, []).append(child.move) else: # Min aka update beta (Opponent's turn) # return alpha, ftu(beta, ftu(child.value)) if root.beta > ftu(child.value): root.beta = ftu(child.value) root.value.setdefault(root.beta, []).append(child.move) def rec_abp_heuristic(self, root: Tree, alpha=-999, beta=999, level=0): # Alpha Beta Pruning if debug: print("\t" * level, color(root.color), "Enter: ", root.value, "->", root.move) old_val = root.value if root.move is not None: # AKA this is root, the move is what opponent made to get here (none so we don't have to redo move on our board) self.board.make_move(root.move, root.color) #self.board.show_board() if len( root.children ) == 0: # Passed node has no children aka this is lowest level/leaf root.value = {self.board_points(): []} if debug: print("\t" * level, "LEAF: ", root.value, "->", root.move) else: # Evaluate heuristic for child, retrieve value, update alphabeta, continue with next child if appropriate root.alpha = alpha root.beta = beta if debug: print("\t" * 16, "CHILDREN:", end=" ") for child in root.children: if debug: print(child.move, end=", ") if debug: print("(", color(self.opponent[root.color]), ")", sep="") for child in root.children: if root.alpha >= root.beta: # Break out of loop once alpha >= beta (Pruning) if debug: print("PRUNING") break self.rec_abp_heuristic(child, root.alpha, root.beta, level + 1) self.set_alpha_beta( root, child, root.color ) # Apply alpha/beta values based on min/max of child to current node if debug: print("\t" * level, color(root.color), "New Value: ", root.value, "->", root.move) if root.move is not None: self.board.undo() if debug: print("\t" * level, color(root.color), "Exit: ", root.value, "->", root.move) #print(max(list(root.value), key = abs), "\t", root.move, "->", root.value) #if abs(max(list(root.value), key = abs)) > 2: #print("\t" * level, "Enter: ", old_val, "->", root.move) #print("\t" * level, "Exit: ", root.value, "->", root.move) def rec_abp_v2(self, root: Tree, alpha=-999, beta=999): if root.move is not None: # AKA this is root, the move is what opponent made to get here (none so we don't have to redo move on our board) self.board.make_move(root.move, root.color) else: root.value = {} if len(root.children) == 0: root.value = self.board_points() if root.move is not None: self.board.undo() return root.value else: if color == self.color: #MaximizingPlayer #val = -999 for child in root.children: ''' val = max(val, rec_abp_v2(child, alpha, beta)) alpha = max(alpha, val) ''' val = self.rec_abp_v2(child, alpha, beta) if alpha > val: #Alpha > Val root.alpha = alpha else: #Val > Alpha alpha = val if root.move is None: #Root node, ie save the move to get here root.value.setdefault(alpha, []).append(child.move) root.alpha = alpha if alpha >= beta: break if root.move is not None: self.board.undo() return alpha else: #Minimizing Player #val = 999 for child in root.children: ''' val = min(val, alphabeta(child, alpha, beta)) beta = min(val, beta) ''' val = self.rec_abp_v2(child, alpha, beta) if beta < val: #Beta < Val root.beta = beta else: beta = val if root.move is None: root.value.setdefault(beta, []).append(child.move) root.beta = beta if alpha >= beta: break if root.move is not None: self.board.undo() return beta
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 #---------------------------------# self.area = self.row * self.col self.count = 0 if self.area <= 39: self.depth = 8 elif self.area <= 49: self.depth = 5 elif self.area <= 79: self.depth = 4 else: self.depth = 4 #get move of current game state def get_move(self, move): if len(move) != 0: self.board.make_move(move, self.opponent[self.color]) else: self.color = 1 #----------------------------------------------------------# # MINIMAX GAME TREE SEARCH AGENT #----------------------------------------------------------# root = MinimaxTree(self.opponent[self.color]) self.recursive_dfs(root, self.depth) self.recursive_minimax(root) move_list = root.data[list(root.data)[0]] current = move_list[0] #self.board.make_move(current, self.color) best_mcts_move = current move = current #----------------------------------------------------------# ''' #returns some random move from the list #--- DEBUGGING PURPOSES --- moves_user = self.board.get_all_possible_moves(self.color) moves_opponent = self.board.get_all_possible_moves(self.opponent[self.color]) print("---USER MOVES---") for item in moves_user: for i in item: print(i.seq) print("---OPPONENT MOVES---") for item in moves_opponent: for i in item: print(i.seq) board_sim = copy.deepcopy(self.board) move_sim = moves_user[0] print("Simulation Making move:" + str(move_sim)) board_sim.make_move(move_sim[0], self.color) print("----- SIMULATION B -----") board_sim.show_board() print("Simulation score =" + str(self.board_heuristic(board_sim))) print("Terminal? :" + str(board_sim.is_win(self.color))) print("----------------------") #current2 = Move(moves_user[randint(0, len(moves_user) - 1)]) #self.board.make_move(list(current2[0]), self.color) #print(current2) print("AI Making Move:" + str(move))''' #----------------------------------------------------------# # MONTE CARLO TREE SEARCH AGENT #----------------------------------------------------------# m_list = self.board.get_all_possible_moves(self.color) if len(m_list) == 1: self.board.make_move(m_list[0][0], self.color) return m_list[0][0] root = MonteCarloTree(self.board, self.color, m_list) self.board.make_move(best_mcts_move, self.color) move = best_mcts_move #----------------------------------------------------------# return move # -------- CHECKERS BOARD HEURISTIC FUNCTION --------# #Sources Referred: #https://github.com/techwithtim/Python-Checkers-AI/blob/master/checkers/board.py #https://www.cs.huji.ac.il/~ai/projects/old/English-Draughts.pd @staticmethod def check_distance(p1, p2): #for two given checker pieces return the distance #using the distance formula sqrt((x2-x1)^2 + (y2-y1)^2) dist = sqrt(((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)) return dist def board_heuristic(self, board): score = 0 king_score = 10 + (self.col - 1) pawns_b = [] #black pawns pawns_w = [] #white pawns kings_b = [] #black kings kings_w = [] #white kings for r in range(self.row): for c in range(self.col): piece = board.board[r][c] if piece.color == "B": if piece.is_king: kings_b.append((r, c)) else: pawns_b.append((r, c)) elif piece.color == "W": if piece.is_king: kings_w.append((r, c)) else: pawns_w.append((r, c)) for pb in pawns_b: score = score + pb[0] + 10 for pw in pawns_w: score = score - (10 + (self.row - 1 - pw[0])) for kb in kings_b: score = score + king_score distance = 0 for kw in kings_w: distance = distance + self.check_distance(kb, kw) for pw in pawns_w: distance = distance + self.check_distance(kb, pw) if len(kings_w) + len(pawns_w) != 0: score = score - (distance / (len(kings_w) + len(pawns_w))) for kw in kings_w: score = score + king_score distance = 0 for kb in kings_b: distance = distance + self.check_distance(kw, kb) for pb in pawns_b: distance = distance + self.check_distance(kw, pb) if len(kings_b) + len(pawns_b) != 0: score = score - (distance / (len(kings_b) + len(pawns_b))) if self.color == 2: return score else: return -score #Sources Referred: #https://www.geeksforgeeks.org/depth-first-search-or-dfs-for-a-graph/ def recursive_dfs(self, root: MinimaxTree, depth=1): if depth == 0: pass else: if root.move is not None: self.board.make_move(root.move, root.color) all_moves_list = self.board.get_all_possible_moves( self.opponent[root.color]) for r in range(len(all_moves_list)): for c in range(len(all_moves_list[r])): root.child_nodes.append( MinimaxTree(self.opponent[root.color], all_moves_list[r][c])) for node in root.child_nodes: self.recursive_dfs(node, depth - 1) if root.move is not None: self.board.undo() #-------- MINIMAX ALGORITHM-------# #Sources Referred: #https://www.geeksforgeeks.org/minimax-algorithm-in-game-theory-set-1-introduction/?ref=lbp #https://www.geeksforgeeks.org/minimax-algorithm-in-game-theory-set-4-alpha-beta-pruning/ #https://github.com/techwithtim/Python-Checkers-AI/blob/master/minimax/algorithm.py def min_or_max(self, color): if color == self.color: return max else: return min def minimax(self, color, children): min_or_max = self.min_or_max(color) hash_table = {} for child in children: for val in child.data.keys(): hash_table.setdefault(val, []).append(child.move) return {min_or_max(hash_table): hash_table[min_or_max(hash_table)]} def recursive_minimax(self, root: MinimaxTree): if root.move is not None: self.board.make_move(root.move, root.color) if len(root.child_nodes) == 0: root.data = {self.board_heuristic(self.board): []} else: for node in root.child_nodes: self.recursive_minimax(node) root.data = self.minimax(root.color, root.child_nodes) if root.move is not None: self.board.undo() # -------- MONTE CARLO TREE SEARCH ALGORITHM-------# # Sources Referred: # https://www.geeksforgeeks.org/ml-monte-carlo-tree-search-mcts/ # https://int8.io/monte-carlo-tree-search-beginners-guide/ # https://www.analyticsvidhya.com/blog/2019/01/monte-carlo-tree-search-introduction-algorithm-deepmind-alphago/ def simulate(self, board, move): #print("SIMULATE") #board_sim = copy.deepcopy(board) #board_sim.make_move(move, self.color) board.make_move(move, self.color) score = self.board_heuristic(board) #board_sim return score, board #,board_sim def expansion(self, node: MonteCarloTree): #print("EXPANSION") #print(node) if node.board.is_win(self.color) > 0 or node.board.is_win( self.opponent[self.color]) > 0: node.expanded = True return for move_set in node.move_list: for move in move_set: score, new_board = self.simulate(node.board, move) m_list = new_board.get_all_possible_moves(self.color) new_mcts_node = MonteCarloTree(new_board, self.color, m_list) new_mcts_node.move = move new_mcts_node.parent_node = node new_mcts_node.board_eval = score node.child_nodes.append(new_mcts_node) node.board.undo() #print("FINAL EXPANSION:") #print(node) def rollout(self, node: MonteCarloTree, steps=0): #print("ROLLOUT") #print(node) if node.board.is_win(self.color) > 0 or node.board.is_win( self.opponent[self.color]) > 0: #or steps > 1000: node.no_of_wins += int(node.board.is_win(self.color)) node.no_of_steps = steps #print("Roll-BACKPROPOGATE") self.backpropogate(node) return while node.board.is_win(self.color) < 1 or node.board.is_win( self.opponent[self.color]) < 1: self.expansion(node) #Recursive Expansion children = node.child_nodes for child in children: if not child.expanded: #print("ROLLOUT RECURSE") self.rollout(child, steps + 1) #print("END ROLLOUT RECURSE") return else: pass def backpropogate(self, node: MonteCarloTree): #print("BACKPROPOGATE") #print(node) #update UCB1 value too if node.parent_node is None: #print("reached root node") return if node.parent_node is not None: node.parent_node.ucb1_eval += node.ucb1_eval node.parent_node.no_of_wins += node.no_of_wins node.parent_node.no_of_steps += node.no_of_steps node.board.undo() #print("Recurse-BACKPROPOGATE") self.backpropogate(node.parent_node) #print("End Recurse Backpropogate") def monte_carlo_tree_search(self, node: MonteCarloTree): #print("MCTS SEARCH START") self.expansion(node) #print(node) for nd in node.child_nodes: #should be a while loop, and always start from the root self.rollout(nd) root_wins = node.no_of_wins self.ucb1_evaluation(root_wins, nd) best_child = self.choose_best_child(node) return best_child.move def choose_best_child(self, node: MonteCarloTree): children = sorted(node.child_nodes, key=attrgetter('ucb1_eval'), reverse=True) return children[0] def ucb1_evaluation(self, no_of_wins_r, node: MonteCarloTree): #print("No. of steps = " + str(node.no_of_steps)) #print("No. of wins at root = " + str(no_of_wins_r)) #print("No. of wins at this node = " + str(node.no_of_wins)) node.ucb1_eval = node.no_of_steps + 2 * math.sqrt( (math.log(no_of_wins_r)) / (node.no_of_wins + 1)) return '''
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 self.movecount = 1 self.file = f"{self.col}-{self.row}-{self.color}-{randint(0, 500)}-test.txt" self.start = time.time() self.theta1, self.theta2 = self.get_theta() self.cutoff = self.get_cutoff() # self.theta = [8.61043154e+00, 4.48291855e+00, 7.78473553e+00, -7.07767178e-14,2.06230092e+00, 1.18768964e+00]#, 0] # self.theta = [-24.13, -7.87, -17.89, -16.67, -6.99, 7.22, 1.19, 0.72, # -4.2, -4.52, -2.49, -3.14, 5.69, 0.02, 3.53, -3.58, 9.37, # -3.81, -1.58, -1.75, 2.51, 0.26, 18.3, 10.25, 3.63, # 3.69, 1.32, -4.03] # self.theta = [-57.35, -6.41, -2.09, -38.9, -3.91, 6.48, 11.97, -0.39, 27.23, 11.11, -22.04, -11.36, 39.62, -41.32, # 55.17, 24.54, 16.05, 12.08, 10.46, -17.8, 5.61, -7.38, 48.46, 20.26, 4.3, 2.54, 0.0, 0.0] # self.theta77 = [-1.49, 0.41, 0.0, -0.19, -0.07, 0.25, 0.13, 0.0, 0.0, 0.09, -0.28, -0.53, 3.83, -3.95, # 1.88, 0.93, 0.08, 0.25, 0.17, 0.0, -0.22, 0.0, -0.24, -0.2, -0.02, 0.03, 0.0, 0.0] # self.theta98 = [-1.76, -0.4, 0.03, -0.08, 0.16, 0.3, 0.16, 0.55, -0.38, -0.17, -0.12, 0.28, 2.8, -2.77, # 1.82, 0.82, 0.22, 0.1, 0.09, -0.38, -0.09, -1.31, 0.78, 0.42, -0.02, 0.15, 0.0, 0.0] def get_move(self, move): print(self.color) self.time = time.time() # if self.time - self.start > 400: # self.depth = 4 if len(move) != 0: self.board.make_move(move, self.opponent[self.color]) else: self.color = 1 moves = self.board.get_all_possible_moves(self.color) self.depth = self.get_depth() # index = randint(0,len(moves)-1) # inner_index = randint(0,len(moves[index])-1) # move = moves[index][inner_index] # print(moves) move = moves[0][0] if len(moves) == 1 and len( moves[0]) == 1 else self.minimax_move(moves) self.board.make_move(move, self.color) self.movecount += 1 # with open(self.file, 'a') as f: # f.write(f"Movecount:{self.movecount} Total time:{time.time()-self.start} This move takes:{time.time()-self.time} Depth:{self.depth}\n") return move def get_cutoff(self): if self.row == 7 and self.col == 7: return (5, 3) else: return (6, 3) def get_depth(self): if self.row == 7 and self.col == 7: return 0 else: return 4 def get_theta(self): theta771_start = [ -3.05, -1.9, 0.04, -0.85, -0.06, 0.43, 0.21, 0.0, -0.18, 0.56, -0.32, -0.25, 2.9, -2.56, 1.23, 0.06, 0.44, 0.89, 0.67, 0.27, -0.0, 0.0, 0.64, 0.56, 0.07, 0.91, 0.0, 0.0 ] theta771_mid = [ -2.84, -1.09, -0.05, -0.67, 0.22, 0.42, 0.24, 0.0, -0.18, 0.72, 0.26, 0.38, 3.44, -2.88, 1.83, 0.04, 0.3, 0.79, 0.11, -0.02, -0.14, 0.0, 0.36, 0.3, -0.45, 0.9, 0.0, 0.0 ] theta771_last = [ -3.01, -1.01, -0.06, 0.13, 0.42, 0.18, 0.03, 0.0, -0.51, 0.75, 1.31, 1.26, 2.86, -2.55, 1.5, 0.22, 0.05, 0.42, 0.2, -0.06, -0.16, 0.0, 0.53, 0.45, -0.26, 0.44, 0.0, 0.0 ] theta772_start = [ 2.07, -0.44, 0.29, 0.45, 0.32, -0.46, -0.04, 0.0, 0.37, -0.25, 0.16, 0.17, 0.0, 0.0, -2.18, -0.62, -0.16, -1.0, 0.24, 0.72, 0.2, 0.0, 0.12, 0.04, 0.75, -1.29, 3.36, -2.93 ] theta772_mid = [ 1.93, 0.09, 0.16, 0.37, 0.28, -0.18, -0.17, 0.0, 0.02, -0.28, 0.02, 0.13, 0.0, 0.0, -2.3, -1.12, -0.07, -0.63, 0.38, 0.08, 0.16, 0.0, -0.4, 0.43, 0.54, -0.74, 3.47, -3.05 ] theta772_last = [ 1.51, 0.44, -0.01, 0.11, 0.16, -0.16, -0.14, 0.0, -0.17, 0.47, -0.16, 0.02, 0.0, 0.0, -2.79, -0.98, 0.0, 0.14, 0.4, -0.08, 0.0, 0.0, -0.26, 1.25, 0.06, -0.07, 2.32, -2.19 ] theta981_start = [ -2.85, -0.22, 0.11, -0.54, 0.31, -0.03, 0.07, 0.0, -0.19, 0.45, -0.17, 0.45, 2.09, -1.88, 2.46, 1.24, -0.04, 0.64, 0.41, -0.36, 0.11, 0.25, -0.01, 0.09, -0.34, 0.96, 0.0, 0.0 ] theta981_mid = [ -1.95, -1.35, 0.11, -0.44, 0.11, 0.16, 0.02, 0.0, -0.18, 0.57, 0.26, 0.54, 2.37, -2.06, 1.22, -0.19, 0.3, 0.9, 0.43, 0.09, 0.05, -0.06, 0.19, 0.19, -0.04, 0.69, 0.0, 0.0 ] theta981_last = [ -3.22, -1.85, 0.18, 0.48, 0.23, 0.14, -0.18, 0.0, -0.53, 1.25, 0.93, 0.03, 3.33, -3.01, 2.08, 0.05, 0.15, 0.65, 0.27, 0.03, -0.13, -1.0, -0.24, -0.25, 0.01, 0.03, 0.0, 0.0 ] theta982_start = [ 1.47, -0.0, 0.21, 0.5, 0.09, -0.09, 0.05, 0.0, 0.12, -0.45, 0.15, 0.55, 0.0, 0.0, -2.5, -0.49, 0.05, -0.45, 0.24, 0.24, 0.29, 0.29, 0.02, 0.12, 0.26, -0.26, 2.31, -2.11 ] theta982_mid = [ 1.22, -0.16, 0.29, 0.22, 0.28, -0.21, -0.05, 0.0, 0.38, -0.24, -0.31, 0.59, 0.0, 0.0, -2.04, -1.22, 0.14, -0.54, 0.12, -0.07, 0.05, -0.04, 0.31, 0.57, 0.41, -0.88, 3.14, -2.84 ] theta982_last = [ 2.12, -0.19, 0.19, -0.23, 0.33, -0.17, -0.11, 0.0, -0.44, 0.32, 0.16, 0.0, 0.0, 0.0, -2.06, -1.58, 0.09, -0.16, 0.46, -0.19, 0.0, -0.94, -0.7, 0.74, 1.33, -0.08, 4.14, -3.98 ] if self.row == 7 and self.col == 7: return (theta771_start, theta771_mid, theta771_last), (theta772_start, theta772_mid, theta772_last) else: return (theta981_start, theta981_mid, theta981_last), (theta982_start, theta982_mid, theta982_last) def minimax_move(self, moves: [list]): best = [] max_value = -math.inf for chess in moves: for move in chess: val = self.min_value(move, self.depth, -math.inf, math.inf) if val > max_value: best = [move] max_value = val elif val == max_value: best.append(move) return best[0] def min_value(self, move, depth, alpha, beta): self.board.make_move(move, self.color) if depth == 0: u = self.utility(self.board, self.color) print(u) self.board.undo() return u moves = self.board.get_all_possible_moves(self.opponent[self.color]) moves = [m for sub in moves for m in sub] # moves = self.reorder(self.get_u_list(moves, self.board, self.opponent[self.color]), reverse = False) if len(moves) == 0: u = +1000 self.board.undo() return u min_val = math.inf for move in moves: min_val = min(self.max_value(move, depth - 1, alpha, beta), min_val) beta = min(beta, min_val) if alpha >= beta: self.board.undo() return min_val self.board.undo() return min_val def max_value(self, move, depth, alpha, beta): self.board.make_move(move, self.opponent[self.color]) if depth == 0: u = self.utility(self.board, self.opponent[self.color]) print(u) self.board.undo() return u moves = self.board.get_all_possible_moves(self.color) moves = [m for sub in moves for m in sub] # moves = self.reorder(self.get_u_list(moves, self.board, self.color), reverse = True) if len(moves) == 0: u = -1000 self.board.undo() return u max_val = -math.inf for move in moves: max_val = max(self.min_value(move, depth - 1, alpha, beta), max_val) alpha = max(alpha, max_val) if alpha >= beta: self.board.undo() return max_val self.board.undo() return max_val def u_after_move(self, move, board, color): board.make_move(move, color) u = self.utility(board, color) board.undo() return u def get_u_list(self, moves, board, color): u_list = {} for chess in moves: for move in chess: u_list[move] = self.u_after_move(move, board, color) return u_list def reorder(self, u_list, reverse): return sorted(u_list, key=lambda x: u_list[x], reverse=reverse) def utility(self, board, color): wking, bking = self.wking_bking(board) wcount, bcount = self.wcount_bcount(board) wdis, bdis = self.wdis_bdis(board) wedge, bedge = self.wedge_bedge(board) wcenter, bcenter = self.wcenter_bcenter(board) wback, bback = self.wback_bback(board) wdiag, bdiag = self.wdiag_bdiag(board) wdog, bdog = self.wdog_bdog(board) wbridge, bbridge = self.wbridge_bbridge(board) wuptriangle, buptriangle = self.wuptriangle_buptriangle(board) wdowntriangle, bdowntriangle = self.wdowntriangle_bdowntriangle(board) woreo, boreo = self.woreo_boreo(board) board.show_board() if color == 1: wmoveable, weatable = self.moveables(board, 2) bmoveable, beatable = 0, 0 else: wmoveable, weatable = 0, 0 bmoveable, beatable = self.moveables(board, 1) if self.color == 1: features = [ wcount, wking, wdis, wback, wedge, wcenter, wdiag, wdog, wbridge, wuptriangle, wdowntriangle, woreo, wmoveable, weatable, bcount, bking, bdis, bback, bedge, bcenter, bdiag, bdog, bbridge, buptriangle, bdowntriangle, boreo, bmoveable, beatable ] print(str([i for i in features])) if bcount > self.cutoff[0]: return sum(x * t for x, t in zip(features, self.theta1[0])) elif self.cutoff[1] < bcount <= self.cutoff[0]: return sum(x * t for x, t in zip(features, self.theta1[1])) else: return sum(x * t for x, t in zip(features, self.theta1[2])) else: features = [ wcount, wking, wdis, wback, wedge, wcenter, wdiag, wdog, wbridge, wuptriangle, wdowntriangle, woreo, wmoveable, weatable, bcount, bking, bdis, bback, bedge, bcenter, bdiag, bdog, bbridge, buptriangle, bdowntriangle, boreo, bmoveable, beatable ] print(str([i for i in features])) if wcount > self.cutoff[0]: return sum(x * t for x, t in zip(features, self.theta2[0])) elif self.cutoff[1] < wcount <= self.cutoff[0]: return sum(x * t for x, t in zip(features, self.theta2[1])) else: return sum(x * t for x, t in zip(features, self.theta2[2])) def features(self, board, color): ''' :param board: :return: white_features, black_features features order = [count, king, dis, back, edge, center, diag, dog, bridge, uptriangle, downtriangle, oreo, moveable, eatable] ''' wfeature = [0 for _ in range(14)] bfeature = [0 for _ in range(14)] wfeature[0], bfeature[0] = board.white_count, board.black_count for r in range(board.row): # count edge wfeature[4] += (board.board[r][0].color == "W") + ( board.board[r][board.col - 1].color == "W") bfeature[4] += (board.board[r][0].color == "B") + ( board.board[r][board.col - 1].color == "B") for c in range(board.col): wfeature[3] += (board.board[board.row - 1][c].color == "B" ) # count back bfeature[3] += (board.board[0][c].color == "B") # count back wfeature[5] += (board.board[int( board.row / 2)][c].color == "W") + ( board.board[int(board.row / 2) + 1][c].color == "W" ) # count center bfeature[5] += (board.board[int( board.row / 2)][c].color == "B") + ( board.board[int(board.row / 2) + 1][c].color == "B" ) # count center if board.board[r][c].color == 'W': if board.board[r][c].is_king: wfeature[1] += 1 # count king wfeature[2] += board.row - 1 - r # count dis elif board.board[r][c].color == 'B': if board.board[r][c].is_king: bfeature[1] += 1 # count king bfeature[2] += r # count dis def wcount_bcount(self, board): return board.white_count, board.black_count def wking_bking(self, board): bking, wking = 0, 0 for r in range(self.board.row): for c in range(self.board.col): if self.board.board[r][c].color == "B": bking += self.board.board[r][c].is_king elif self.board.board[r][c].color == "W": wking += self.board.board[r][c].is_king return wking, bking def moveables(self, board, color): moves = [ m for chess in board.get_all_possible_moves(color) for m in chess ] eatable = 0 for m in moves: if len(m.seq) > 2: eatable += (len(m.seq) - 1) continue if math.sqrt((m.seq[0][0] - m.seq[1][0])**2 + (m.seq[0][1] - m.seq[1][1])**2) > 1: eatable += 1 # print(f"len(moves): {len(moves)}, eatable: {eatable}") return len(moves), eatable def wback_bback(self, board): bback = sum(board.board[0][i].color == "B" for i in range(board.col)) wback = sum(board.board[board.row - 1][i].color == "W" for i in range(board.col)) return wback, bback def wedge_bedge(self, board): bedge = sum((board.board[i][0].color == "B") + (board.board[i][board.col - 1].color == "B") for i in range(board.row)) wedge = sum((board.board[i][0].color == "W") + (board.board[i][board.col - 1].color == "W") for i in range(board.row)) # print(f"wedge: {wedge}, bedge: {bedge}") return wedge, bedge def wcenter_bcenter(self, board): wcenter = sum((board.board[int(board.row / 2)][i].color == "W") + \ (board.board[int(board.row / 2) + 1][i].color == "W") for i in range(board.col)) bcenter = sum((board.board[int(board.row / 2)][i].color == "B") + \ (board.board[int(board.row / 2) + 1][i].color == "B") for i in range(board.col)) # print(f"wcenter: {wcenter}, bcenter: {bcenter}") return wcenter, bcenter def wdiagonal_bdiagonal(self, board): bdiagonal = sum(board.board[i][i].color == "B" for i in range(board.row // 4, 3 * board.row // 4)) + \ sum(board.board[board.row - 1 - i][board.row - 1 - i].color == "B" for i in range(board.row)) wdiagonal = sum(board.board[i][i].color == "W" for i in range(board.row)) + \ sum(board.board[board.row - 1 - i][board.row - 1 - i].color == "W" for i in range(board.row)) # print(f"wdiagonal: {wdiagonal}, bdiagonal: {bdiagonal}") return wdiagonal, bdiagonal def wdiag_bdiag(self, board): bc, wc = 0, 0 for r in range(board.row - 1): bc += (board.board[r][r].color == "B") + (board.board[r + 1][r].color == "B") + ( board.board[r][r + 1].color == "B") \ + (board.board[r][board.col - 1 - r].color == "B") + ( board.board[r + 1][board.col - 1 - r].color == "B") + \ (board.board[r][board.col - 2 - r].color == "B") wc += (board.board[r][r].color == "W") + (board.board[r + 1][r].color == "W") + ( board.board[r][r + 1].color == "W") \ + (board.board[r][board.col - 1 - r].color == "W") + ( board.board[r + 1][board.col - 1 - r].color == "W") + \ (board.board[r][board.col - 2 - r].color == "W") bc += (board.board[board.row - 1][0].color == "B") + ( board.board[board.row - 1][board.row - 1].color == "B") wc += (board.board[board.row - 1][0].color == "W") + ( board.board[board.row - 1][board.row - 1].color == "W") # print(f"wdiag: {wc}, bdiag: {bc}") return wc, bc def wdog_bdog(self, board): wc = (board.board[board.row - 1][board.col - 1].color == "." and board.board[board.row - 1][ board.col - 2].color == "W" \ and board.board[board.row - 2][board.col - 1].color == "B") + \ (board.board[board.row - 1][0].color == "." and board.board[board.row - 1][1].color == "W" \ and board.board[board.row - 2][0].color == "B") bc = (board.board[0][0].color == "." and board.board[0][1].color == "B" \ and board.board[1][0].color == "W") + \ (board.board[0][board.col - 1].color == "." and board.board[0][board.col - 2].color == "B" \ and board.board[1][board.col - 1].color == "W") # print(f"wdog: {wc}, bdog: {bc}") return wc, bc def wbridge_bbridge(self, board): bc = sum( board.board[0][c].color == "B" and board.board[0][c + 2].color == "B" for c in range(1, board.col - 3)) wc = sum(board.board[board.row - 1][c].color == "W" and board.board[board.row - 1][c + 2].color == "W" for c in range(1, board.col - 3)) # print(f"wbridge: {wc}, bbridge: {bc}") return wc, bc def wuptriangle_buptriangle(self, board): bcount, wcount = 0, 0 for r in range(1, board.row - 1): for c in range(board.col - 2): if board.board[r][c].color == "B" and board.board[r - 1][ c + 1].color == "B" and board.board[r][c + 2].color == "B": bcount += 1 if board.board[r][c].color == "W" and board.board[r - 1][ c + 1].color == "W" and board.board[r][c + 2].color == "W": wcount += 1 # print(f"wuptriangle: {wcount}, buptriangle: {bcount}") return wcount, bcount def wdowntriangle_bdowntriangle(self, board): bcount, wcount = 0, 0 for r in range(board.row - 1): for c in range(board.col - 2): if board.board[r][c].color == "B" and board.board[r + 1][ c + 1].color == "B" and board.board[r][c + 2].color == "B": bcount += 1 if board.board[r][c].color == "W" and board.board[r + 1][ c + 1].color == "W" and board.board[r][c + 2].color == "W": wcount += 1 # print(f"wdowntriangle: {wcount}, bdowntriangle: {bcount}") return wcount, bcount def woreo_boreo(self, board): ''' :param board: :return: triangle pattern in the last row ''' boreo = sum(board.board[0][c].color == "B" and board.board[1][c + 1].color == "B" \ and board.board[0][c + 2].color == "B" for c in range(0, board.col - 2)) woreo = sum(board.board[board.row - 1][c].color == "W" and board.board[board.row - 2][c + 1].color == "W" \ and board.board[board.row - 1][c + 2].color == "W" for c in range(0, board.col - 2)) # print(f"woreo: {woreo}, boreo: {boreo}") return woreo, boreo def wdis_bdis(self, board): wdis = sum(board.row - 1 - i for i in range(board.row) for j in range(board.col) if board.board[i][j].color == "W") bdis = sum(i for i in range(board.row) for j in range(board.col) if board.board[i][j].color == "B") return wdis, bdis
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 def get_move(self, move): if len(move) != 0: self.board.make_move(move, self.opponent[ self.color]) # Run opponent's move for self.board else: self.color = 1 root = Tree(self.opponent[self.color]) #Tree root self.rec_tree(root, search_depth) #Set up tree self.rec_min_max_heuristic(root) avail_moves = root.value[list(root.value)[0]] #cur_move = avail_moves[0] cur_move = avail_moves[randint(0, len(avail_moves) - 1)] #print(avail_moves) self.board.make_move(cur_move, self.color) # Make the optimal move move = cur_move return move def ftu(self, color): #Function to use (min vs max by color) if color == self.color: # Calculate Min return max else: # Calculate Max return min def min_max(self, children, color): # Returns dict -> {Max/min value: Moves to get here} ftu = self.ftu(color) #Use corresponding min or max depending on color value_map = {} for child in children: for v in child.value.keys(): value_map.setdefault(v, []).append( child.move ) # D: {heuristic value: Move to make to get here} # print(value_map) return {ftu(value_map): value_map[ftu(value_map)]} def board_points( self): # 5 + row number for pawns, 5 + row number + 2 for kings ''' def board_points(self): # 5 + row number for pawns, 5 + row number + 2 for kings king_pts_value = 5 + ( self.row - 1) + 2 # 5 pts for piece, self.row -1 pts for pts at end of board, + 1 for being king pts = 0 for i in range(self.row): for j in range(self.col): checker = self.board.board[i][j] if checker.color == 'B': # For black side pieces if checker.is_king: pts += king_pts_value else: pts += 5 + checker.row elif checker.color == 'W': # FOr white side pieces # pts -= (11 - checker.row) # 5 + (6 - Row) if checker.is_king: pts -= king_pts_value else: pts -= (5 + ( self.row - checker.row - 1)) # 5 + (Num of rows - Row - 1) eg. 5x5 board, 5th row is 5(num) - 4(row) -1 = 0 if abs(pts) > 2: self.dif_val = True # if debug: print(color(root.color), pts, -pts) return pts if self.color == 1 else -pts # BLACK(1) GOES FIRST, so positive points, if self.color == white(2), then return white pieces as positive points ''' pts = 0 for i in range(self.row): for j in range(self.col): checker = self.board.board[i][j] if checker.color == 'B': # For black side pieces pts += 5 + checker.row if checker.is_king: # 2 additional pts for king pts += 2 elif checker.color == 'W': # FOr white side pieces pts -= 11 - checker.row # 5 + (6 - Row) if checker.is_king: # 2 additional pts for king pts -= 2 return pts if self.color == 1 else -pts def print_tree(self, root, level=0): # print("PRINTING TREE") print("\t" * level, root.value, "->", root.move) if len(root.children) != 0: # Not Leaf node for child in root.children: self.print_tree(child, level + 1) def rec_tree(self, root: Tree, level=1): #Create tree up to depth level if level == 0: pass else: if root.move is not None: # Not root of tree self.board.make_move(root.move, root.color) #Check if win here maybe? avail_moves = self.board.get_all_possible_moves( self.opponent[root.color]) for i in range(len(avail_moves)): for j in range(len(avail_moves[i])): #print(root) root.children.append( Tree(self.opponent[root.color], avail_moves[i][j])) for child in root.children: self.rec_tree(child, level - 1) if root.move is not None: self.board.undo() def rec_min_max_heuristic(self, root: Tree): #Apply min_max heuristic to tree if root.move is not None: #If not root of tree, make the move required to get here self.board.make_move(root.move, root.color) if len(root.children) == 0: #Passed node has no children pass #Evaluate heuristic for board(and return?) root.value = {self.board_points(): []} else: #Evaluate rec_heuristic for children, then retrieve values and apply min/max as appropriate for child in root.children: self.rec_min_max_heuristic(child) root.value = self.min_max(root.children, root.color) if root.move is not None: self.board.undo()
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 def get_move(self, move): # make opponents move if len(move) != 0: self.board.make_move(move, self.opponent[self.color]) # switch turn else: self.color = 1 # find player 1 next move moves = self.board.get_all_possible_moves(self.color) alpha = -INFINITY beta = INFINITY result = self.move_ordering(self.color) for move in moves: for m in move: limit = 0 self.board.make_move(m, self.color) val = self.min_value(limit + 1, alpha, beta) if val > alpha: alpha = val result = m self.board.undo() self.board.make_move(result, self.color) return result def max_value(self, limit, alpha, beta): if limit == MINIMAX_DEPTH or self.board.is_win( self.color) == self.color: return self.heuristic() #alpha = -INFINITY # result = -infinity # move = self.move_ordering(self.color) # if not move: # return alpha # self.board.make_move(move,self.color) # v = self.min_value(limit+1,alpha,beta) # self.board.undo() # if v >= beta: # return INFINITY # alpha = max(alpha,v) val = -INFINITY moves = self.board.get_all_possible_moves(self.color) for move in moves: for m in move: self.board.make_move(m, self.color) val = max(val, self.min_value(limit + 1, alpha, beta)) self.board.undo() alpha = max(alpha, val) if alpha >= beta: return val return val def min_value(self, limit, alpha, beta): if limit == MINIMAX_DEPTH or self.board.is_win( self.color) == self.opponent[self.color]: return self.heuristic() #beta = INFINITY # result = infinity # move = self.move_ordering(self.opponent[self.color]) # if not move: # return beta # self.board.make_move(move,self.opponent[self.color]) # v = self.max_value(limit+1,alpha,beta) # self.board.undo() # if alpha >= v: # return -INFINITY # beta = min(beta,v) # return beta val = INFINITY moves = self.board.get_all_possible_moves(self.opponent[self.color]) for move in moves: for m in move: self.board.make_move(m, self.opponent[self.color]) val = min(val, self.max_value(limit + 1, alpha, beta)) self.board.undo() beta = min(beta, val) if alpha >= beta: # pruning - go to next move (?) return val return val def heuristic(self): black = 0 white = 0 #if (self.board.black_count+self.board.white_count) > (0.4* self.board.p * self.board.col): # Decide between early game and mid,end game for row in range(self.board.row): for col in range(self.board.col): if self.board.board[row][col].color == "B": if self.board.board[row][col].is_king: # King = 10 black += self.board.row else: black += (5 + self.board.board[row][col].row - (0.5 * self.board.row) ) # reg piece = 5 + rows on oponent side elif self.board.board[row][col].color == "W": if self.board.board[row][col].is_king: # King = 10 white += self.board.row else: white += (5 + (self.board.row * 0.5) - self.board.board[row][col].row ) # reg piece = 5 + rows on oponent s if self.color == 1: return black - white else: return white - black def move_ordering(self, player): best_move_value = -INFINITY if (player == self.color) else INFINITY moves = self.board.get_all_possible_moves(player) if moves: best_move = moves[0][0] else: return False for move in moves: for m in move: self.board.make_move(m, player) v = self.heuristic() if v > best_move_value if ( player == self.color) else v < best_move_value: best_move_value = v best_move = m self.board.undo() return best_move
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 # ---------- What we added ----------- self.calc_time = datetime.timedelta(seconds=3) self.max_moves = 35 self.wins = {} self.plays = {} self.max_depth = 0 self.C = 1.4 self.colors = {1: "B", 2: "W"} self.letters = {"B": 1, "W": 2} self.states = [] def run_sim(self, board): player = self.colors[self.color] number = self.letters[player] visited_states = set() expand = True for i in range(self.max_moves): moves = board.get_all_possible_moves(number) if len(moves) == 0: return if all( self.plays.get((player, x)) for move in moves for x in move): max_move = self.selection(moves, player) else: index = randint(0, len(moves) - 1) inner_index = randint(0, len(moves[index]) - 1) max_move = moves[index][inner_index] board.make_move(max_move, number) if expand == True and (player, max_move) not in self.plays: expand = False self.expand(player, max_move) visited_states.add((player, max_move)) winner = board.is_win("W") if winner == 1 or winner == 2 or winner == -1: break if player == "W": player = "B" number = 1 else: player = "W" number = 2 if winner == 0: return elif winner == -1: winner == self.colors[self.color] else: winner = self.colors[winner] self.back_propagate(visited_states, winner) def selection(self, moves, player): max = -100000 max_move = "" sum_plays = 0 for g in moves: for x in g: sum_plays = sum_plays + self.plays.get((player, x), 0) for g in moves: for x in g: try: one = self.wins[(player, x)] / self.plays[(player, x)] score = one + self.C * sqrt( log(sum_plays) / self.plays[(player, x)]) except: score = -100000 if score > max: max = score max_move = x return max_move def back_propagate(self, visited_states, winner): for player, move in visited_states: if (player, move) not in self.plays: continue self.plays[(player, move)] += 1 if player == winner: self.wins[(player, move)] += 1 def expand(self, player, move): self.plays[(player, move)] = 0 self.wins[(player, move)] = 0 def get_move(self, move): first = False if len(move) != 0: self.board.make_move(move, self.opponent[self.color]) else: self.color = 1 first = True player = self.colors[self.color] moves = self.board.get_all_possible_moves(self.color) index = randint(0, len(moves) - 1) inner_index = randint(0, len(moves[index]) - 1) move = moves[index][inner_index] if first: self.board.make_move(move, self.color) return move games = 0 begin = datetime.datetime.utcnow() new_board = deepcopy(self.board) while datetime.datetime.utcnow() - begin < self.calc_time: self.run_sim(new_board) games += 1 max_move = self.selection(moves, player) if max_move == "": max_move = move self.board.make_move(max_move, self.color) if max_move == "": return move return max_move
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = 2 self.mcts = MCTS(TreeNode(self.board, self.color, None, None)) self.total_time_remaining = 479 self.time_divisor = row * col * 0.5 self.timed_move_count = 2 def get_move(self, move) -> Move: ''' prune tree with opponent move MCTS ''' # Start timer start_time = time() # Check if opponent gave a turn and execute it if len(move) != 0: self.play_move(move, OPPONENT[self.color]) # If first move of game, change self.color and make random move else: self.color = 1 self.mcts.root = TreeNode(self.board, self.color, None, None) moves = self.board.get_all_possible_moves(self.color) first_move = moves[0][1] self.play_move(first_move, self.color) return first_move # Check if only one move is possible moves = self.board.get_all_possible_moves(self.color) if len(moves) == 1 and len(moves[0]) == 1: self.play_move(moves[0][0], self.color) return moves[0][0] # Set up time limit time_limit = self.total_time_remaining / self.time_divisor # MCTS move_chosen = self.mcts.search(time_limit) self.play_move(move_chosen, self.color) # Change time divisor self.time_divisor -= 0.5 - 1/self.timed_move_count self.timed_move_count += 1 # Decrement time remaining and return self.total_time_remaining -= time() - start_time return move_chosen def play_move(self, move, color): """ Updates board and tree root using Move given, either Move we just played or Move given by opponent. """ self.board.make_move(move, color) for child in self.mcts.root.children.items(): if str(move) == str(child[0]) and child[1] is not None: self.mcts.root = child[1] self.mcts.root.parent = None return self.mcts.root = TreeNode(self.board, OPPONENT[color], None, None)
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 # Returns optimal value for current player def get_move(self, move): alpha = -1000 value = -1000 beta = 1000 bestMove = None if len(move) != 0: # If the opponent started first self.board.make_move(move, self.opponent[self.color]) else: self.color = 1 # Make a list of all possible moves that our AI can make our_moves = self.board.get_all_possible_moves(self.color) # Iterate through list of all our moves for x in range(len(our_moves)): for y in range(len(our_moves[x])): # Make a move on the copy/theoretical board self.board.make_move(our_moves[x][y], self.color) currentScore = self.alphaBetaMin(alpha, beta, 1) self.board.undo() if currentScore >= value: value = currentScore bestMove = our_moves[x][y] #print("New bestMove", bestMove, "current best score:", currentScore) alpha = currentScore #print("Decision?", bestMove) self.board.make_move(bestMove, self.color) return bestMove def alphaBetaMin(self, alpha, beta, depth): ''' # Check if our AI is black and we won #if self.color == self.board.is_win(self.color): if self.color == self.board.is_win("B"): return 1000 # Check if our AI (black) lost #elif self.color == 1 and self.board.is_win(self.color) == 2: elif self.color == 1 and self.board.is_win("B") == 2: return -1000 # Check if our AI (white) lost #elif self.color == 2 and self.board.is_win(self.color) == 1: elif self.color == 2 and self.board.is_win("W") == 1: return -1000 # Check if opponent will tie #if self.board.is_win(self.color) == -1: if self.board.is_win("B") == -1: return 0 ''' if depth == 3: return self.get_heuristic_score2() else: value = 1000 # Go through every possible move opponent_moves = self.board.get_all_possible_moves( self.opponent[self.color]) for x in opponent_moves: for move in x: # Make move for opponent self.board.make_move(move, self.opponent[self.color]) value = min(value, self.alphaBetaMax(alpha, beta, depth + 1)) self.board.undo() beta = min(beta, value) if alpha >= beta: return value return value def alphaBetaMax(self, alpha, beta, depth): ''' # Check if our AI is black and we won #if self.color == self.board.is_win(self.opponent[self.color]): if self.color == self.board.is_win("B"): return 1000 # Check if our AI (black) lost #elif self.color == 1 and self.board.is_win(self.opponent[self.color]) == 2: elif self.color == 1 and self.board.is_win("B") == 2: return -1000 # Check if our AI (white) lost #elif self.color == 2 and self.board.is_win(self.opponent[self.color]) == 1: elif self.color == 2 and self.board.is_win("W") == 1: return -1000 # Check if opponent will tie #if self.board.is_win(self.opponent[self.color]) == -1: if self.board.is_win("B") == -1: return 0 ''' if depth == 3: return self.get_heuristic_score2() else: value = -1000 # Go through every possible move our_moves = self.board.get_all_possible_moves(self.color) for x in our_moves: for move in x: self.board.make_move(move, self.color) value = max(value, self.alphaBetaMin(alpha, beta, depth + 1)) self.board.undo() alpha = max(alpha, value) if alpha >= beta: return value return value def closeToBecomingKing(self, color, row_position): if self.color == 1: # Our color is black return row_position else: # our color is white return (self.board.row - row_position - 1) def get_heuristic_score2(self): num_black_kings = 0 num_white_kings = 0 num_safe_piece_black = 0 num_safe_piece_white = 0 num_back_black = 0 num_back_white = 0 closer_black = 0 closer_white = 0 #score = 0 for x in range(len(self.board.board)): for y in range(len(self.board.board[x])): # Check if it's our checker piece if (self.board.board[x][y].get_color() == 'B'): # Check if it's a king if (self.board.board[x][y].is_king == True): num_black_kings += 1 else: # Check how close checker piece is to becoming King closer_black += self.closeToBecomingKing(self.color, x) cp = self.board.board[x][y].get_location() # Check if black checker piece is in the back if (cp[0] == 0): num_back_black += 1 # Check if it's an edge piece row 0, row n, col 0, col n if (cp[0] == 0 or cp[0] == self.board.row - 1): num_safe_piece_black += 1 if (cp[1] == 0 or cp[1] == self.board.col - 1): num_safe_piece_black += 1 if (cp[0] == 0 and cp[1] == 0): num_safe_piece_black -= 1 if (cp[0] == 0 and cp[1] == self.board.col - 1): num_safe_piece_black -= 1 if (cp[0] == self.board.row - 1 and cp[1] == 0): num_safe_piece_black -= 1 if (cp[0] == self.board.row - 1 and cp[1] == self.board.col - 1): num_safe_piece_black -= 1 # Check for safe pieces that are not part of the edge if (cp[0] != 0 and cp[0] != self.board.row - 1): if (cp[1] != 0 and cp[1] != self.board.col - 1): is_safe = True if (self.board.board[x + 1][y - 1].get_color() == 'W'): if (self.board.board[x - 1][y + 1].get_color() == '.'): is_safe = False if (self.board.board[x + 1][y + 1].get_color() == 'W'): if (self.board.board[x - 1][y - 1].get_color() == '.'): is_safe = False if (self.board.board[x - 1][y + 1].get_color() == 'W' and self.board.board[x - 1][y + 1].is_king): if (self.board.board[x + 1][y - 1].get_color() == '.'): is_safe = False if (self.board.board[x - 1][y - 1].get_color() == 'W' and self.board.board[x - 1][y - 1].is_king): if (self.board.board[x + 1][y + 1].get_color() == '.'): is_safe = False if (is_safe == True): #print("safe piece counted") num_safe_piece_black += 1 #else: #print(x, y) #print("safe piece not counted") #score -= 2 ''' # Check for safe pieces that are part of the edges is_safe = True # Check for safe piece on edge (column - 1) if (cp[1] == self.board.col - 1): if(self.board.board[x + 1][y - 1].get_color() == 'W'): is_safe = False # Check for safe piece on edge (0) if (cp[1] == 0): if(self.board.board[x + 1][y + 1].get_color() == 'W'): is_safe = False # check for safe piece on edge (column - 1) when a King if (cp[1] == self.board.col - 1 and ((cp[0] > 0) or (cp[0] < self.board.row - 1))): if(self.board.board[x - 1][y - 1].get_color() == 'W'): is_safe = False if(self.board.board[x + 1][y - 1].get_color() == 'W'): is_safe = False # check for safe piece on edge (0) when a King if (cp[1] == 0 and ((cp[0] > 0) or (cp[0] < self.board.row - 1))): if(self.board.board[x - 1][y + 1].get_color() == 'W'): is_safe = False if(self.board.board[x + 1][y + 1].get_color() == 'W'): is_safe = False if (is_safe == True): num_safe_piece_black += 1 ''' elif (self.board.board[x][y].get_color() == 'W'): if (self.board.board[x][y].is_king == True): num_white_kings += 1 else: closer_white += self.closeToBecomingKing(2, x) # Check if it's a corner piece either (0, 0), (0, n), (n, 0), or (n, n) cp = self.board.board[x][y].get_location() # Check if white checker piece is in the back if (cp[0] == self.board.row - 1): num_back_white += 1 # Check if it's an edge piece row 0, row n, col 0, col n if (cp[0] == 0 or cp[0] == self.board.row - 1): num_safe_piece_white += 1 if (cp[1] == 0 or cp[1] == self.board.col - 1): num_safe_piece_white += 1 if (cp[0] == 0 and cp[1] == 0): num_safe_piece_white -= 1 if (cp[0] == 0 and cp[1] == self.board.col - 1): num_safe_piece_white -= 1 if (cp[0] == self.board.row - 1 and cp[1] == 0): num_safe_piece_white -= 1 if (cp[0] == self.board.row - 1 and cp[1] == self.board.col - 1): num_safe_piece_white -= 1 # Check for white safe pieces that are not part of the edge if (cp[0] != 0 and cp[0] != self.board.row - 1): if (cp[1] != 0 and cp[1] != self.board.col - 1): is_safe = True if (self.board.board[x - 1][y - 1].get_color() == 'B'): if (self.board.board[x + 1][y + 1].get_color() == '.'): is_safe = False if (self.board.board[x - 1][y + 1].get_color() == 'B'): if (self.board.board[x + 1][y - 1].get_color() == '.'): is_safe = False if (self.board.board[x + 1][y + 1].get_color() == 'B' and self.board.board[x + 1][y + 1].is_king): if (self.board.board[x - 1][y - 1].get_color() == '.'): is_safe = False if (self.board.board[x + 1][y - 1].get_color() == 'B' and self.board.board[x + 1][y - 1].is_king): if (self.board.board[x - 1][y + 1].get_color() == '.'): is_safe = False if (is_safe == True): num_safe_piece_white += 1 if self.color == 1: score = 10 * (self.board.black_count - self.board.white_count) #print("Score after diff in counts:", score) #print('safe black:', num_safe_piece_black, 'safe white:', num_safe_piece_white, 'safe score:', num_safe_piece_black - num_safe_piece_white) score += 5 * (num_black_kings - num_white_kings) #print("Score after diff in Ks:", score) #score += 2*(closer_black - closer_white) score += 2 * (num_safe_piece_black - num_safe_piece_white) #print("Score after diff in safe pieces:", score) score += 2 * (num_back_black - num_back_white) #print("Score after back row pieces:", score) elif self.color == 2: score = 10 * (self.board.white_count - self.board.black_count) #print("Score after diff in counts:", score) #print('safe black:', num_safe_piece_black, 'safe white:', num_safe_piece_white, 'safe score:', num_safe_piece_black - num_safe_piece_white) score += 5 * (num_white_kings - num_black_kings) #print("Score after diff in Ks:", score) #score += 2*(closer_black - closer_white) score += 2 * (num_safe_piece_white - num_safe_piece_black) #print("Score after diff in safe pieces:", score) score += 2 * (num_back_white - num_back_black) #print("Score after back row pieces:", score) return score
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 self.f = open("debug.txt", "w") def heuristic_black(self): count_b = 0 count_w = 0 for i in range(int(len(self.board.board) / 2)): for j in range(len(self.board.board[i])): if self.board.board[i][j].color == 1: if self.board.board[i][j].is_king == True: count_b += 10 else: count_b += 5 else: if self.board.board[i][j].is_king == True: count_w += 10 else: count_w += 7 for i in range(int(len(self.board.board) / 2), len(self.board.board)): for j in range(len(self.board.board[i])): if self.board.board[i][j].color == 1: if self.board.board[i][j].is_king == True: count_b += 10 else: count_b += 7 else: if self.board.board[i][j].is_king == True: count_w += 10 else: count_w += 5 # for i in self.board.board: # for j in i: # # if j.color == 1: # if j.is_king == True: # count_b += 7 + self.row # else: # count_b += 5 + (self.row - j.row) # elif j.color == 2: # if j.is_king == True: # count_w += 7 + self.row # else: # count_w += 5 + j.row return count_b - count_w def get_move(self, move): if len(move) != 0: self.board.make_move(move, self.opponent[self.color]) else: self.color = 1 moves = self.board.get_all_possible_moves(self.color) self.f.write("Curr Moves: " + str(moves) + '\n') if len(moves) == 1 and len(moves[0]) == 1: move = moves[0][0] self.board.make_move(move, self.color) return move move = self.minimax(moves) self.f.write("Chosen Move: " + str(move) + '\n') # index = randint(0,len(moves)-1) # inner_index = randint(0,len(moves[index])-1) # move = moves[index][inner_index] self.board.make_move(move, self.color) return move def minimax(self, moves): dic_l1 = dict() for peice in range(len(moves)): for i in range(len(moves[peice])): move = moves[peice][i] self.board.make_move(move, self.color) if self.board.is_win(self.color) == self.color: self.board.undo() return moves[peice][i] l2_moves = self.board.get_all_possible_moves( self.opponent[self.color]) # print("Opponent Moves: \n peice: ",peice, "\n dir: ",i, "\nMoves\n", l2_moves) dic_l2 = dict() for opp_peice in range(len(l2_moves)): for j in range(len(l2_moves[opp_peice])): move = l2_moves[opp_peice][j] self.board.make_move(move, self.opponent[self.color]) l3_moves = self.board.get_all_possible_moves( self.color) dic_l3 = dict() # print("L3 ",l3_moves) for my_peice in range(len(l3_moves)): flag = 0 for k in range(len(l3_moves[my_peice])): move = l3_moves[my_peice][k] self.board.make_move(move, self.color) value = -1 if self.color == 1: value = (self.board.black_count / (self.board.black_count + self.board.white_count)) * 100 else: value = (self.board.white_count / (self.board.black_count + self.board.white_count)) * 100 key = str(my_peice) + ' ' + str(k) # print(key, ' ', value) dic_l3[key] = value self.board.undo() if self.board.is_win(self.color) == self.color: flag = 1 break if flag == 1: break if len(dic_l3) == 0: key = str(opp_peice) + ' ' + str(j) dic_l2[key] = int(0x40000) self.board.undo() else: inverse = [(value, key) for key, value in dic_l3.items()] l2_value = max(inverse)[0] key = str(opp_peice) + ' ' + str(j) dic_l2[key] = l2_value self.board.undo() if len(dic_l2) == 0: key = str(peice) + ' ' + str(i) dic_l1[key] = int(-0x40000) self.board.undo() else: inverse = [(value, key) for key, value in dic_l2.items()] l1_value = min(inverse)[0] key = str(peice) + ' ' + str(i) dic_l1[key] = l1_value self.board.undo() inverse = [(value, key) for key, value in dic_l1.items()] l0_value = max(inverse)[1] # print(dic_l1) # print(l0_value) x, y = l0_value.split(' ') return moves[int(x)][int(y)]
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 self.search_lim = 5 self.current_node = TreeNode(None, self.color) def get_move(self, move): if len(move) != 0: # print("|" + str(move) + "|") self.board.make_move(move, self.opponent[self.color]) #print("Player", self.opponent[self.color], "make move", move) if len(self.current_node.child_node) != 0: for child in self.current_node.child_node: if str(child.move) == str(move): self.current_node = child else: self.color = 1 self.current_node.player = self.color for i in range(NS): self.mcts(self.current_node) #self.board.show_board() #print("mcts counter:", i) move = self.current_node.child_node[0] for child in self.current_node.child_node: if move.uct() < child.uct(): move = child self.board.make_move(move.move, self.color) # print("Player", self.color, "make move", move.move, "with a winrate of", move.winrate(), "simulated", move.simulation) self.current_node = move return move.move def mcts(self, node): if node.simulation >= minVisit: #print("depth:", depth) node.simulation += 1 if not len(node.child_node): moves = self.board.get_all_possible_moves(node.player) for move in moves: for eachmove in move: node.child_node.append( TreeNode(eachmove, self.opponent[node.player], node)) # proceed next = self.mcts_selection(node) self.board.make_move(next.move, node.player) result = self.board.is_win(node.player) if result: if result == self.opponent[node.player]: node.win += 1 elif result == node.player: next.win += 1 next.simulation += 1 self.board.undo() return result #self.board.show_board() result = self.mcts(next) self.board.undo() # propagate up if result == self.opponent[node.player]: node.win += 1 return result else: result = self.simulate(node.player) node.simulation += 1 if result == self.opponent[node.player]: node.win += 1 #print("simulating", result) return result def mcts_selection(self, node): # Select optimal UCB node current = node.child_node[0] for child in node.child_node: #print(current.uct()) if current.uct() < child.uct(): current = child #print("player", node.player, "pick", current.move) return current def simulate(self, player): win = 0 counter = 0 fake_board = Board(self.col, self.row, self.p) self.copy_board(fake_board) # print("DIT ME DIEN") # fake_board.show_board() # totaltime = 0 while win == 0: moves = fake_board.get_all_possible_moves(player) if len(moves) == 1: index = 0 elif len(moves) == 0: win = self.opponent[player] break else: index = randint(0, len(moves) - 1) if len(moves[index]) == 1: inner_index = 0 else: inner_index = randint(0, len(moves[index]) - 1) move = moves[index][inner_index] fake_board.make_move(move, player) counter += 1 # bt = time.time() if fake_board.tie_counter >= fake_board.tie_max: win = -1 # totaltime += time.time() - bt # print("self.board.is_win():", time.time() - bt) player = self.opponent[player] # #print("total time is_win:", totaltime) # #bt = time.time() # for i in range(counter): # self.board.undo() # #rint("total time undo:", time.time() - bt) # fake_board.show_board() return win def copy_board(self, board): """ EZ game :return: ez board """ board.tie_counter = self.board.tie_counter board.tie_max = self.board.tie_max board.board = copy.deepcopy(self.board.board) board.saved_move = copy.deepcopy(self.board.saved_move) board.black_count = self.board.black_count board.white_count = self.board.white_count
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 self.bestMove = None self.blackVal = 0 #------- self.whiteVal = 0 #------- def get_move(self, move): if len(move) != 0: self.board.make_move(move, self.opponent[self.color]) else: self.color = 1 moves = self.board.get_all_possible_moves(self.color) cloneBoard = copy.deepcopy(self.board) self.minimax(cloneBoard, 2, True) move = self.bestMove self.board.make_move(move, self.color) return move def minimax(self, cloneBoard, depth, maximizingPlayer): if (depth == 0): return self.evaluate(cloneBoard) if (maximizingPlayer == True): maximizerMoves = cloneBoard.get_all_possible_moves(self.color) value = -math.inf for move in maximizerMoves: for x in move: clone = copy.deepcopy(cloneBoard) clone.make_move(x, self.color) score = self.minimax(clone, depth - 1, False) if (score > value): value = score self.bestMove = x return value else: minimizerMoves = cloneBoard.get_all_possible_moves( self.opponent[self.color]) value = math.inf for move in minimizerMoves: for x in move: clone = copy.deepcopy(cloneBoard) clone.make_move(x, self.opponent[self.color]) score = self.minimax(clone, depth - 1, True) if (score < value): value = score return value def evaluate(self, board): # for x in range(self.row): # for y in range(self.col): # if (self.board.board[x][y].is_king): # if self.board.board[x][y].color == "B": # # print("%s king row:%d col:%d" % (self.board.board[x][y].color, x,y)) # self.blackVal += 7 # else: # self.whiteVal += 7 if (self.color == 1): return self.blackVal + 5 * board.black_count - 5 * board.white_count else: return self.whiteVal + 5 * board.white_count - 5 * board.black_count # class StudentAI(): # def __init__(self,col,row,p): # self.col = col # self.row = row # self.p = p # self.board = Board(col,row,p) # self.board.initialize_game() # self.color = '' # self.opponent = {1:2,2:1} # self.color = 2 # self.bestMove = None # def get_move(self,move): # if len(move) != 0: # self.board.make_move(move,self.opponent[self.color]) # else: # self.color = 1 # moves = self.board.get_all_possible_moves(self.color) # cloneBoard = copy.deepcopy(self.board) # self.minimax(cloneBoard, 2, True) # move = self.bestMove # self.board.make_move(move,self.color) # return move # def minimax(self, cloneBoard, depth, maximizingPlayer): # if(depth == 0): # return self.evaluate(cloneBoard) # if(maximizingPlayer == True): # maximizerMoves = cloneBoard.get_all_possible_moves(self.color) # value = -math.inf # for move in maximizerMoves: # for x in move: # clone = copy.deepcopy(cloneBoard) # clone.make_move(x, self.color) # score = self.minimax(clone, depth - 1, False) # if(score > value): # value = score # self.bestMove = x # return value # else: # minimizerMoves = cloneBoard.get_all_possible_moves(self.opponent[self.color]) # value = math.inf # for move in minimizerMoves: # for x in move: # clone = copy.deepcopy(cloneBoard) # clone.make_move(x, self.opponent[self.color]) # score = self.minimax(clone, depth - 1, True) # if(score < value): # value = score # return value # def evaluate(self, board): # if(self.color == 1): # return board.black_count - board.white_count # else: # return board.white_count - board.black_count
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 def get_move(self, move): if len(move) != 0: self.board.make_move(move, self.opponent[self.color]) else: self.color = 1 moves = self.board.get_all_possible_moves(self.color) best_move = moves[0][0] #self.board.make_move(best_move, self.color) #best_score = self.board_score( self.color ) #self.board.undo() move = self.minMax(self.color, 3, -999999999, best_move, 999999999, best_move)[1] self.board.make_move(move, self.color) return move def minMax(self, player, depth, best_score, best_move, opponent_score, opponent_move): if depth == 0: return self.board_score( player ), best_move # get all the moves of the current player moves = self.board.get_all_possible_moves(player) # Itterate through each move for i in moves: for ii in i: # change to new game state self.board.make_move(ii, player) if (player == self.color): opponent_score = self.minMax(self.opponent[self.color], depth-1, best_score, best_move,opponent_score, opponent_move)[0] if (best_score < opponent_score): best_score = opponent_score best_move = ii # opponent's turn: find the best score based on player's move elif (player == self.opponent[self.color]): best_score = self.minMax(self.color, depth-1, best_score, best_move,opponent_score, opponent_move)[0] if (opponent_score > best_score): opponent_score = best_score opponent_move = ii self.board.undo() return best_score, best_move, opponent_score, opponent_move def board_score(self, color): ## @param color: color of player making the move ## Heuristics to Evaluate with ## Normal Piece : 1000 pts ## King Piece : 2000 pts ## Rows away from enemy end if Normal : (rows - curr_row / rows) * 1000 ## Amount of Pieces : (Amount of pieces left) / (self.col * self.p / 2) * 100 ## Randomization : randomInt (0-10) player_points = 0 opponent_points = 0 for c in range(self.col): for r in range(self.row): current_piece = self.board.board[c][r] if current_piece.get_color() == color: if current_piece.is_king == True: player_points += 2000 else: player_points += 1000 if color == 1: player_points += ((self.row - r) / self.row) * 1000 else: player_points += (r / self.row) * 1000 elif current_piece.get_color() == self.opponent[color]: if current_piece.is_king == True: opponent_points += 2000 else: opponent_points += 1000 if self.opponent[color] == 1: opponent_points += ((self.row - r) / self.row) * 1000 else: opponent_points += (r / self.row) * 1000 else: pass if color == 1: player_points += ((self.board.white_count / (self.col * self.p / 2)) * 100) opponent_points += ((self.board.black_count / (self.col * self.p / 2)) * 100) else: player_points += ((self.board.black_count / (self.col * self.p / 2)) * 100) opponent_points += ((self.board.white_count / (self.col * self.p / 2)) * 100) randomization = randint(0, 50) return player_points - opponent_points + randomization
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 self.depth = 4 self.a = -math.inf self.b = math.inf def get_move(self, move): if len(move) != 0: self.board.make_move(move, self.opponent[self.color]) else: self.color = 1 moves = self.board.get_all_possible_moves(self.color) # index = randint(0, len(moves) - 1) # inner_index = randint(0, len(moves[index]) - 1) # move = moves[index][inner_index] move = self.select_move(moves) # print(move) self.board.make_move(move, self.color) return move def select_move(self, moves): """take a list of moves and select the best one, return move""" copySelf = copy.deepcopy(self) return abPruning(copySelf, 0, self.a, self.b, self.color) # return simple_search(copySelf) def heuristic_func(self, move, color): """take a move, evaluate it's heuristic value return the value""" result = 0 colorDict = {"B": 1, "W": 2, ".": 0} self.board.make_move(move, color) # print (self.board.board) black = [] white = [] for row in self.board.board: for checker in row: if colorDict[checker.get_color()] == 1: black.append(checker.get_location()) else: white.append(checker.get_location()) # for row in self.board.board: # for checker in row: # add = 1 if colorDict[checker.get_color()] == self.color else -1 # y = checker.get_location()[1] # if self.color == 1: # # if getProtected(checker.get_location(), black) and add == 1: # # result += 5 # if checker.is_king: # result += (self.board.row + 1 + y) * add # else: # result += (y + 5) * add # else: # # if getProtected(checker.get_location(), white) and add == 1: # # result += 5 # if checker.is_king: # result += (self.board.row + 1) * add # else: # result += (4 + self.board.row - y) * add for row in self.board.board: for checker in row: y = checker.get_location()[1] x = checker.get_location()[0] if colorDict[checker.get_color()] == self.color: if self.color == 2 and y == self.board.row - 1: result += 0.5 elif self.color == 1 and y == 0: result += 0.5 if x == 0 or x == self.board.col - 1: result += 0.25 if checker.is_king: result += 3.0 + 0.5 * abs(y - (self.board.row / 2.0)) else: result += 1.5 else: if checker.is_king: result -= 3.0 else: result -= 1.5 self.board.undo() return result
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 def get_move(self, move): if len(move) != 0: self.board.make_move(move, self.opponent[ self.color]) # Run opponent's move for self.board else: self.color = 1 root = Tree(self.opponent[self.color]) #Tree root self.rec_tree(root, search_depth) self.rec_heuristic(root) avail_moves = root.value[list(root.value)[0]] cur_move = avail_moves[0] #print(avail_moves) self.board.make_move(cur_move, self.color) # Make the optimal move move = cur_move return move def ftu(self, color): #Function to use (min vs max by color) if color == self.color: # Calculate Min return max else: # Calculate Max return min def min_max(self, children, color): # Returns dict -> {Max/min value: Moves to get here} ftu = self.ftu(color) #Use corresponding min or max depending on color value_map = {} for child in children: for v in child.value.keys(): value_map.setdefault(v, []).append( child.move ) # D: {heuristic value: Move to make to get here} # print(value_map) return {ftu(value_map): value_map[ftu(value_map)]} def board_points( self): # 5 + row number for pawns, 5 + row number + 2 for kings pts = 0 for i in range(self.row): for j in range(self.col): checker = self.board.board[i][j] if checker.color == 'B': # For black side pieces pts += 5 + checker.row if checker.is_king: # 2 additional pts for king pts += 2 elif checker.color == 'W': # FOr white side pieces pts -= 11 - checker.row # 5 + (6 - Row) if checker.is_king: # 2 additional pts for king pts -= 2 return pts if self.color == "B" else -pts def print_tree(self, root, level=0): # print("PRINTING TREE") print("\t" * level, root.value, "->", root.move) if len(root.children) != 0: # Not Leaf node for child in root.children: self.print_tree(child, level + 1) def rec_tree(self, root: Tree, level=1): if level == 0: pass else: if root.move is not None: # Not root of tree self.board.make_move(root.move, root.color) #Check if win here maybe? avail_moves = self.board.get_all_possible_moves( self.opponent[root.color]) for i in range(len(avail_moves)): for j in range(len(avail_moves[i])): #print(root) root.children.append( Tree(self.opponent[root.color], avail_moves[i][j])) for child in root.children: self.rec_tree(child, level - 1) if root.move is not None: self.board.undo() def rec_heuristic(self, root: Tree): if root.move is not None: self.board.make_move(root.move, root.color) if len(root.children) == 0: #Passed node has no children pass #Evaluate heuristic for board(and return?) root.value = {self.board_points(): []} else: #Evaluate rec_heuristic for children, then retrieve values and apply min/max as appropriate for child in root.children: self.rec_heuristic(child) root.value = self.min_max(root.children, root.color) if root.move is not None: self.board.undo()
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 self.halftime = time.time() + 240 self.endtime = time.time() + 360 self.finaltime = time.time() + 460 self.root = Node(0, 0) def get_move(self, move): if len(move) != 0: self.board.make_move(move, self.opponent[self.color]) self.update_root_tree(move) else: self.color = 1 moves = self.board.get_all_possible_moves(self.color) if len(moves) == 1 and len(moves[0]) == 1: self.board.make_move(moves[0][0], self.color) self.update_root_tree(moves[0][0]) return moves[0][ 0] # if only one move available, choose immediately count = 0 # decides how long to run the mcts depending on time remaining if time.time() > self.finaltime: return self.choose_best_move(self.board, moves, self.color) elif time.time() > self.endtime: t_end = time.time() + 4 elif time.time() > self.halftime: t_end = time.time() + 7 else: t_end = time.time() + 10 while time.time() < t_end: node, board = self.selection() node = self.expansion(node, board) result = self.simulation(node, board) self.backpropagation(node, result) count += 1 max_sim = 0 max_child = self.root.children[0] for each in self.root.children: if each.si > max_sim: max_child = each max_sim = each.si self.board.make_move(max_child.move, self.color) self.update_root_tree(max_child.move) return max_child.move def selection(self): node_found = False board = copy.deepcopy(self.board) node = self.root color = self.color while not node_found: if len(node.children) == 0: node_found = True else: max_uct = 0 max_child = node.children[0] for each in node.children: if each.si == 0 or each.parent.si == 0: uct = 9999 else: uct = (each.wi / each.si) + each.c * (math.sqrt( math.log(each.parent.si) / each.si)) if uct > max_uct: max_child = each max_uct = uct node = max_child board.make_move(max_child.move, color) color = 3 - color return node, board def expansion(self, node, board): new_color = self.color if node.depth % 2 == 1: new_color = 3 - self.color outer_childs = board.get_all_possible_moves(new_color) for child in outer_childs: for move in child: node.children.append(Node(0, 0, move, node)) if len(outer_childs) == 0: return node return node.children[randint(0, len(node.children) - 1)] def simulation(self, node, board): sim_color = self.color if node.depth % 2 == 1: sim_color = 3 - self.color loops = 0 while not board.is_win(sim_color): sim_color = 3 - sim_color moves = board.get_all_possible_moves(sim_color) index = randint(0, len(moves) - 1) inner_index = randint(0, len(moves[index]) - 1) board.make_move(moves[index][inner_index], sim_color) if loops > 50: # game takes too long, return based on who has advantage on current board state if board.black_count - board.white_count >= 2: return 1 elif board.white_count - board.black_count >= 2: return 2 else: return -1 loops += 1 if loops == 0: sim_color = 3 - sim_color return board.is_win(sim_color) def backpropagation(self, node, result): win = False node_color = self.color if node.depth % 2 == 0: node_color = 3 - self.color if result == node_color: win = True while node is not None: node.si += 1 if win: node.wi += 1 win = not win node = node.parent def update_root_tree(self, move): check = False for each in self.root.children: if str(each.move) == str(move): self.root = each self.root.parent = None check = True break if not check: self.root = Node(0, 0) # essentially attempts to prevent the opponent from capturing a piece, stall the game to a tie def choose_best_move(self, board, moves, color): best_move = moves[0][0] num = 9999 for i, each in enumerate(moves): for j, move in enumerate(each): board.make_move(move, color) opp_moves = board.get_all_possible_moves(3 - color) rank = 0 for opp in opp_moves: for opp_move in opp: str_move = str(opp_move) if abs(int(str_move[1]) - int(str_move[7])) == 2: rank += 1 if rank < num: num = rank best_move = move board.undo() board.make_move(best_move, color) return best_move
class StudentAI(): def __init__(self,col,row,p): self.row = row self.col = col self.p = p self.board = Board(col,row,p) self.board.initialize_game() self.color = '' self.opponent = {1:2,2:1} self.color = 2 self.turn_color = {1: "B", 2: "W"} def get_move(self, move): if len(move) != 0: self.board.make_move(move, self.opponent[self.color]) else: self.color = 1 moves = self.board.get_all_possible_moves(self.color) curr_distance = self.king_distance(self.board, self.turn_color[self.color]) aggressive = False late_game = False if self.board.black_count + self.board.white_count <= 8: late_game = True if self.color == 1 and self.board.black_count >= self.board.white_count: aggressive = True if self.color == 2 and self.board.white_count >= self.board.black_count: aggressive = True depth = 4 alpha = -math.inf beta = math.inf best_moves = [] for row in moves: for move in row: board_copied = copy.deepcopy(self.board) board_copied.make_move(move, self.color) curr = self.MinValue(board_copied, depth-1, alpha, beta) if late_game: distance_diff = self.king_distance(board_copied, self.turn_color[self.color]) - curr_distance if aggressive: curr += distance_diff/1000 else: curr -= distance_diff/1000 if curr > alpha: alpha = curr best_moves = [move] elif curr == alpha: best_moves.append(move) best_move = random.choice(best_moves) self.board.make_move(best_move, self.color) return best_move def MaxValue(self, board, depth, alpha, beta): moves = board.get_all_possible_moves(self.color) if depth == 0: # print(self.evaluate(board)) return self.evaluate(board) elif len(moves) == 0: if self.checkWinner(board.board, self.color): # print("1", self.color, depth) return 999 else: # print("2", self.color, depth) return -999 val = -math.inf for row in moves: for move in row: board_copied = copy.deepcopy(board) board_copied.make_move(move, self.color) val = max(val, self.MinValue(board_copied, depth-1, alpha, beta)) alpha = max(alpha, val) if alpha >= beta: return val return val def MinValue(self, board, depth, alpha, beta): moves = board.get_all_possible_moves(self.opponent[self.color]) if depth == 0: # print(self.evaluate(board)) return self.evaluate(board) if len(moves) == 0: if self.checkWinner(board.board, self.color): # print("3", self.color, depth) return 999 else: # print("4", self.color, depth) return -999 val = math.inf for row in moves: for move in row: board_copied = copy.deepcopy(board) board_copied.make_move(move, self.opponent[self.color]) val = min(val, self.MaxValue(board_copied, depth-1, alpha, beta)) beta = min(beta, val) if alpha >= beta: return val return val def evaluate(self,board): if self.color == 1: return board.black_count - board.white_count + self.boardEval1(board, "b")/100 else: return board.white_count - board.black_count + self.boardEval1(board, "w")/100 def boardEval1(self, board, color): val = 0 for i, row in enumerate(board.board): for j, col in enumerate(row): extra = 0 if j == 0 or j == len(row): extra = 4 if color == "b": pawn_val = 5 + i + extra king_val = 5 + len(board.board) + 2 + extra if i == 0: pawn_val = 10 + extra else: pawn_val = 5 + (len(board.board) - 1 - i) + extra king_val = 5 + len(board.board) + 2 + extra if i == len(board.board) - 1: pawn_val = 10 + extra curr_color = board.board[i][j].get_color().lower() if curr_color != '.': if curr_color == color: king = board.board[i][j].is_king if king: val += king_val else: val += pawn_val else: king = board.board[i][j].is_king if king: val -= king_val else: val -= pawn_val return val def checkWinner(self, board, color): my_color = self.turn_color[color] oppo_color = self.turn_color[self.opponent[color]] for row in range(self.row): for col in range(self.col): checker = board[row][col] if checker.color == my_color: return True elif checker.color == oppo_color: return False def king_distance(self, board, color): k1 = [] k2 = [] min_distance = 100 for row in range(board.row): for col in range(board.col): checker = board.board[row][col] if checker.color != ".": if checker.is_king and checker.color == color: k1.append([row, col]) elif checker.color != color: k2.append([row, col]) for i in k1: for j in k2: d = self.cal_distance(i,j) if self.cal_distance(i,j) < min_distance: min_distance = d # print(k1, k2, min_distance) return min_distance def cal_distance(self, p1, p2): return math.sqrt(math.pow(p1[0]-p2[0], 2) + math.pow(p1[1]-p2[1], 2))
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 self.search_depth = 5 self.debug = True self.time_used = 0 self.transposition_table = dict() def get_move(self, move): if self.debug: current_move_elapsed = time.time() if len(move) != 0: self.board.make_move(move, self.opponent[self.color]) else: self.color = 1 moves = self.board.get_all_possible_moves(self.color) # index = randint(0,len(moves)-1) # inner_index = randint(0,len(moves[index])-1) # move = moves[index][inner_index] how_many_moves = 0 for outer_index in range(len(moves)): how_many_moves += len(moves[outer_index]) if how_many_moves == 1: if self.debug: self.time_used += (time.time() - current_move_elapsed) print("Total elapsed time (in seconds):", self.time_used) self.board.make_move(moves[0][0], self.color) return moves[0][0] depth = round((4 / how_many_moves) + self.search_depth) if self.debug: print(how_many_moves, "possible moves") print("Depth:", depth) best_move = None best_move_score = -math.inf for outer_index in range(len(moves)): for inner_index in range(len(moves[outer_index])): self.board.make_move(moves[outer_index][inner_index], self.color) move_score = self.search(depth, moves[outer_index][inner_index], self.color, -math.inf, math.inf) self.board.undo() if move_score > best_move_score: best_move_score = move_score best_move = moves[outer_index][inner_index] self.board.make_move(best_move, self.color) if self.debug: self.time_used += (time.time() - current_move_elapsed) print("Total elapsed time (in seconds):", self.time_used) return best_move def search(self, depth, move, turn, alpha, beta): current_key = self.get_key() if current_key in self.transposition_table.keys( ) and depth == self.transposition_table[current_key].depth: return self.transposition_table[current_key].value winner = self.board.is_win(turn) win_return = 1000 + depth if winner != 0: if winner == -1: self.transposition_table[current_key] = TTEntry(0, depth) return 0 if self.color == winner: self.transposition_table[current_key] = TTEntry( win_return, depth) return win_return self.transposition_table[current_key] = TTEntry(-win_return, depth) return -win_return if depth == 0: black = 0 white = 0 for x in range(self.board.row): for y in range(self.board.col): if self.board.board[x][y].color == "W": white += 5 if self.board.board[x][y].is_king: white += self.row + 2 else: white += x if self.board.board[x][y].color == "B": black += 5 if self.board.board[x][y].is_king: black += self.row + 2 else: black += (self.row - x - 1) score = black - white if self.color == 1: # 1 = black self.transposition_table[current_key] = TTEntry(score, depth) return score self.transposition_table[current_key] = TTEntry(-score, depth) return -score # 2 = white if turn == self.color: # min worst = math.inf possible_moves = self.board.get_all_possible_moves( self.opponent[self.color]) for x in range(len(possible_moves)): for y in range(len(possible_moves[x])): self.board.make_move(possible_moves[x][y], self.opponent[self.color]) current = self.search(depth - 1, possible_moves[x][y], self.opponent[self.color], alpha, beta) self.board.undo() if current < worst: worst = current if current < beta: beta = current if beta <= alpha: break else: continue break self.transposition_table[current_key] = TTEntry(worst, depth) return worst else: # max best = -math.inf possible_moves = self.board.get_all_possible_moves(self.color) for x in range(len(possible_moves)): for y in range(len(possible_moves[x])): self.board.make_move(possible_moves[x][y], self.color) current = self.search(depth - 1, possible_moves[x][y], self.color, alpha, beta) self.board.undo() if current > best: best = current if current > alpha: alpha = current if beta <= alpha: break else: continue break self.transposition_table[current_key] = TTEntry(best, depth) return best def get_key(self): key = "" for x in range(self.board.row): for y in range(self.board.col): if self.board.board[x][y].color == "B": key += "1" elif self.board.board[x][y].color == "W": key += "2" else: key += "0" return key
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 def get_move(self, move): if len(move) != 0: self.board.make_move(move, self.opponent[self.color]) else: self.color = 1 moves = self.board.get_all_possible_moves(self.color) moveValues = [] for i in range(len(moves)): for j in range(len(moves[i])): self.board.make_move(moves[i][j], self.color) moveValues.append((moves[i][j], self.minimax(self.board, 3, self.opponent[self.color], float('-inf'), float('inf')))) self.board.undo() move = max(moveValues, key=lambda x: x[1])[0] self.board.make_move(move, self.color) return move def static_eval(self, boardState): blackValue = 0 whiteValue = 0 for i in range(boardState.row): for j in range(boardState.col): checker = boardState.board[i][j] if checker.color == '.': continue elif checker.color == 'B': if checker.is_king: blackValue += 7 + boardState.row else: blackValue += 5 + checker.row else: if checker.is_king: whiteValue += 7 + boardState.row else: whiteValue += 5 + (boardState.row - checker.row) if self.color == 1: return blackValue - whiteValue else: return whiteValue - blackValue def generate_children(self, player) -> [Board]: children = [] checkers = self.board.get_all_possible_moves(player) for moveList in checkers: for move in moveList: boardCopy = deepcopy(self.board) boardCopy.make_move(move, player) children.append(boardCopy) return children def minimax(self, boardState, depth, max_player, alpha, beta): if depth == 0 or boardState.is_win(max_player): return self.static_eval(boardState) if max_player: best = float('-inf') for child in self.generate_children(self.color): candidate = self.minimax(child, depth - 1, False, alpha, beta) best = max(best, candidate) alpha = max(alpha, candidate) if alpha >= beta: break return best else: best = float('inf') for child in self.generate_children(self.opponent[self.color]): candidate = self.minimax(child, depth - 1, True, alpha, beta) best = min(best, candidate) beta = min(beta, candidate) if alpha >= beta: break return best
class StudentAI(): def __init__(self, col, row, p): self.col = col self.row = row self.p = p self.board = Board(col, row, p) self.board.initialize_game() self.number_of_move = 0 self.EARLY_GAME = 10 self.MID_GAME = 20 self.END_GAME = 30 self.run_time_depth = 5 self.opponent = {1: 2, 2: 1} self.color = 2 def get_move(self, move): if len(move) != 0: self.board.make_move(move, self.opponent[self.color]) else: self.color = 1 move = self.best_move() if self.board.row == self.board.col: self.run_time_depth = self.get_depth(True) if self.board.row != self.board.col: self.run_time_depth = self.get_depth(False) self.board.make_move(move, self.color) self.number_of_move += 1 return move # ALPHA BETA PRUNE STARTS HERE def best_move(self) -> Move: moves = self.board.get_all_possible_moves(self.color) smart_move = Move([]) alpha = -math.inf beta = math.inf for checkers in moves: for move in checkers: self.board.make_move(move, self.color) heuristic = self.alpha_beta_prune(1, self.opponent[self.color], alpha, beta) self.board.undo() if heuristic > alpha: alpha = heuristic smart_move = Move(move) return smart_move def alpha_beta_prune(self, depth, player, alpha, beta) -> float: all_moves = self.board.get_all_possible_moves(player) if depth is self.run_time_depth: return self.evaluate() if not all_moves: winplayer = self.board.is_win(self.opponent[player]) if winplayer == self.color: return 100000 elif winplayer == self.opponent[self.color]: return -100000 else: return 0 if player is self.color: current_score = -math.inf for checker in all_moves: for move in checker: self.board.make_move(move, player) heuristic = self.alpha_beta_prune(depth + 1, self.opponent[player], alpha, beta) self.board.undo() current_score = max(heuristic, current_score) alpha = max(current_score, alpha) if alpha >= beta: break return current_score else: current_score = math.inf for checker in all_moves: for move in checker: self.board.make_move(move, player) heuristic = self.alpha_beta_prune(depth + 1, self.opponent[player], alpha, beta) self.board.undo() current_score = min(heuristic, current_score) beta = min(current_score, beta) if alpha >= beta: break return current_score # make new function to evaluate king def evaluate(self) -> float: white_king = 0 white_chess = 0 black_king = 0 black_chess = 0 white_king_list = list() black_king_list = list() white_chess_list = list() black_chess_list = list() for all_checkers in self.board.board: for checker in all_checkers: if checker.is_king: if checker.color == "W": white_king += 1 white_king_list.append(checker) if checker.color == "B": black_king += 1 black_king_list.append(checker) else: if checker.color == "W": white_chess += 2 white_chess_list.append(checker) if checker.color == "B": black_chess += 2 black_chess_list.append(checker) white_chess, black_chess = self.get_score( checker, white_chess, black_chess) king_dis = 1 if self.color == 1: score = self.board.black_count - self.board.white_count + ( black_king * 8 + black_chess) - (white_chess + white_king * 5) for checker in black_king_list: for opponent in white_chess_list: king_dis += self.calculate_distance( checker.row, checker.col, opponent.row, opponent.col) else: score = 1 + self.board.white_count - self.board.black_count + ( white_king * 8 + white_chess) - (black_chess + black_king * 5) for checker in white_king_list: for opponent in black_chess_list: king_dis += self.calculate_distance( checker.row, checker.col, opponent.row, opponent.col) return score / king_dis def calculate_distance(self, first_row, first_col, second_row, second_col) -> float: a = abs(first_row - second_row) b = abs(first_col - second_col) return max(a, b) def get_depth(self, is_equal): if self.number_of_move != 0: if is_equal: if self.number_of_move <= 5: return 3 if self.number_of_move <= self.EARLY_GAME: return 5 if self.number_of_move <= self.END_GAME: return 7 return 3 else: if 1 <= self.number_of_move <= 5: return 3 if 6 <= self.number_of_move <= 10: return 5 if self.number_of_move % 2 == 0: return 5 return 3 return 3 def get_score(self, checker, white_chess, black_chess): if checker.col == 0 or checker.col == self.col - 1: if checker.color == "W": white_chess += 1 if checker.color == "B": black_chess += 1 if checker.col - 1 > 0 and checker.row - 1 > 0: if self.board.board[checker.row - 1][checker.col - 1].color == "W": white_chess += 0.5 if self.board.board[checker.row - 1][checker.col - 1].color == "B": black_chess += 0.5 if checker.col - 1 > 0 and checker.row + 1 <= self.row - 1: if self.board.board[checker.row + 1][checker.col - 1].color == "W": white_chess += 0.5 if self.board.board[checker.row + 1][checker.col - 1].color == "B": black_chess += 0.5 if checker.col + 1 <= self.col - 1 and checker.row - 1 > 0: if self.board.is_in_board(checker.row - 1, checker.col + 1): if self.board.board[checker.row - 1][checker.col + 1].color == "W": white_chess += 0.5 if self.board.board[checker.row - 1][checker.col + 1].color == "B": black_chess += 0.5 if checker.col + 1 < self.col - 1 and checker.row + 1 <= self.row - 1: if self.board.is_in_board(checker.row + 1, checker.col + 1): if self.board.board[checker.row + 1][checker.col + 1] == "W": white_chess += 0.5 if self.board.board[checker.row + 1][checker.col + 1] == "B": black_chess += 0.5 return white_chess, black_chess