My First Python Project - Tic Tac Toe Game
Updated: Aug 3, 2022
In this article I want to talk about my struggles with the project and explain in detail how it works. This will let me work through the problem, and practice my understanding of Python (similar to the idea of Learning While You Teach). As a note, in order to keep this article short, this isn't an introduction to Python. Rather it's an explanation my game. I won't be explaining basic concepts like: what lists are; how to use functions; or data types.
You can view my full code here on Github.
The creator of the project broke what was needed to do down into simple steps. Each step required a function to be written. Below is are the sections of the article that you can skip to.
In every function I first gave a brief overview of what the function was supposed to do, then I went into detail about how it worked.
Step 1: display_board()
We are writing out a function that can display the board. Portilla suggested setting up your board as a list, where each index 1-9 corresponds with a number on a number pad. This becomes a 3x3 representation.
Below I imported the clear-output function first. Then I set up my function calling it "display_board" with having an argument of board. Then I used 5 lines of print() statements. The first, third and fifth line concatenates index positions on the board 1, 2, 3; 4, 5, 6; and 7, 8, 9 respectively. The second and fourth lines prints several "-" to create horizontal separators.
from IPython.display import clear_output def display_board(board): print(board+' | '+board+' | '+board) print('----------') print(board+' | '+board+' | '+board) print('----------') print(board+' | '+board+' | '+board)
Creating a test board called test_board adding in 'X's and 'O's to fill the board.
Step 2: player_input()
The next step was to create a function which takes a user's input and assigns their marker as either X or O.
First I create a variable and set it to a random string, the important part is not having it set to either 'X' or 'O'. Then I created a while loop that continually runs while the choice (or input) is not 'X' or 'O'. Then I ask for Player one to choose either X or O. Depending on their choice I either assign the variable choice to 'X' or 'O'. If they did not input either 'X' or 'O' and instead input something else like 'y', then it prints out an error message and asks them to choose either 'X' or 'O'.
def player_input(): choice = 'wrong' #while choice is not a digit while choice not in ['X', 'O']: choice = input("Player 1: Please choose X or O ") if choice not in ['X','O']: print("Sorry I don't understand. Please choose X or O.") elif choice == 'X': return ('X', 'O') else: return ('O', 'X')
Step 3: place_marker()
The third step is to create a function which takes in the board, a marker and a position and assigns it to the board.
The arguments are: board, marker, position. The board is the list object with the board. If we use our test_board then the board is ['#','X','O','X','O','X','O','X','O','X']. The marker is the player's marker either 'X' or 'O'. And the position is a number 1-9 that corresponds to a position on the board. The function first takes in the board, and assigns the marker to the position the player choose. For example if we had a brand new board called board =[' ',' ',' ',' ',' ',' ',' ',' ',' ','']. It would look like an empty board. And if Player 1 was 'X' and choose position 3, then we would put 'X' in board (3rd index of the board). The board would now be = [' ',' ',' ','X',' ',' ',' ',' ',' ',''].
As a note the reason why the first index is not important is because we begin the list at index 1, and in Python programming the first position of a list is 0. So by having our position starting at 1 it's actually starting at the 2nd index in the list.
def place_marker(board, marker, position): board[position] = marker
Step 4: win_check()
Create a function that takes in a board and a mark, to check if that mark has won.
I used if statements for different conditions. The first three conditions indicates the vertical positions of winning (or having three of the same marks 'X' or 'O' in a row vertically). Conditions four and five, check for when three of the same marks are in a row in a diagonal pattern. The next three conditions check for when three of the same marks are in a row horizontally. If any one of these conditions are met then they return the boolean, True. The final condition is the 'else' statement" is if none of these are present then return False.
def win_check(board, mark): #vertical positions if board == mark and board == mark and board == mark: return True elif board == mark and board == mark and board == mark: return True elif board == mark and board == mark and board == mark: return True # diagnoal positions elif board == mark and board == mark and board == mark: return True print(board) elif board == mark and board == mark and board == mark: return True # horizontal positions elif board == mark and board == mark and board == mark: return True elif board == mark and board == mark and board == mark: return True elif board == mark and board == mark and board == mark: return True else: return False
Step 5: choose_first()
Next create a function that uses the random module to decide which player goes first.
First import the random module. Then make a variable called 'choice' equal to random.randint(1,2). This means it will choose a random number, either 1 or 2. If the number is 1, then it returns "Player 1", else (or when the number is 2), it returns "Player 2".
import random def choose_first(): choice = random.randint(1,2) if choice == 1: return "Player 1" else: return "Player 2"
Step 6: space_check()
Now to create a function that returns a boolean (either True or False) to indicate whether a space on the board is freely available.
For each position or index of the board list, I checked if it was empty (because it was a list I checked to see if the indexes was empty, or had an empty string). For example if the position chosen was 1 and it was empty then I would return True. I did this for all of the positions. If none of these conditions were True, it would return False.
def space_check(board, position): if position == 1 and board == ' ': return True elif position == 2 and board == ' ': return True elif position == 3 and board == ' ': return True elif position == 4 and board == ' ': return True elif position == 5 and board == ' ': return True elif position == 6 and board == ' ': return True elif position == 7 and board == ' ': return True elif position == 8 and board == ' ': return True elif position == 9 and board == ' ': return True else: return False
Step 7: full_board_check()
Next is to create a function that checks to see if the board is full and returns a boolean.
Similar to the method above but instead of checking each position separately like checking position 1, then position 2, etc. I checked if all of the positions were full. This means I checked to see if they were not empty. I used the and condition word and linked all of the positions because that would mean the board is full. If it is then it would return True. If it's not, meaning the board is not completely full it returns False.
def full_board_check(board): if board != ' ' and board != ' 'and board != ' ' and board != ' ' and board != ' ' and board != ' ' and board != ' ' and board != ' ' and board != ' ': return True else: return False
Step 8: player_choice()
Create a function that asks the player to choose a position on the board (1-9) and returns that position for later use.
First is defining the initial variables: choice which is set to a string, then acceptable_range which includes a range between 1 and 10. And finally within_range that is set to false initially. Then create a while loop that is continually running while the choice is not a digit and not within range. In the while loop I ask the player to choose a position (1-9) and set that answer to choice. Next check to see if the choice is a digit, if it's not then tell the player it's not a digit and to input a digit. Then assuming the choice is a digit (integer) check to see if it's within the range of 1 and 10. If it is then create a new variable called new_position to the integer of choice and set within_range to be true. If it's a digit but not within range then print out a message that says to input a number between 1 and 9.
Finally use the function space_check(), to see if the position they chose is an available space. If it is return this new_position (the position the player chose) to use for later.
def player_choice(board): # Intitial variables choice = 'wrong' acceptable_range = range(1, 10) #range of (1-9) within_range = False while choice.isdigit() == False or within_range == False: choice = input("Choose a position (1-9): ") #Digit check if choice.isdigit() == False: print("Sorry, but you did not enter an integer. Please try again") #Range check, assume idsigit() is true if choice.isdigit() == True: if int(choice) in acceptable_range: new_position = int(choice) #assign the choice as an integer to a new variable within_range = True else: print("Sorry please input a number between 1 and 9") within_range = False #if they input anything outside of the range # Use space_check to see if the space is true if space_check(board, new_position) == True: #return position for later use return new_position else: pass
Step 9: replay()
Then I created a function to ask the player if they would like to replay the game or not. Based on their answer it would return a boolean.
First is to create a variable called choice, similar to what I've done before and set it equal to a string. Then there is a while loop that keeps running while the player's input is not 'Y' or 'N'. In the while look it will continually ask the player if they would like to play again. If the player answers 'Y' then it returns True if the player answers 'N' it returns false. If the player inputs an answer that is neither 'Y' or 'N', a message pops up and the loop continues to run.
def replay(): choice = 'wrong' while choice not in ['Y', 'N']: choice = input("Would you like to play again? Y or N: ") if choice not in ['Y', 'N']: print("Please choose either Y or N. ") if choice == "Y": return True else: return False
Step 10: play_game()
Finally we get to the final function, which is putting all of the previous functions together and lets us play the game. Below I will go into how the game should work.
First Player 1 chooses a marker
Then the game tells which player goes first
Players take turns picking a position and each time the program updates the board
This goes on until either there's a Tie or a Win, depending on which a statement will be printed
At the end it will ask if the player wants to replay the game, if they say yes the game restarts, if not then the game is over
Now I will be explaining my code.
The first part is creating a variable called game_on and setting it to True. This lets the game continue playing. Next I create a game_board that is empty. And then I created two variables p1 and p2 and set those to the values player_input() returns. This will let the marker the player chooses to be assigned to a marker. Then I created a variable called turn to set to the returned value from choose_first(), which randomly choose which player goes first. And finally it will print which player will go first.
The next part is of my own creation. It displays a board that shows the different positions a player could choose. Mostly because while playing the game I would forget the positions on the board.
Then I created a while loop that runs while the game is going, hence the game_on variable. Then it goes to whichever player's turn it is. Depending on if the turn is Player 1 or Player 2. For each player it displays the game_board and then creates a variable called position and sets it to the position the player chooses , using the player_choice function. Then it places the players marker on the board at the position they choose. During this while look it continually checks for three conditions:
if a player has won, if they have it will display the game board and print a message and ask if the player wants to replay. If they player does want to replay it continues the game and resets the game_board and goes back to the top of the while loop.
If it is a tie then it displays the board and gives a message, it also breaks out of the game.
Lastly if it's neither a win or a tie, then it moves to the next player's turn (which has the exact same code with a few changes).
print('Welcome to Tic Tac Toe!') def play_game(): game_on = True game_board = [' ',' ',' ',' ',' ',' ',' ',' ',' ',' '] p1, p2 = player_input() turn = choose_first() print(turn + ' will go first') display_board(index_board) print('\nAbove displays the numbered position you may choose') while game_on == True: #Player 1 Turn if turn == 'Player 1': # Show board display_board(game_board) # Choose a position position = player_choice(game_board) # Place marker on the position place_marker(game_board, p1, position) # If player 1 wins if win_check(game_board, p1): display_board(game_board) print("Congrats Player 1! You've won the game!") # Ask to replay replay_game = replay() if replay_game == True: game_on = True game_board = [' ',' ',' ',' ',' ',' ',' ',' ',' ',' '] else: game_on = False print('The game is over') else: # If it's a tie if full_board_check(game_board): display_board(game_board) print('The game is a draw') break # If it's not a win or a tie else: turn = 'Player 2' # Player 2 turn. else: # Show board display_board(game_board) # Choose a position position = player_choice(game_board) # Place marker on the position place_marker(game_board, p2, position) # If player 2 wins if win_check(game_board, p2): display_board(game_board) print("Congrats Player 2! You've won the game!") # Ask to replay the game replay_game = replay() if replay_game == True: game_on = True game_board = [' ',' ',' ',' ',' ',' ',' ',' ',' ',' '] else: game_on = False print('The game is over') else: # If it's a tie if full_board_check(game_board): display_board(game_board) print('The game is a draw') break # If it's not a tie or win else: turn = 'Player 1' # Run the function play_game()
In total, not including the time spent writing this article, I spent over 5 hours working on this program. I did not struggle with steps 1-9 as much as I thought I would. Before moving onto step 10, I checked with the solutions to make sure my previous functions were all correct. Generally my code was correct and returned the necessary values but it was more complicated than it needed to be. For instance for step 4, the function win_check() the solution that was provide was:
def win_check(board,mark): return ((board == mark and board == mark and board == mark) or # across the top (board == mark and board == mark and board == mark) or # across the middle (board == mark and board == mark and board == mark) or # across the bottom (board == mark and board == mark and board == mark) or # down the middle (board == mark and board == mark and board == mark) or # down the middle (board == mark and board == mark and board == mark) or # down the right side (board == mark and board == mark and board == mark) or # diagonal (board == mark and board == mark and board == mark)) # diagonal
Which is much shorter than mine and uses the boolean operator: or. Also for the player_input(), I didn't initially return the markers. I didn't realize I would need these values later. Also for the player_marker function my initial code looked like:
def place_marker(board, marker, position): if position == 1: board = marker elif position == 2: board = marker elif position == 3: board = marker elif position == 4: board = marker elif position == 5: board = marker elif position == 6: board = marker elif position == 7: board = marker elif position == 8: board = marker elif position == 9: board = marker else: pass
While correct was unnecessary. Instead the code ' board[position] = marker'. Works the same, since it puts the position on the board and set that to the marker. This is a much simpler and cleaner version of my code, which was clunky. There were other smaller instances of this but I hope with time my code will get clearer and concise.
My biggest challenge was putting all of the functions together to run the game. I understood that a while loop should be used, because the game needed to run until either a player won, there was a tie, or if a player wanted to replay the game. I also had trouble understanding how to save the returned values from functions for later. I thought I had to create a variable in the function itself but I ran into the problem of scope (see the LEGB Rule). Until I realized the easiest solution was to create a value that was equal to whatever a function returned in the play_game() function rather than the initial function. After I realized that the rest of the code was easier to do.
One of the best tips I got for understanding your code. Is to insert print statements to see if it was returning the correct values and executing what I wanted to.
I struggled with the game, specifically with putting it all together. Which was the point of the project, to challenge myself. I had trouble with returning the correct values and compiling my functions together to create a simple game. I'm hoping with time my code will get cleaner and more concise. I may come back to this project in the future and try re-coding it to see how much my programming has improved. I'm excited for the next project in the course and will share it with you.
If you would like to know when I upload my next Python project sign up for my newsletter, where every Friday I send out: 1 new blog post; 2 things I learned that week; and 3 resources.