From 15f097b32e7c6ec30f5e92103767ab796e9b693a Mon Sep 17 00:00:00 2001 From: NikolajDanger Date: Sat, 1 Aug 2020 23:28:51 +0200 Subject: [PATCH] :robot: Better AI --- Gwendolyn.py | 15 ++- funcs/games/fourInARow.py | 197 +++++++++++++++++++++++++++++++------- 2 files changed, 174 insertions(+), 38 deletions(-) diff --git a/Gwendolyn.py b/Gwendolyn.py index 56befd8..16be994 100644 --- a/Gwendolyn.py +++ b/Gwendolyn.py @@ -32,7 +32,7 @@ async def fiar(channel,command,user): oldImage = await channel.send(file = discord.File("resources/games/4InARowBoards/board"+str(channel)+".png")) if gameDone == False: if gwendoTurn: - await asyncio.sleep(2) + await asyncio.sleep(1) response, showImage, deleteImage, gameDone, gwendoTurn = fourInARowAI(str(channel)) await channel.send(response) logThis(response,str(channel)) @@ -67,11 +67,22 @@ async def fiar(channel,command,user): await oldImage.add_reaction("7️⃣") except: logThis("Image deleted before I could react to all of them") - +# else: +# with open("resources/games/oldImages/fourInARow"+str(channel), "w") as f: +# f.write(str(oldImage.id)) + if gameDone: with open("resources/games/games.json", "r") as f: data = json.load(f) + try: + with open("resources/games/oldImages/fourInARow"+str(channel), "r") as f: + oldImage = await channel.fetch_message(int(f.read())) + + await oldImage.delete() + except: + logThis("The old image was already deleted") + winner = data["4 in a row games"][str(channel)]["winner"] if winner != 0: with open("resources/games/games.json","w") as f: diff --git a/funcs/games/fourInARow.py b/funcs/games/fourInARow.py index 72a2288..8eb02cc 100644 --- a/funcs/games/fourInARow.py +++ b/funcs/games/fourInARow.py @@ -1,9 +1,25 @@ import json import random +import copy +import math from . import fourInARowDraw from funcs import logThis +AIScores = { + "middle": 1, + "two in a row": 5, + "three in a row": 50, + "enemy two in a row": -25, + "enemy three in a row": -200, + "enemy win": -10000, + "win": 420, +} + +rowCount = 6 +columnCount = 7 +easy = True + # Starts the game def fourInARowStart(channel, user, opponent): with open("resources/games/games.json", "r") as f: @@ -15,17 +31,24 @@ def fourInARowStart(channel, user, opponent): if opponent.lower() in ["gwendolyn",""," "]: opponent = "Gwendolyn" - board = [ [ 0 for i in range(7) ] for j in range(6) ] + board = [ [ 0 for i in range(columnCount) ] for j in range(rowCount) ] + players = [user,opponent] + random.shuffle(players) data["4 in a row games"][channel] = {"board": board,"winner":0,"win direction":"", - "win coordinates":[0,0],"players":[user,opponent],"turn":0} + "win coordinates":[0,0],"players":players,"turn":0} with open("resources/games/games.json", "w") as f: json.dump(data,f,indent=4) fourInARowDraw.drawImage(channel) - return "Started game against "+opponent+". It's "+user+"'s turn", True, False, False, False + gwendoTurn = False + + if players[0] == "Gwendolyn": + gwendoTurn = True + + return "Started game against "+opponent+". It's "+players[0]+"'s turn", True, False, False, gwendoTurn else: return "There's already a 4 in a row game going on in this channel", False, False, False, False else: @@ -39,15 +62,10 @@ def placePiece(channel : str,player : int,column : int): if channel in data["4 in a row games"]: board = data["4 in a row games"][channel]["board"] - placementx, placementy = -1, column - - for x in range(len(board)): - if board[x][column] == 0: - placementx = x + board = placeOnBoard(board,player,column) - if placementx != -1: - board[placementx][placementy] = player + if board != None: data["4 in a row games"][channel]["board"] = board turn = (data["4 in a row games"][channel]["turn"]+1)%2 data["4 in a row games"][channel]["turn"] = turn @@ -55,7 +73,8 @@ def placePiece(channel : str,player : int,column : int): with open("resources/games/games.json", "w") as f: json.dump(data,f,indent=4) - won, winDirection, winCoordinates = isWon(channel) + logThis("Checking for win") + won, winDirection, winCoordinates = isWon(data["4 in a row games"][channel]["board"]) if won != 0: gameWon = True @@ -89,6 +108,20 @@ def placePiece(channel : str,player : int,column : int): else: return "There's no game in this channel", False, False, False, False +# Returns a board where a piece has been placed in the column +def placeOnBoard(board,player,column): + placementx, placementy = -1, column + + for x in range(len(board)): + if board[x][column] == 0: + placementx = x + + board[placementx][placementy] = player + + if placementx == -1: + return None + else: + return board # Parses command def parseFourInARow(command, channel, user): @@ -117,25 +150,20 @@ def parseFourInARow(command, channel, user): else: return "I didn't get that", False, False, False, False -def isWon(channel): - logThis("Checking for win",channel) +# Checks if someone has won the game and returns the winner +def isWon(board): won = 0 winDirection = "" winCoordinates = [0,0] - with open("resources/games/games.json", "r") as f: - data = json.load(f) - - game = data["4 in a row games"][channel]["board"] - - for line in range(len(game)): - for place in range(len(game[line])): + for line in range(len(board)): + for place in range(len(board[line])): if won == 0: - piecePlayer = game[line][place] + piecePlayer = board[line][place] if piecePlayer != 0: # Checks horizontal - if place <= 7-4: - pieces = [game[line][place+1],game[line][place+2],game[line][place+3]] + if place <= columnCount-4: + pieces = [board[line][place+1],board[line][place+2],board[line][place+3]] else: pieces = [0] @@ -145,8 +173,8 @@ def isWon(channel): winCoordinates = [line,place] # Checks vertical - if line <= 6-4: - pieces = [game[line+1][place],game[line+2][place],game[line+3][place]] + if line <= rowCount-4: + pieces = [board[line+1][place],board[line+2][place],board[line+3][place]] else: pieces = [0] @@ -156,8 +184,8 @@ def isWon(channel): winCoordinates = [line,place] # Checks right diagonal - if line <= 6-4 and place <= 7-4: - pieces = [game[line+1][place+1],game[line+2][place+2],game[line+3][place+3]] + if line <= rowCount-4 and place <= columnCount-4: + pieces = [board[line+1][place+1],board[line+2][place+2],board[line+3][place+3]] else: pieces = [0] @@ -167,8 +195,8 @@ def isWon(channel): winCoordinates = [line,place] # Checks left diagonal - if line <= 6-4 and place >= 3: - pieces = [game[line+1][place-1],game[line+2][place-2],game[line+3][place-3]] + if line <= rowCount-4 and place >= 3: + pieces = [board[line+1][place-1],board[line+2][place-2],board[line+3][place-3]] else: pieces = [0] @@ -180,16 +208,113 @@ def isWon(channel): return won, winDirection, winCoordinates - +# Plays as the AI def fourInARowAI(channel): + logThis("Figuring out best move") with open("resources/games/games.json", "r") as f: data = json.load(f) - foundPlace = False + board = data["4 in a row games"][channel]["board"] + player = data["4 in a row games"][channel]["players"].index("Gwendolyn")+1 - while foundPlace == False: - placement = random.randint(0,6) - if data["4 in a row games"][channel]["board"][0][placement] == 0: - foundPlace = True - return placePiece(channel,2,placement) + scores = [-math.inf,-math.inf,-math.inf,-math.inf,-math.inf,-math.inf,-math.inf] + for column in range(0,columnCount): + testBoard = copy.deepcopy(board) + testBoard = placeOnBoard(testBoard,player,column) + if testBoard != None: + scores[column] = minimax(testBoard,4,player%2+1,player,False) + logThis("Best score for column "+str(column)+" is "+str(scores[column])) + + possibleScores = scores.copy() + + while min(possibleScores) <= (max(possibleScores) - max(possibleScores)/10): + possibleScores.remove(min(possibleScores)) + + print(possibleScores) + highest_score = random.choice(possibleScores) + + indices = [i for i, x in enumerate(scores) if x == highest_score] + placement = random.choice(indices) + return placePiece(channel,player,placement) + +# Calculates points for a board +def AICalcPoints(board,player): + score = 0 + otherPlayer = player%2+1 + + # Adds points for middle placement + for line in range(len(board)): + if board[line][3] == player: + score += AIScores["middle"] + + # Checks horizontal + for line in range(rowCount): + rowArray = [int(i) for i in list(board[line])] + for place in range(columnCount-3): + window = rowArray[place:place+4] + score += evaluateWindow(window,player,otherPlayer) + + # Checks Vertical + for column in range(columnCount): + columnArray = [int(i[column]) for i in list(board)] + for place in range(rowCount-3): + window = columnArray[place:place+4] + score += evaluateWindow(window,player,otherPlayer) + + # Checks right diagonal + for line in range(rowCount-3): + for place in range(columnCount-3): + window = [board[line][place],board[line+1][place+1],board[line+2][place+2],board[line+3][place+3]] + score += evaluateWindow(window,player,otherPlayer) + + # Checks left diagonal + for line in range(rowCount-3): + for place in range(3,columnCount): + window = [board[line][place],board[line+1][place-1],board[line+2][place-2],board[line+3][place-3]] + score += evaluateWindow(window,player,otherPlayer) + + + ## Checks if anyone has won + #won = isWon(board)[0] + + ## Add points if AI wins + #if won == player: + # score += AIScores["win"] + + return score + + +def evaluateWindow(window,player,otherPlayer): + if window.count(player) == 4: + return AIScores["win"] + elif window.count(player) == 3 and window.count(0) == 1: + return AIScores["three in a row"] + elif window.count(player) == 2 and window.count(0) == 2: + return AIScores["two in a row"] + elif window.count(otherPlayer) == 4: + return AIScores["enemy win"] + else: + return 0 + +def minimax(board, depth, player , originalPlayer, maximizingPlayer): + terminal = ((isWon(board)[0] != 0) or (0 not in board[0])) + if depth == 0 or terminal: + points = AICalcPoints(board,originalPlayer) + return points + if maximizingPlayer: + value = -math.inf + for column in range(0,columnCount): + testBoard = copy.deepcopy(board) + testBoard = placeOnBoard(testBoard,player,column) + if testBoard != None: + value = max(value,minimax(testBoard,depth-1,player%2+1,originalPlayer,False)) + return value + else: + value = math.inf + for column in range(0,columnCount): + testBoard = copy.deepcopy(board) + testBoard = placeOnBoard(testBoard,player,column) + if testBoard != None: + value = min(value,minimax(testBoard,depth-1,player%2+1,originalPlayer,True)) + return value