Tic Tac Toe
This is a tic-tac-toe game that runs in your terminal with no dependencies. All you need is a standard installation of Python 3.x.
Imports and table setup
The first step is to import sys
and random
. Once again, we're using random.choice()
to decide what the computer does, and sys.exit()
to terminate the program.
import random
import sys
Next, we need a way to check for a winner. The following 2D array lists all of the winning combinations of marks in Tic-tac-toe. Each of the numbers represents a square on the 9x9 grid starting from 0. They have to start from 0 because the grid is actually an array itself, and each of these numbers is an array index.
WINNING_BOARDS = [
# Rows first
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
# columns
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
# diagonals
[0, 4, 8],
[6, 4, 2]
]
Utility functions
After that, we need a way to check the current board against the winning marks list above. Since the computer and the player are playing on the same board, the function needs to know whether to check for X's or O's on each space. The win
variable is initially set to False. Then it loops over winningBoards
, assigns each number to a list of temporary index values (i_0
through i_2
), and checks each of those indices in board
to see if they match the specified marker. So if the supplied marker is "X", and board[0]
, board[1]
, and board[2]
all equal "X" (see the first row of WINNING_BOARDS
above), this function returns True.
Checking the board state
def checkBoard(board, marker, winningBoards):
""" Check a 'board' with a specified marker against a list (winningBoards) to
see if all of the required spaces are occupied by the specified marker.
Returns True if all three 'winningBoard' indexes of 'board' match 'marker'.
"""
win = False
for row in winningBoards:
i_0 = row[0]
i_1 = row[1]
i_2 = row[2]
# If the submitted board has the specificed marker in all three
# indexes, then it's a winner.
if board[i_0] == marker and board[i_1] == marker and board[i_2] == marker:
win = True
return win
Finding and picking spaces
This next function allows the program to figure out which spaces on the board are free. It builds and returns a list of array indices with a value of 0.
def getFreeSpaces(board):
freeSpaces = []
curIndex = 0
# 1 - Player's owns it
# 2 - Computer owns it
# 0 - Empty
for b in board:
if b == 0:
freeSpaces.append(curIndex)
curIndex = curIndex + 1
return freeSpaces
After that, we need a way for the computer to pick a space to mark each turn. This function first gets a list of free spaces using getFreeSpaces()
. Then it uses random.choice()
to select one of them.
def compPickSpace(board):
#print(getFreeSpaces(board))
freeSpaces = getFreeSpaces(board)
choice = random.choice(freeSpaces)
return choice
Prompting the user to pick a spot is a bit more involved. We start with getFreeSpaces()
to get what's available and use input()
to get the user's choice.
def userPickSpace(board):
freeSpaces = getFreeSpaces(board)
userInput = input("Choose a number: ").strip()
By default, input()
returns a string. This section first tries to convert the user's input to an integer with the aptly named int()
function within a try..catch
block. If unsuccessful, it checks to see if the user entered the letter "q". If so, it kills the program and returns 0 to the OS to signal error-free termination. If the input was not "q", execution continues to yell at the user and return 9 from the function.
try:
choice = int(userInput)
except:
if userInput == 'q':
print("Goodbye!")
sys.exit(0)
print("You must enter an INTEGER!")
return 9
Skipping
else
Python's
if
blocks do not require anelse
block. If anelse
block is not specified, and theif
condition evaluates to False, execution simply proceeds to the next block or statement of code. That means this chunk of code:if userInput == 'q': print("Goodbye!") sys.exit(0) print("You must enter an INTEGER!") return 9
is the same as this chunk of code:
if userInput == 'q': print("Goodbye!") sys.exit(0) else: print("You must enter an INTEGER!") return 9
Which way is better? It all comes down to personal preference and the style guide for whatever project you're working on.
Assuming that the user's input is successfully converted to an integer, it checks if the user's choice is in the freeSpaces
array. If so, it returns the choice. Else, it tells the user that the space is already taken and returns 9.
if choice in freeSpaces:
return choice
else:
print("That space is taken!")
return 9
Displaying output
Drawing the board is the next step. This function iterates over the board and copies its values into a temporary array. Why does it do this? Because it makes more sense to display occupied spaces as X's and O's.
def drawBoard(board):
ds = []
curIndex = 0
for s in board:
if s == 1:
ds.append("X")
elif s == 2:
ds.append("Y")
else:
ds.append(curIndex)
curIndex = curIndex + 1
The next section uses six print()
calls and the f-string syntax to print out a nice grid to show the player. It also acts as a menu showing the user which spaces are free.
# A little bit of vertical space improves the interface.
print("")
print(f"{ds[0]} | {ds[1]} | {ds[2]}")
print("- - - - -")
print(f"{ds[3]} | {ds[4]} | {ds[5]}")
print("- - - - -")
print(f"{ds[6]} | {ds[7]} | {ds[8]}")
print("")
The main
function
This is where it all comes together. We kick things off by welcoming the user, setting up an empty grid, and setting the win
variable to 0.
def main():
print("Welcome to Tic Tac Toe!")
board = [
0,0,0,
0,0,0,
0,0,0
]
win = 0
Next, we enter the main game loop. This loop always starts by drawing the board. Then it checks for the first game ending condition: out of moves. If True, it sets win
to 3 and breaks the loop.
while True:
# Draw the board
drawBoard(board)
# Check if we're out of free spaces (tie)
if len(getFreeSpaces(board)) == 0:
win = 3
break
This next section checks to see of the player's input is not equal to 9, which is the stand-in I chose for invalid input. Next, it changes that spot on the board to the player's marker. If the choice made that turn causes the player to win, it sets win
to 2 and breaks the loop.
# Ask the user to pick a square and update the board
userChoice = userPickSpace(board)
if userChoice != 9:
# If the player's choice was valid, continue
board[userChoice] = 2
# If the player won, break
if checkBoard(board, 2, WINNING_BOARDS):
win = 2
break
If the player didn't win, the computer takes their turn. If the computer's move wins the game, then win
is set to 1 and the loop breaks.
# Ask the computer for a choice and update the board, but ONLY IF there are moves left to make.
if len(getFreeSpaces(board)) > 0:
compChoice = compPickSpace(board)
board[compChoice] = 1
# After both moves, check if the player or computer won.
if checkBoard(board, 1, WINNING_BOARDS):
win = 1
break
In each of the three cases above, win
always ends up as 1, 2, or 3. When the loop break, execution continues to the next section of code. This final section draws the board one more time and tells the user whether it was a win, loss, or draw.
# Draw the board one final time
drawBoard(board)
if win == 1:
print("You lose...")
elif win == 2:
print("YOU WIN!")
else:
print("It's a tie!")
Finally, the very last line in the file runs main()
and starts the game loop.
main()
Full source!
import random
import sys
WINNING_BOARDS = [
# Rows first
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
# columns
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
# diagonals
[0, 4, 8],
[6, 4, 2]
]
def checkBoard(board, marker, winningBoards):
""" Check a 'board' with a specified marker against a list (winningBoards) to
see if all of the required spaces are occupied by the specified marker.
Returns True if all three 'winningBoard' indexes of 'board' match 'marker'.
"""
win = False
for row in winningBoards:
i_0 = row[0]
i_1 = row[1]
i_2 = row[2]
# If the submitted board has the specificed marker in all three
# indexes, then it's a winner.
if board[i_0] == marker and board[i_1] == marker and board[i_2] == marker:
win = True
return win
def getFreeSpaces(board):
freeSpaces = []
curIndex = 0
# 1 - Player's owns it
# 2 - Computer owns it
# 0 - Empty
for b in board:
if b == 0:
freeSpaces.append(curIndex)
curIndex = curIndex + 1
return freeSpaces
def compPickSpace(board):
#print(getFreeSpaces(board))
freeSpaces = getFreeSpaces(board)
choice = random.choice(freeSpaces)
return choice
def userPickSpace(board):
freeSpaces = getFreeSpaces(board)
userInput = input("Choose a number: ").strip()
try:
choice = int(userInput)
except:
if userInput == 'q':
print("Goodbye!")
sys.exit(0)
print("You must enter an INTEGER!")
return 9
#print(f"Choice: {choice}")
#print(f"free spaces: {freeSpaces}")
if choice in freeSpaces:
return choice
else:
print("That space is taken!")
return 9
def drawBoard(board):
ds = []
curIndex = 0
for s in board:
if s == 1:
ds.append("X")
elif s == 2:
ds.append("Y")
else:
ds.append(curIndex)
curIndex = curIndex + 1
# A little bit of vertical space improves the interface.
print("")
print(f"{ds[0]} | {ds[1]} | {ds[2]}")
print("- - - - -")
print(f"{ds[3]} | {ds[4]} | {ds[5]}")
print("- - - - -")
print(f"{ds[6]} | {ds[7]} | {ds[8]}")
print("")
### The `main` function
def main():
print("Welcome to Tic Tac Toe!")
board = [
0,0,0,
0,0,0,
0,0,0
]
win = 0
while True:
# Draw the board
drawBoard(board)
# Check if we're out of free spaces (tie)
if len(getFreeSpaces(board)) == 0:
win = 3
break
# Ask the user to pick a square and update the board
userChoice = userPickSpace(board)
if userChoice != 9:
# If the player's choice was valid, continue
board[userChoice] = 2
# If the player won, break
if checkBoard(board, 2, WINNING_BOARDS):
win = 2
break
# Ask the computer for a choice and update the board, but ONLY IF there are moves left to make.
if len(getFreeSpaces(board)) > 0:
compChoice = compPickSpace(board)
board[compChoice] = 1
# After both moves, check if the player or computer won.
if checkBoard(board, 1, WINNING_BOARDS):
win = 1
break
# Draw the board one final time
drawBoard(board)
if win == 1:
print("You lose...")
elif win == 2:
print("YOU WIN!")
else:
print("It's a tie!")
main()