From 0f368f77317ce5cf20ed09c2ca9fefb21bbb93ff Mon Sep 17 00:00:00 2001 From: jona605a Date: Mon, 10 Aug 2020 00:57:24 +0200 Subject: [PATCH] :robot: HexAI! --- funcs/games/hex.py | 102 ++++++++++++++++++++--------------------- funcs/games/hexDraw.py | 4 +- 2 files changed, 52 insertions(+), 54 deletions(-) diff --git a/funcs/games/hex.py b/funcs/games/hex.py index 7f9ac1b..f85cb17 100644 --- a/funcs/games/hex.py +++ b/funcs/games/hex.py @@ -246,9 +246,13 @@ def hexAI(channel): data = json.load(f) board = data[channel]["board"] - player = data[channel]["players"].index("Gwendolyn")+1 - #difficulty = data[channel]["difficulty"] - lastMove = data[channel]["gameHistory"][-1] + player = (data[channel]["players"].index("Gwendolyn")+1) % 2 + difficulty = data[channel]["difficulty"] + """ + if len(data[channel]["gameHistory"]): + lastMove = data[channel]["gameHistory"][-1] + else: + lastMove = (5,5) # These moves are the last move +- 2. moves = [[(lastMove[0]+j-2,lastMove[1]+i-2) for i in range(5) if lastMove[1]+i-2 in range(11)] for j in range(5) if lastMove[0]+j-2 in range(11)] @@ -263,33 +267,29 @@ def hexAI(channel): if board[candidate[0]][candidate[1]] == 0: chosenMove = candidate logThis("Last move was "+str(lastMove)) - logThis("Chosen move is "+str(chosenMove)) - """ - scores = [-math.inf,-math.inf,-math.inf,-math.inf,-math.inf,-math.inf,-math.inf] - for column in range(0,BOARDWIDTH): + logThis("Chosen move is "+str(chosenMove)) """ + + possiblePlaces = [i for i,v in enumerate(sum(board,[])) if v == 0] + judgements = [-math.inf]*len(possiblePlaces) # All possible moves are yet to be judged + + + for i in possiblePlaces: testBoard = copy.deepcopy(board) - # Testing a move - testBoard = placeOnHexBoard(testBoard,player,column) - # Evaluating that move - if testBoard != None: - scores[column] = minimaxHex(testBoard,difficulty,player%2+1,player,-math.inf,math.inf,False) - logThis("Best score for column "+str(column)+" is "+str(scores[column])) + testBoard[i // BOARDWIDTH][i % BOARDWIDTH] = 1 + # Testing a move and evaluating it + judgements[i] = minimaxHex(testBoard,difficulty,-math.inf,math.inf,False) + logThis("Best score for place {} is {}".format((i // BOARDWIDTH,i % BOARDWIDTH),judgements[i])) - possibleScores = scores.copy() - - while (min(possibleScores) < (max(possibleScores)*0.9)): - possibleScores.remove(min(possibleScores)) - - highest_score = random.choice(possibleScores) - - indices = [i for i, x in enumerate(scores) if x == highest_score] - """ + bestScore = max(judgements) # the value of the best score(s) + indices = [i for i, x in enumerate(judgements) if x == bestScore] # which moves got that score? + i = random.choice(indices) + chosenMove = (i // BOARDWIDTH , i % BOARDWIDTH) placement = "abcdefghijk"[chosenMove[1]]+str(chosenMove[0]+1) return placeHex(channel,placement, "Gwendolyn") def evaluateBoard(board): - score = {1:0, 2:0} + scores = {1:0, 2:0} winner = 0 # Here, I use Dijkstra's algorithm to evaluate the board, as proposed by this article: https://towardsdatascience.com/hex-creating-intelligent-adversaries-part-2-heuristics-dijkstras-algorithm-597e4dcacf93 for player in [1,2]: @@ -316,47 +316,45 @@ def evaluateBoard(board): visited.add(u) #logThis("Distance from player {}'s start to {} is {}".format(player,u,Distance[u])) if u[player-1] == 10: # if the right coordinate of v is 10, it means we're at the goal - score[player] = Distance[u] # A player's score is the shortest distance to goal. Which equals the number of remaining moves they need to win if unblocked by the opponent. + scores[player] = Distance[u] # A player's score is the shortest distance to goal. Which equals the number of remaining moves they need to win if unblocked by the opponent. break else: logThis("For some reason, no path to the goal was found. ") - if score[player] == 0: + if scores[player] == 0: winner = player break # We don't need to check the other player's score, if player1 won. - return score, winner + return scores[2]-scores[1], winner -def minimaxHex(board, depth, player , originalPlayer, alpha, beta, maximizingPlayer): +def minimaxHex(board, depth, alpha, beta, maximizingPlayer): # The depth is how many moves ahead the computer checks. This value is the difficulty. - if depth == 0 or 0 not in sum(board,[0]): + if depth == 0 or 0 not in sum(board,[]): score = evaluateBoard(board) return score # if final depth is not reached, look another move ahead: - if maximizingPlayer: - value = -math.inf - for column in range(0,BOARDWIDTH): + if maximizingPlayer: # red player predicts next move + maxEval = -math.inf + possiblePlaces = [i for i,v in enumerate(sum(board,[])) if v == 0] + for i in possiblePlaces: testBoard = copy.deepcopy(board) - testBoard = placeOnHexBoard(testBoard,player,column) - if testBoard != None: - evaluation = minimaxHex(testBoard,depth-1,player%2+1,originalPlayer,alpha,beta,False) - if evaluation < -9000: evaluation += AIScoresHex["avoid losing"] - value = max(value,evaluation) - alpha = max(alpha,evaluation) - if beta <= alpha: - break - return value - else: - value = math.inf - for column in range(0,BOARDWIDTH): + testBoard[i // BOARDWIDTH][i % BOARDWIDTH] = 1 # because maximizingPlayer is Red which is number 1 + evaluation = minimaxHex(testBoard,depth-1,alpha,beta,False) + maxEval = max(maxEval, evaluation) + alpha = max(alpha, evaluation) + if beta <= alpha: + break + return maxEval + else: # blue player predicts next move + minEval = math.inf + possiblePlaces = [i for i,v in enumerate(sum(board,[])) if v == 0] + for i in possiblePlaces: testBoard = copy.deepcopy(board) - testBoard = placeOnHexBoard(testBoard,player,column) - if testBoard != None: - evaluation = minimaxHex(testBoard,depth-1,player%2+1,originalPlayer,alpha,beta,True) - if evaluation < -9000: evaluation += AIScoresHex["avoid losing"] - value = min(value,evaluation) - beta = min(beta,evaluation) - if beta <= alpha: - break - return value + testBoard[i // BOARDWIDTH][i % BOARDWIDTH] = 2 # because minimizingPlayer is Blue which is number 2 + evaluation = minimaxHex(testBoard,depth-1,alpha,beta,True) + minEval = min(minEval, evaluation) + beta = min(beta, evaluation) + if beta <= alpha: + break + return minEval diff --git a/funcs/games/hexDraw.py b/funcs/games/hexDraw.py index 48df046..d765872 100644 --- a/funcs/games/hexDraw.py +++ b/funcs/games/hexDraw.py @@ -21,8 +21,8 @@ LINETHICKNESS = 15 HEXTHICKNESS = 6 # This is half the width of the background lining between every hex X_THICKNESS = HEXTHICKNESS * math.cos(math.pi/6) Y_THICKNESS = HEXTHICKNESS * math.sin(math.pi/6) -BACKGROUND_COLOR = (235,235,235) -BETWEEN_COLOR = BACKGROUND_COLOR +BACKGROUND_COLOR = (230,230,230) +BETWEEN_COLOR = (231,231,231) BLANK_COLOR = "lightgrey" PIECECOLOR = {1:(237,41,57),2:(0,165,255),0:BLANK_COLOR} # player1 is red, player2 is blue BOARDCOORDINATES = [ [(X_OFFSET + HEXAGONWIDTH*(column + row/2),Y_OFFSET + HEXAGONHEIGHT*row) for column in range(11)] for row in range(11)] # These are the coordinates for the upperleft corner of every hex