Tic-tac-toe using Python

Tic Tac Toe Featured Image

In this article, we will be going through the steps of creating Tic-tac-toe using Python Language from scratch.

Tic Tac Toe Gameplay

About the game

Tic-tac-toe is a two-player game, that is played on a 3×3 square grid. Each player occupies a cell in turns, with the objective of placing three marks in a horizontal, vertical, or diagonal pattern. One player uses cross 'X' as his marker, while the other uses a naught 'O'.


Step 1: Tic-tac-toe Design

We will be playing Tic-tac-toe on the command line, therefore, the first thing we have to do is create a design for our tic-tac-toe.

Design
Tic-tac-toe Design

If a player has to mark a particular box, he must enter the corresponding number shown in the grid. Suppose, we wish to occupy the center block, then we will input 5 in the terminal. This grid can be generated by:

# Function to print Tic Tac Toe
def print_tic_tac_toe(values):
	print("\n")
	print("\t     |     |")
	print("\t  {}  |  {}  |  {}".format(values[0], values[1], values[2]))
	print('\t_____|_____|_____')

	print("\t     |     |")
	print("\t  {}  |  {}  |  {}".format(values[3], values[4], values[5]))
	print('\t_____|_____|_____')

	print("\t     |     |")

	print("\t  {}  |  {}  |  {}".format(values[6], values[7], values[8]))
	print("\t     |     |")
	print("\n")

In the code above, the function creates our tic-tac-toe game according to the values delivered as an argument. Here the argument, values is a list containing the status of each cell in the grid.


Step 2: Store information using data structures

The core of any game is the game mechanics behind it. Since this is a fairly easy game to create, the mechanics involved are simple too.

At any instant of time, we need two crucial information:

  • Status of the grid – We must have a data structure that stores each cell’s state, that is, whether it is occupied or vacant.
  • Each player’s moves We must somehow have the knowledge of each player’s past and present moves, that is, the positions occupied by 'X' and 'O'.

Note: Both the information could have been accessed using the status of the grid, but it would have required to traverse it every time we need the player’s positions. This can be called as time vs. space complexity trade-off. It is a general technique to conserve time.

# Function for a single game of Tic Tac Toe
def single_game(cur_player):

	# Represents the Tic Tac Toe
	values = [' ' for x in range(9)]
	
	# Stores the positions occupied by X and O
	player_pos = {'X':[], 'O':[]}

Status of the grid is managed by a list of characters, which can have three possible values,

  • ' ' – A vacant cell
  • 'X' – A cell occupied by player X
  • 'O' – A cell occupied by player O

Each player’s moves are stored as a dictionary of a list of integers. The keys are 'X' and 'O' for the respective player. Their corresponding lists contain the numbers given to the grid cells, they occupy.

Note: The variable cur_player, stores the current player making the move, as in 'X' or 'O'.


Step 3: Game Loop

Every game, has some kind of game loop, which runs until some player wins or the game ends in a draw. In tic-tac-toe, each loop iteration refers to a single move any player makes.

# Game Loop for a single game of Tic Tac Toe
while True:
	print_tic_tac_toe(values)

Step 4: Handle player input

In every game iteration, a player must input his move.

# Try exception block for MOVE input
try:
	print("Player ", cur_player, " turn. Which box? : ", end="")
	move = int(input())	
except ValueError:
	print("Wrong Input!!! Try Again")
	continue

# Sanity check for MOVE inout
if move < 1 or move > 9:
	print("Wrong Input!!! Try Again")
	continue

# Check if the box is not occupied already
if values[move-1] != ' ':
	print("Place already filled. Try again!!")
	continue

We create a try block, in case a player enters some unintended value. Such an event must not stop the game, therefore, we handle the exception of ValueError and continue with our game.

We need to perform some sanity checks, like value entered is a valid position and if it is a valid position, is it already occupied?


Step 5: Update information

According to the player input, we need to update the information for the smooth functioning of the game.

# Update game information

# Updating grid status 
values[move-1] = cur_player

# Updating player positions
player_pos[cur_player].append(move)

The values list updates the cell occupied according to the current player. The player position adds the position just taken by the current player.

After updating the values list and calling the print_tic_tac_toe() function, the grid looks like this:

Update After Turn
Tic-tac-toe after 5 turns.
Last move: ‘X’ at 2

Step 6: Check win or draw

After each move, we have to check whether any player won the game or the game has been drawn. It can be checked by:

Function Calls:

# Function call for checking win
if check_win(player_pos, cur_player):
	print_tic_tac_toe(values)
	print("Player ", cur_player, " has won the game!!")		
	print("\n")
	return cur_player

# Function call for checking draw game
if check_draw(player_pos):
	print_tic_tac_toe(values)
	print("Game Drawn")
	print("\n")
	return 'D'

If any player wins, then the single_game() function returns the current player, who made the move. In case, the game is drawn, 'D' is sent back.

Functions:

# Function to check if any player has won
def check_win(player_pos, cur_player):

	# All possible winning combinations
	soln = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 4, 7], [2, 5, 8], [3, 6, 9], [1, 5, 9], [3, 5, 7]]

	# Loop to check if any winning combination is satisfied
	for x in soln:
		if all(y in player_pos[cur_player] for y in x):

			# Return True if any winning combination satisfies
			return True
	# Return False if no combination is satisfied		
	return False		

# Function to check if the game is drawn
def check_draw(player_pos):
	if len(player_pos['X']) + len(player_pos['O']) == 9:
		return True
	return False	

check_win() – The function has all the winning combinations. All it does is, it checks whether any of the winning combinations is satisfied by the current player’s positions. If it does, it returns True. If none of the combinations is satisfied, then the function returns False.

check_draw() – The draw condition is fairly simple, as the game is drawn when all ‘nine’ positions are taken.


Step 7: Switch the current player

Since each player only moves once at a time, therefore after every successful move, we have to swap the current player.

# Switch player moves
if cur_player == 'X':
	cur_player = 'O'
else:
	cur_player = 'X'

As far as a single game is concerned, this is all we need to do. But this article also presents a scoreboard system for keeping track, if the players want to play multiple games.


Step 8: Enter player names

It is mandatory for any scoreboard to display each player names.

if __name__ == "__main__":

	print("Player 1")
	player1 = input("Enter the name : ")
	print("\n")

	print("Player 2")
	player2 = input("Enter the name : ")
	print("\n")

Step 9: Store game-related information

The information like the current player, the choice of players (take cross or naught), the available options (cross and naught), and the scoreboard need to be stored.

# Stores the player who chooses X and O
cur_player = player1

# Stores the choice of players
player_choice = {'X' : "", 'O' : ""}

# Stores the options
options = ['X', 'O']

# Stores the scoreboard
score_board = {player1: 0, player2: 0}

By default, the current player is the player who entered the name first.


Step 10: Design scoreboard

The scoreboard is stored as a dictionary, where keys are the player names and values are their win number.

# Function to print the score-board
def print_scoreboard(score_board):
	print("--------------------------------")
	print("       	   SCOREBOARD       ")
	print("--------------------------------")

	players = list(score_board.keys())
	print("   ", players[0], "    ", score_board[players[0]])
	print("   ", players[1], "    ", score_board[players[1]])

	print("--------------------------------\n")

To display the scoreboard, we need the player names. The keys are extracted using .keys() function and then converted to list, so that it can indexed while displaying the scores.


Step 11: Outer Game Loop

We need another game loop, for managing multiple matches of Tic-tac-toe. Each match, the current player chooses his mark ('X'or 'O'). The menu for choosing must be displayed in every game iteration:

# Game Loop for a series of Tic Tac Toe
# The loop runs until the players quit 
while True:

	# Player choice Menu
	print("Turn to choose for", cur_player)
	print("Enter 1 for X")
	print("Enter 2 for O")
	print("Enter 3 to Quit")

The scoreboard and menu look like this:

Score Board Menu
Scoreboard and Menu

Step 12: Handle and Assign Player Choice

Each iteration, we have to handle and store current player’s choice.

# Try exception for CHOICE input
try:
	choice = int(input())	
except ValueError:
	print("Wrong Input!!! Try Again\n")
	continue

# Conditions for player choice	
if choice == 1:
	player_choice['X'] = cur_player
	if cur_player == player1:
		player_choice['O'] = player2
	else:
		player_choice['O'] = player1

elif choice == 2:
	player_choice['O'] = cur_player
	if cur_player == player1:
		player_choice['X'] = player2
	else:
		player_choice['X'] = player1
		
elif choice == 3:
	print("Final Scores")
	print_scoreboard(score_board)
	break	

else:
	print("Wrong Choice!!!! Try Again\n")

According to the player’s choice, the data has been stored. This is important since after each game finishes, it will tell us which player won.


Step 13: Execute the match

After storing all the necessary information, it is time to execute an independent match and store the winning mark.

# Stores the winner in a single game of Tic-tac-toe
winner = single_game(options[choice-1])

Step 14: Update the scoreboard

We need to update the scoreboard after each match of Tic-tac-toe.

# Updates the scoreboard according to the winner
if winner != 'D' :
	player_won = player_choice[winner]
	score_board[player_won] = score_board[player_won] + 1

print_scoreboard(score_board)

If the game has not ended in a draw, then we update the scoreboard.


Step 15: Switch the choosing player

It is a generous idea, that each player must have the opportunity to choose which mark they want. To do so, we swap the value in cur_player.

# Switch player who chooses X or O
if cur_player == player1:
	cur_player = player2
else:
	cur_player = player1

Complete Working Code

# Function to print Tic Tac Toe
def print_tic_tac_toe(values):
	print("\n")
	print("\t     |     |")
	print("\t  {}  |  {}  |  {}".format(values[0], values[1], values[2]))
	print('\t_____|_____|_____')

	print("\t     |     |")
	print("\t  {}  |  {}  |  {}".format(values[3], values[4], values[5]))
	print('\t_____|_____|_____')

	print("\t     |     |")

	print("\t  {}  |  {}  |  {}".format(values[6], values[7], values[8]))
	print("\t     |     |")
	print("\n")


# Function to print the score-board
def print_scoreboard(score_board):
	print("\t--------------------------------")
	print("\t       	   SCOREBOARD       ")
	print("\t--------------------------------")

	players = list(score_board.keys())
	print("\t   ", players[0], "\t    ", score_board[players[0]])
	print("\t   ", players[1], "\t    ", score_board[players[1]])

	print("\t--------------------------------\n")

# Function to check if any player has won
def check_win(player_pos, cur_player):

	# All possible winning combinations
	soln = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 4, 7], [2, 5, 8], [3, 6, 9], [1, 5, 9], [3, 5, 7]]

	# Loop to check if any winning combination is satisfied
	for x in soln:
		if all(y in player_pos[cur_player] for y in x):

			# Return True if any winning combination satisfies
			return True
	# Return False if no combination is satisfied		
	return False		

# Function to check if the game is drawn
def check_draw(player_pos):
	if len(player_pos['X']) + len(player_pos['O']) == 9:
		return True
	return False		

# Function for a single game of Tic Tac Toe
def single_game(cur_player):

	# Represents the Tic Tac Toe
	values = [' ' for x in range(9)]
	
	# Stores the positions occupied by X and O
	player_pos = {'X':[], 'O':[]}
	
	# Game Loop for a single game of Tic Tac Toe
	while True:
		print_tic_tac_toe(values)
		
		# Try exception block for MOVE input
		try:
			print("Player ", cur_player, " turn. Which box? : ", end="")
			move = int(input())	
		except ValueError:
			print("Wrong Input!!! Try Again")
			continue

		# Sanity check for MOVE inout
		if move < 1 or move > 9:
			print("Wrong Input!!! Try Again")
			continue

		# Check if the box is not occupied already
		if values[move-1] != ' ':
			print("Place already filled. Try again!!")
			continue

		# Update game information

		# Updating grid status 
		values[move-1] = cur_player

		# Updating player positions
		player_pos[cur_player].append(move)

		# Function call for checking win
		if check_win(player_pos, cur_player):
			print_tic_tac_toe(values)
			print("Player ", cur_player, " has won the game!!")		
			print("\n")
			return cur_player

		# Function call for checking draw game
		if check_draw(player_pos):
			print_tic_tac_toe(values)
			print("Game Drawn")
			print("\n")
			return 'D'

		# Switch player moves
		if cur_player == 'X':
			cur_player = 'O'
		else:
			cur_player = 'X'

if __name__ == "__main__":

	print("Player 1")
	player1 = input("Enter the name : ")
	print("\n")

	print("Player 2")
	player2 = input("Enter the name : ")
	print("\n")
	
	# Stores the player who chooses X and O
	cur_player = player1

	# Stores the choice of players
	player_choice = {'X' : "", 'O' : ""}

	# Stores the options
	options = ['X', 'O']

	# Stores the scoreboard
	score_board = {player1: 0, player2: 0}
	print_scoreboard(score_board)

	# Game Loop for a series of Tic Tac Toe
	# The loop runs until the players quit 
	while True:

		# Player choice Menu
		print("Turn to choose for", cur_player)
		print("Enter 1 for X")
		print("Enter 2 for O")
		print("Enter 3 to Quit")

		# Try exception for CHOICE input
		try:
			choice = int(input())	
		except ValueError:
			print("Wrong Input!!! Try Again\n")
			continue

		# Conditions for player choice	
		if choice == 1:
			player_choice['X'] = cur_player
			if cur_player == player1:
				player_choice['O'] = player2
			else:
				player_choice['O'] = player1

		elif choice == 2:
			player_choice['O'] = cur_player
			if cur_player == player1:
				player_choice['X'] = player2
			else:
				player_choice['X'] = player1
		
		elif choice == 3:
			print("Final Scores")
			print_scoreboard(score_board)
			break	

		else:
			print("Wrong Choice!!!! Try Again\n")

		# Stores the winner in a single game of Tic Tac Toe
		winner = single_game(options[choice-1])
		
		# Edits the scoreboard according to the winner
		if winner != 'D' :
			player_won = player_choice[winner]
			score_board[player_won] = score_board[player_won] + 1

		print_scoreboard(score_board)
		# Switch player who chooses X or O
		if cur_player == player1:
			cur_player = player2
		else:
			cur_player = player1

Time for a Gameplay!

All the steps to create the game has been finished. Now is the time to play the game.

Player 1
Enter the name : Luffy


Player 2
Enter the name : Sanji


	--------------------------------
	       	   SCOREBOARD       
	--------------------------------
	    Luffy 	     0
	    Sanji 	     0
	--------------------------------

Turn to choose for Luffy
Enter 1 for X
Enter 2 for O
Enter 3 to Quit
1


	     |     |
	     |     |   
	_____|_____|_____
	     |     |
	     |     |   
	_____|_____|_____
	     |     |
	     |     |   
	     |     |


Player  X  turn. Which box? : 5


	     |     |
	     |     |   
	_____|_____|_____
	     |     |
	     |  X  |   
	_____|_____|_____
	     |     |
	     |     |   
	     |     |


Player  O  turn. Which box? : 1


	     |     |
	  O  |     |   
	_____|_____|_____
	     |     |
	     |  X  |   
	_____|_____|_____
	     |     |
	     |     |   
	     |     |


Player  X  turn. Which box? : 9


	     |     |
	  O  |     |   
	_____|_____|_____
	     |     |
	     |  X  |   
	_____|_____|_____
	     |     |
	     |     |  X
	     |     |


Player  O  turn. Which box? : 2


	     |     |
	  O  |  O  |   
	_____|_____|_____
	     |     |
	     |  X  |   
	_____|_____|_____
	     |     |
	     |     |  X
	     |     |


Player  X  turn. Which box? : 3


	     |     |
	  O  |  O  |  X
	_____|_____|_____
	     |     |
	     |  X  |   
	_____|_____|_____
	     |     |
	     |     |  X
	     |     |


Player  O  turn. Which box? : 7


	     |     |
	  O  |  O  |  X
	_____|_____|_____
	     |     |
	     |  X  |   
	_____|_____|_____
	     |     |
	  O  |     |  X
	     |     |


Player  X  turn. Which box? : 6


	     |     |
	  O  |  O  |  X
	_____|_____|_____
	     |     |
	     |  X  |  X
	_____|_____|_____
	     |     |
	  O  |     |  X
	     |     |


Player  X  has won the game!!


	--------------------------------
	       	   SCOREBOARD       
	--------------------------------
	    Luffy 	     1
	    Sanji 	     0
	--------------------------------

Turn to choose for Sanji
Enter 1 for X
Enter 2 for O
Enter 3 to Quit
2


	     |     |
	     |     |   
	_____|_____|_____
	     |     |
	     |     |   
	_____|_____|_____
	     |     |
	     |     |   
	     |     |


Player  O  turn. Which box? : 5


	     |     |
	     |     |   
	_____|_____|_____
	     |     |
	     |  O  |   
	_____|_____|_____
	     |     |
	     |     |   
	     |     |


Player  X  turn. Which box? : 3


	     |     |
	     |     |  X
	_____|_____|_____
	     |     |
	     |  O  |   
	_____|_____|_____
	     |     |
	     |     |   
	     |     |


Player  O  turn. Which box? : 2


	     |     |
	     |  O  |  X
	_____|_____|_____
	     |     |
	     |  O  |   
	_____|_____|_____
	     |     |
	     |     |   
	     |     |


Player  X  turn. Which box? : 8


	     |     |
	     |  O  |  X
	_____|_____|_____
	     |     |
	     |  O  |   
	_____|_____|_____
	     |     |
	     |  X  |   
	     |     |


Player  O  turn. Which box? : 1


	     |     |
	  O  |  O  |  X
	_____|_____|_____
	     |     |
	     |  O  |   
	_____|_____|_____
	     |     |
	     |  X  |   
	     |     |


Player  X  turn. Which box? : 9


	     |     |
	  O  |  O  |  X
	_____|_____|_____
	     |     |
	     |  O  |   
	_____|_____|_____
	     |     |
	     |  X  |  X
	     |     |


Player  O  turn. Which box? : 6


	     |     |
	  O  |  O  |  X
	_____|_____|_____
	     |     |
	     |  O  |  O
	_____|_____|_____
	     |     |
	     |  X  |  X
	     |     |


Player  X  turn. Which box? : 7


	     |     |
	  O  |  O  |  X
	_____|_____|_____
	     |     |
	     |  O  |  O
	_____|_____|_____
	     |     |
	  X  |  X  |  X
	     |     |


Player  X  has won the game!!


	--------------------------------
	       	   SCOREBOARD       
	--------------------------------
	    Luffy 	     2
	    Sanji 	     0
	--------------------------------

Turn to choose for Luffy
Enter 1 for X
Enter 2 for O
Enter 3 to Quit
3
Final Scores
	--------------------------------
	       	   SCOREBOARD       
	--------------------------------
	    Luffy 	     2
	    Sanji 	     0
	--------------------------------


Conclusion

We hope that this article was fun as well as informative to the reader. I’ve also uploaded the code on Github. You can visit here for the code. If there are any suggestions for the game, feel free to comment.