Search code examples
pythonprocessingtic-tac-toe

need help finding solution to deciding turns for tic-tac-toe


I'm working on a tic tac toe game in processing. I can't figure out how to make a way to have X and O swap to vaguely imitate swapping turns I have seen someone did it like the code below but for some reason, I either get an error or it just doesn't work when I hook it up to my preexisting code.

I should have Elaborated more I plan (attempt) to use the Minimax algorithm to make this game unwinnable

print("Begin")

global top_left, top_middle, top_right
global middle_left, center, middle_right
global bottem_left, bottem_middle, bottem_right

#these are the variables used to check if someone has put their move their already
#0 = empty
#1 = Circle
#2  = X

top_left = 0
top_middle = 0
top_right = 0

middle_left = 0
center = 0
middle_right = 0

bottem_left = 0
bottem_middle = 0
bottem_right = 0

#code for changing turns
turn = 1
def turn_changer():
    global turn
    if turn == 1:
        turn = 2
    else:
        turn = 1

#board setup
def setup():
    size(600,600)

#this hurt my brain trying to fully understand
#lines dividing board
def draw():
    for y in range(3):
        for x in range(3):
            rect(200*x,200*y,200,200)
    #hope this is not what geomtry is like

    #top left ellipse
    if top_left == 1:
        ellipse(100,100,150,150)

    #top left X
    elif top_left == 2:
        line(0,0,200,200)
        line(200,0,0,200)

    #top middle ellipse
    if top_middle == 1:
        ellipse(300,100,150,150)

    #top middle  X
    elif top_middle == 2:
        line(200,0,400,200)
        line(400,0,200,200)

    #top right ellipse
    if top_right == 1:
        ellipse(500,100,150,150)

    #top right X
    elif top_right == 2:
        line(400,0,600,200)
        line(600,0,400,200)

    #middle left ellipse
    if middle_left == 1:
        ellipse(100,300,150,150)

    #middle left X
    elif middle_left == 2:
        line(0,200,200,400)
        line(200,200,0,400)

    #middle ellipse
    if center == 1:
        ellipse(300,300,150,150)

    #middle X
    elif center == 2:
        line(200,200,400,400)
        line(400,200,200,400)

    #middle right ellipse
    if middle_right == 1:
        ellipse(500,300,150,150)

    #middle right X
    elif middle_right == 2:
        line(400,200,600,400)
        line(600,200,400,400)

    #bottem left ellipse
    if bottem_left == 1:
        ellipse(100,500,150,150)

    #bottem left  X
    elif bottem_left == 2:
        line(0,400,200,600)
        line(200,400,0,600)

    #bottem middle ellipse
    if bottem_middle == 1:
        ellipse(300,500,150,150)

    #bottem middle X
    elif bottem_middle == 2:
        line(200,400,400,600)
        line(400,400,200,600)

    #bottem right ellipse
    if bottem_right == 1:
        ellipse (500,500,150,150)

    #bottem right Xw
    elif bottem_right == 2:
        line(400,400,600,600)
        line(600,400,400,600)


#dectects the quardnates where the mouse clicked and prints them
def mousePressed():
    println( (mouseX, mouseY) )

    #top left square hitbox
    if (mouseX > 0 and mouseX < 200) and (mouseY > 0 and mouseY < 200):
        top_left =+ turn
        turn_changer()
        print("top left")



    #top middle square hitbox 
    elif (mouseX > 200 and mouseX < 400) and (mouseY > 0 and mouseY < 200): 
        top_middle = turn      
        turn_changer()
        print(turn)
        print("top middle")


    #top right square hitbox  
    elif (mouseX > 400 and mouseX < 600) and (mouseY > 0 and mouseY < 200):  
        top_right = turn
        turn_changer()
        print("top right")

    #middle left square hitbox
    elif (mouseX > 0  and mouseX < 200) and (mouseY > 200 and mouseY < 400):  
        middle_left = turn
        turn_changer()
        print("middle left")

    #center square hitbox
    elif (mouseX > 200 and mouseX < 400) and (mouseY > 200 and mouseY < 400):  
        center = turn
        turn_changer()
        print("middle")  

    #middle right square hitbox
    elif (mouseX > 400 and mouseX < 600) and (mouseY > 200 and mouseY < 400):  
        middle_right = turn
        turn_changer()
        print("middle right") 

    #bottem left square hitbox
    elif (mouseX > 0 and mouseX < 200) and (mouseY > 400 and mouseY < 600):  
        bottem_left = turn
        turn_changer()
        print("bottem left")

    #bottem middle square hitbox
    elif (mouseX > 200 and mouseX < 400) and (mouseY > 400 and mouseY < 600):  
        bottem_middle = turn
        turn_changer()
        print("bottem middle")

    #bottem right square hitbox
    elif (mouseX > 400 and mouseX < 600) and (mouseY > 400 and mouseY < 600):  
        bottem_right = turn
        turn_changer()
        print("bottem right")

Solution

  • I respect that you're learning on your own, and so I took some time to learn python's basics to give you something to think about. I'm not a python buff (yet), so I may have done some misguided manipulations somewhere (so if a better coder than me is reading this and spots something awful, let me know), but I believe that this is mostly good stuff.

    I used class since I tend to think in OOP (and so will you after a while). Instead of seeing a grid with X and O, I see the game like this:

    Tic-tac-toe

    1. One game is an object.
    2. A Game manages:

      A Grid (which is an object, too).

      Who's turn it is (and when it's the AI turn, how the AI should play).

      When the game ends.

    3. A grid manages:

      9 Cases (which are objects, too).

    4. A Case manages:

      It draws itself (so... it's coordinates and stuff).

      If there's a X or O on it.

      If it's been clicked

    I fully realize that objects are a huge bump in the learning curve when you start programming, but I'm insisting here because I've seen A LOT of hardcoding in your code, and it's the kind of stuff which will cause you problems when you scale up your projects.

    Hardcoding, like how you check which case have been clicked, isn't inherently bad, but it makes everything more difficult. It's part of the things you sometimes learn "the hard way", so here's my advice: when you ninja-code something (short code snippets which are written quickly and won't be part of something bigger), it's not great but it does the job. In any other context, it must be motivated by some specific need to be a good practice, and even there it can be avoided most of the time.

    Here's commented code based on what I just wrote. I didn't do the whole tic-tac-toe game, just up to the part where it switch turns between the players or the player/AI (I put a boolean up there that let you switch between human opponent and AI opponent). What is missing is mostly the AI logic (I put a temporary one where it selects the first case it finds) and the victory conditions.

    The boolean is currently in "player vs player" mode. Change it to True to let the AI take over the O side.

    # Player 1 (X) is human and play first
    # Player 2 (O) is cpu
    # You can change this boolean to play hotseat with a human if you want:
    _AIPlayer = False
    
    # Game own a grid, count turns and do any other game-specific concepts
    # One "game of tic-tac-toe" would equal one of this object
    class Game:
        def __init__(self):
            self.Grid = Grid(self) # creating the grid we'll use
            self.TurnCount = 0 # first turn is turn number zero
    
        def Render(self):
            # when you draw the game, in fact it asks it's grid to draw itself
            self.Grid.Render()
    
        def Play(self):
            # if it's the CPU's turn, let him play, else the game will wait for the player before going forward
            # if there is no cpu player, the mouse can be used by player two
            # the difference is that the cpu will do it's turn as a consequence of the player's turn
            # and then add +1 to the turn count, while player 2 is exactly like player one but with O instead of X
            # the game will check X and O to see who win, not a player class (but it could have been designed that way if needed)
            if self.GetCurrentPlayer() == "O" and _AIPlayer:
                self.AITurn()
    
        def GetCurrentPlayer(self):
            # return which's player is currently playing
            if self.TurnCount % 2 == 0:
                return "X"
            else:
                return "O"
    
        def AITurn(self):
            # this is a dumb placeholder
            # your AI logic will be used here
            # for now it just put a O on the first available case
            print("AI turn")
            for c in self.Grid.Cases:
                if c.XO == "":
                    c.XO = self.GetCurrentPlayer()
                    break
            self.TurnCount += 1
    
    
    # Grid class is the whole grid
    class Grid:
        def __init__(self, game):
            # the grid knows the game. I could use the global variable instead, but I dislike
            # this kind of spaghetti. It would have worked, though.
            # It's usually best to make everything you can dynamic, i.e. not hardcoded. 
            # It's easier to maintain and fix bugs that way, and you can upscale more easily too
            # for an example, I could use this code to run several tic-tac-toe games in the
            # same window at the same time with only a few modifications
            self.Game = game
            self.Cases = []
            for i in range(3):
                for j in range(3):
                    self.Cases.append(GridCase(i, j))
    
        def Render(self):
            # when you draw the grid, in fact it ask it's cases to draw themselves
            for c in self.Cases:
                c.Render()
    
        def CaseClicked(self, xPos, yPos):
            # this checks which case was clicked when it's a player
            # since we don't care about the case's coordinated, we ask them if they have been clicked instead
            for c in self.Cases:
                if c.Clicked(xPos, yPos, self.Game.GetCurrentPlayer()):
                    self.Game.TurnCount += 1
                    return
    
    
    # GridCase is each instance of 1 case in the grid
    class GridCase:    
        def __init__(self, gridX, gridY):
            # gridX and gridY are useful to know which case is part of which line
            self.gridX = gridX
            self.gridY = gridY
            # I hardcoded the case's width and height, but you could totally make them dynamic
            # and decide "on the fly" how big the grid will be. And it would still work.
            self.w = 200  # width
            self.h = 200  # height
            # these coordinates are in pixels, and are useful to draw the case and for hit detection
            self.x = self.w * gridX # x coordinate of the case
            self.y = self.h * gridY # y coordinate of the case
            # the "content" of the case
            self.XO = ""  # X or O as a character (it could be anything, I choose to stick to these)
    
        def Render(self):
            # the lines positions are dynamic: they'll be calculated from the case's perspective
            # every case top left corner is in fact: (self.x, self.y)
            rect(self.x, self.y, self.w, self.h)
            # if the case has content, it'll be drawn at the same time than the case
            if self.XO == "X":
                line(self.x , self.y, self.x+self.w, self.y+self.h)
                line(self.x, self.y+self.h, self.x+self.w, self.y)
            elif self.XO == "O":
                ellipse(self.x+(self.w/2),self.y+(self.h/2), self.w*0.75, self.h*0.75)
    
        def SetXO(self, XO):
            self.XO = XO
    
        def Clicked(self, xPos, yPos, car):
            # if the case is free and the click was inside it's boundaries, then attribute it to the current player
            # the return True to tell that a sign was just placed
            if self.XO == "" and xPos > self.x and xPos < self.x + self.w and yPos > self.y and yPos < self.y + self.h:
                self.XO = car
                return True
            return False
    
    
    # globals
    _game = Game()
    
    def setup():
        size(600,600)
    
    def draw():
        # background wipes the screen "clean" (here it paints it black)
        # then we can draw the current state of the grid
        # here we could do without but I wanted you to know about it
        background(0)
        # draw the grid, then let the players do their thing
        _game.Render()
        # here you should check for game end conditions (victory or draw)
        _game.Play()
    
    def mouseClicked():
       # listeing to mouse clicks
       _game.Grid.CaseClicked(mouseX, mouseY)
    

    You should copy and paste this code in a Processing.py IDE and try it. Fiddle around and read the comments. You can learn a lot here if you try. If you have questions, ask away in the comments with my handle and I'll come back and give you a hand.

    And... have fun!