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 # 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.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 def evalFunction(self, newBoard, color): blackScore = 0 whiteScore = 0 totalPieces = 0 for x in range(0, newBoard.row): for y in range(0, newBoard.col): checker = newBoard.board[x][y] if checker.get_color() == 'W': if checker.is_king: whiteScore += 5 + newBoard.row + 2 totalPieces += 1 else: whiteScore += 5 + checker.row totalPieces += 1 if checker.row == 0 or checker.row == newBoard.row: whiteScore += 5 elif checker.get_color() == 'B': if checker.is_king: blackScore += 5 + newBoard.row + 2 totalPieces += 1 else: blackScore += 5 + (newBoard.row - checker.row) totalPieces += 1 if checker.col == 0 or checker.col == newBoard.col: blackScore += 5 if color == 1: return (blackScore - whiteScore) / totalPieces else: return (whiteScore - blackScore) / totalPieces def alphaBeta(self, newBoard, depth, alpha, beta, maxPlayer): # myScore = -9999 # enemyScore = 9999 # # if maxPlayer: # prevTurn = self.opponent[self.color] # else: # prevTurn = self.color # if(newBoard.is_win(prevTurn)) or (newBoard.is_win(prevTurn) == 0): # print(newBoard.is_win(prevTurn)) if depth == 0: # return the score # if maxPlayer: score = self.evalFunction(newBoard, self.color) return score # return the heuristic value of the board. else: # if it is max player, then for every child, choose the one with the max score if maxPlayer: score = -9999 # newMoves gets all possible moves (child nodes) newMoves = newBoard.get_all_possible_moves(self.color) for elem in newMoves: for move in elem: # print("max making move: ", move) newBoard.make_move(move, self.color) score = max( score, self.alphaBeta(newBoard, depth - 1, alpha, beta, False)) # if score >= myScore: # myScore = score # score = max(score, self.alphaBeta(newBoard, depth - 1, alpha, beta, False)) alpha = max(alpha, score) # prunes if alpha >= beta: # beta cut off # print("undo: ", move) newBoard.undo() break # print("undo: ", move) newBoard.undo() return score else: # if min player, choose the minimum score score = 9999 newMoves = newBoard.get_all_possible_moves( self.opponent[self.color]) for elem in newMoves: for move in elem: # print("min making move: ", move) newBoard.make_move(move, self.opponent[self.color]) score = min( score, self.alphaBeta(newBoard, depth - 1, alpha, beta, True)) # score = min(score, self.alphaBeta(newBoard, depth - 1, alpha, beta, True)) # if score <= enemyScore: # enemyScore = score beta = min(beta, score) # prunes if alpha >= beta: # alpha cut off # print("undo: ", move) newBoard.undo() break # print("undo: ", move) newBoard.undo() return score 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) bestScore = -9999 bestMove = Move([]) for elem in moves: for move in elem: # for elem in move: # make deep copy of board # print("max making move: ", move) self.board.make_move(move, self.color) score = self.alphaBeta(self.board, 3, -9999, 9999, False) # print("score: ", score) if score >= bestScore: bestScore = score bestMove = move # print("undo: ", move) self.board.undo() # print("best move:", bestMove) print("best score: ", bestScore) self.board.make_move(bestMove, self.color) return bestMove
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 self.movecount = 0 self.simulate_times = 100 # self.file = f"{self.col}-{self.row}-{self.color}-data.txt" # self.file = open(f"{self.col}-{self.row}-data.txt", "a") def get_move(self, move): self.movecount += 1 if len(move) != 0: self.board.make_move(move, self.opponent[self.color]) else: self.color = 1 self.file = f"{self.col}-{self.row}-{self.color}-data.txt" moves = self.get_moves(self.board, self.color) move = self.monte_carlo_tree(moves, self.simulate_times) self.board.make_move(move, self.color) return move def monte_carlo_tree(self, moves: [], simulate_times: int): s_parent = simulate_times * len(moves) best_uct = -math.inf best_move = 0 for move in moves: wins = self.simulate(move, simulate_times) uct = wins / simulate_times + math.sqrt( 2 * math.log(s_parent) / simulate_times) if uct > best_uct: best_move = move index = randint(0, len(moves) - 1) move = moves[index] return move def simulate(self, move, simulate_times): win = 0 self.board.make_move(move, self.color) for _ in range(simulate_times): curr_turn = self.opponent[self.color] t = 0 moves = self.get_moves(self.board, curr_turn) while len(moves) > 0 and t < 50: move = self.rollout(moves) self.board.make_move(move, curr_turn) curr_turn = self.opponent[curr_turn] moves = self.get_moves(self.board, curr_turn) t += 1 win += 1 if curr_turn != self.color else 0 if t != 50 else 0.5 self.undo(self.board, t) print(win / simulate_times * 100) bf, wf = self.board_to_feature(self.board, self.color) self.write_to_file(bf, wf, win / simulate_times * 100) self.board.undo() return win def board_to_feature(self, board, color): # result = "" # result += f"{board.white_count/self.total} {board.black_count/self.total} " # # wking,bking = self.wking_bking(board) # result += f"{wking/self.total} {bking/self.total} " # # wback, bback = self.wback_bback(board) # result += f"{wback/self.total} {bback/self.total} " # # wedge, bedge = self.wedge_bedge(board) # result += f"{wedge/self.total} {bedge/self.total} " # # wdiagonal, bdiagonal = self.wdiagonal_bdiagonal(board) # result += f"{wdiagonal/self.total} {bdiagonal/self.total} " # # wdis, bdis = self.wdis_bdis(board) # result += f"{wdis/self.total} {bdis/self.total} " # # result += str(self.movecount) 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) if self.color == 1: wmoveable, weatable = self.moveables(board, 2) bmoveable, beatable = 0, 0 else: wmoveable, weatable = 0, 0 bmoveable, beatable = self.moveables(board, 1) return [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] 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 ######### help function ######### def rollout(self, moves): '''Random roll a move from moves''' return moves[randint(0, len(moves) - 1)] def get_moves(self, board, turn): return [ m for chess in board.get_all_possible_moves(turn) for m in chess ] def undo(self, board, times): for _ in range(times): board.undo() def write_to_file(self, wfeatures, bfeatures, win_rate): with open(self.file, "a") as f: w = ' '.join(str(x) for x in wfeatures) b = ' '.join(str(x) for x in bfeatures) f.write(w + ' ' + b + ' ' + str(win_rate) + '\n')
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.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 #---------------------------------# 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 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 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 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 # new params ''' new data clearification: feature_matrix = [[X1, X2, ..., X_feature_size, Y], ... [X1m, X2m, ..., X_feature_size_m, Y]] ''' self.movecount = 0 self.feature_size = 5 self.thetas = np.random.rand(self.feature_size) self.feature_matrix = np.empty((0, self.feature_size)) 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) def make_action(self,state): # TODO: implement this to return action with max Q value for a in get_all_action(state): return action with max(self.Q_table[state, action]) #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] #print(moves) move = self.minimax_move(moves) #move = self.monte_carlo_tree([m for chess in moves for m in chess], 10, 10) self.board.make_move(move, self.color) self.movecount += 1 return move def minimax_move(self, moves: [list]) : best = [] max_value = - math.inf depth = 4 for chess in moves: for move in chess: val = self.max_value(move, depth, -math.inf, math.inf) if val > max_value: best = [move] max_value = val elif val == max_value: best.append(move) if len(best) == 1: return best[0] # print(len(best)) # best[randint(0,len(best)-1)] return self.monte_carlo_tree(best, 10, 10) def explore(self, state): epsilon = 0.2 if random.uniform(0,1) < epsilon: # TODO : implement select random move else: # TODO: implement explore max reward def monte_carlo_tree(self, moves: [], simulate_times: int, s_parent: int): best_uct = 0 best_move = 0 for move in moves: wins = self.simulate(move, simulate_times) uct = wins/simulate_times + math.sqrt(2*math.log(s_parent)/simulate_times) if uct > best_uct: best_move = move return best_move def simulate(self, move, s): wins = 0 board = copy.deepcopy(self.board) board.make_move(move, self.color) for i in range(s): curr_turn = self.opponent[self.color] t = 0 for turn in range(20): if board.is_win(self.color) == self.color: wins = 1 self.undo(board,t) break elif board.is_win(self.opponent[self.color]) == self.opponent[self.color]: self.undo(board, t) break moves = board.get_all_possible_moves(curr_turn) index = randint(0,len(moves)-1) inner_index = randint(0,len(moves[index])-1) board.make_move(moves[index][inner_index], curr_turn) curr_turn = self.opponent[curr_turn] t += 1 else: wins = 0.5 self.undo(board, t) return wins def undo(self, board, times): for _ in range(times): board.undo() def train_one_episode(self): # This is training function for one episode, start a new board and # update Q value until win or loss # QUESTION IS HOW TO DECIDE OPPONENT MOVE? BY SELF-TRAIN? AND HOW TO SELF-TRAIN? new_board = Board() new_board.initialize_game() turn = '' def min_value(self, move, depth, alpha, beta): self.board.make_move(move, self.opponent[self.color]) if depth == 0: #u = self.utility(self.board) u = self.utility_with_theta(self.board) self.board.undo() return u min_val = math.inf for chess in self.board.get_all_possible_moves(self.color): for move in chess: 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.color) if depth == 0: #u = self.utility(self.board) u = self.utility_with_theta(self.board) self.board.undo() return u max_val = - math.inf for chess in self.board.get_all_possible_moves(self.opponent[self.color]): for move in chess: 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 utility(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 bback = sum(self.board.board[0][i].color == "B" for i in range(self.board.col)) wback = sum(self.board.board[self.board.row-1][i].color == "W" for i in range(self.board.col)) bedge = sum(self.board.board[i][0].color == "B"+self.board.board[i][self.board.col-1].color == "B" for i in range(self.board.row)) wedge = sum(self.board.board[i][0].color == "W"+self.board.board[i][self.board.col-1].color == "W" for i in range(self.board.row)) time_param = math.log(self.movecount) b = 3*self.board.black_count + bking * time_param + bback * (1/time_param) + bedge * (1/time_param) w = 3*self.board.white_count + wking * time_param + wback * (1/time_param) + wedge * (1/time_param) # b = self.board.black_count # w = self.board.white_count #print("black:",b," white:",w) return b-w if self.color == 1 else w-b #################################################### ### Training heuristics using Linear Regression #### #################################################### def utility_with_theta(self, board): X_black, X_white = self.get_X(board) b = X_black.dot(self.thetas) w = X_white.dot(self.thetas) return b - w if self.color == 1 else w - b def get_X(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 bback = sum(self.board.board[0][i].color == "B" for i in range(self.board.col)) wback = sum(self.board.board[self.board.row-1][i].color == "W" for i in range(self.board.col)) bedge = sum(self.board.board[i][0].color == "B"+self.board.board[i][self.board.col-1].color == "B" for i in range(self.board.row)) wedge = sum(self.board.board[i][0].color == "W"+self.board.board[i][self.board.col-1].color == "W" for i in range(self.board.row)) X_black = np.array([self.board.black_count, bking, bback, bedge, self.movecount]) X_white = np.array([self.board.white_count, wking, wback, wedge, self.movecount]) return X_black, X_white # def model(self, X, thetas): # # X = [count, king, back, edge, time] # # thetas = [t1, t2, t3, t4, t5] # #return thetas[0] * X[0] + thetas[1] * X[1] + thetas[2] * X[2] + thetas[3] * X[3] + thetas[4] * X[4] # return X*thetas def train(self): # TODO: Training thetas by linear regression and mean square error gradient descent self.thetas = np.random.rand(5) epoch = 0.01 alpha = 0.05 while True: thetas = thetas - self.Gradient() * alpha if self.Gradient() * alpha < epoch: break return thetas def simulate_times(self, color, simulate_times): wins = 0 for _ in simulate_times: wins += self.simulate_lr(color) return wins 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 def MSE(self, thetas): # TODO: get mean square error by simulation wins = self.simulate_times(self.color, 20) utility = self.model(self.get_X(self.board), thetas) def Gradient(self, mse): # TODO: calculate gradient according to mse pass def move_by_qtable(self): # TODO: maybe write a function to choose move from feature matrix accordingly pass
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 new_move = self.MiniMax() if len(new_move.seq) == 0: random_moves = self.board.get_all_possible_moves(self.color) index = randint(0, len(random_moves) - 1) inner_index = randint(0, len(random_moves[index]) - 1) random_move = random_moves[index][inner_index] self.board.make_move(random_move, self.color) return random_move else: self.board.make_move(new_move, self.color) return new_move def get_value(self, input_color): # add more condition value = 0 count = 0 if input_color == 1: color = 'B' for row in range(0, self.row): for col in range(0, self.col): checker = self.board.board[row][col] if checker.color == color: # more weight to cornor if (checker.col == 0 and checker.row == self.row - 1): value += 3 if (checker.col == self.col - 1 and checker.row == self.row - 1): value += 3 if checker.is_king: # not addding row num # so that moving forward and backward is equal value += 10 else: value += checker.row # give more weight # encourage pawns to become king if checker.row + 1 > self.row / 2: value += (checker.row + 1 - self.row / 2) * 2 if row == self.row - 1: value += 5 elif col == self.col - 1 or row * col == 0: value += 2 elif checker.color != color and checker.color != '.': if checker.is_king: value -= 2 * (checker.row) # value -= 10 else: value -= 0.5 * (checker.row) if row == self.row - 1 or col == self.col - 1 or row * col == 0: value -= 2 else: color = 'W' for row in range(0, self.row): for col in range(0, self.col): checker = self.board.board[row][col] if checker.color == color: # more weight to corner if (checker.col == 0 and checker.row == 0): value += 3 if (checker.col == self.col - 1 and checker.row == 0): value += 3 if checker.is_king: value += 10 else: abs_row = 8 - checker.row value += abs_row if abs_row > self.row / 2: value += (abs_row - self.row / 2) * 2 if row == 0: value += 5 elif col == self.col - 1 or row * col == 0: value += 2 # handle Tie elif checker.color != color and checker.color != '.': if checker.is_king: value -= 2 * (checker.row) # value -= 10 else: value -= 0.5 * (checker.row) if row == self.row - 1 or col == self.col - 1 or row * col == 0: value -= 2 return value def Cutoff(self, depth): return depth >= 4 # B -> Max def get_MaxVal(self, depth, alpha, beta): color = self.color if self.Cutoff(depth): return self.get_value(self.color) val = -inf next_moves = self.board.get_all_possible_moves(color) for checker_index in range(0, len(next_moves)): for move_index in range(0, len(next_moves[checker_index])): next_move = next_moves[checker_index][move_index] self.board.make_move(next_move, color) val = max(val, self.get_MinVal(depth + 1, alpha, beta)) if val >= beta: self.board.undo() return val alpha = max(alpha, val) self.board.undo() return val def get_MinVal(self, depth, alpha, beta): color = self.opponent[self.color] if self.Cutoff(depth): return self.get_value(self.color) val = inf next_moves = self.board.get_all_possible_moves(color) for checker_index in range(0, len(next_moves)): for move_index in range(0, len(next_moves[checker_index])): next_move = next_moves[checker_index][move_index] self.board.make_move(next_move, color) val = min(val, self.get_MaxVal(depth + 1, alpha, beta)) if val <= alpha: self.board.undo() return val beta = min(beta, val) self.board.undo() return val def MiniMax(self): alpha = -inf beta = inf best_move = Move([]) moves = self.board.get_all_possible_moves(self.color) for checker_index in range(0, len(moves)): for move_index in range(0, len(moves[checker_index])): move = moves[checker_index][move_index] if len(move) == 0: pass depth = 0 # *(1) start: undo later self.board.make_move(move, self.color) value = self.get_MinVal(depth + 1, alpha, beta) if value > alpha: alpha = value best_move = move # *(1) end 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 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.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.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 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.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
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.color = '' self.opponent = {1: 2, 2: 1} self.color = 2 def num_pieces(self, color): if color == 1: openent_pieces = self.board.black_count elif color == 2: openent_pieces = self.board.white_count return openent_pieces def get_max(self, depth, iterations): opp_color = self.opponent[self.color] #best = (0,0) if depth == iterations: moves = self.board.get_all_possible_moves(self.color) opponent_pieces = self.num_pieces( opp_color) #number of opponent pieces before our move we_eat = 0 #selects max taken pieces for x, checker in enumerate(moves): for v, move in enumerate(moves[x]): self.board.make_move(moves[x][v], self.color) # we make second move temp = opponent_pieces - self.num_pieces(opp_color) if temp > we_eat: we_eat = temp #best = (x,v) self.board.undo() return (we_eat, 0) else: moves = self.board.get_all_possible_moves(self.color) max = -(self.num_pieces(self.color)) our_pieces = self.num_pieces( self.color) # our pieces before our move for i, checker in enumerate(moves): for j, move in enumerate(moves[i]): self.board.make_move(moves[i][j], self.color) #Make move min = self.get_min(depth + 1, iterations) if min > max: max = min best = (i, j) elif min == max: if (randint(0, 1)): best = (i, j) self.board.undo() return (max, best) def get_min(self, depth, iterations): min_color = self.opponent[self.color] min = self.num_pieces(min_color) if depth == iterations - 1: min_moves = self.board.get_all_possible_moves(min_color) maxs_pieces = self.num_pieces(self.color) for x, checker in enumerate(min_moves): for y, move in enumerate(min_moves[x]): self.board.make_move(min_moves[x][y], min_color) min_eats = maxs_pieces - self.num_pieces(self.color) max_eats, b = self.get_max(depth + 1, iterations) diff = max_eats - min_eats if diff < min: min = diff self.board.undo() return min else: min_moves = self.board.get_all_possible_moves(min_color) for x, checker in enumerate(min_moves): for y, move in enumerate(min_moves[x]): self.board.make_move(min_moves[x][y], min_color) max_eats, b = self.get_max(depth + 1, iterations) if max_eats < min: min = max_eats self.board.undo() return min def get_move(self, move): opp_color = self.opponent[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) max, best = self.get_max(1, 3) ''' if max == 0 and len(self.board.get_all_possible_moves(opp_color)) < 3: ## Idea to improve choice, if max is tied look at the move which will get us closes ## closest to opponent piece inner = i distance = 100 checker = self.board.get_all_possible_moves(opp_color) opp_checker = checker[0][0].seq[0] #for i,checker in enumerate(moves1): for j,move in enumerate(moves1[0]): temp_dist = math.sqrt(sum([(a - b) ** 2 for a, b in zip(move[0], opp_checker)])) if temp_dist < distance: best = (0,j) ''' move = moves[best[0]][best[1]] self.board.make_move(move, self.color) return move