Tic Tac Toe 🎮 with Python tkinter - part 1
Tags: python, tkinter, tutorial, beginners
Published: 2022-03-06 14:58:44


In this tutorial, we will be creating a basic Tic Tac Toe game 🎮 with Python tkinter. If you are new to tkinter, refer this crash course: https://dev.to/jothinkumar/getting-started-with-tkinter-crash-course-5a7m  
**Webpage version: https://dev.to/jothinkumar/tic-tac-toe-with-html-css-and-js-part-1-4ja2**
## The GUI 👀
Let's go ahead and create a GUI for the game.  
**Step 1:** Create a tkinter window
```python
import tkinter as tk

root = tk.Tk()
root.resizable(False, False)
root.title("Tic Tac Toe")
root.mainloop()
```
![Screenshot of a basic tkinter window](https://cdn.hashnode.com/res/hashnode/image/upload/v1646555891994/fLQd6WfL2.png)  
**Step 2:** Add play area and a label with text "Tic Tac Toe"
```python
import tkinter as tk  
  
root = tk.Tk()  
root.resizable(False, False)  
root.title("Tic Tac Toe")  
  
tk.Label(root, text="Tic Tac Toe", font=('Ariel', 25)).pack()  
  
play_area = tk.Frame(root, width=300, height=300, bg='white')  
XO_points = []  
class XOPoint:  
    def __init__(self, x, y):  
        self.x = x  
        self.y = y  
        self.value = None  
        self.button = tk.Button(play_area, text="", width=10, height=5)  
        self.button.grid(row=x, column=y)  
  
    def reset(self):  
        self.button.configure(text="", bg='white')  
        self.value = None  
for x in range(1, 4):
    for y in range(1, 4):
        XOPoint(x, y) 
play_area.pack(pady=10, padx=10)  
  
root.mainloop()
```

![screenshot of a tkinter window with tic tac toe](https://cdn.hashnode.com/res/hashnode/image/upload/v1646556480858/YhdBUTbPm.png)
**Step 3:** Make the GUI functional  
Let's make the GUI functional by changing button text and color when clicked. For this, we need to make some changes to class "XOPoint" in code.
```python
class XOPoint:  
    def __init__(self, x, y):
        self.x = x  
        self.y = y  
        self.value = None  
        self.button = tk.Button(play_area, text="", width=10, height=5, command=self.set)
        self.button.grid(row=x, column=y)

    def set(self):
        global current_chr
        if not self.value:
            self.button.configure(text=current_chr, bg='snow', fg='black')
            self.value = current_chr 
            if current_chr == "X":
                current_chr = "O"
            elif current_chr == "O":
                current_chr = "X"
  
    def reset(self):  
        self.button.configure(text="", bg='white')  
        self.value = None
```

![Tic Tac Toe functional tkinter GUI](https://cdn.hashnode.com/res/hashnode/image/upload/v1646557832817/xtXcts6rK.gif)
## Detect win and draw 🤔
Let's now implement a logic to detect win/draw.  
**Step 4:** Implement logic to detect win  
We need to check after each move if X or O won the game. There are 8 possible ways in which one can win Tic Tac Toe:

![8 possible ways of winning Tic Tac Toe](https://cdn.hashnode.com/res/hashnode/image/upload/v1646563368479/YUL7azLEZ.png)
Let's modify the code to detect game win.
```python
import tkinter as tk  
  
root = tk.Tk()  
root.resizable(False, False)  
root.title("Tic Tac Toe")  
  
tk.Label(root, text="Tic Tac Toe", font=('Ariel', 25)).pack()
current_chr = "X"
  
play_area = tk.Frame(root, width=300, height=300, bg='white')  
X_points = []
O_points = []
class XOPoint:  
    def __init__(self, x, y):
        self.x = x  
        self.y = y  
        self.value = None  
        self.button = tk.Button(play_area, text="", width=10, height=5, command=self.set)
        self.button.grid(row=x, column=y)

    def set(self):
        global current_chr
        if not self.value:
            self.button.configure(text=current_chr, bg='snow', fg='black')
            self.value = current_chr 
            if current_chr == "X":
                X_points.append(self)
                current_chr = "O"
            elif current_chr == "O":
                O_points.append(self)
                current_chr = "X"
        check_win()
  
    def reset(self):  
        self.button.configure(text="", bg='white')  
        self.value = None
for x in range(1, 4):
    for y in range(1, 4):
        XOPoint(x, y)
class WinningPossibility:
    def __init__(self, x1, y1, x2, y2, x3, y3):
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2
        self.x3 = x3
        self.y3 = y3
    def check(self, for_chr):
        p1_satisfied = False
        p2_satisfied = False
        p3_satisfied = False
        if for_chr == 'X':
            for point in X_points:
                if point.x == self.x1 and point.y == self.y1:
                    p1_satisfied = True
                elif point.x == self.x2 and point.y == self.y2:
                    p2_satisfied = True
                elif point.x == self.x3 and point.y == self.y3:
                    p3_satisfied = True
        elif for_chr == 'O':
            for point in O_points:
                if point.x == self.x1 and point.y == self.y1:
                    p1_satisfied = True
                elif point.x == self.x2 and point.y == self.y2:
                    p2_satisfied = True
                elif point.x == self.x3 and point.y == self.y3:
                    p3_satisfied = True
        return all([p1_satisfied, p2_satisfied, p3_satisfied])
winning_possibilities = [
    WinningPossibility(1, 1, 1, 2, 1, 3),
    WinningPossibility(2, 1, 2, 2, 2, 3),
    WinningPossibility(3, 1, 3, 2, 3, 3),
    WinningPossibility(1, 1, 2, 1, 3, 1),
    WinningPossibility(1, 2, 2, 2, 3, 2),
    WinningPossibility(1, 3, 2, 3, 3, 3),
    WinningPossibility(1, 1, 2, 2, 3, 3),
    WinningPossibility(3, 1, 2, 2, 1, 3)
]
def check_win():
    for possibility in winning_possibilities:
        if possibility.check('X'):
            print("X won!")
            return
        elif possibility.check('O'):
            print("O won!")
            return
play_area.pack(pady=10, padx=10)  

root.mainloop()
```
**Step 5:** Detect draw  
If all 9 squares were filled and still no one won the game, it's a draw! Detecting game draw is simple. Just append the following code to the function "check_win".
```python
if len(X_points) + len(O_points) == 9:
        print("Draw!")
```
## Enhancements
Now that everything works fine, we can improve it further.  
**Step 6:** Status Label  
Tic Tac Toe is a GUI game and displaying game result in GUI is better than printing them in console. So, let's create a tkinter Label via which we can display result. Add the following code:
```python
status_label = tk.Label(root, text="", font=('Ariel', 15), bg='green', fg='snow')
status_label.pack(fill=tk.X)
```
Also, we need to make some changes to function "check_win":
```python
def check_win():
    for possibility in winning_possibilities:
        if possibility.check('X'):
            status_label.configure(text="X won!")
            return
        elif possibility.check('O'):
            status_label.configure(text="O won!")
            return
    if len(X_points) + len(O_points) == 9:
        status_label.configure(text="Draw!")
```
![Python tkinter Tic Tac Toe working demo](https://cdn.hashnode.com/res/hashnode/image/upload/v1646573789896/3i-aArFXi.gif)
**Step 7:** Display whose turn it is  
The status label is not used till someone wins the game. Let's use it to display if it's X's turn or O's turn! We need to make some changes to function "set" in class "XOPoint".
```python
def set(self):
        global current_chr
        if not self.value:
            self.button.configure(text=current_chr, bg='snow', fg='black')
            self.value = current_chr
            if current_chr == "X":
                X_points.append(self)
                current_chr = "O"
                status_label.configure(text="O's turn")
            elif current_chr == "O":
                O_points.append(self)
                current_chr = "X"
                status_label.configure(text="X's turn")
        check_win()
```
Also, the text property of status label must be changed:
```python
status_label = tk.Label(root, text="X's turn", font=('Ariel', 15), bg='green', fg='snow')
```

![Python tkinter Tic Tac Toe working demo](https://cdn.hashnode.com/res/hashnode/image/upload/v1646574794546/B1iaYgOTO.gif)
**Step 8:** Create Play again button  
Once the game is over, the game needs to be disabled and a button to play the game again must be displayed. Let's add the following code to disable the game once it is over:
```python
XO_points = []
def disable_game():
    for point in XO_points:
        point.button.configure(state=tk.DISABLED)
```
We need to make changes to the function "check_win" and to the for loops that create "XOPoint" objects:
```python
for x in range(1, 4):
    for y in range(1, 4):
        XO_points.append(XOPoint(x, y))

def check_win():
    for possibility in winning_possibilities:
        if possibility.check('X'):
            status_label.configure(text="X won!")
            disable_game()
            return
        elif possibility.check('O'):
            status_label.configure(text="O won!")
            disable_game()
            return
    if len(X_points) + len(O_points) == 9:
        status_label.configure(text="Draw!")
        disable_game()
```
![Python tkinter Tic Tac Toe working demo](https://cdn.hashnode.com/res/hashnode/image/upload/v1646575620434/t_RZjeoVk.gif)
Add the following code to display Play again button once the game is over:
```python
def play_again():
    global current_chr
    current_chr = 'X'
    for point in XO_points:
        point.button.configure(state=tk.NORMAL)
        point.reset()
    status_label.configure(text="X's turn")
    play_again_button.pack_forget()
play_again_button = tk.Button(root, text='Play again', font=('Ariel', 15), command=play_again)
```
We also need to make changes to function "disable_game" and function "reset" in class "XOPoint":
```python
def disable_game():
    for point in XO_points:
        point.button.configure(state=tk.DISABLED)
    play_again_button.pack()

def reset(self):
        self.button.configure(text="", bg='lightgray')
        if self.value == "X":
            X_points.remove(self)
        elif self.value == "O":
            O_points.remove(self)
        self.value = None
```
![Python tkinter Tic Tac Toe working demo](https://cdn.hashnode.com/res/hashnode/image/upload/v1646576404833/tLwM-yGes.gif)
## Summary
The steps done in this tutorial are:
1. Create a tkinter window.
2. Add play area and a label with text "Tic Tac Toe".
3. Make the GUI functional.
4. Implement logic to detect win.
5. Detect draw.
6. Status Label.
7. Display whose turn it is.
8. Create Play again button.

**Final code:**
```python
import tkinter as tk

root = tk.Tk()
root.resizable(False, False)
root.title("Tic Tac Toe")

tk.Label(root, text="Tic Tac Toe", font=('Ariel', 25)).pack()
status_label = tk.Label(root, text="X's turn", font=('Ariel', 15), bg='green', fg='snow')
status_label.pack(fill=tk.X)
def play_again():
    global current_chr
    current_chr = 'X'
    for point in XO_points:
        point.button.configure(state=tk.NORMAL)
        point.reset()
    status_label.configure(text="X's turn")
    play_again_button.pack_forget()
play_again_button = tk.Button(root, text='Play again', font=('Ariel', 15), command=play_again)

current_chr = "X"

play_area = tk.Frame(root, width=300, height=300, bg='white')
XO_points = []
X_points = []
O_points = []
class XOPoint:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.value = None
        self.button = tk.Button(play_area, text="", width=10, height=5, command=self.set)
        self.button.grid(row=x, column=y)

    def set(self):
        global current_chr
        if not self.value:
            self.button.configure(text=current_chr, bg='snow', fg='black')
            self.value = current_chr
            if current_chr == "X":
                X_points.append(self)
                current_chr = "O"
                status_label.configure(text="O's turn")
            elif current_chr == "O":
                O_points.append(self)
                current_chr = "X"
                status_label.configure(text="X's turn")
        check_win()

    def reset(self):
        self.button.configure(text="", bg='lightgray')
        if self.value == "X":
            X_points.remove(self)
        elif self.value == "O":
            O_points.remove(self)
        self.value = None
for x in range(1, 4):
    for y in range(1, 4):
        XO_points.append(XOPoint(x, y))
class WinningPossibility:
    def __init__(self, x1, y1, x2, y2, x3, y3):
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2
        self.x3 = x3
        self.y3 = y3
    def check(self, for_chr):
        p1_satisfied = False
        p2_satisfied = False
        p3_satisfied = False
        if for_chr == 'X':
            for point in X_points:
                if point.x == self.x1 and point.y == self.y1:
                    p1_satisfied = True
                elif point.x == self.x2 and point.y == self.y2:
                    p2_satisfied = True
                elif point.x == self.x3 and point.y == self.y3:
                    p3_satisfied = True
        elif for_chr == 'O':
            for point in O_points:
                if point.x == self.x1 and point.y == self.y1:
                    p1_satisfied = True
                elif point.x == self.x2 and point.y == self.y2:
                    p2_satisfied = True
                elif point.x == self.x3 and point.y == self.y3:
                    p3_satisfied = True
        return all([p1_satisfied, p2_satisfied, p3_satisfied])
winning_possibilities = [
    WinningPossibility(1, 1, 1, 2, 1, 3),
    WinningPossibility(2, 1, 2, 2, 2, 3),
    WinningPossibility(3, 1, 3, 2, 3, 3),
    WinningPossibility(1, 1, 2, 1, 3, 1),
    WinningPossibility(1, 2, 2, 2, 3, 2),
    WinningPossibility(1, 3, 2, 3, 3, 3),
    WinningPossibility(1, 1, 2, 2, 3, 3),
    WinningPossibility(3, 1, 2, 2, 1, 3)
]
def disable_game():
    for point in XO_points:
        point.button.configure(state=tk.DISABLED)
    play_again_button.pack()
def check_win():
    for possibility in winning_possibilities:
        if possibility.check('X'):
            status_label.configure(text="X won!")
            disable_game()
            return
        elif possibility.check('O'):
            status_label.configure(text="O won!")
            disable_game()
            return
    if len(X_points) + len(O_points) == 9:
        status_label.configure(text="Draw!")
        disable_game()
play_area.pack(pady=10, padx=10)

root.mainloop()
```
GitHub repo link: https://github.com/Jothin-kumar/tic-tac-toe
![Python tkinter Tic Tac Toe working demo](https://cdn.hashnode.com/res/hashnode/image/upload/v1646576404833/tLwM-yGes.gif)
If you find this article useful, drop a like ⭐ and follow me to get all my latest content.

Part 2 of this tutorial: https://dev.to/jothinkumar/tic-tac-toe-with-python-tkinter-part-2-9mp