I made a tic-tac-toe game that is a player vs. player version (no AI). I have tried not to utilize objects because I haven't really gotten there yet. A previous version (see here) utilized global variables, but now I've gotten around that!
The code:
#Objective: Create a tic-tac-toe game within the command-line
#Version: Player versus Player
board = [[0 for _ in range(3)] for _ in range(3)]
player1 = input("What's your name, player1? ")
player2 = input("What's your name, player2? ")
player1_score = 0
player2_score = 0
pieces = ("x", "o")
piece1 = input("What piece would player1 like to play as? ")
while piece1 not in pieces:
piece1 = input("I'm sorry, could you please enter either 'x' or 'o'? ")
for piece in pieces:
if piece != piece1:
piece2 = piece
players_pieces = {piece1: player1, piece2: player2}
players_scores = {player1: player1_score, player2: player2_score}
#--------FIX: MAKE A CLASS OF USER_DEFINED EXCEPTION
#--------SO THAT IT WILL BE A LOOP THAT GOES UNTIL THE EXCEPTION NO LONGER EXISTS
try:
goal_score = int(input("What score would you like to play up to? "))
except ValueError:
print("I'm sorry, did you submit a number or not? Please retry:")
goal_score = int(input("How many rounds would you like to play? "))
current_score = 0
#------------------------------------DEFINE DO ROUND (SUB-FUNCTIONS)
def InitializeGame():
board = [["." for _ in range(3)] for _ in range(3)]
turn = 1
round = 1
return turn, round, board
#FIX LATER: EXCEPTION HANDLING FOR TYPES THAT ARE NOT INTEGERS
def IncrementValues(turn, round):
try:
if turn != None:
turn += 1
if round != None:
round += 1
return turn, round
except TypeError:
print("Incorrect type.")
def Reset():
board = InitializeGame()[2]
turn = InitializeGame()[0]
return board, turn
def DrawBoard(board):
print("\n\n\n")
print(" ---------------------------------")
for i in range(3):
#Draw each row
linetodraw = ""
for j in range(3):
linetodraw += " | " + board[i][j]
linetodraw += " |"
print(linetodraw)
print(" ---------------------------------")
def IsValid(move):
#move is a number
if not move.isdigit():
return False
#move length
if len(move) > 1:
return False
#move number
if int(move) < 1:
print("Start at index 1")
return False
#no probs, so return true
return True
def GetMove():
move = input("Enter move: ")
while not IsValid(move):
move = input("Invalid move! Enter a valid move: ")
return move
def PositionBoard():
positions = [[0 for _ in range(3)] for _ in range(3)]
#Indexes a board one-to-nine
i = 1
for j in range(3):
for k in range(3):
positions[j][k] = i
i += 1
return positions
def CheckIfMoveAlreadyMade(board1):
spaces_already_taken = []
board2 = PositionBoard()
for i in range(3):
for j in range(3):
if board1[i][j] != ".":
#Store board-space in list
spaces_already_taken.append(board2[i][j])
return spaces_already_taken
def Tie(argument_list):
if len(argument_list) < 9:
return False
return True
def UpdateMove(moves_made, move):
# Check if move already made
while int(move) in moves_made:
move = input("That space is already taken! Enter a new move: ")
continue
return move
def CheckThree(board):
# Check each row for three-in-a-row
for i in range(2, -1, -1):
for j in range(2, -1, -1):
if (board[i][j] != "."):
if (board[i][j] == board[i][j - 1]) and (board[i][j] == board[i][j - 2]):
return board[i][j]
# Check each column
for j in range(3):
for i in range(3):
if (board[i][j] != "."):
if (board[i][j] == board[i - 1][j]) and (board[i][j] == board[i - 2][j]):
return board[i][j]
# Check diagonally
if (board[1][1] != "."):
if (((board[1][1] == board[0][0]) and (board[0][0] == board[2][2])
or (board[1][1] == board[0][2]) and (board[0][2] == board[2][0]))):
return board[i][j]
def UpdateBoard(board, move, turn):
if turn % 2 != 0:
current_piece = piece1
else:
current_piece = piece2
board1 = PositionBoard()
for i in range(3):
for j in range(3):
if (move == str(board1[i][j])):
board[i][j] = current_piece
return board
#------------------------------------DEFINE MAIN FUNCTIONS
def ContinueGame(current_score, goal_score):
#Return false if game should end, true if game not over
if (current_score >= goal_score):
return False
else:
return True
def DoRound(board, turn):
#Draw the Board
DrawBoard(board)
if not Tie(CheckIfMoveAlreadyMade(board)):
#Get move from user
move = GetMove()
#Check if the move is valid
if int(move) not in CheckIfMoveAlreadyMade(board):
UpdateBoard(board, move, turn)
else:
move = UpdateMove(CheckIfMoveAlreadyMade(board), move)
UpdateBoard(board, move, turn)
else:
print("Round is a tie!")
#--------------------------------------MAIN PROCESSES
#Initializes game
InitializeGame()
#Sets default values for first round
turn, round = InitializeGame()[0:2]
board = InitializeGame()[2]
#FIX LATER: MAKE IT SO THAT THE WINNER OF EACH ROUND GOES FIRST, OR SHOULD IT BE LOSER???
while ContinueGame(current_score, goal_score):
#If the round is not a tie (i.e. spaces not filled up) and no one's won
if not Tie(CheckIfMoveAlreadyMade(board)) and not CheckThree(board):
#Print some information
print("The spaces are labeled 1-9, starting from top-left, going rightwards, horizontally")
print("Turn:", turn)
print("Round:", round)
if players_scores[player1] != players_scores[player2]:
new_scores = {player1_score: player1, player2_score: player2}
for key in new_scores.keys():
if key == current_score:
current_winner = new_scores[key]
print("Current winner:", current_winner)
print("%s's score:" % current_winner, current_score)
else:
current_loser = new_scores[key]
print("Current loser:", current_loser)
print("%s's score:" % current_loser, players_scores[current_loser])
else:
print("Currently, it's a tie")
print("\n")
if turn % 2 != 0:
print("It's your move, %s!" %player1)
else:
print("It's your move, %s!" %player2)
#Do a round of the game
DoRound(board, turn)
turn = IncrementValues(turn, None)[0]
#If the round is a tie
elif not CheckThree(board):
print("\n")
print("It must be a tie!")
board = Reset()[0]
turn = Reset()[1]
round = IncrementValues(None, round)[1]
print("\n")
print("Starting a new round...")
print("\n")
#If someone has won the round
else:
#Print statistics for winner of round
winner_of_round = ""
loser_of_round = ""
# Determine player
# Loop through each player
#NOTE: THIS ONLY WORKS FOR A TWO-PERSON GAME
for piece in players_pieces.keys():
if piece == CheckThree(board):
winner_of_round = players_pieces[piece]
else:
loser_of_round = players_pieces[piece]
# Increment score by one for player
players_scores[winner_of_round] += 1
# print statements
print("\n")
print("%s wins round %d out of %d" %(winner_of_round, round, goal_score))
#Print scores
print("%s score: %d" %(winner_of_round, players_scores[winner_of_round]))
print("%s score: %d" %(loser_of_round, players_scores[loser_of_round]))
#FIX LATER: I'M NOT SURE EXACTLY WHY I'M MAKING THIS A TUPLE, BUT OH WELL...
scores = tuple(players_scores.values())
#Reset a bunch of stuff
#Reset board and turn
board = Reset()[0]
turn = Reset()[1]
# Increment round, score
round = IncrementValues(None, round)[1]
current_score = max(scores)
#ONLY prints this statement if starting a new round
if ContinueGame(current_score, goal_score):
print("Starting new round...")
print("\n")
#Updates each score
player1_score = players_scores[player1]
player2_score = players_scores[player2]
if not ContinueGame(current_score, goal_score):
print("\n\n\n")
print("GAME OVER!")
print("Final scores:")
print("Winner: %s" %winner_of_round, "Score: %d" %players_scores[winner_of_round])
print("Loser: %s" %loser_of_round, "Score: %d" %players_scores[loser_of_round])
print("%s wins!" %winner_of_round)
laughter = "HAHA"
print("Sorry, %s, but YOU LOSE! %s to the quadrillionth" %(loser_of_round, laughter*10))
1 Answer 1
Okay...wow, that is a lot of code. I'm not going to get all the points, but let's start at the beginning. You're missing an entry point - the usual:
if __name__ == "__main__":
# begin the game
This is where you should be defining initial variables and values which will live inside the program, including the Player class and instantiating it. From the look of your code, you're not up to doing classes yet, so let's leave that for another day.
Next is naming of variables. You're using a mix of snake_case and camelCase. If you're going to do Python, stick with snake_case.
So if I grab all the code that is outside functions (anything starting with def
), and pop it all after an entry point, it runs.
The next point I have is there is a bit of inferred information with the players do not have when they start the game. For instance:
piece1 = input("What piece would player1 like to play as? ")
Doesn't explain what input is necessary. Maybe you can add "[enter x or o]" at the end of the prompt?
pieces = ("x", "o")
for piece in pieces:
if piece != piece1:
piece2 = piece
As a suggestion, you could shortcut the above code by making pieces a list instead of a tuple, so that when the first player selects cross or naught, pop that from the list and you have your answer for which piece that player two has to play as.
I've noticed that you not only define the board: board = [[0 for _ in range(3)] for _ in range(3)]
at the start of the code, but you also have the same code inside the function def InitializeGame():
. You might want to remove any duplication you have.
Another point I see is that you pass None
around quite a bit. You might want to change your functions and the calling code so you only pass specific data. For instance, you have:
def IncrementValues(turn, round):
and you use:
turn = IncrementValues(turn, None)[0]
round = IncrementValues(None, round)[1]
But I would change this to:
def IncrementValues(turn = None, round = None):
and make the callers:
turn = IncrementValues(turn=turn)[0]
round = IncrementValues(round=round)[1]
Okay, I'm going to wrap this answer up as I've given you quite a bit to digest already. I'd like to make a point on the remaining construction of the game, specifically the while ContinueGame(current_score, goal_score):
loop and your construction of the board.
If I was making this game, I would use a simple string representation for the board and not list of lists. As you know the board is 3x3, this is a simple 9-character string like "-x-oox--xo"
. The only thing that cares about the formatting of this string is the print function.
Updating a string is easy, and checking if the slice is a "-"
(hence available to be changed) is a simple action too. This would remove a huge amount of complexity in your current code.
The ContinueGame(current_score, goal_score)
loop from first view is really unnecessary in both a construction sense and a real-life sense. I mean, you don't agree with someone that you'll play 8 and only 8 games, right? You can get up in the middle of a game and leave too, right?
That's what you should head towards. An input system which will allow players to exit at any time. An input system which can handle player names, player board selections, and players wanting to leave at any time.
So give all that go, and I look forward to seeing your next iteration posted here on Code Review.
Thanks for reading