Gizmo
Gizmo

Reputation: 39

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")

Upvotes: 0

Views: 411

Answers (4)

Tiago Oliveira
Tiago Oliveira

Reputation: 47

When the button is pressed check if the turn is equal to 1 or 2 and with that you tell the program to display a cross or a circle instead of making a function to change that value.

Upvotes: 1

laancelot
laancelot

Reputation: 3207

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!

Upvotes: 1

horse
horse

Reputation: 501

Set the variable 'turn' as a global (outside of the 'turn_changer' function / making sure this code is only ran once, not every time your game loops). Run your game logic, with the player (X or O) being determined by the value of 'turn'. After the player makes their move call 'turn_changer' so the turn changes. Repeat.

Using the code you provided, I added this:

def game():
    print (turn)
    turn_changer()
    print (turn)
    turn_changer()
    print (turn)

if __name__ == "__main__":
    game()

and the turn_changer() function seems to work correctly

Upvotes: 0

laancelot
laancelot

Reputation: 3207

There are SO MANY WAYS you could do this...

  1. You can flip a variable (kinda like you just showed). Since there are only two players, I would probably flip a boolean instead: myBoolean = !myBoolean after every turn.
  2. You can count the turns and use the modulo operator. Then you both know which turn it is right now AND the total number of turns for the current game. Let's say that you have a global TurnCount variable. If you do TurnCount % 2 the result will either be 0 or 1, making it a surefire way to know which turn it is. This operator is really useful and you should remember it either way!
  3. If there are no difference in the gameplay between player 1 and 2 except for their X and O, you can flip between the character 'X' and the character 'O' instead of 1 and 2 like in your code snippet. This variable can be used to show visually which symbol will placed or straight up to place one symbol or the other. Simple and efficient.

So it mostly depends on how ninja you code will be. If you're doing a "simpel hot seat" where players takes turn clicking with the same mouse, method 3 is great. If you want to show "statistics" after the game, method 2 will work nicely. Method 1 will work too, but it's more rough, even if it's simple. You can add more code if you need tailored advice.

Have fun!

Upvotes: 1

Related Questions