Create Tic Tac Toe with Python
Tags: python
Published: 2020-11-03 22:55:36

This post will demonstrate how to make Tic Tac Toe in Python. Hopefully this will help someone relatively new to Python see how to make a simple program. Note that Python 3.7 is used for this tutorial although most versions of Python 3 should work.

Chances are high you have heard of Tic Tac Toe but I will briefly explain the game just in case. The game is played on a 3x3 grid that starts out initially blank. Players then alternate placing their letter (X for player 1, O for player 2) inside a blank square of the grid. If one player gets 3 in a line either vertically, horizontally, or diagonally, they win the game. If the grid is filled and there are no 3-in-a-rows, then the game is a tie which is the result if both players play properly.

We will encode the rules of the game in Python with the program asking for the moves of each player and then displaying the current state of the board. To build the game, we will need:
- a way to display the current state of the game
- a way to update to grid with a move
- a way to check for the end of the game
- a way to verify if a move is valid
- prompts for players to make their moves

A sample run of the game
```
0   1   2   
3   4   5   
6   7   8   
Player 1 [X], make your move: 4
0   1   2   
3   X   5   
6   7   8   
Player 2 [O], make your move: 1
0   O   2   
3   X   5   
6   7   8   
Player 1 [X], make your move: 3
0   O   2   
X   X   5   
6   7   8   
Player 2 [O], make your move: 5
0   O   2   
X   X   O   
6   7   8   
Player 1 [X], make your move: 6
0   O   2   
X   X   O   
X   7   8   
Player 2 [O], make your move: 0
O   O   2   
X   X   O   
X   7   8   
Player 1 [X], make your move: 2
O   O   X   
X   X   O   
X   7   8   
Player 1 Wins
Good Game!
```

**a way to display the current state of the game**
Our program will be a *console* program since it will not have a GUI. Instead, it will collect moves in the console through the players typing input and it will display the board after each move. We can display the current state of the game by printing out the 3x3 grid row by row with spaces between each square. Player 1 will use 'X', Player 2 will use 'Y', and blanks will be represented as '-'. The board will start out initially with all squares as blank. For displaying purposes, blanks will show a number corresponding to what a player will type to place their letter in that square (0 to 8). 
```
PLAYER_ONE_MOVE = "X"
PLAYER_TWO_MOVE = "O"
BLANK = "-"
BOARD_SIZE = 3
VALID_MOVES = ["0", "1", "2", "3", "4", "5", "6", "7", "8"]

board = [[BLANK, BLANK, BLANK], [BLANK, BLANK, BLANK], [BLANK, BLANK, BLANK]]

def print_board():
    for i in range(BOARD_SIZE):
        for j in range(BOARD_SIZE):
            next_square = board[i][j]
            if next_square == BLANK:
                next_square = BOARD_SIZE * i + j
            print(next_square, end = "   ")
        print("")
```

**a way to update to grid with a move**
A move is a number from 0 to 8 which corresponds to a specific square. If we think about it, *move // BOARD_SIZE* gives us the row number and *move % BOARD_SIZE* gives us the column number. Note that in Python, "//" is integer division where as "/" will give a floating point result. We will place an "X" or "O" depending on which player is making the move. We will always print the board after a move so that the next player can see the current state of the board.
```
def update_board(move, is_player_one):
    row = move // BOARD_SIZE
    column = move % BOARD_SIZE
    if is_player_one:
        board[row][column] = PLAYER_ONE_MOVE
    else:
        board[row][column] = PLAYER_TWO_MOVE
    print_board()
```

**a way to check for the end of the game**
A game can end because the board becomes full or because one player gets 3-in-a-row. We will make one function for checking if the board is full and another to check for 3-in-a-rows. The board is full if all squares are not blank so we can just use nested for loops to check for this. Detecting 3-in-a-rows can be done by checking for 1 of 8 of the possible ways to get one (3 horizontal, 3 vertical, 2 diagonals). Note that "\" is used to have the boolean condition across multiple lines.
```
def board_full():
    for i in range(BOARD_SIZE):
        for j in range(BOARD_SIZE):
            if board[i][j] == BLANK:
                return False
    return True

def is_win(letter):
    if (board[0][0] == letter and board[0][1] == letter and board[0][2] == letter) \
            or (board[1][0] == letter and board[1][1] == letter and board[1][2] == letter) \
            or (board[2][0] == letter and board[2][1] == letter and board[2][2] == letter) \
            or (board[0][0] == letter and board[1][0] == letter and board[2][0] == letter) \
            or (board[0][1] == letter and board[1][1] == letter and board[2][1] == letter) \
            or (board[0][2] == letter and board[1][2] == letter and board[2][2] == letter) \
            or (board[0][0] == letter and board[1][1] == letter and board[2][2] == letter) \
            or (board[0][2] == letter and board[1][1] == letter and board[2][0] == letter):
       return True
    return False
```

**a way to verify if a move is valid**
An attempted move is valid if it is a number from 0 to 8 (representing a square) and if that square is not already taken. We can first check if the move is in a predetermined list of valid moves and return False otherwise. If it is a valid move, we just need to make sure that square currently has a blank.
```
def valid_move(move):
    if move not in VALID_MOVES:
        return False
    move = int(move)
    row = move // BOARD_SIZE
    column = move % BOARD_SIZE
    return board[row][column] == BLANK
```

**prompts for players to make their moves**
Unlike languages such as Java, there is not a sense of a main method. *if \_\_name\_\_ == '\_\_main\_\_'* will evaluate to true if the source file is run as the main program. The game loop and player input will be placed in this if block. 

One iteration of the game loop consists of two player moves. The game loop runs until the board is full or a win is detected early. Input for player moves is retrieved through input() and is re-prompted repeatedly if the move is not a valid one. The board is updated and printed after each move so that the next player can make an informed decision. The final result is printed to the console in the case of a player win or a tie.
```
if __name__ == '__main__':
    print_board()
    while not board_full():
        # player 1
        player_one_move = input("Player 1 [X], make your move: ")
        while not valid_move(player_one_move):
            player_one_move = input("Player 1 [X], please enter a proper move: ")
        update_board(int(player_one_move), True)
        if is_win(PLAYER_ONE_MOVE):
            print("Player 1 Wins")
            break
        elif board_full():
            print("Tie")
            break
        # player 2
        player_two_move = input("Player 2 [O], make your move: ")
        while not valid_move(player_two_move):
            player_two_move = input("Player 2 [O], please enter a proper move: ")
        update_board(int(player_two_move), False)
        if is_win(PLAYER_TWO_MOVE):
            print("Player 2 Wins")
            break
    print("Good Game!")
```

**Challenge**
Hopefully you got something out of this. You can test if you understand it by seeing if you can build Connect Four!