Building a Tic Tac Toe Game with Python and Tkinter: A GUI-based Journey into Classic Fun

The New Geek
15 min readJun 11, 2023

--

Tic Tac Toe, also known as Noughts and Crosses, is a classic paper-and-pencil game enjoyed by people of all ages. Its popularity stems from its simplicity and strategic gameplay. The game is typically played on a 3x3 grid, where two players take turns marking X and O symbols on the board. The objective is to form a line of three matching symbols horizontally, vertically, or diagonally. Despite its straightforward rules, Tic Tac Toe offers an engaging challenge that requires critical thinking and foresight to outmaneuver the opponent.

Tic Tac Toe is often one of the first games people learn as children due to its easy-to-understand rules and minimal equipment requirements. It can be played with a simple sheet of paper and a pencil, making it accessible in various settings such as classrooms, family gatherings, and even on the go. Additionally, Tic Tac Toe serves as a foundation for understanding fundamental game concepts, such as turn-based gameplay and win conditions.

While the traditional pen-and-paper version of Tic Tac Toe has its charm, implementing the game with a graphical user interface (GUI) introduces a new level of interactivity and enhances the user experience. The code provided offers a Python implementation of Tic Tac Toe with a GUI using the Tkinter library. This allows players to enjoy the game by clicking on a virtual game board and observing real-time updates. Exploring the code behind this implementation not only provides insights into creating a GUI-based game but also demonstrates the application of basic game logic and event handling in Python.

In the following sections, we will delve into the details of the code, discussing how the GUI is created, how the game logic is implemented, and how players can interact with the game. Let’s embark on this journey to uncover the inner workings of a classic game transformed into an interactive experience!

Overview of the Code

The code provided implements a Tic Tac Toe game using Python and the Tkinter library to create a graphical user interface (GUI). It encapsulates the game logic within a TicTacToe class, allowing players to interact with the game board by clicking on virtual buttons representing the cells.

At a high level, the code initializes the game window, sets up the game board, and defines the necessary functions to handle button clicks, check for a winner or draw, switch players, and reset the game. The main goal is to provide an interactive and intuitive experience for players, allowing them to play the game seamlessly.

The TicTacToe class is the heart of the code and serves as a container for all the game-related functionality. When the code is executed, an instance of this class is created, and the run method is called to start the GUI event loop.

The game window is created using Tkinter with the title "Tic Tac Toe". The game board is represented by a 3x3 grid of buttons, with each button initialized with blank text. These buttons serve as the interactive elements where players can make their moves.

When a player clicks on a button, the on_button_click method is invoked. This method first checks if the clicked cell on the game board is empty. If it is, the player's symbol (X or O) is placed on the board, and the button's text is updated accordingly. The method then checks if there is a winner by calling the check_winner function. If there is a winner, a message box is displayed, showing the winning player. If there is a draw, the check_draw function is called, and if a draw is detected, a message box is displayed indicating the draw.

If the game is still ongoing, the switch_player function is called to change the current player. The next player's symbol will be placed on the board when they make their move.

The reset_game method is responsible for resetting the game after a win or draw. It resets the player to "X", clears the board, and updates the button texts to blank.

By running the code, players can enjoy the game by clicking on the buttons and observing the board’s updates and any messages regarding wins or draws.

In the following sections, we will explore the specific details of the game logic, including how the winner is checked, how draws are detected, and how the GUI elements are managed. Let’s dive deeper into the code to uncover its functionality!

GUI Implementation with Tkinter

The Tkinter library is a popular choice for creating GUI applications in Python. It provides a set of tools and widgets to build interactive interfaces. In the Tic Tac Toe code, Tkinter is utilized to create the game window and set up the game board using buttons.

To begin, the Tkinter library is imported at the top of the code:

import tkinter as tk
from tkinter import messagebox

The tkinter module is imported as tk, which is a common convention. Additionally, the messagebox module is imported separately to display message boxes for win and draw conditions.

The game window is initialized in the __init__ method of the TicTacToe class:

self.window = tk.Tk()
self.window.title("Tic Tac Toe")

The tk.Tk() function creates the main window for the game. self.window is an instance variable that holds a reference to the game window. The title method is used to set the window's title to "Tic Tac Toe".

Next, the game board is set up using buttons in a 3x3 grid. This is done within nested for loops in the __init__ method:

self.buttons = [[None for _ in range(3)] for _ in range(3)]

for i in range(3):
for j in range(3):
self.buttons[i][j] = tk.Button(
self.window,
text=" ",
font=("Arial", 20),
width=10,
height=5,
command=lambda row=i, col=j: self.on_button_click(row, col)
)
self.buttons[i][j].grid(row=i, column=j)

The self.buttons list serves as a 2D grid to store the button objects representing the cells of the game board. It is initialized with None values.

Within the nested for loops, a tk.Button object is created for each cell. The button's appearance and behavior are customized using various arguments. The text argument sets the initial text of the button to a blank space. The font, width, and height arguments define the visual aspects of the button. The command argument specifies the function to be executed when the button is clicked. In this case, the lambda function is used to pass the current row and column as arguments to the on_button_click method.

Finally, the buttons are placed on the game window grid using the grid method, specifying their respective row and column positions.

This implementation of the GUI using Tkinter allows players to interact with the Tic Tac Toe game by clicking on the buttons, triggering the on_button_click method to handle the game logic. The buttons visually represent the game board and provide an intuitive way for players to make their moves.

In the next sections, we will explore how the game logic is implemented, including handling button clicks, checking for a winner or draw, and managing the state of the game.

Game Logic

The game logic in the TicTacToe class handles various aspects of the game, such as keeping track of the current player, maintaining the state of the game board, handling button clicks, checking for a winner or draw, and switching players.

Current Player and Game Board State

  • The TicTacToe class has an instance variable self.current_player that stores the symbol of the current player ("X" or "O").
  • The game board is represented by a 2D list called self.board. Each element in the list represents a cell on the game board and is initially set to a blank space (" ").

Event Handling when a Button is Clicked:

  • The on_button_click method is called when a button representing a cell is clicked. It takes the row and column indices of the clicked button as arguments.
  • It first checks if the clicked cell on the game board is empty using self.board[row][col] == " ".
  • If the cell is empty, the current player’s symbol is placed on the board by updating self.board[row][col] and the button's text using self.buttons[row][col]["text"].
  • The method then checks if there is a winner by invoking the check_winner function.
  • If a winner is detected, the show_winner_message method displays a message box showing the winning player.
  • If no winner is found, the check_draw function is called to check for a draw condition.
  • If a draw is detected, the show_draw_message method displays a message box indicating a draw.
  • If the game is still ongoing, the switch_player method is called to change the current player.

Let’s take a closer look at the code snippets for the relevant methods:

def on_button_click(self, row, col):
if self.board[row][col] == " ":
self.board[row][col] = self.current_player
self.buttons[row][col]["text"] = self.current_player
if self.check_winner():
self.show_winner_message()
self.reset_game()
elif self.check_draw():
self.show_draw_message()
self.reset_game()
else:
self.switch_player()

def check_winner(self):
# Check rows
for row in self.board:
if row[0] == row[1] == row[2] != " ":
return True

# Check columns
for col in range(3):
if self.board[0][col] == self.board[1][col] == self.board[2][col] != " ":
return True

# Check diagonals
if (
self.board[0][0] == self.board[1][1] == self.board[2][2] != " "
or self.board[0][2] == self.board[1][1] == self.board[2][0] != " "
):
return True

return False

def check_draw(self):
for row in self.board:
if " " in row:
return False
return True

def switch_player(self):
if self.current_player == "X":
self.current_player = "O"
else:
self.current_player = "X"

These methods work together to update the game state, check for a winner, detect a draw, switch players, and handle the event of a button click. The logic ensures that the game progresses smoothly, allowing players to take turns and determine the outcome of the game.

In the next sections, we will explore how the code displays messages for a winner or draw, as well as how the game can be reset after these events occur.

Checking for a Winner

The code implements a straightforward algorithm to check for a winning condition in Tic Tac Toe. It examines the game board’s rows, columns, and diagonals to determine if a player has formed a line of three matching symbols (X or O).

The check_winner method is responsible for performing these checks. Let's explore the algorithm step by step:

Checking Rows:

  • The algorithm begins by iterating over each row in the game board using a for loop.
  • Within each row, it checks if all three elements are equal and not equal to a blank space (“ “).
  • If a row is found where all three elements are equal, it indicates that a player has formed a line, and the method returns True, signifying a win.

Checking Columns:

  • The algorithm uses a nested for loop to iterate over each column index.
  • For each column index, it checks if all three elements in that column are equal and not equal to a blank space.
  • If a column is found where all three elements are equal, it returns True to indicate a win.

Checking Diagonals:

  • The algorithm checks the two diagonal lines on the game board.
  • It first examines the top-left to bottom-right diagonal line (0,0 -> 1,1 -> 2,2) and then the top-right to bottom-left diagonal line (0,2 -> 1,1 -> 2,0).
  • For each diagonal line, it checks if all three elements are equal and not equal to a blank space.
  • If either diagonal line has three matching symbols, the method returns True to indicate a win.

Returning False:

  • If none of the checks find a winning condition, the method returns False, indicating that there is no winner.

The code’s implementation of the check_winner method ensures that all possible lines in the game board are examined to determine if a player has won. By systematically checking rows, columns, and diagonals, it accurately identifies a winning condition.

Here is the code snippet for the check_winner method:

def check_winner(self):
# Check rows
for row in self.board:
if row[0] == row[1] == row[2] != " ":
return True

# Check columns
for col in range(3):
if self.board[0][col] == self.board[1][col] == self.board[2][col] != " ":
return True

# Check diagonals
if (
self.board[0][0] == self.board[1][1] == self.board[2][2] != " "
or self.board[0][2] == self.board[1][1] == self.board[2][0] != " "
):
return True

return False

The clarity and simplicity of the algorithm make it efficient for determining a winner in a Tic Tac Toe game. By understanding this logic, we can appreciate how the code evaluates the game board to detect winning conditions accurately.

Handling Draw Condition:

In addition to checking for a winner, the code also handles the draw condition where all spaces on the game board are filled without a player achieving a winning line. The check_draw function is responsible for detecting this condition.

Here’s an explanation of how the code checks for a draw condition and displays a message box when a draw occurs:

Checking for a Draw:

  • The check_draw function iterates over each row in the game board using a for loop.
  • Within each row, it checks if there is an empty space (“ “) using the in operator.
  • If any empty space is found, the function returns False, indicating that the game is not a draw.
  • If the loop completes without finding any empty spaces, it means that all spaces are filled, and the function returns True, indicating a draw.

Displaying a Message Box:

  • When the check_draw function returns True, indicating a draw, the show_draw_message method is called to display a message box.
  • The messagebox.showinfo function from the Tkinter library is used to show the message box with the appropriate message.
  • In this case, the message box simply displays the text “It’s a draw!” to inform the players that the game ended in a draw.

Here is the code snippet for the check_draw and show_draw_message methods:

def check_draw(self):
for row in self.board:
if " " in row:
return False
return True

def show_draw_message(self):
messagebox.showinfo("Tic Tac Toe", "It's a draw!")

The check_draw function efficiently determines if the game has ended in a draw by scanning for any empty spaces on the game board. If none are found, it returns True, triggering the display of the message box.

By implementing this logic, the code ensures that players are informed when a game ends in a draw, providing a clear indication that neither player achieved a winning line despite filling all the spaces on the board.

Resetting the Game

The code provides functionality to reset the game after a win or draw condition. This ensures that players can start a new game without needing to close and reopen the application. The reset_game method is responsible for resetting the game state.

Here’s an explanation of how the game can be reset and the actions performed by the reset_game method:

Resetting the Player and Board:

  • The reset_game method resets the current player to "X", indicating that the first player to move in the next game will be "X".
  • It also resets the game board by reinitializing self.board as a 2D list filled with blank spaces (" ").

Updating Button Text:

  • After resetting the player and board, the method updates the text on each button to blank spaces (“ “) to visually indicate an empty game board.
  • It achieves this by iterating over the self.buttons list, which stores the button objects representing the cells on the game board.
  • For each button, it sets the text to a blank space using the ["text"] attribute.

Here is the code snippet for the reset_game method:

def reset_game(self):
self.current_player = "X"
self.board = [[" " for _ in range(3)] for _ in range(3)]

for i in range(3):
for j in range(3):
self.buttons[i][j]["text"] = " "

By calling the reset_game method, the game state is effectively reset, allowing players to start a new game with a fresh board and the first move assigned to "X".

This functionality ensures a seamless and convenient experience for players, enabling them to play multiple games without interruptions. Whether the game ends in a win or draw, the ability to reset the game provides a sense of continuity and encourages continued engagement with the Tic Tac Toe application.

Running the Game:

To execute the Tic Tac Toe game, the code creates an instance of the TicTacToe class and runs the GUI event loop. This allows the game window to be displayed and enables players to interact with the graphical interface.

Here’s an explanation of how the game is executed:

Creating an Instance of the TicTacToe Class:

  • In the __name__ == "__main__" block at the end of the code, an instance of the TicTacToe class is created:
if __name__ == "__main__":
game = TicTacToe()
game. Run()
  • The TicTacToe class is instantiated, and the resulting object is assigned to the variable game.
  • This step initializes the game window, sets up the game board, and prepares the necessary event handling.

Running the GUI Event Loop:

  • After creating the game instance, the run method of the TicTacToe class is called:
game. Run()
  • The run method starts the GUI event loop provided by the Tkinter library.
  • This event loop continuously listens for user interactions, such as button clicks, and responds accordingly.
  • It ensures that the game remains interactive and responsive, updating the game board and handling player moves as events occur.

By creating an instance of the TicTacToe class and running the GUI event loop, the game becomes fully functional and ready for players to enjoy. The GUI window appears on the screen, allowing players to click on the buttons and experience the interactive nature of the Tic Tac Toe game.

The event loop ensures that the game can respond to player inputs and update the graphical elements in real-time, making it possible for players to see the current state of the game and any messages that may appear during gameplay, such as winning notifications or draw messages.

Running the game in this manner provides a seamless and engaging experience for players, allowing them to enjoy the classic game of Tic Tac Toe through a user-friendly graphical interface.

Conclusion

In this article, we explored the code for a Tic Tac Toe game implemented with Python and the Tkinter library, providing a graphical user interface (GUI) for an interactive gaming experience. We discussed several key points regarding the code’s functionality and purpose.

We began by introducing the concept of Tic Tac Toe as a popular and simple game enjoyed by people of all ages. We then delved into the code’s overview, highlighting its purpose in creating a GUI-based Tic Tac Toe game. The code effectively initializes the game window, sets up the game board using buttons, and implements the necessary game logic.

The game logic within the TicTacToe class handles important aspects of the game, such as tracking the current player and maintaining the state of the game board. We explored how the code handles button clicks, updates the board, checks for a winner or draw, and switches players. The algorithm for checking a winning condition was explained, encompassing checks for rows, columns, and diagonals.

Furthermore, we discussed how the code handles a draw condition by detecting when all spaces are filled without a winner. The message box display for a draw was explained, ensuring players are notified when the game ends in a draw.

The code also provides the ability to reset the game after a win or draw, allowing players to start a new game without restarting the application. The resetting mechanism was detailed, covering the resetting of the player, clearing the board, and updating the button texts.

Lastly, we discussed how the game is run by creating an instance of the TicTacToe class and executing the GUI event loop. This enables players to interact with the game window and enjoy the interactive and responsive nature of the game.

By understanding and exploring the code for this Tic Tac Toe game, readers have gained insights into creating GUI-based games using Tkinter. The code serves as a foundation for understanding basic game logic, event handling, and GUI implementation. With this knowledge, readers can further explore and expand upon the code to create more complex GUI-based games, leveraging the flexibility and functionality provided by Tkinter.

In conclusion, this code provides an accessible and interactive implementation of Tic Tac Toe, demonstrating how Python and Tkinter can be used to create engaging GUI-based games. It not only allows players to enjoy a classic game but also serves as a stepping stone for readers to delve into the realm of game development and explore the endless possibilities of creating their own GUI-based games.

Entire Code

import tkinter as tk
from tkinter import messagebox

class TicTacToe:
def __init__(self):
self.window = tk.Tk()
self.window.title("Tic Tac Toe")
self.current_player = "X"
self.board = [[" " for _ in range(3)] for _ in range(3)]
self.buttons = [[None for _ in range(3)] for _ in range(3)]

for i in range(3):
for j in range(3):
self.buttons[i][j] = tk.Button(
self.window,
text=" ",
font=("Arial", 20),
width=10,
height=5,
command=lambda row=i, col=j: self.on_button_click(row, col)
)
self.buttons[i][j].grid(row=i, column=j)

def on_button_click(self, row, col):
if self.board[row][col] == " ":
self.board[row][col] = self.current_player
self.buttons[row][col]["text"] = self.current_player
if self.check_winner():
self.show_winner_message()
self.reset_game()
elif self.check_draw():
self.show_draw_message()
self.reset_game()
else:
self.switch_player()

def check_winner(self):
# Check rows
for row in self.board:
if row[0] == row[1] == row[2] != " ":
return True

# Check columns
for col in range(3):
if self.board[0][col] == self.board[1][col] == self.board[2][col] != " ":
return True

# Check diagonals
if (
self.board[0][0] == self.board[1][1] == self.board[2][2] != " "
or self.board[0][2] == self.board[1][1] == self.board[2][0] != " "
):
return True

return False

def show_winner_message(self):
messagebox.showinfo("Tic Tac Toe", f"Player {self.current_player} wins!")

def check_draw(self):
for row in self.board:
if " " in row:
return False
return True

def show_draw_message(self):
messagebox.showinfo("Tic Tac Toe", "It's a draw!")

def switch_player(self):
if self.current_player == "X":
self.current_player = "O"
else:
self.current_player = "X"

def reset_game(self):
self.current_player = "X"
self.board = [[" " for _ in range(3)] for _ in range(3)]
for i in range(3):
for j in range(3):
self.buttons[i][j]["text"] = " "

def run(self):
self.window.mainloop()

if __name__ == "__main__":
game = TicTacToe()
game. Run()

--

--