9
\$\begingroup\$

I am here to ask for some more experienced programmers to critique my first project. I wanted to see where I lack and what I can improve on. Thank you for taking time out of your day to help me better my skills as a programmer! I hope I did a good job for being a beginner but I know there is much more to learn!

game_board = ['1','2','3','4','5','6','7','8','9']
original_board = ['1','2','3','4','5','6','7','8','9']
player1 = ''
player2 = ''
isFull = False
game_on = True
winner = False
def display_board(board):
 print(board[0]+'|'+board[1]+'|'+board[2])
 print(board[3]+'|'+board[4]+'|'+board[5])
 print(board[6]+'|'+board[7]+'|'+board[8])
def player_input():
 global player1
 global player2
 while player1 != 'X' or player1 != 'O':
 player1 = input("Which do you want to be? (X or O): ")
 player1 = player1.upper()
 if player1 == 'X':
 print("Player 1 has chosen X.")
 player1 = 'X'
 player2 = 'O'
 break
 elif player1 == 'O':
 print("Player 1 has chosen O.")
 player1 = 'O'
 player2 = 'X'
 break
 return player1
def place_marker(board, player, position):
 board[position-1] = player
 
 return board[position-1]
def win_check(board):
 global player1
 global player2
 global game_on
 global winner
 if board[0] == 'O' and board[1] == 'O' and board[2] == 'O':
 
 if player1 == 'O':
 print("Player 1 (O) has won!")
 game_on = False
 winner = True
 else:
 print("Player 2 (O) has won!")
 game_on = False
 winner = True
 elif board[0] == 'O' and board[4] == 'O' and board[8] == 'O':
 
 if player1 == 'O':
 print("Player 1 (O) has won!")
 game_on = False
 winner = True
 else:
 print("Player 2 (O) has won!")
 game_on = False
 winner = True
 elif board[2] == 'O' and board[4] == 'O' and board[6] == 'O':
 if player1 == 'O':
 print("Player 1 (O) has won!")
 game_on = False
 winner = True
 else:
 print("Player 2 (O) has won!")
 game_on = False
 winner = True
 elif board[0] == 'O' and board[3] == 'O' and board[6] == 'O':
 if player1 == 'O':
 print("Player 1 (O) has won!")
 game_on = False
 winner = True
 else:
 print("Player 2 (O) has won!")
 game_on = False
 winner = True
 elif board[1] == 'O' and board[4] == 'O' and board[7] == 'O':
 if player1 == 'O':
 print("Player 1 (O) has won!")
 game_on = False
 winner = True
 else:
 print("Player 2 (O) has won!")
 game_on = False
 winner = True
 elif board[2] == 'O' and board[5] == 'O' and board[8] == 'O':
 if player1 == 'O':
 print("Player 1 (O) has won!")
 game_on = False
 winner = True
 else:
 print("Player 2 (O) has won!")
 game_on = False
 winner = True
 elif board[3] == 'O' and board[4] == 'O' and board[5] == 'O':
 if player1 == 'O':
 print("Player 1 (O) has won!")
 game_on = False
 winner = True
 else:
 print("Player 2 (O) has won!")
 game_on = False
 winner = True
 elif board[6] == 'O' and board[7] == 'O' and board[8] == 'O':
 if player1 == 'O':
 print("Player 1 (O) has won!")
 game_on = False
 winner = True
 else:
 print("Player 2 (O) has won!")
 game_on = False
 winner = True
 
 if board[0] == 'X' and board[1] == 'X' and board[2] == 'X':
 
 if player1 == 'X':
 print("Player 1 (X) has won!")
 game_on = False
 winner = True
 else:
 print("Player 2 (X) has won!")
 game_on = False
 winner = True
 elif board[0] == 'X' and board[4] == 'X' and board[8] == 'X':
 
 if player1 == 'X':
 print("Player 1 (X) has won!")
 game_on = False
 winner = True
 else:
 print("Player 2 (X) has won!")
 game_on = False
 winner = True
 elif board[2] == 'X' and board[4] == 'X' and board[6] == 'X':
 if player1 == 'X':
 print("Player 1 (X) has won!")
 game_on = False
 winner = True
 else:
 print("Player 2 (X) has won!")
 game_on = False
 winner = True
 elif board[0] == 'X' and board[3] == 'X' and board[6] == 'X':
 if player1 == 'X':
 print("Player 1 (X) has won!")
 game_on = False
 winner = True
 else:
 print("Player 2 (X) has won!")
 game_on = False
 winner = True
 elif board[1] == 'X' and board[4] == 'X' and board[7] == 'X':
 if player1 == 'X':
 print("Player 1 (X) has won!")
 game_on = False
 winner = True
 else:
 print("Player 2 (X) has won!")
 game_on = False
 winner = True
 elif board[2] == 'X' and board[5] == 'X' and board[8] == 'X':
 if player1 == 'X':
 print("Player 1 (X) has won!")
 game_on = False
 winner = True
 else:
 print("Player 2 (X) has won!")
 game_on = False
 winner = True
 elif board[3] == 'X' and board[4] == 'X' and board[5] == 'X':
 if player1 == 'X':
 print("Player 1 (X) has won!")
 game_on = False
 winner = True
 else:
 print("Player 2 (X) has won!")
 game_on = False
 winner = True
 elif board[6] == 'X' and board[7] == 'X' and board[8] == 'X':
 if player1 == 'X':
 print("Player 1 (X) has won!")
 game_on = False
 winner = True
 else:
 print("Player 2 (X) has won!")
 game_on = False
 winner = True
def space_check(board, position):
 isTaken = False
 if board[position - 1] == 'X' or board[position - 1] == 'O':
 print("That position is already taken choose again.")
 isTaken = True
 else:
 pass
 return isTaken
 
def full_board_check(board):
 global isFull
 global original_board
 for num in range(9):
 if board[num] == original_board[num]:
 return isFull
 else:
 isFull = True
 return isFull
def player_choice(board):
 good = False
 while good == False:
 choice = int(input("Choose a spot: "))
 space_check(board, choice)
 if space_check(board, choice) == False:
 good = True
 return choice
 return choice
def replay():
 global game_board
 global player1
 global player2
 global isFull
 global game_on
 
 keep_playing = True
 while keep_playing == True:
 replay = input("Do you want to replay? (Y or N): ")
 replay = replay.upper()
 if replay == 'Y':
 game_board = ['1','2','3','4','5','6','7','8','9']
 player1 = ''
 player2 = ''
 game_on = True
 isFull = False
 elif replay == 'N':
 keep_playing = False
 print("Thanks for playing!")
print("Welcome to Tic Tac Toe!")
while isFull == False:
 display_board(game_board)
 player_input()
 while game_on:
 
 full_board_check(game_board)
 if isFull == True and winner == False:
 print("The game is a tie!")
 game_on = False
 break
 if game_on:
 print("Player 1's turn.")
 place_marker(game_board, player1, player_choice(game_board))
 display_board(game_board)
 win_check(game_board)
 full_board_check(game_board)
 if isFull == True and winner == False:
 print("The game is a tie!")
 game_on = False
 break
 if game_on:
 print("Player 2's turn.")
 place_marker(game_board, player2, player_choice(game_board))
 display_board(game_board)
 win_check(game_board)
 
 break
asked May 25, 2021 at 1:01
\$\endgroup\$

2 Answers 2

11
\$\begingroup\$

I'll point out some useful starting points for improving your code. I would additionally recommend looking at some other implementations, for some further inspiration regarding logic and code structure. There are a lot on this site alone, here's a starting point from recent memory: Tic-tac-toe in python with OOP.


Code structure

You rely on a few global variables (game_board, original_board, player1, player2, isFull, game_on, winner). This is generally not considered good practice for a variety of reasons and it can be avoided in most cases.


Naming

PEP 8: "Function names should be lowercase, with words separated by underscores as necessary to improve readability. Variable names follow the same convention as function names."


Unnecessary return

Most of your functions return a value, but since they change the global variables the return value is never needed / used (e.g. player_input, place_marker, full_board_check). In your current implementation you could simply delete those return statements as they're never used. In an improved version I recommend using the return values instead of writing to global variables.


display_board

Very rarely in Python (and programming in general) should you hardcode indices as it makes code less readable, maintainable and scalable. Consider this approach using list slicing:

def display_board(board):
 row_length = 3
 for i in range(0, len(board), row_length):
 print('|'.join(board[i:i+row_length]))

Now if you ever want to change the board size / row length or the column separator, it will be a lot easier.


player_input

Consider this improved function:

def player_input():
 global player1
 global player2
 options = {'X', 'O'}
 while True:
 player1 = input(f"Which do you want to be? ({' or '.join(options)}): ").upper()
 if player1 in options:
 options.remove(player1)
 player2 = options.pop()
 break
 print(f"Player 1 has chosen {player1}.")
 print(f"Player 2 is {player2}.")

A few things to notice here:

  • Your while loop had redundant logic: It checks if player1 is not in {'X', 'O'} each iteration, but once player1 is either of those values it immediately breaks. Meaning that the while-condition will never be relevant.
  • The assignment if player1 == 'X': ... player1 = 'X' is redundant.
  • We can pull out the print statement to the end of the function, as the text is the exact same, except for the value of player1. f-Strings provide a really convenient and redable way to include variables in a string.
  • If we decide to change player symbols, this function only requires changes in a single place. The options for player symbols should probably be moved to a module constant to make changing them even easier.

win_check

This is probably the function you could and should save yourself the most work on. Manually writing out all the possible winning configurations is error-prone and hard to maintain. I'd recommend looking at some other implementations (and the corresponding reviews) to get an idea of how to make this easier for yourself.

Everytime you find yourself copying or repeating code, you should stop to think if there's a pattern there. A naive implementation is often a good starting point to familiarize yourself with the problem. Once you have an understanding for the underlying logic you should start looking to improve your approach. For example: Even if you still manually check all the possible winning configurations, you should pull out the common part of the different conditions into a separate function. I'm talking about this part, which repeats 10+ times in this single function:

if player1 == 'O':
 print("Player 1 (O) has won!")
 game_on = False
 winner = True
else:
 print("Player 2 (O) has won!")
 game_on = False
 winner = True

This code snippet should also be simplified to something like

winner_number = 1 if player1 == 'O' else 2
print(f"Player {winner_number} (O) has won!")
game_on = False
winner = True

Another consideration for further improvements:

These two conditions

if board[0] == 'O' and board[1] == 'O' and board[2] == 'O'
if board[0] == 'X' and board[1] == 'X' and board[2] == 'X'

use the same logic. The only variable is the symbol that's checked. This should be an indication that a variable should be used instead of all the possible values. We only need to check if the spots contain the same symbol, not which one exactly it is:

if board[0] == board[1] == board[2]:
 winning_symbol = board[0]
 winning_number = 1 if player1 == winning_symbol else 2
 print(f"Player {winning_number} ({winning_symbol}) has won!")
 game_on = False
 winner = True

Again, the code inside the condition should be put into its own function to reduce code duplication.


These suggestions are merely starting points for improvements, there are a lot more simplifications that can be done. You're always welcome to come back to CodeReview with an improved version to get another round of feedback. =)

answered May 25, 2021 at 10:32
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Thank you so much for the review! I am currently going through your suggestions and will keep them in mind for the next project I work on. Thank you again for taking time out of your day to help a newbie like myself. \$\endgroup\$ Commented May 25, 2021 at 21:19
7
\$\begingroup\$

You need to add exception handling in player_choice. Currently, if I type in 'Y', the game crashes with error code:

Traceback (most recent call last):
 File "/Users/jake/Documents/TTRreview.py", line 299, in <module>
 place_marker(game_board, player2, player_choice(game_board))
 File "/Users/jake/Documents/TTRreview.py", line 246, in player_choice
 choice = int(input("Choose a spot: "))
ValueError: invalid literal for int() with base 10: 'y'

also, if I type an integer larger that 9 or smaller than one, the game behaves oddly, because python simply rolls over the array instead of giving an error. Change the code to read:

def player_choice(board):
 good = False
 while good == False:
 try:
 choice = int(input("Choose a spot: ")) 
 except:
 continue #takes care of the game crashing with bad input
 if choice < 1 or choice > 9:
 continue #takes care of the game behaving oddly because of how python handles arrays
 space_check(board, choice)
 if space_check(board, choice) == False:
 good = True
 return choice
 return choice

Lastly, make sure you make comments on your code :)

answered May 25, 2021 at 3:08
\$\endgroup\$
3
  • \$\begingroup\$ Thank you for finding a flaw in my code. Thank you for showing me how to fix it and adding comments explaining what does what. I will start making a habit to adding comments in my code. \$\endgroup\$ Commented May 25, 2021 at 21:20
  • 1
    \$\begingroup\$ Only add comments for "business logic" in the code, things that are unique to that program. Avoid obvious comments like "converts this string to uppercase" \$\endgroup\$ Commented May 25, 2021 at 21:57
  • 1
    \$\begingroup\$ Yes, but if you have to choose, err on the side of more comments rather than fewer. Anyone who works on from your code in the future will thank you \$\endgroup\$ Commented May 25, 2021 at 22:25

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.