Creating GUI enabled Tic Tac Toe in Python

The best way of learning a programming language is by doing exciting projects. And what would be better than making our favorite Tic Tac Toe in a Python game and that too with graphics? Isn’t it great! And if you think it will be tough, you are wrong, my friend. It’s super easy to make tic tac toe in Python. All you need is a basic understanding of Python’s programming language.


For making this project with a Graphical Interface (where user interaction is possible), we will use Python’s Tkinter
module, which is very easy to implement. Also, we will use fundamental concepts of Python like lists, if conditions, and functions. So, what are we waiting for? Let’s learn how to make a tic tac toe in Python.

Note: This code is for Python version 3 and above. The logic will be the same for version 2, but there will be syntactical changes.

Making the Graphical Interface for Tic Tac Toe in Python

tic tac toe in python

Our goal is to make an interface like the above. For this purpose, in Python, we have a pre-installed library called Tkinter, which is used to create such graphics.
As this module comes pre-installed with Python, we can import it directly.

from tkinter import *

Using * after import means we have imported all the methods, and variables of the tkinter library.

root=Tk()

We have created a Tk widget and assigned it to a variable root. Tk is the parent window containing a title bar and some other decorations provided by the window manager. We have to create this widget before any other widget, and there can be only one such type of widget.

label1=Label(root,text="player1 : X",font="times 15")
label1.grid(row=0,column=1)
label2=Label(root,text="player2 : O",font="times 15")
label2.grid(row=0,column=2)

The above line of code creates a Label widget, where we can write something in the parent window. In the first parameter, we give the value of the parent window, in this case, the root. As the second parameter, we give what text we have to display. We have set the font type in the third parameter to ‘times 15’. You can change it according to your wish.

After that, we want to display this text in the 0th-row 1st column, and for that, we have used label1.grid(row=0,column=1).
As tic tac toe is a two-player game, we need two such labels. So, we have created label2 similar to label1.

Creating cells in tic tac toe

b1=Button(root,width=20,height=10,command=lambda:define_sign(1))
b1.grid(row=1,column=1)

In a tic tac toe, there are a total of 9 cells. And for every cell, we need to create a button. So that when a player clicks a button, we will know which cell. In the above code, we have used the Button method of Tkinter. As the first parameter, we have given the value root as we want the button to be inside the parent window. In the second and third parameters, we have set the values of the button’s width and height. You can play with those values for better understanding. After that, we need to do something when this button gets click, so in the 4th parameter, we are giving the command to call the ‘define_sign()’ using the lambda keyword whenever this button is clicked.

Don’t get confused if you don’t know the lambda keyword. For now, think of it as a way to call the define_sign() method when button1 is clicked. We have passed ‘1’ to the define_sign() method because button1 has been clicked. And don’t worry about the define_sign() method. We will understand it when we know the concept used behind the game.

After that, we will put the button in the 1st row and 1 column just as we put the Label widget.
Similarly, we have to create 8 more buttons same as button1.

b2=Button(root,width=20,height=10,command=lambda:define_sign(2))
b2.grid(row=1,column=2)
b3=Button(root,width=20,height=10,command=lambda: define_sign(3))
b3.grid(row=1,column=3)
b4=Button(root,width=20,height=10,command=lambda: define_sign(4))
b4.grid(row=2,column=1)
b5=Button(root,width=20,height=10,command=lambda: define_sign(5))
b5.grid(row=2,column=2)
b6=Button(root,width=20,height=10,command=lambda: define_sign(6))
b6.grid(row=2,column=3)
b7=Button(root,width=20,height=10,command=lambda: define_sign(7))
b7.grid(row=3,column=1)
b8=Button(root,width=20,height=10,command=lambda: define_sign(8))
b8.grid(row=3,column=2)
b9=Button(root,width=20,height=10,command=lambda: define_sign(9))
b9.grid(row=3,column=3)
root.mainloop() #Implements the main window apllication

Voila! We have successfully created the graphical interface for our game. Wasn’t that easy? Believe me, the rest of the code is as easy as this.

How is the Winner decided in Tic Tac Toe?

In tic tac toe, there are two players playing the game. ‘X’ is the mark used by Player 1, and ‘Player2 uses O’. The Winner is decided if all the values of any of the rows or columns have the same mark (‘X’ or ‘O’). If the mark in either of the diagonals is identical, the Winner is decided.

So, there are eight combinations when the Winner can be decided.

Implementing the Tic Tac Toe Logic in Python –

def result(boards,mark):
    return ((boards[1] == boards[2] == boards [3] == mark) or (boards[4] == boards[5] == boards [6] == mark) or (boards[7] == boards[8] == boards [9] == mark) or (boards[1] == boards[4] == boards [7] == mark) or (boards[2] == boards[5] == boards [8] == mark) or (boards[3] == boards[6] == boards [9] == mark) or (boards[1] == boards[5] == boards [9] == mark) or (boards[3] == boards[5] == boards [7] == mark))

Here, we will create a function named result(), which will return True if the mark in all three cells is the same. In the first parameter, ‘boards’, we will give a list containing the mark on each cell. For example, if player1 had clicked on button3, this list will have the value=’X’ at index 3.

The second parameter, ‘mark,’ contains the value either ’X’ or ‘O.’ This function will check if the list passed (boards) has the mark (‘X’ or ‘O’) in these cells or not. If yes, it will return True; otherwise, False.

The board will always contain ‘1’ at index 0 because the index of a list starts with 0, so we have to adjust to that.

For example, if the value passed in the first parameter is -[1, ‘X’, ‘O’, ‘O’, ‘X’, ‘X’, ‘O’, ‘O’, ‘X’, ‘X’] and the mark = ’X’ our function will check if these values satisfy the conditions in our function.
Here, boards[1]==boards[5]==boards[9]==’X’, so our function will return True.

Implementing the Constants and define_sign() function

This list contains all the cell numbers of the tic tac tow board. With the help of these numbers, you can fetch the respective mark.

#A list containing all the cell numbers
numbers=[1,2,3,4,5,6,7,8,9]

We’ll initialize the playing icons for the players. ‘X’ will be for player 1 and ‘O’ will be for player 2. As the game progresses, the value in this variable changes according to which player is currently playing.

y='X' for player1 and 'O' for player2
y=""

We’ll initialize a variable x which counts the number of chances played by the players. The game logic will update this variable whenever a player clicks on aboard.

#x is the counter to keep counting the number of chances
x=0

Board is an array that stores the respective mark made by each player. This list is accessed with the help of a numbers list and this way the python understands board values.

"""boards is a list to store the mark with respect to the cell number"""
boards=["board"]*10

Destroys is a function that clears all the Tkinter widgets and destroys them. Whenever a game ends, this function is triggered, causing the Tkinter window to close itself.

def destroys():
    # destroys the window when called
    root.destroy()

Now, all that is left is the define_sign() function. Whenever a click is made on any of the buttons, this method is called (check the ‘command’ parameter of the Button method of Tkinter), containing the information of the button in its argument.

def define_sign(number):
    global x,y,numbers
    """ Checking which button has been clicked and checking if the button 
    has been already clicked or not to avoid over-writing """
    if number==1 and number in numbers:
        numbers.remove(number)
       
        # If the value of x is even, Person1 will play and vivee versa
        if x%2==0:
            y='X'
            boards[number]=y
        elif x%2!=0:
            y='O'
            boards[number]=y
        #Using config, we write mark the button with appropriate value. 
        b1.config(text=y)
        x=x+1
        mark=y
        # Here we are calling the result() to decide whether we have got the winner or not
        if(result(boards,mark) and mark=='X' ):
            #If Player1 is the winner show a dialog box stating the winner
            showinfo("Result","Player1 wins")
            #Call the destroy function to close the GUI
            destroys()
        elif(result(boards,mark) and mark=='O'):
            showinfo("Result","Player2 wins")
            destroys()
        
    if number==2 and number in numbers:
        numbers.remove(number)
        if x%2==0:
            y='X'
            boards[number]=y
        elif x%2!=0:
            y='O'
            boards[number]=y
            
        b2.config(text=y)
        x=x+1
        mark=y
        if(result(boards,mark)and mark=='X' ):
            showinfo("Result","Player1 wins")
            destroys()
        elif(result(boards,mark)and mark=='O' ):
            showinfo("Reuslt","Player2 wins")
            destroys()
        
    if number==3 and number in numbers:
        numbers.remove(number)
        if x%2==0:
            y='X'
            boards[number]=y

        elif x%2!=0:
            y='O'
            boards[number]=y    
        b3.config(text=y)
        x=x+1
        mark=y
        if(result(boards,mark)and mark=='X'):
            showinfo("Result","Player1 wins")
            destroys()
        elif(result(boards,mark) and mark=='O'):
            showinfo("Result","Player2 wins")
            destroys()
        
    if number==4 and number in numbers:
        numbers.remove(number)
        if x%2==0:
            y='X'
            boards[number]=y
        
        elif x%2!=0:
            y='O'
            boards[number]=y  
        b4.config(text=y)
        x=x+1
        mark=y
        if(result(boards,mark)and mark=='X'):
            showinfo("Result","Player1 wins")
            destroys()
        elif(result(boards,mark) and mark=='O'):
            showinfo("Result","Player2 wins")
            destroys()
        
    if number==5 and number in numbers:
        numbers.remove(number)
        if x%2==0:
            y='X'
            boards[number]=y
        elif x%2!=0:
            y='O'
            boards[number]=y
                       
        b5.config(text=y)
        x=x+1
        mark=y
        if(result(boards,mark)and mark=='X' ):
            showinfo("Result","Player1 wins")
            destroys()
        elif(result(boards,"O")and mark=='O'):
            showinfo("Result","Player2 wins")
            destroys()
        
    if number==6 and number in numbers:
        numbers.remove(number)
        if x%2==0:
            y='X'
            boards[number]=y
        elif x%2!=0:
            y='O'
            boards[number]=y

        b6.config(text=y)
        x=x+1
        mark=y
        if(result(boards,mark) and mark=='X'):
            showinfo("Result","Player1 wins")
            destroys()
        elif(result(boards,mark)and mark=='O'):
            showinfo("Result","Player2 wins")
            destroys()
        
    if number==7 and number in numbers:
        numbers.remove(number)
        if x%2==0:
            y='X'
            boards[number]=y

        elif x%2!=0:
            y='O'
            boards[number]=y

        b7.config(text=y)
        x=x+1
        mark=y
        if(result(boards,mark) and mark=='X' ):
            showinfo("Result","Player1 wins")
            destroys()
        elif(result(boards,mark) and mark=='O'):
            print("Player2 wins")
            showinfo("Result","Player2 wins")
            destroys()
        
    if number==8 and number in numbers:
        numbers.remove(number)
        if x%2==0:
            y='X'
            boards[number]=y

        elif x%2!=0:
            y='O'
            boards[number]=y
            
        b8.config(text=y)
        x=x+1
        mark=y
        if(result(boards,mark) and mark=='X'):
            print("Player1 wins")
            showinfo("Result","Player1 wins")
            destroys()
        elif(result(boards,"O")and mark=='O'):
            print("Player2 wins")
            showinfo("Result","Player2 wins")
            destroys()
    if number==9 and number in numbers:
        numbers.remove(number)
        if x%2==0:
            y='X'
            boards[number]=y

        elif x%2!=0:
            y='O'
            boards[number]=y
            
        b9.config(text=y)
        x=x+1
        mark=y
        if(result(boards,mark) and mark=='X'):
            print("Player1 wins")
            showinfo("Result","Player1 wins")
            destroys()
        elif(result(boards,mark) and mark=='O'):
            print("Player2 wins")
            showinfo("Result","Player2 wins")
            destroys()
            
    # If we have not got any winner, display the dialogbox stating the match has bee tied.
    if(x>8 and result(boards,'X')==False and result(boards,'O')==False):
        showinfo("Result","Match Tied")
        destroys()

Full Code for Tic Tac Toe in Python

from tkinter import *
from tkinter.messagebox import showinfo
import warnings

#Removes all the warning from the output
warnings.filterwarnings('ignore')

root=Tk()


numbers=[1,2,3,4,5,6,7,8,9] 
# y='X' for player1 and 'O' for player2
y=""
# x is the counter to keep counting the number of chances
x=0
#boards is a list to store the mark with respect to the cell number
boards=["board"]*10

def result(boards,mark):
    return ((boards[1] == boards[2] == boards [3] == mark) 
            or (boards[4] == boards[5] == boards [6] == mark) 
            or (boards[7] == boards[8] == boards [9] == mark) 
            or (boards[1] == boards[4] == boards [7] == mark) 
            or (boards[2] == boards[5] == boards [8] == mark)
            or (boards[3] == boards[6] == boards [9] == mark)
            or (boards[1] == boards[5] == boards [9] == mark) 
            or (boards[3] == boards[5] == boards [7] == mark))


def define_sign(number):
    global x,y,numbers
    """ Checking which button has been clicked and checking if the button has been already clicked or not to avoid over-writing"""
    if number==1 and number in numbers:
        numbers.remove(number)
       
        # If the value of x is even, Person1 will play and vivee versa
        if x%2==0:
            y='X'
            boards[number]=y
        elif x%2!=0:
            y='O'
            boards[number]=y
        #Using config, we write mark the button with appropriate value. 
        b1.config(text=y)
        x=x+1
        mark=y
        # Here we are calling the result() to decide whether we have got the winner or not
        if(result(boards,mark) and mark=='X' ):
            #If Player1 is the winner show a dialog box stating the winner
            showinfo("Result","Player1 wins")
            #Call the destroy function to close the GUI
            destroys()
        elif(result(boards,mark) and mark=='O'):
            showinfo("Result","Player2 wins")
            destroys()
        
    if number==2 and number in numbers:
        numbers.remove(number)
        if x%2==0:
            y='X'
            boards[number]=y
        elif x%2!=0:
            y='O'
            boards[number]=y
            
        b2.config(text=y)
        x=x+1
        mark=y
        if(result(boards,mark)and mark=='X' ):
            showinfo("Result","Player1 wins")
            destroys()
        elif(result(boards,mark)and mark=='O' ):
            showinfo("Reuslt","Player2 wins")
            destroys()
        
    if number==3 and number in numbers:
        numbers.remove(number)
        if x%2==0:
            y='X'
            boards[number]=y

        elif x%2!=0:
            y='O'
            boards[number]=y    
        b3.config(text=y)
        x=x+1
        mark=y
        if(result(boards,mark)and mark=='X'):
            showinfo("Result","Player1 wins")
            destroys()
        elif(result(boards,mark) and mark=='O'):
            showinfo("Result","Player2 wins")
            destroys()
        
    if number==4 and number in numbers:
        numbers.remove(number)
        if x%2==0:
            y='X'
            boards[number]=y
        
        elif x%2!=0:
            y='O'
            boards[number]=y  
        b4.config(text=y)
        x=x+1
        mark=y
        if(result(boards,mark)and mark=='X'):
            showinfo("Result","Player1 wins")
            destroys()
        elif(result(boards,mark) and mark=='O'):
            showinfo("Result","Player2 wins")
            destroys()
        
    if number==5 and number in numbers:
        numbers.remove(number)
        if x%2==0:
            y='X'
            boards[number]=y
        elif x%2!=0:
            y='O'
            boards[number]=y
                       
        b5.config(text=y)
        x=x+1
        mark=y
        if(result(boards,mark)and mark=='X' ):
            showinfo("Result","Player1 wins")
            destroys()
        elif(result(boards,"O")and mark=='O'):
            showinfo("Result","Player2 wins")
            destroys()
        
    if number==6 and number in numbers:
        numbers.remove(number)
        if x%2==0:
            y='X'
            boards[number]=y
        elif x%2!=0:
            y='O'
            boards[number]=y

        b6.config(text=y)
        x=x+1
        mark=y
        if(result(boards,mark) and mark=='X'):
            showinfo("Result","Player1 wins")
            destroys()
        elif(result(boards,mark)and mark=='O'):
            showinfo("Result","Player2 wins")
            destroys()
        
    if number==7 and number in numbers:
        numbers.remove(number)
        if x%2==0:
            y='X'
            boards[number]=y

        elif x%2!=0:
            y='O'
            boards[number]=y

        b7.config(text=y)
        x=x+1
        mark=y
        if(result(boards,mark) and mark=='X' ):
            showinfo("Result","Player1 wins")
            destroys()
        elif(result(boards,mark) and mark=='O'):
            print("Player2 wins")
            showinfo("Result","Player2 wins")
            destroys()
        
    if number==8 and number in numbers:
        numbers.remove(number)
        if x%2==0:
            y='X'
            boards[number]=y

        elif x%2!=0:
            y='O'
            boards[number]=y
            
        b8.config(text=y)
        x=x+1
        mark=y
        if(result(boards,mark) and mark=='X'):
            print("Player1 wins")
            showinfo("Result","Player1 wins")
            destroys()
        elif(result(boards,"O")and mark=='O'):
            print("Player2 wins")
            showinfo("Result","Player2 wins")
            destroys()
    if number==9 and number in numbers:
        numbers.remove(number)
        if x%2==0:
            y='X'
            boards[number]=y

        elif x%2!=0:
            y='O'
            boards[number]=y
            
        b9.config(text=y)
        x=x+1
        mark=y
        if(result(boards,mark) and mark=='X'):
            print("Player1 wins")
            showinfo("Result","Player1 wins")
            destroys()
        elif(result(boards,mark) and mark=='O'):
            print("Player2 wins")
            showinfo("Result","Player2 wins")
            destroys()
            
    # If we have not got any winner, display the dialogbox stating the match has bee tied.
    if(x>8 and result(boards,'X')==False and result(boards,'O')==False):
        showinfo("Result","Match Tied")
        destroys()
        


label1=Label(root,text="player1 : X",font="times 15")
label1.grid(row=0,column=1)


l2=Label(root,text="player2 : O",font="times 15")
l2.grid(row=0,column=2)


def destroys():
    # destroys the window when called
    root.destroy()


b1=Button(root,width=20,height=10,command=lambda:define_sign(1))
b1.grid(row=1,column=1)
b2=Button(root,width=20,height=10,command=lambda:define_sign(2))
b2.grid(row=1,column=2)
b3=Button(root,width=20,height=10,command=lambda: define_sign(3))
b3.grid(row=1,column=3)
b4=Button(root,width=20,height=10,command=lambda: define_sign(4))
b4.grid(row=2,column=1)
b5=Button(root,width=20,height=10,command=lambda: define_sign(5))
b5.grid(row=2,column=2)
b6=Button(root,width=20,height=10,command=lambda: define_sign(6))
b6.grid(row=2,column=3)
b7=Button(root,width=20,height=10,command=lambda: define_sign(7))
b7.grid(row=3,column=1)
b8=Button(root,width=20,height=10,command=lambda: define_sign(8))
b8.grid(row=3,column=2)
b9=Button(root,width=20,height=10,command=lambda: define_sign(9))
b9.grid(row=3,column=3)
root.mainloop()

Must Read:

Conclusion-

  • The time complexity for implementing tic tac toe in Python is O(1).
  • The minimum number of turns required to win is 5.
  • If both players play optimally, the result will end in a draw.
  • You can add much exciting stuff to this project, like you can ask to play again, or you can add a scoreboard displaying the number of wins of each player. You can ask the players for their names. Try to run this tic tac toe in Python in your local system, and we are there for you if you face any problems.

Try to run the programs on your side and let me know if you have any queries.

Happy Coding!

Subscribe
Notify of
guest
3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
PRATHAM HEGDE
PRATHAM HEGDE
3 years ago

can u explain the define_sign() method and logic used

Python Pool
Admin
3 years ago
Reply to  PRATHAM HEGDE

define_sign() function manages the turns of players in the game and also checks for win conditions for both the players. For each of the players, if the win condition is satisfied, the win message will be displayed and all the Tkinter widgets will be destroyed. The variables x, y, and numbers stores the value of boards.

PRATHAM HEGDE
PRATHAM HEGDE
3 years ago
Reply to  Python Pool

thank u very much
and if u can pls explain the lines given below
 

if

x

%

2

==

0

:
            

y

=

'X'
            

boards[number]

=

y
        

elif

x

%

2

!

=

0

:
            

y

=

'O'
            

boards[number]

=

y
        

#Using config, we write mark the button with appropriate value.
        

b1.config(text

=

y)
        

x

=

x

+

1
        

mark

=

y