2
\$\begingroup\$

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))
\$\endgroup\$
1
\$\begingroup\$

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

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.