Home » Tutorials » How to Make a Tic-Tac-Toe Game in Python

How to Make a Tic-Tac-Toe Game in Python

Imagine stepping back into the warmth of childhood memories, where the simplest joys often came from games sketched out on scraps of paper, Tic-Tac-Toe being a beloved classic among them. with its grid of nine squares, has been a universal symbol of innocent strategy, where two players take turns marking spaces in a 3×3 grid with Xs and Os. The goal is simple yet captivating: to be the first to get three of your marks in a row, whether it be vertically, horizontally, or diagonally.

In today’s tutorial, we are going to build a Tic-Tac-Toe game in Python, you’ll have a good understanding of how it is made. Let’s get started!

Table of Contents

Getting Started

We’re going to create a Tic-Tac-Toe game using a GUI framework in Python. So, the First thing you have to do is install the tkinter library by running this command:

$ pip install tk

Imports

We start by importing the tkinter library, which allows us to create a graphical user interface (GUI), together with the random library, which generates random choices that we will use to determine the starting player.

from tkinter import *
import random

Tic Tac Toe Game Functions

mark_cell Function

Next, we create a mark_cell function. When the player clicks on a cell, this function first checks if the cell is empty and the game is still ongoing (no winner yet). If the cell is empty and there’s no winner, it marks the cell with the current player’s symbol (x or o).

After checking for a winner, if one exists, the function highlights the winning combination, updates the score, and declares the winner through the label. If there’s no winner, it switches the player’s turns until all the cells on the board are marked. If there is still no winner then, it declares a tie.

def mark_cell(row, col):
   global current_player
   if board[row][col]['text'] == "" and not check_winner():
       board[row][col]['text'] = current_player
       if check_winner():
           highlight_winner()
           label.config(text=(current_player + " wins!"))
           update_score(current_player)
       elif check_tie():
           label.config(text="It's a tie!")
       else:
           current_player = next_player(current_player)
           label.config(text=(current_player + "'s turn"))

highlight_winner Function

Then, we create this function to search for a winning combination by checking the rows, columns, and diagonals. Upon finding one, it triggers the highlight_buttons function to change the color of the cells forming that combination.

def highlight_winner():
   for i in range(3):
       if board[i][0]['text'] == board[i][1]['text'] == board[i][2]['text'] != "":
           highlight_buttons([(i, 0), (i, 1), (i, 2)])
           return
       if board[0][i]['text'] == board[1][i]['text'] == board[2][i]['text'] != "":
           highlight_buttons([(0, i), (1, i), (2, i)])
           return
   if board[0][0]['text'] == board[1][1]['text'] == board[2][2]['text'] != "":
       highlight_buttons([(0, 0), (1, 1), (2, 2)])
       return
   if board[0][2]['text'] == board[1][1]['text'] == board[2][0]['text'] != "":
       highlight_buttons([(0, 2), (1, 1), (2, 0)])
       return

highlight_buttons

def highlight_buttons(coords):
   for coord in coords:
       row, col = coord
       board[row][col].config(bg='green')  # Change the color here

The above function changes the color of the cells of the winning combination to green.

check_winner

def check_winner():
   for i in range(3):
       if board[i][0]['text'] == board[i][1]['text'] == board[i][2]['text'] != "":
           return True
       if board[0][i]['text'] == board[1][i]['text'] == board[2][i]['text'] != "":
           return True
   if board[0][0]['text'] == board[1][1]['text'] == board[2][2]['text'] != "":
       return True
   if board[0][2]['text'] == board[1][1]['text'] == board[2][0]['text'] != "":
       return True
   return False

As its name suggests, this function checks for a winner in the current board configuration by looking for a winning combination. If such a combination is found, it returns True, signaling to the calling function that a winner has been identified. Otherwise, it returns False.

check_tie

def check_tie():
   for row in board:
       for button in row:
           if button['text'] == "":
               return False
   return True

This one looks through all the cells in the board to verify if there are empty ones, which in that case will return as False meaning there is no tie yet. Otherwise, if all cells are marked it returns as True indicating that there is a tie.

next_player

The below function switches players. It verifies the index of the current player and moves to the next one in a cyclic manner.

def next_player(player):
   return players[(players.index(player) + 1) % 2]

restart_game

def restart_game():
   global current_player
   for row in range(3):
       for col in range(3):
           board[row][col]['text'] = ""
           board[row][col].config(bg='SystemButtonFace')  # Reset button colors
   current_player = random.choice(players)
   label.config(text=(current_player + "'s turn"))

This function resets the game to its initial state by emptying the cells on the board and resetting the colors of the winning combination. It also selects a random player to start if the game doesn’t end in a tie, updating the label to indicate whose turn it is.

update_score

def update_score(player):
   if player == "X":
       x_score.set(x_score.get() + 1)
   else:
       o_score.set(o_score.get() + 1)
   score_label.config(text="Score - X: {}  O: {}".format(x_score.get(), o_score.get()))

The final function in our code increments the winning player’s score by 1 and updates the score label’s text to display the new score.

Main Window and Player, Board, Label Creation

root = Tk()
root.title("Simple Tic-Tac-Toe - The Pycodes")

players = ["X", "O"]
current_player = random.choice(players)

board = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]

label = Label(text=(current_player + "'s turn"), font=('consolas', 40))
label.pack(side="top")

This part is divided into four sections:

  • First, we establish our main window and give it a title.
  • Second, we set up two players, X and O, and select the starting player at random.
  • Third, we construct the board, organizing it into three rows and three columns to form nine empty cells.
  • Fourth, we create a label to display which player’s turn it is to play.

Score and Score Label Creation

x_score = IntVar()
o_score = IntVar()

x_score.set(0)
o_score.set(0)

score_label = Label(text="Score - X: {}  O: {}".format(x_score.get(), o_score.get()), font=('consolas', 20))
score_label.pack(side="top")

Here, we created variables that are going to store the scores of the two players and give them the initial value of 0 then we created a label that will display those scores.

Creating Restart Button and Buttons Frame

restart_button = Button(text="Restart", font=('consolas', 20), command=restart_game)
restart_button.pack(side="top")

buttons_frame = Frame(root)
buttons_frame.pack()

This code generates a button that, when clicked, triggers the restart_game function. It also creates a frame designed to hold the game’s buttons.

Creating Buttons for the Board

for row in range(3):
   for col in range(3):
       board[row][col] = Button(buttons_frame, text="", font=('consolas', 50), width=4, height=1,
                                command=lambda row=row, col=col: mark_cell(row, col))
       board[row][col].grid(row=row, column=col)

What we have done here is create a button in each cell of the board, ensuring that clicking any button within these cells triggers the mark_cell function.

Main Loop

root.mainloop()

Finally, this part is what keeps the main window running (in a loop) until the user exits willingly.

Example

Full Code

from tkinter import *
import random


def mark_cell(row, col):
   global current_player
   if board[row][col]['text'] == "" and not check_winner():
       board[row][col]['text'] = current_player
       if check_winner():
           highlight_winner()
           label.config(text=(current_player + " wins!"))
           update_score(current_player)
       elif check_tie():
           label.config(text="It's a tie!")
       else:
           current_player = next_player(current_player)
           label.config(text=(current_player + "'s turn"))


def highlight_winner():
   for i in range(3):
       if board[i][0]['text'] == board[i][1]['text'] == board[i][2]['text'] != "":
           highlight_buttons([(i, 0), (i, 1), (i, 2)])
           return
       if board[0][i]['text'] == board[1][i]['text'] == board[2][i]['text'] != "":
           highlight_buttons([(0, i), (1, i), (2, i)])
           return
   if board[0][0]['text'] == board[1][1]['text'] == board[2][2]['text'] != "":
       highlight_buttons([(0, 0), (1, 1), (2, 2)])
       return
   if board[0][2]['text'] == board[1][1]['text'] == board[2][0]['text'] != "":
       highlight_buttons([(0, 2), (1, 1), (2, 0)])
       return


def highlight_buttons(coords):
   for coord in coords:
       row, col = coord
       board[row][col].config(bg='green')  # Change the color here


def check_winner():
   for i in range(3):
       if board[i][0]['text'] == board[i][1]['text'] == board[i][2]['text'] != "":
           return True
       if board[0][i]['text'] == board[1][i]['text'] == board[2][i]['text'] != "":
           return True
   if board[0][0]['text'] == board[1][1]['text'] == board[2][2]['text'] != "":
       return True
   if board[0][2]['text'] == board[1][1]['text'] == board[2][0]['text'] != "":
       return True
   return False


def check_tie():
   for row in board:
       for button in row:
           if button['text'] == "":
               return False
   return True


def next_player(player):
   return players[(players.index(player) + 1) % 2]



def restart_game():
   global current_player
   for row in range(3):
       for col in range(3):
           board[row][col]['text'] = ""
           board[row][col].config(bg='SystemButtonFace')  # Reset button colors
   current_player = random.choice(players)
   label.config(text=(current_player + "'s turn"))




def update_score(player):
   if player == "X":
       x_score.set(x_score.get() + 1)
   else:
       o_score.set(o_score.get() + 1)
   score_label.config(text="Score - X: {}  O: {}".format(x_score.get(), o_score.get()))




root = Tk()
root.title("Simple Tic-Tac-Toe - The Pycodes")


players = ["X", "O"]
current_player = random.choice(players)


board = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]


label = Label(text=(current_player + "'s turn"), font=('consolas', 40))
label.pack(side="top")


x_score = IntVar()
o_score = IntVar()


x_score.set(0)
o_score.set(0)


score_label = Label(text="Score - X: {}  O: {}".format(x_score.get(), o_score.get()), font=('consolas', 20))
score_label.pack(side="top")


restart_button = Button(text="Restart", font=('consolas', 20), command=restart_game)
restart_button.pack(side="top")


buttons_frame = Frame(root)
buttons_frame.pack()


for row in range(3):
   for col in range(3):
       board[row][col] = Button(buttons_frame, text="", font=('consolas', 50), width=4, height=1,
                                command=lambda row=row, col=col: mark_cell(row, col))
       board[row][col].grid(row=row, column=col)

root.mainloop()

Happy Coding!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top