0

i am trying to learn python and have been trying to programm a simple tictactoe game. Below is the code:

import random


class TicTacToe03:

    def __init__(self):
        # first, the board
        self.board = self.board = [" ", " ", " ", " ", " ", " ", " ", " ", " "]

        # then, the symbols the players will choose
        self.playerSymbol = ""
        self.aiSymbol = ""

        # the game is always ongoing, unless somebody won
        self.gameOngoing = True

        # you also need to tell the ai how different conditions are evaluated
        self.scoreBoard = {
            self.playerSymbol: -10,  # if the player wins, it is seen as smth bad from the ai
            self.aiSymbol: 10,
            "tie": 0
        }

    # every tictactoe game starts by drawing the board
    def drawBoard(self):
        print("""
          {} | {} | {}
         -----------
          {} | {} | {}
         -----------
          {} | {} | {}
        """.format(*self.board))

    # then, eveybody chooses what the like
    def chooseYourPlayer(self):
        self.playerSymbol = input("choose your player X/O ").upper()

        if self.playerSymbol == "X":
            self.aiSymbol = "O"
        else:
            self.aiSymbol = "X"

    # everybody has to behave and leave their turn when it's over
    def nextPlayer(self, player):
        if player == self.playerSymbol:
            return self.aiSymbol
        return self.playerSymbol

    # ppl also have to be able to make moves
    def move(self, player, index):
        self.board[index] = player

    # and find what moves they CAN make, so they don't override each-other
    def availableMoves(self):

        availableMoves = []

        for fieldIndex in range(len(self.board)):
            if self.board[fieldIndex] == " ":
                availableMoves.append(fieldIndex)  # yes, i append indexes, not empty fields
        return availableMoves

    def getMoves(self, player):

        playerMoves = []

        for fieldIndex in range(len(self.board)):
            if self.board[fieldIndex] == player:
                playerMoves.append(fieldIndex)
        return playerMoves

    # here is the algo to check who won, based on a set of winPos is subset of playerMoves
    def won(self):

        winningPositions = [{0, 1, 2}, {3, 4, 5}, {6, 7, 8},
                            {0, 4, 8}, {2, 4, 6}, {0, 3, 6},
                            {1, 4, 7}, {2, 5, 8}]

        for player in ("X", "O"):
            playerPositions = self.getMoves(player)
            for position in winningPositions:
                if position.issubset(playerPositions):
                    print(player + "wins")
                    self.gameOngoing = False
                    return player
        if self.board.count(" ") == 0:  # and this dude outside of the main loop
            print("Guess it's a draw")
            self.gameOngoing = False
            return "tie"

    def minimax(self, isMaximizing, player):

        if not self.gameOngoing:  # base in minimax: if this is an end-state, return it's evaluation
            return self.scoreBoard[self.won()]

        if isMaximizing:
            bestScore = float("-Infinity")  # the worst eval for max
            for move in self.availableMoves():
                self.move(player, move)
                score = self.minimax(False, self.nextPlayer(player))  # you should pick the max of what your opponent chooses
                self.move(" ", move)
                bestScore = max(bestScore, score)
            return bestScore
        else:
            bestScore = float("Infinity")
            for move in self.availableMoves():
                self.move(player, move)
                score = self.minimax(True, self.nextPlayer(player))
                self.move(" ", move)
                bestScore = min(bestScore, score)
            return bestScore

    def findOptimalMove(self, player):

        goldenMiddle = 0
        choices = []

        for move in self.availableMoves():
            self.move(player, move)
            score = self.minimax(True, player)
            self.move(" ", move)
            if score > goldenMiddle:
                choices = [move]  # this is a one-element list
                break
            elif score == goldenMiddle:
                choices.append(move)

        # here is the code block that mustn't be wrongly interpreted:
        # the idea is: you choose a random element, from a ONE-ELEMENT list!!!
        if len(choices) > 0:
            return random.choice(choices)
        else:
            return random.choice(self.availableMoves())

    def play(self):

        self.chooseYourPlayer()

        while self.gameOngoing:
            personMove = int(input("Choose a position (1-9) "))
            self.move(self.playerSymbol, personMove - 1)
            self.drawBoard()

            if not self.gameOngoing:
                break

            print("Computer choosing move...")
            aiMove = self.findOptimalMove(self.aiSymbol)
            self.move(self.aiSymbol, aiMove)
            self.drawBoard()

        print("Thanks for playing :)")


tictactoe = TicTacToe03()
tictactoe.play()

The game "works", as of it displays the fields of the user, however it won't show whether anybody won or not. As you can see, i tried implementing the simple minimax algorithm, but that doesn't work properly either, because the ai just chooses the next available field to go to and not the one it logically should.

As for the errors i am getting: i only got one once, after the board was full and it was the following: IndexError: Cannot choose from an empty sequence (on line 133).

I have also posted another version of this, where the "winning" issue is not present ( TicTacToe and Minimax ), but since nobody was answering that question, I thought it would be helpful asking again (sorry if i did anything wrong)

As always, I remain open to suggestions and ways to improve my code :)

Kristi
  • 27
  • 7
  • The minimax logic looks generally ok to me, but I would get rid of the `gameOngoing` variable. The base case for minimax is either that there are no more moves, or that somebody won. So I think fixing the handling of the base case will be a step forward. – user3386109 May 20 '20 at 20:13

1 Answers1

0

In my opinion, the only issue is that you forgot to call self.won() in the play() method:

def play(self):

    self.chooseYourPlayer()

    while self.gameOngoing:
        personMove = int(input("Choose a position (1-9) "))
        self.move(self.playerSymbol, personMove - 1)
        self.drawBoard()

        self.won()                     #<--- call self.won() here
        if not self.gameOngoing:
            break

        print("Computer choosing move...")
        aiMove = self.findOptimalMove(self.aiSymbol)
        self.move(self.aiSymbol, aiMove)
        self.drawBoard()

    print("Thanks for playing :)")

I think you tried to do something similar in minimax() method. But I didn't understand what did you mean by:

if not self.gameOngoing:
    return self.scoreBoard[self.won()]
Anwarvic
  • 12,156
  • 4
  • 49
  • 69
  • The idea behind that is the base case of minimax: so if the game is no longer ongoing, it should return the evaluation of the state it reached. Btw i tried your suggestion and it still didn't work – Kristi May 21 '20 at 09:55
  • Have you tried it though? because I've tried it and it shows who won and it doesn't throw any errors when the board is complete. Your program has a lot to be handled, but this specific issue has been handled – Anwarvic May 21 '20 at 15:00