2

First of all, I just want to say thank you so much for taking the time to read this. I made a tic tac toe game using graphics in python 3. In order to activate the ai you must click on settings, and then click on the players button. Then once I start to play with the ai, the program errors out saying that the maximum recursion depth has been exceeded. I don't know why my AI_Turn function never stops the recursion. I would really appreciate if someone could help with my project as I am really curious as to how minimax algorithms work and I want to get further into the world of ai but I can't do that if I can't understand this. The fuction where I have the minimax algorithm in is called AI_Turn. The only function that I need help debugging is the AI_Turn function. I am a begginer at coding so if some of my code seems primitive that's why. Anyways, Thank you so much for taking the time to read this.

import turtle, copy

def get_o_win(board):
    if (
           board[0][0] == board[0][1] == board[0][2] == "o"
        or board[1][0] == board[1][1] == board[1][2] == "o"
        or board[2][0] == board[2][1] == board[2][2] == "o"
        or board[0][0] == board[1][0] == board[2][0] == "o"
        or board[0][1] == board[1][1] == board[2][1] == "o"
        or board[0][2] == board[1][2] == board[2][2] == "o"
        or board[0][0] == board[1][1] == board[2][2] == "o"
        or board[2][0] == board[1][1] == board[0][2] == "o"
        ):
        return True
    else:
        return False

def get_X_win(board):
    if (
           board[0][0] == board[0][1] == board[0][2] == "x"
        or board[1][0] == board[1][1] == board[1][2] == "x"
        or board[2][0] == board[2][1] == board[2][2] == "x"
        or board[0][0] == board[1][0] == board[2][0] == "x"
        or board[0][1] == board[1][1] == board[2][1] == "x"
        or board[0][2] == board[1][2] == board[2][2] == "x"
        or board[0][0] == board[1][1] == board[2][2] == "x"
        or board[2][0] == board[1][1] == board[0][2] == "x"
        ):
        return True
    else:
        return False

def get_Win(board):
    if (
           board[0][0] == board[0][1] == board[0][2] != "n"
        or board[1][0] == board[1][1] == board[1][2] != "n"
        or board[2][0] == board[2][1] == board[2][2] != "n"
        or board[0][0] == board[1][0] == board[2][0] != "n"
        or board[0][1] == board[1][1] == board[2][1] != "n"
        or board[0][2] == board[1][2] == board[2][2] != "n"
        or board[0][0] == board[1][1] == board[2][2] != "n"
        or board[2][0] == board[1][1] == board[0][2] != "n"
        ):
        return True
    else:
        return False

def AI_Turn(board, turn):
    board_copy = copy.deepcopy(board) 
    turn = 0

    for _ in board:
        for __ in _:
            if __ != "n":
                turn +=1


    o_win = get_o_win(board_copy)
    x_win = get_o_win(board_copy)

    if o_win: # PLAYER WINS
        return (-1, None)

    elif x_win: # AI WINS
        return (1, None)

    elif turn == 9: #TIE
        return (0, None)

    else:
        if turn%2==0: # Player's Turn
            board_copy = copy.deepcopy(board)
            best = (0, None)

            for x in range(3):
                for y in range(3):
                    if board_copy[x][y] == "n":
                        board_copy[x][y] = "o"
                        turn += 1

                        temporary_score_tuple = AI_Turn(board, turn)
                        best_score = best[0]
                        temporary_score = temporary_score_tuple[0]
                        if temporary_score > best_score:
                            best = (temporary_score,[x,y])

            return best

        else: # AI's Turn
            board_copy = copy.deepcopy(board)
            best = (0, None)

            for x in range(3):
                for y in range(3) :
                    if board_copy[x][y] == "n":
                        board_copy[x][y] = "x"
                        turn += 1

                        temporary_score_tuple = AI_Turn(board, turn)
                        best_score = best[0]
                        temporary_score = temporary_score_tuple[0] 
                        if temporary_score > best_score:
                            best = (temporary_score,[x,y])

            return best[1]



def draw_box(x1,y1,x2,y2,fillcolor,outline,text):
    pointer.pensize(15) 
    pointer.color(outline)
    pointer.goto(x1,y1)
    pointer.begin_fill()
    pointer.fillcolor(fillcolor)
    pointer.down()
    pointer.goto(x2,y1)
    pointer.goto(x2,y2)
    pointer.goto(x1,y2)
    pointer.goto(x1,y1)
    pointer.end_fill() 
    pointer.up()
    pointer.goto(x1+(x2-x1)/2,(y1-0.15)+(y2-y1)/2)
    style = ("Courier", 50, "bold")
    pointer.color("white")
    pointer.write(text, font=style, align="center")
    pointer.pensize(27)

def draw_o(x,y):
    pointer.goto(x,y)
    pointer.shape("circle")
    pointer.color("tomato")
    pointer.shapesize(7,7)
    pointer.stamp()
    pointer.color("Cornflower Blue")
    pointer.shapesize(4.2,4.2)
    pointer.stamp()

def draw_X(x,y):
    pointer.color("navy blue")
    draw_line(x+0.17,y+0.17,x-0.17,y-0.17)
    draw_line(x-0.17,y+0.17,x+0.17,y-0.17)


def load():
    global board, sign, turn, start
    start_ttt()
    with open ("board.txt", "r") as f:
        BOARD = f.readlines()
        b1 = (((BOARD[0].replace("'","")).replace("\n","")).replace(" ","")).split(",")
        b2 = (((BOARD[1].replace("'","")).replace("\n","")).replace(" ","")).split(",")
        b3 = (((BOARD[2].replace("'","")).replace("\n","")).replace(" ","")).split(",")
        board = [b1,b2,b3]

    for x in range(3):
        for y in range(3):
            if board[x][y] == "x":
                draw_X(x + 0.5, y + 0.5)
                turn += 1

            elif board[x][y] == "o":
                draw_o(x + 0.5, y + 0.5)
                turn +=1

def save():
    global board
    with open ("board.txt","w") as f:
        f.write((str(board[0]).strip("[")).strip("]") +"\n")
        f.write((str(board[1]).strip("[")).strip("]") + "\n")
        f.write((str(board[2]).strip("[")).strip("]") + "\n")


def draw_line(x1,y1,x2,y2):
    pointer.up()
    pointer.goto(x1,y1)
    pointer.down()
    pointer.goto(x2,y2)
    pointer.up()

def reset():
    global win, start, control_menu, turn, statistics_menu,settings_menu,player
    pointer.clear()
    pointer.goto(1.45,2.4)
    pointer.color("Black")
    style = ("Courier", 70, "bold")
    pointer.write("TIC TAC TOE", font=style, align="center")
    draw_box(0.5, 2.2, 2.5, 1.8, "blue", "medium blue","Start")
    draw_box(0.5, 1.7, 2.5, 1.3, "Gold", "Goldenrod", "Controls")
    draw_box(0.5,1.2,2.5,0.8,"Green", "Dark Green", "Statistics")
    draw_box(1.8,0.1,2.8,0.6,"Red","Dark Red","Quit")
    draw_box(0.25, 0.1,1.4 , 0.6, "hot pink","deep pink" , "Settings")
    board = [["n","n","n"],["n","n","n"],["n","n","n"]]
    win = False
    start = False
    control_menu = False
    statistics_menu = False
    settings_menu = False
    turn = 0

def start_ttt():
    global turn, win, board
    pointer.clear()
    pointer.color("black")
    draw_line(0,2,3,2)
    draw_line(0,1,3,1)
    draw_line(1,3,1,0)
    draw_line(2,3,2,0)
    turn = 0
    win = False
    board = [["n","n","n"],["n","n","n"],["n","n","n"]]


def box_clicker(x,y):
    global turn, win, start, control_menu, o_placed, x_placed, statistics_menu, settings_menu, player
    if start:
        if not win:
            if turn % 2 == 0: 
                sign = "o"
            else:
                sign = "x" 

            if 0 < x < 0.9:
                if 0.1 < y < 0.9 and board[0][0] == "n":
                    if sign == "x":
                        draw_X(0.5,0.5)
                        x_placed += 1
                    else:
                        draw_o(0.5,0.5)
                        o_placed += 1
                    board[0][0] = sign
                    turn += 1
                elif 1.1 < y < 1.9 and board[0][1] == "n":
                    if sign == "x":
                        draw_X(0.5,1.5)
                        x_placed += 1
                    else:
                        draw_o(0.5,1.5)
                        o_placed += 1
                    board[0][1] = sign
                    turn += 1
                elif 2.1 < y < 2.9 and board[0][2] == "n":
                    if sign == "x":
                        draw_X(0.5,2.5)
                        x_placed += 1
                    else:
                        draw_o(0.5,2.5)
                        o_placed += 1
                    board[0][2] = sign
                    turn += 1

            elif 1.1 < x <1.9:
                if 0 < y < 0.9 and board[1][0] == "n":
                    if sign == "x":
                        draw_X(1.5,0.5)
                        x_placed += 1
                    else:
                        draw_o(1.5,0.5)
                        o_placed += 1
                    board[1][0] = sign
                    turn += 1    
                elif 1.1 < y < 1.9 and board[1][1] == "n":
                    if sign == "x":
                        draw_X(1.5,1.5)
                        x_placed += 1
                    else:
                        draw_o(1.5,1.5)
                        o_placed += 1
                    board[1][1] = sign
                    turn += 1
                elif 2.1 < y < 2.9 and board[1][2] == "n":
                    if sign == "x":
                        draw_X(1.5,2.5)
                        x_placed += 1
                    else:
                        draw_o(1.5,2.5)
                        o_placed += 1
                    board[1][2] = sign
                    turn += 1

            elif 2.1 < x < 2.9:
                if 0 < y < 0.9 and board[2][0] == "n":
                    if sign == "x":
                        draw_X(2.5,0.5)
                        x_placed += 1
                    else:
                        draw_o(2.5,0.5)
                        o_placed += 1
                    board[2][0] = sign
                    turn += 1
                elif 1.1 < y < 1.9 and board[2][1] == "n":
                    if sign == "x":
                        draw_X(2.5,1.5)
                        x_placed += 1
                    else:
                        draw_o(2.5,1.5)
                        o_placed += 1
                    board[2][1] = sign
                    turn += 1
                elif 2.1 < y < 2.9 and board[2][2] == "n":
                    if sign == "x":
                        draw_X(2.5,2.5)
                        x_placed += 1
                    else:
                        draw_o(2.5,2.5)
                        o_placed += 1
                    board[2][2] = sign
                    turn += 1

            if player == 1:     
                if turn%2 == 1:           
                    AImove = AI_Turn(board,turn)
                    print(AImove)
                    board[AImove[0]][AImove[1]] = "x"
                    draw_X(AImove[0] + 0.5, AImove[1] + 0.5)
                    x_placed += 1
                    turn += 1
                    sign = "x"
                    print(board)

            win = get_Win(board)
            if win == True:
                pointer.color("deep pink")
                style = ("Courier", 100, "bold")
                pointer.goto(1.55,1.5)
                pointer.write((sign) + " WINS!", font=style, align="center")
                win = True
                pointer.color("Black")
                if sign == "x":
                    x_wins += 1
                else:
                    o_wins += 1

                with open("Statistics.txt", "r+") as f:
                    stats = f.readlines()
                    oo = int(stats[0])
                    xx = int(stats[1])
                    o_placed += oo
                    x_placed += xx
                    f.write(str(o_placed) + "\n" + str(x_placed))

            elif turn == 9:
                pointer.color("grey")
                pointer.goto(1.55,1.5)
                style = ("Courier", 70, "bold")
                pointer.write("It's a draw...", font=style, align="center")
                pointer.color("Black")

            with open("Statistics.txt", "r+") as f:
                stats = f.readlines()
                oo = int(stats[0])
                xx = int(stats[1])
                o_placed += oo
                x_placed += xx
                f.write(str(o_placed) + "\n" + str(x_placed) + "\n")

    elif start == False and control_menu == False and statistics_menu == False and settings_menu == False:
        if 0.5<x<2.5 and 1.8<y<2.2:
            start = True
            start_ttt()
        elif 1.8<x<2.8 and 0.1<y<0.6:
            turtle.bye()

        elif 0.5<x<2.5 and 1.3<y<1.7: # Controls Button
            pointer.clear()
            draw_box(1.8, 0.6, 2.8, 0.2, "Gold", "Goldenrod", "Back")
            pointer.color("Black")
            pointer.goto(1.5,2.5)
            style = ("Courier", 35, "bold")
            pointer.write("CONTROLS", font = style, align="center")
            pointer.goto(0.3,2.2)
            style = ("Courier", 25, "bold")
            pointer.write("Press s To Save The Current Board", font=style, align="left")
            pointer.goto(0.3,1.9)
            pointer.write("Press l To Load A Previously Saved Board", font=style, align="left")
            pointer.goto(0.3,1.6)
            pointer.write("Press r To Reset The Game", font=style, align="left")

            control_menu = True

        elif 0.5<x<2.5 and 0.8<y<1.2: # Statistics Button
            statistics_menu = True
            pointer.clear()
            draw_box(1.8, 0.6, 2.8, 0.2, "Green", "Dark Green", "Back")
            pointer.color("black")
            pointer.goto(1.5,2.5)
            style = ("Courier", 35, "bold")
            pointer.write("STATISTICS", font = style, align="center")
            pointer.goto(0.3,2.2)
            style = ("Courier", 25, "bold")
            pointer.write("O Placed: " + str(o_placed), font=style, align="left")
            pointer.goto(0.3,1.9)
            pointer.write("X Placed: " + str(x_placed), font=style, align="left")

        elif 0.25<x<1.4 and 0.1<y<0.6: # Settings Button
            settings_menu = True
            pointer.clear()
            pointer.color("black")
            pointer.goto(1.5,2.5)
            style = ("Courier", 35, "bold")
            pointer.write("SETTINGS", font = style, align="center")
            draw_box(2.8, 0.6, 1.8, 0.2, "Hot Pink", "Deep Pink", "Back")
            draw_box(0.45, 1.8 ,0.6, 1.9, "hot pink", "deep pink", "")
            style = ("Courier", 20, "bold")
            pointer.color("Black")            
            pointer.goto(0.54,1.95)
            pointer.write("Players", font=style, align="center")
            if player == 2:
                pointer.goto(0.65,1.79)
                pointer.write("Two Players", font=style, align="left")
            else:
                pointer.goto(-0.1,1.79)
                pointer.write("One Player", font=style, align="left")



    elif control_menu == True and start == False  and statistics_menu == False and settings_menu == False:
        if 1.8<x<2.8 and 0.2<y<0.6:
            reset()

    elif control_menu == False and start == False  and statistics_menu == True and settings_menu == False:
        if 1.8<x<2.8 and 0.2<y<0.6:
            reset()

    elif control_menu == False and start == False  and statistics_menu == False and settings_menu == True:
        if 1.8<x<2.8 and 0.2<y<0.6:
            reset()

        elif 0.45<x<0.6 and 1.8<y<1.9:
            if player == 2:
                draw_box(0.65, 1.77, 1.2, 1.88, "cornflower blue", "cornflower blue", "")
                pointer.goto(-0.1,1.79)
                pointer.color("Black")
                style = ("Courier", 20, "bold") 
                pointer.write("One Player", font=style, align="left")
                player = 1
            else:
                draw_box(-1.1 ,1.77,0.4 ,1.88 , "cornflower blue", "cornflower blue", "")
                pointer.goto(0.65,1.79)
                pointer.color("black")
                style = ("Courier", 20, "bold") 
                pointer.write("Two Players", font=style, align="left")
                player = 2

screen = turtle.Screen()
screen.screensize(100,100)
screen.setworldcoordinates(0,0,3,3)
turtle.bgcolor("Cornflower Blue")
pointer = turtle.Turtle()
pointer.hideturtle()
pointer.up()
pointer.speed(0)
pointer.pensize(27)

reset()
o_placed = 0
x_placed = 0
x_wins = 0
o_wins= 0
player = 2


screen.onclick(box_clicker)

def bye():
    turtle.bye()

screen.onkey(reset,"r")
screen.onkey(save,"s")
screen.onkey(load,"l")
screen.onkey(bye,"q")

screen.listen()
screen.mainloop()
Epik_Guy
  • 21
  • 1
  • 1
    That's a ton of code. Can you not narrow it down to where the problem is? For example, why do we need to care about your GUI? I think you should by the least point out where the recursive function is! – goodvibration Nov 23 '19 at 06:18
  • I'm sorry, this is my first time using stack overflow so next time I will try to make my code more concise. Thank you. – Epik_Guy Nov 23 '19 at 19:00

1 Answers1

0

It looks like you have some logic issues in AI_Turn. The infinite recursion issue is due to:

temporary_score_tuple = AI_Turn(board, turn) I think you meant to pass board_copy rather than board (because board has not been changed so your routine will just keep checking a board with no moves).

I can see a range of additional issues ranging from typos (e.g. x_win = get_o_win(board_copy) - should be get_x_win) through to variable scope problems.

I'd suggest removing the complexity of the user interface and attempting to run this function within a test harness with some logging added (this is also a better format to use when posting to stack overflow because the other code is irrelevant to your problem). Note: The below is very rough and just to get you started in the right direction (I have fixed a few issues but there are a number more for you to find; you can also run this at https://repl.it/repls/MajorQuixoticCategories). Hint: try starting with the board setup one move away from a win.

import copy 

def get_o_win(board):
    if (
           board[0][0] == board[0][1] == board[0][2] == "o"
        or board[1][0] == board[1][1] == board[1][2] == "o"
        or board[2][0] == board[2][1] == board[2][2] == "o"
        or board[0][0] == board[1][0] == board[2][0] == "o"
        or board[0][1] == board[1][1] == board[2][1] == "o"
        or board[0][2] == board[1][2] == board[2][2] == "o"
        or board[0][0] == board[1][1] == board[2][2] == "o"
        or board[2][0] == board[1][1] == board[0][2] == "o"
        ):
        return True
    else:
        return False

def get_X_win(board):
    if (
           board[0][0] == board[0][1] == board[0][2] == "x"
        or board[1][0] == board[1][1] == board[1][2] == "x"
        or board[2][0] == board[2][1] == board[2][2] == "x"
        or board[0][0] == board[1][0] == board[2][0] == "x"
        or board[0][1] == board[1][1] == board[2][1] == "x"
        or board[0][2] == board[1][2] == board[2][2] == "x"
        or board[0][0] == board[1][1] == board[2][2] == "x"
        or board[2][0] == board[1][1] == board[0][2] == "x"
        ):
        return True
    else:
        return False

def AI_Turn(board, turn):
    turn = 0

    for _ in board:
        for __ in _:
            if __ != "n":
                turn +=1
    print('turn: '.join(str(turn)))

    o_win = get_o_win(board)
    x_win = get_o_win(board)

    if o_win: # PLAYER WINS
        return (-1, None)

    elif x_win: # AI WINS
        return (1, None)

    elif turn == 9: #TIE
        return (0, None)

    else:
        if turn%2==0: # Player's Turn
            board_copy = copy.deepcopy(board)
            best = (0, None)

            for x in range(3):
                for y in range(3):
                    if board_copy[x][y] == "n":
                        board_copy[x][y] = "o"
                        turn += 1
                        print('User Trying:')
                        print ([''.join(['{:3}'.format(item) for item in row]) for row in board_copy])

                        temporary_score_tuple = AI_Turn(board_copy, turn)
                        best_score = best[0]
                        temporary_score = temporary_score_tuple[0]
                        if temporary_score > best_score:
                            best = (temporary_score,[x,y])

            return best

        else: # AI's Turn
            board_copy = copy.deepcopy(board)
            best = (0, None)

            for x in range(3):
                for y in range(3) :
                    if board_copy[x][y] == "n":
                        board_copy[x][y] = "x"
                        turn += 1
                        print('AI Trying:')
                        print ([''.join(['{:3}'.format(item) for item in row]) for row in board_copy])

                        temporary_score_tuple = AI_Turn(board_copy, turn)
                        best_score = best[0]
                        temporary_score = temporary_score_tuple[0] 
                        if temporary_score > best_score:
                            best = (temporary_score,[x,y])

            return best

print(AI_Turn([['n','n','n'],['n','n','n'],['n','n','n']], 0))
Brits
  • 14,829
  • 2
  • 18
  • 31
  • Ok thank you so much for the answer, I will try doing what you said and I hope I can fix it... – Epik_Guy Nov 23 '19 at 18:49
  • Thank you @Brits for the code but I don't get what this line does print ([''.join(['{:3}'.format(item) for item in row]) for row in board_copy]) – Epik_Guy Nov 23 '19 at 19:06
  • That line outputs board_copy in a readable format. You could do this using a function (and that would probably be more readable) but this way was shorter. Take a look at https://stackoverflow.com/questions/17870612/printing-a-two-dimensional-array-in-python if you want to try to understand why this works (but as a beginner you may just want to trust that it does work for now; its only there to help you debug and should be removed from the final version). – Brits Nov 23 '19 at 19:58
  • ok so I've been working and I tried to approach this at a slightly different method but still came up empty handed... I fixed the maximum recursion depth but now it is giving coordinates that are completely incorrect... – Epik_Guy Dec 05 '19 at 02:44
  • Your logic was nearly there but there were a few issues that might have confused things (particularly the scope of board_copy. I've put up something that should be close to working at https://trinket.io/python/6d1d9fe304 – Brits Dec 05 '19 at 09:18