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
- Imports
- Tic Tac Toe Game Functions
- Main Window and Player, Board, Label Creation
- Score and Score Label Creation
- Creating Restart Button and Buttons Frame
- Creating Buttons for the Board
- Main Loop
- Example
- Full Code
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!