Tic-tac-toe, or noughts and crosses, is a common two-player game on a 3 x 3 square grid - the rules are simple: players put their corresponding symbol (a circle or a cross) onto the grid until they get 3 of their symbols in a row, column or main diagonal.
Now, decomposing the problem:
There would be three main tasks for the program: processing inputs from the user, generating a move for the computer (which in this case would be random, so we need to import random at the top of the program) and deciding whether someone, if anyone, has won yet.
On top of this, it needs to keep updating the board and outputting it when appropriate. Since the board itself is quite essential to the game, we'll start by making a 2D array that represents the board.
For now we can say that the computer is 'X' and the user is 'O' (either way it doesn't matter).
Overall we'll have 5 functions, the names and purposes of which are outlined below:
To differentiate between the 9 different spaces, we can label them from 1 to 9, and to make the creation of the board slightly easier, we can define smaller lists first that make up individual rows, and put them together in the final 2D array.
row_one = ["+", "-", "-", "-", "-", "-", "-", "-",
"+", "-", "-", "-", "-", "-", "-", "-",
"+", "-", "-", "-", "-", "-", "-", "-", "+"]
row_two = ["|", " ", " ", " ", " ", " ", " ", " ",
"|", " ", " ", " ", " ", " ", " ", " ",
"|", " ", " ", " ", " ", " ", " ", " ", "|"]
row_three = ["| ", "1", " | ", "2", " | ", "3", " |"]
row_four = ["| ", "4", " | ", "5", " | ", "6", " |"]
row_five = ["| ", "7", " | ", "8", " | ", "9", " |"]
game_board = [row_one, row_two, row_three, row_two, row_one,
row_two, row_four, row_two, row_one,
row_two, row_five, row_two, row_one]row_one = ["+", "-", "-", "-", "-", "-", "-", "-",
"+", "-", "-", "-", "-", "-", "-", "-",
"+", "-", "-", "-", "-", "-", "-", "-", "+"]
row_two = ["|", " ", " ", " ", " ", " ", " ", " ",
"|", " ", " ", " ", " ", " ", " ", " ",
"|", " ", " ", " ", " ", " ", " ", " ", "|"]
row_three = ["| ", "1", " | ", "2", " | ", "3", " |"]
row_four = ["| ", "4", " | ", "5", " | ", "6", " |"]
row_five = ["| ", "7", " | ", "8", " | ", "9", " |"]
game_board = [row_one, row_two, row_three, row_two, row_one,
row_two, row_four, row_two, row_one,
row_two, row_five, row_two, row_one]We'll first create a function for outputting the board:
def display_board(): # accepts 1 parameter for the board's current status and outputs it
for item in game_board:
for value in item:
print(value, end='')
print("")+-------+-------+-------+ | | | | | 1 | 2 | 3 | | | | | +-------+-------+-------+ | | | | | 4 | 5 | 6 | | | | | +-------+-------+-------+ | | | | | 7 | 8 | 9 | | | | | +-------+-------+-------+
def display_board(): # accepts 1 parameter for the board's current status and outputs it
for item in game_board:
for value in item:
print(value, end='')
print("")+-------+-------+-------+ | | | | | 1 | 2 | 3 | | | | | +-------+-------+-------+ | | | | | 4 | 5 | 6 | | | | | +-------+-------+-------+ | | | | | 7 | 8 | 9 | | | | | +-------+-------+-------+
We can then create a function that receives the user's input and checks whether it's valid. Before asking for a move, it also needs to check whether someone has already won - we'll call victory_for() here and write it in the Processing section.
def enter_move():
victory = victory_for()
if not victory:
while True:
try:
user_move = int(input("Enter your move: "))
user_move = str(user_move)
if user_move in game_board[2]:
game_board[2][game_board[2].index(user_move)] = 'O'
break
elif user_move in game_board[6]:
game_board[6][game_board[6].index(user_move)] = 'O'
break
elif user_move in game_board[10]:
game_board[10][game_board[10].index(user_move)] = 'O'
break
else:
print("That move is already done, choose another.")
except:
print("I can't do anything with that: enter an ACTUAL move from the game_board above.")
display_board()
draw_move()
else:
print(victory)def enter_move():
victory = victory_for()
if not victory:
while True:
try:
user_move = int(input("Enter your move: "))
user_move = str(user_move)
if user_move in game_board[2]:
game_board[2][game_board[2].index(user_move)] = 'O'
break
elif user_move in game_board[6]:
game_board[6][game_board[6].index(user_move)] = 'O'
break
elif user_move in game_board[10]:
game_board[10][game_board[10].index(user_move)] = 'O'
break
else:
print("That move is already done, choose another.")
except:
print("I can't do anything with that: enter an ACTUAL move from the game_board above.")
display_board()
draw_move()
else:
print(victory)The main processing would be deciding whether or not someone has won and to see what moves are available so that the computer can generate its own move. The players take turns, so after the user moves, enter_move() hands over to the computer by calling draw_move(), which we'll write in the next section. First we'll make a function for creating a list of available moves:
def free_fields():
global availible_moves # create list of moves
availible_moves = []
for i in game_board:
for j in i:
if j == '1' or j == '2' or j == '3' or j == '4' or j == '5' or j == '6' or j == '7' or j == '8' or j == '9':
availible_moves.append(j)def free_fields():
global availible_moves # create list of moves
availible_moves = []
for i in game_board:
for j in i:
if j == '1' or j == '2' or j == '3' or j == '4' or j == '5' or j == '6' or j == '7' or j == '8' or j == '9':
availible_moves.append(j)We'll then create a function that determines whether someone has won - in this case we just have to check all 8 possible winning lines (3 rows, 3 columns and 2 diagonals). It returns a message saying who has won, "We drew." if the board is full with no winner, or False if the game should carry on.
def victory_for(): # Analyses the board's status in order to check which player has won
if game_board[2][1] == game_board[2][3] and game_board[2][3] == game_board[2][5]:
if game_board[2][1] == 'O': return "You won!"
elif game_board[2][1] == 'X': return "I won!"
elif game_board[6][1] == game_board[6][3] and game_board[6][3] == game_board[6][5]:
if game_board[6][1] == 'O': return "You won!"
elif game_board[6][1] == 'X': return "I won!"
elif game_board[10][1] == game_board[10][3] and game_board[10][3] == game_board[10][5]:
if game_board[10][1] == 'O': return "You won!"
elif game_board[10][1] == 'X': return "I won!"
elif game_board[2][1] == game_board[6][1] and game_board[6][1] == game_board[10][1]:
if game_board[2][1] == 'O': return "You won!"
elif game_board[2][1] == 'X': return "I won!"
elif game_board[2][3] == game_board[6][3] and game_board[6][3] == game_board[10][3]:
if game_board[2][3] == 'O': return "You won!"
elif game_board[2][3] == 'X': return "I won!"
elif game_board[2][5] == game_board[6][5] and game_board[2][5] == game_board[10][5]:
if game_board[2][5] == 'O': return "You won!"
elif game_board[2][5] == 'X': return "I won!"
elif game_board[2][1] == game_board[6][3] and game_board[6][3] == game_board[10][5]:
if game_board[2][1] == 'O': return "You won!"
elif game_board[2][1] == 'X': return "I won!"
elif game_board[10][1] == game_board[6][3] and game_board[6][3] == game_board[2][5]:
if game_board[10][1] == 'O': return "You won!"
elif game_board[10][1] == 'X': return "I won!"
else:
free_fields()
if len(availible_moves) > 0: return False
else: return "We drew."def victory_for(): # Analyses the board's status in order to check which player has won
if game_board[2][1] == game_board[2][3] and game_board[2][3] == game_board[2][5]:
if game_board[2][1] == 'O': return "You won!"
elif game_board[2][1] == 'X': return "I won!"
elif game_board[6][1] == game_board[6][3] and game_board[6][3] == game_board[6][5]:
if game_board[6][1] == 'O': return "You won!"
elif game_board[6][1] == 'X': return "I won!"
elif game_board[10][1] == game_board[10][3] and game_board[10][3] == game_board[10][5]:
if game_board[10][1] == 'O': return "You won!"
elif game_board[10][1] == 'X': return "I won!"
elif game_board[2][1] == game_board[6][1] and game_board[6][1] == game_board[10][1]:
if game_board[2][1] == 'O': return "You won!"
elif game_board[2][1] == 'X': return "I won!"
elif game_board[2][3] == game_board[6][3] and game_board[6][3] == game_board[10][3]:
if game_board[2][3] == 'O': return "You won!"
elif game_board[2][3] == 'X': return "I won!"
elif game_board[2][5] == game_board[6][5] and game_board[2][5] == game_board[10][5]:
if game_board[2][5] == 'O': return "You won!"
elif game_board[2][5] == 'X': return "I won!"
elif game_board[2][1] == game_board[6][3] and game_board[6][3] == game_board[10][5]:
if game_board[2][1] == 'O': return "You won!"
elif game_board[2][1] == 'X': return "I won!"
elif game_board[10][1] == game_board[6][3] and game_board[6][3] == game_board[2][5]:
if game_board[10][1] == 'O': return "You won!"
elif game_board[10][1] == 'X': return "I won!"
else:
free_fields()
if len(availible_moves) > 0: return False
else: return "We drew."This involves looking at the available moves, using the previous 'free_fields' function, and randomly selecting one, before updating the board to reflect the computer's move.
def draw_move():
victory = victory_for()
if not victory:
free_fields()
computer_move = random.choice(availible_moves)
if computer_move in game_board[2]:
game_board[2][game_board[2].index(computer_move)] = 'X'
elif computer_move in game_board[6]:
game_board[6][game_board[6].index(computer_move)] = 'X'
elif computer_move in game_board[10]:
game_board[10][game_board[10].index(computer_move)] = 'X'
display_board()
enter_move()
else:
print(victory)def draw_move():
victory = victory_for()
if not victory:
free_fields()
computer_move = random.choice(availible_moves)
if computer_move in game_board[2]:
game_board[2][game_board[2].index(computer_move)] = 'X'
elif computer_move in game_board[6]:
game_board[6][game_board[6].index(computer_move)] = 'X'
elif computer_move in game_board[10]:
game_board[10][game_board[10].index(computer_move)] = 'X'
display_board()
enter_move()
else:
print(victory)The turns take care of themselves: draw_move() finishes by calling enter_move(), and enter_move() finishes by calling draw_move(), so the two functions keep handing over to each other. Each one checks victory_for() first, and as soon as it returns a result message instead of False, that message is printed and the game ends. The computer goes first, so we just need to call draw_move() and it'll run from there.
draw_move()draw_move()That's it! Thanks for reading, and don't forget to check out the source code on GitHub!