Search code examples
pythonartificial-intelligenceminimax

How can I figure out why my mini-max tic-tac-toe AI does not work?


I am trying to make a minimax tic-tac-toe game because I am a novice at Python and I am trying to figure out how a simple AI-mini-max game works. The AI for some reason still goes in order in which the spot turns up on the list for the board.

Ex: if the top right spot was first in the "b" list, it picks it first. It looks like it is calculating the scores, but I do not think it is utilizing them for some reason. I want it to not go in order of what the spaces in the board. I can't figure out a way to replace the code that places the "O".

import random
import math

three = [0, 0, 0]
game = True
turnai = False
result = ""
b = [" ", " ", " ", " ", " ", " ", " ", " ", " "]
x = "X"
o = "O"
#Spots on the board Ex: ur = upper right, mm = middle middle, lm = lower middle
ur = b[2]
um = b[1]
ul = b[0]
ml = b[3]
mm = b[4]
mr = b[5]
ll = b[6]
lm = b[7]
lr = b[8]
cw = " "

AI = ""
player = ""

#Board setup
def board(ul, um, ur, ml, mm, mr, ll, lm, lr):
    print("|" + " " + ul + " " + "|" + " " + um + " " + "|" + " " + ur + " " + "|")
    print("|" + " " + ml + " " + "|" + " " + mm + " " + "|" + " " + mr + " " + "|")
    print("|" + " " + ll + " " + "|" + " " + lm + " " + "|" + " " + lr + " " + "|")


board(ul, um, ur, ml, mm, mr, ll, lm, lr)
print("This is the game of tic-tac-toe")
print("You will be playing against an AI")
print("Type where you want to place your letter Ex: ur = upper right, mm = middle middle, and ll = lower right")

first = "P"
player = "X"
AI = "O"

#Checks if someone has won
def checkwinner():
    ur = b[2]
    um = b[1]
    ul = b[0]
    ml = b[3]
    mm = b[4]
    mr = b[5]
    ll = b[6]
    lm = b[7]
    lr = b[8]
    row1 = [ul, ml, ll]
    row2 = [um, mm, lm]
    row3 = [ur, mr, lr]
    column1 = [ul, um, ur]
    column2 = [ml, mm, mr]
    column3 = [ll, lm, lr]
    diagonal1 = [ul, mm, lr]
    diagonal2 = [ur, mm, ll]
    if row1 == ["X", "X", "X"] or row2 == ["X", "X", "X"] or row3 == ["X", "X", "X"] or column1 == ["X", "X",
                                                                                                    "X"] or column2 == [
        "X", "X", "X"] or column3 == ["X", "X", "X"] or diagonal1 == ["X", "X", "X"] or diagonal2 == ["X", "X",
                                                                                                      "X"]:
        if player == x:
            print("You win! (X)")
            return "X"
        if player != x:
            print("You lose!")
            return "O"
    if row1 == ["O", "O", "O"] or row2 == ["O", "O", "O"] or row3 == ["O", "O", "O"] or column1 == ["O", "O",
                                                                                                    "O"] or column2 == [
        "O", "O", "O"] or column3 == ["O", "O", "O"] or diagonal1 == ["O", "O", "O"] or diagonal2 == ["O", "O",
                                                                                                      "O"]:
        if player == o:
            print("You win! (O)")
            return "X"
        if player != o:
            print("You lose")
            return "O"
    if b[0] != " " and b[1] != " " and b[2] != " " and b[3] != " " and b[4] != " " and b[5] != " " and b[
        6] != " " and b[7] != " " and b[8] != " ":
        print("TIE!")
        winner = True
        return "0"
    return "null"
#Minimax Algorithm
def minimax(b, depth, isMaximizing):
            result = checkwinner()
            if result != "null":
                score = scores[result] + score
                return score

            if (isMaximizing):
                bestScore = -math.inf
                j = 0
                for str in b:
                    if str == " ":
                        b[j] = AI
                        score = minimax(b, depth + 1, False) + score
                        b[j] = " "
                        bestScore = max(score, bestScore)
                    j += 1
                return bestScore

            else:
                bestScore = math.inf
                k = 0
                for str in b:
                    if str == " ":
                        b[k] = player
                        score = minimax(b, depth + 1, True) + score
                        b[k] = " "
                        bestScore = min(score, bestScore)
                    k += 1
                return bestScore


#Game Start loop
if (first == "P"):
    while (game == True):
        i = 0
        scores = {
            'O': 1,
            'X': -1,
            '0': 0
        }
#AI turn
        bestScore = -math.inf
        turnai = False
        i = 0
        for str in b:
            if str == " ":
                b[i] = AI
                score = minimax(b, 0, True)
                b[i] = " "
                print(score)
                if score > bestScore and turnai == False:
                    bestScore = score
                    b[i] = AI
                    turnai = True
            i += 1
        turnai = False
        print("")
        # b = [ul, um, ur, ml, mm, mr, ll, lm, lr]
        ur = b[2]
        um = b[1]
        ul = b[0]
        ml = b[3]
        mm = b[4]
        mr = b[5]
        ll = b[6]
        lm = b[7]
        lr = b[8]
#Prints Board
        board(b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8])
        cw = checkwinner()
#Checks if game ended
        if cw == "X" or cw == "O" or cw == "0":
            game = False
            break
#Player turn
        print("Where do you want to place your letter?")
        turn = input(": ")
        if turn == "ur" and ur == " ":
            b[2] = player
            uru = True
        if turn == "um" and um == " ":
            b[1] = player
            umu = True
        if turn == "ul" and ul == " ":
            b[0] = player
            ulu = True
        if turn == "mr" and mr == " ":
            b[5] = player
            mru = True
        if turn == "mm" and mm == " ":
            b[4] = player
            mmu = True
        if turn == "ml" and ml == " ":
            b[3] = player
            mlu = True
        if turn == "lr" and lr == " ":
            b[8] = player
            lru = True
        if turn == "lm" and lm == " ":
            b[7] = player
            lmu = True
        if turn == "ll" and ll == " ":
            b[6] = player
            llu = True
#Prints Board
        board(b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8])
        sw = checkwinner()
#Checks if game needs to be ended
        if cw == "X" or cw == "O" or cw == "0":
            game = False
            break


Solution

    • As a general style rule try to keep your line lengths < 100 characters.
    • You shouldn't use str as a variable name as it is a builtin.
    • You can use enumerate to get the index and the value from a list as you iterate.
    • scores should be defined in minimax because that's the only place it is used.
    • AI is taking the first spot available because of this section of code:

      bestScore = -math.inf
      turnai = False
      i = 0
      for str in b:
          if str == " ":
              b[i] = AI
              score = minimax(b, 0, True)
              b[i] = " "
              print(score)
              if score > bestScore and turnai == False: # Allways true first loop
                  bestScore = score
                  b[i] = AI 
                  turnai = True                         # ^ Never true in later loops
      

      Which ironically only occurs when turnai == false

    • You don't actually need your turnai boolean in game loop because you explicitly code both the ai and user turns.
    • You don't need to check if a boolean == True just say while game:
    • You don't need to break if your control bool is set to false.
    • If the user enters invalid input they lose their turn.
    • you have sw = checkwinner() then you examine the value of cw

    I could fix this for you but I think I've given you enough to work with here and I will tell you that your concept is sound(ish) so if you just keep with it a little further you should get some satisfactory results...

    The major logical flaw here is that you never actually count how many wins a given choice leaves... Your minimax function just returns 1 every time.