3
\$\begingroup\$

So iam making a game using OOP in python however is this the best practise when defining your objects? are you meant to define them all at the end of the script like I've done, or are you meant to define them after each class you make? Thanks in advance and any other improvements would be much appreciated!

import random # Import random module to use for computer random move import os # Import system from os module to be able to clear the console # The following function print the current board to the command window. def print_board(board): print("\n") print(" |", board[0], "|", board[1], "|", board[2], " | 1 | 2 | 3") print(" |", "--+---+---", "|") print(" |", board[3], "|", board[4], "|", board[5], " | 4 | 5 | 6") print(" |", "--+---+---", "|") print(" |", board[6], "|", board[7], "|", board[8], " | 7 | 8 | 9") # This function takes the existing board, position input from player, # marker type (either x or o) and returns the updated board based on the arguments. def update_board(board, position, marker): board[position-1] = marker print_board(board) return board # Add any other functions below for your program # Function to allow the user to select the game mode they wish to play either, # Player vs Player, Player vs Computer or their Special equivalent. def game_selection(): while True: print("\tWELCOME TO TIC - TAC - TOE!") game_mode = input("""\nSELECT YOUR GAME MODE FROM BELOW! - Player Vs Player - Type 'PvP' - Player vs Computer - Type 'PvC' Type your desired game mode here: """).strip() if game_mode in ('PvP', 'pvp', 'PVP'): pVp.instructions() return pVp.play_game() # Calls method for Player vs Player elif game_mode in ('PvC', 'pvc', 'PVP'): pVc.instructions() return pVc.play_game() # Calls method for Player vs Computer else: print("\n-- INVALID GAME MODE Type 'PvP' or 'PvC' --") # This function is called whenever the initial game ends,and give the user the # option to play another game or to exit out the console. def play_again(): while True: play_another = input("\nThank you for playing! \ Would you like to play again (Type 'yes') or enter any key to exit!: ").lower() if not play_another == "yes": exit(0) # Execute a successful exit return None # Define a class for Player class Player: # Player class has instance variables for their counter,and if they have won (True/False) def __init__(self, counter): self.has_won = False self.counter = counter # Method to allow player objects to move and select a unoccupied position on the board, # from 1-9,along with error checking/validation of there input e.g.input is a integer. def move(self): while True: try: player_input = int(input(f"\nWhere do you want to place your {self.counter}?(1-9): ")) except ValueError: print("\nInvalid Input not an number,Try Again.") continue if player_input in range(1, 10) and board[player_input - 1] == " ": update_board(board, player_input, self.counter) return self.game_state(self.counter) else: print("\nPlease enter a position between 1-9 ,which is also empty.") # Method which allows the board to be reset back to empty, and to reset the has_won # value back to 'False'. So the user can choose to play a fresh match. def reset(self): global board board = [" " for move in range(9)] self.has_won = False # Method allows a player to move an existing counter to any location on the board def move_exisiting_piece(self): if self.counter not in board: # Check if the player has a counter on the board return None while True: # Ask user if they want to move an exisiting piece or not answer = input("\nDo you want to move an existing piece (yes/no): ").lower() if answer == "yes": break if answer == "no": return None while True: # Loop which asks user to choose the counter they want to move try: location = int(input("\nWhat's the location of the your counter" " you want to move (1-9): ")) except ValueError: print("\nYou did not enter a valid location on the board!") if location in range(1, 10) and board[location - 1] == self.counter: board[location - 1] = " " while True: # Loop to ask user where to move the exisiting counter try: move_location = int(input("\nWhere on the board is the new location" " you want to place the piece? (1-9): ")) except ValueError: print("\nNot a valid location,Try Again.") if move_location in range(1, 10) and board[move_location - 1] == " ": update_board(board, move_location, self.counter) return self.game_state(self.counter) else: print("\nThis location is not avaliable to place your piece.") else: print(f"\nYou do not have a {self.counter} in this location! Try Again.") # This method is used to check the game state after every indivdual move to check if # the game is a draw, a win or to continue playing. def game_state(self, counter): horizontal_win = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] vertical_win = [[1, 4, 7], [2, 5, 8], [3, 6, 9]] diagnol_win = [[1, 5, 9], [3, 5, 7]] for x, y, z in horizontal_win: # For loop to check for horizontal win if board[x-1] == board[y-1] == board[z-1] != " ": print("\nPlayer with counter " + board[x-1] + " has won!") self.has_won = True return 1 for x, y, z in vertical_win: # For loop to check for vertical win if board[x-1] == board[y-1] == board[z-1] != " ": print("\nPlayer with counter " + board[x-1] + " has won!") self.has_won = True return 1 for x, y, z in diagnol_win: # For loop to check for diagnol win if board[x-1] == board[y-1] == board[z-1] != " ": print("\nPlayer with counter " + board[x-1] + " has won!") self.has_won = True return 1 # Use a for loop to check if board is full and there is no winner (draw) if len([c for c in board if c in ('X', 'O')]) == 9: print("\nTHIS GAME IS A DRAW! NOBODY WINS!") self.has_won = True return 1 return 0 # Return 0 if no win/draw is found # Class for the computer, which inherited values and methods from the Player class class Computer(Player): def __init__(self, counter): Player.__init__(self, counter) # Method to allow computer to select the optimal position to place a counter def cpu_move(self): # Create a 2D array of all possible combinations to play winning_combos = [[0, 1, 2], [0, 2, 1], [2, 1, 0], [3, 4, 5], [3, 5, 4], [4, 5, 3], [6, 7, 8], [6, 8, 7], [7, 8, 6], [0, 3, 6], [0, 6, 3], [3, 6, 0], [1, 4, 7], [1, 7, 4], [4, 7, 1], [2, 5, 8], [2, 8, 5], [5, 8, 2], [0, 4, 8], [0, 8, 4], [8, 4, 0], [2, 4, 6], [2, 6, 4], [6, 4, 2]] # For each value in each sub array of win_combos we check for a winning position # for the computer.If there is none then we check if the human player has a # winning turn next, if so we place a 'O' in that position for x, y, z in winning_combos: if board[4] == " ": print("\nThe COMPUTER has placed a 'O' in position 5") update_board(board, 5, self.counter) return self.game_state(self.counter) elif board[x] == "O" and board[y] == "O" and board[z] == " ": print("\nThe COMPUTER has placed a 'O' in position " + str(z+1)) update_board(board, z+1, self.counter) return self.game_state(self.counter) elif board[x] == "X" and board[y] == "X" and board[z] == " ": print("\nThe COMPUTER has placed a 'O' in position " + str(z+1)) update_board(board, z+1, self.counter) return self.game_state(self.counter) # If non of the operations above work then get the computer to select a # random position on the board to place a 'O'. while True: move = random.randint(1, 9) if board[move-1] == " ": print("\nThe COMPUTER has placed a 'O' in position " + str(move)) update_board(board, move, self.counter) return self.game_state(self.counter) # Create Game class that takes argument for the who each player is and what game mode to play class Game: def __init__(self,player1 ,player2, mode): self.player1 = player1 self.player2 = player2 self.mode = mode def instructions(self): os.system("cls") print(f"\tWELCOME TO {self.player1} VS {self.player2}!") print(f"""\nRULES: \n1. {self.player1} you're COUNTER 'X' and {self.player2} is COUNTER 'O'. 2. The first player to get 3 of their counters in a row, (up, down, across, or diagonally) is the winner. 3. When all 9 squares are full, the game is over. 4. If no player has 3 marks in a row, the game ends in a tie. 5. In this mode you also have the option to move an existing counter of your own!""") # Method which plays the oppropriate game mode depending on def play_game(self): # Print out the initial board to the console print_board(board) if self.mode == "PVP": while True: print("\n -PLAYER ONE'S TURN (X)-") if player1.move_exisiting_piece() is None: player1.move() if player1.has_won: # If player 1 wins, reset the board and has_won values player1.reset() player2.reset() return None print("\n -PLAYER TWO'S TURN (O)-") if player2.move_exisiting_piece() is None: player2.move() if player2.has_won: # If player 2 wins, reset the board and has_won values player1.reset() player2.reset() return None elif self.mode == "PVC": while True: print("\n -PLAYER ONE'S TURN (X)-") if player1.move_exisiting_piece() is None: player1.move() if player1.has_won: # If player 1 wins, reset the board and has_won values player1.reset() computer.reset() return None computer.cpu_move() if computer.has_won: # If computer wins, reset the board and has_won values player1.reset() computer.reset() return None # Program main starts from here # Global variable for the board board = [" " for move in range(9)] # Define the require objects player1 = Player("X") # Player 1 object player2 = Player("O") # Player 2 object computer = Computer("O") # Computer player object pVp = Game("PLAYER 1", "PLAYER 2", "PVP") # Player vs Player game mode object pVc = Game("PLAYER 1", "COMPUTER", "PVC") # Player vs Computer game mode object # If this is the main file execute the Tic Tac Toe Game if __name__ == "__main__": while True: # Run a loop to let user select game mode / play again game_selection() play_again() ```
\$\endgroup\$
2
  • \$\begingroup\$@M_ so here was my answer, if any question let me know, if it helped please remember to Accted the answer and upvote\$\endgroup\$CommentedJan 26, 2021 at 14:01
  • \$\begingroup\$I added a final review, is actually pretty good overall. Remember to accept the answer so it can be removed from the unanswered queue.\$\endgroup\$CommentedJan 26, 2021 at 14:34

1 Answer 1

1
\$\begingroup\$
  1. Everything in Python is an Object ---> Is everything an object in Python like Ruby?

So what you refer seems to be the Variable declaration ---> Python Variable Declaration.

"There is no such thing as "variable declaration" or "variable initialization" in Python."

However is normally called define a variable or "assignment".

  1. Where you define the variable depends of the context. There are some best prectices for instance:

A. Define the Variables Close to the function call.

B. Define Constant and Global Variable all in Upper Case.

C. The variable are written in a snake_case style --> Snake case See also --> What is the naming convention in Python for variable and function names? .


Consideration of your code

  1. In your case, define the variables as you did at the bottom is actually ok, And I tell you more. Because you used `if name' == 'main' shield, is very common to do this:

    # If this is the main file execute the Tic Tac Toe Game if __name__ == "__main__": board = [" " for move in range(9)] player1 = Player("X") player2 = Player("O") computer = Computer("O") pVp = Game("PLAYER 1", "PLAYER 2", "PVP") pVc = Game("PLAYER 1", "COMPUTER", "PVC") while True: # Run a loop to let user select game mode / play again game_selection() play_again() 
  2. To many Comments. The comments should be added only when is needed to make further clarification, for instance player1 = Player("X") # Player 1 object is very obvious what's is going on, hence a comment is not really needed here.Is important also for maintainability, imagine if you need to change the code, there is too much 'comment noise' around.

  3. Use DocStrings for function instead of comment.

Not only looks better in most of the IDE or Editor (because of the highlighting) but it looks more professional. But probably the most important aspect is that if How you officially document your code in Python. IDE and other tool use it, (like when you run the help function).

Final review

import random import os def print_board(board): """The following function print the current board to the command window.""" print("\n") print(" |", board[0], "|", board[1], "|", board[2], " | 1 | 2 | 3") print(" |", "--+---+---", "|") print(" |", board[3], "|", board[4], "|", board[5], " | 4 | 5 | 6") print(" |", "--+---+---", "|") print(" |", board[6], "|", board[7], "|", board[8], " | 7 | 8 | 9") def update_board(board, position, marker): """This function takes the existing board, position input from player, marker type (either x or o) and returns the updated board based on the arguments.""" board[position-1] = marker print_board(board) return board def game_selection(): print("\n--- WELCOME TO TIC - TAC - TOE! ---") while True: game_mode = input("""\nSELECT YOUR GAME MODE FROM BELOW! - Player Vs Player - Type 'PvP' - Player vs Computer - Type 'PvC' Type your desired game mode here: """).strip() if game_mode in ('PvP', 'pvp', 'PVP'): pVp.instructions() return pVp.play_game() elif game_mode in ('PvC', 'pvc', 'PVP'): pVc.instructions() return pVc.play_game() else: print("\n-- INVALID GAME MODE Type 'PvP' or 'PvC' --") def play_again(): """This function is called whenever the initial game ends,and give the user the option to play another game or to exit out the console.""" while True: play_another = input("\nThank you for playing! \ Would you like to play again (Type 'yes') or enter any key to exit!: ").lower() if not play_another == "yes": exit(0) # Execute a successful exit os.system("cls") return None class Player: """Player class has instance variables for their counter,and if they have won (True/False)""" def __init__(self, counter): self.has_won = False self.counter = counter def move(self): # NOTE: """"Method to allow player objects to move and select a unoccupied position on the board, from 1-9,along with error checking/validation of there input e.g.input is a integer.""" while True: try: player_input = int(input(f"\nWhere do you want to place your {self.counter}?(1-9): ")) except ValueError: print("\nInvalid Input not an number,Try Again.") continue if player_input in range(1, 10) and board[player_input - 1] == " ": update_board(board, player_input, self.counter) return self.game_state(self.counter) else: print("\nPlease enter a position between 1-9 ,which is also empty.") def reset(self): """Method which allows the board to be reset back to empty, and to reset the has_won value back to 'False'. So the user can choose to play a fresh match.""" global board board = [" " for move in range(9)] self.has_won = False def move_exisiting_piece(self): # NOTE: move an existing counter to any location on the board if self.counter not in board: # NOTE: Check if the player has a counter on the board return None while True: answer = input("\nDo you want to move an existing piece (yes/no): ").lower() if answer == "yes": break if answer == "no": return None while True: try: location = int(input("\nWhat's the location of the your counter" " you want to move (1-9): ")) except ValueError: print("\nYou did not enter a valid location on the board!") if location in range(1, 10) and board[location - 1] == self.counter: board[location - 1] = " " while True: # Loop to ask user where to move the exisiting counter try: move_location = int(input("\nWhere on the board is the new location" " you want to place the piece? (1-9): ")) except ValueError: print("\nNot a valid location,Try Again.") if move_location in range(1, 10) and board[move_location - 1] == " ": update_board(board, move_location, self.counter) return self.game_state(self.counter) else: print("\nThis location is not avaliable to place your piece.") else: print(f"\nYou do not have a {self.counter} in this location! Try Again.") def game_state(self, counter): """Used to check the game state after every indivdual move to check if the game is a draw, a win or to continue playing.""" horizontal_win = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] vertical_win = [[1, 4, 7], [2, 5, 8], [3, 6, 9]] diagnol_win = [[1, 5, 9], [3, 5, 7]] for x, y, z in horizontal_win: # NOTE: Check for horizontal win if board[x-1] == board[y-1] == board[z-1] != " ": print("\nPlayer with counter " + board[x-1] + " has won!") self.has_won = True return 1 for x, y, z in vertical_win: # NOTE: Check for vertical win if board[x-1] == board[y-1] == board[z-1] != " ": print("\nPlayer with counter " + board[x-1] + " has won!") self.has_won = True return 1 for x, y, z in diagnol_win: # NOTE: Check for diagnol win if board[x-1] == board[y-1] == board[z-1] != " ": print("\nPlayer with counter " + board[x-1] + " has won!") self.has_won = True return 1 if len([c for c in board if c in ('X', 'O')]) == 9: # FAQ: Check if board is full an there is no winner (draw) print("\nTHIS GAME IS A DRAW! NOBODY WINS!") self.has_won = True return 1 return 0 # Return 0 if no win/draw is found class Computer(Player): """Class for the computer, which inherited values and methods from the Player class""" def __init__(self, counter): Player.__init__(self, counter) def cpu_move(self): # NOTE: allow computer to select the optimal position to place a counter # Create a 2D array of all possible combinations to play winning_combos = [[0, 1, 2], [0, 2, 1], [2, 1, 0], [3, 4, 5], [3, 5, 4], [4, 5, 3], [6, 7, 8], [6, 8, 7], [7, 8, 6], [0, 3, 6], [0, 6, 3], [3, 6, 0], [1, 4, 7], [1, 7, 4], [4, 7, 1], [2, 5, 8], [2, 8, 5], [5, 8, 2], [0, 4, 8], [0, 8, 4], [8, 4, 0], [2, 4, 6], [2, 6, 4], [6, 4, 2]] # NOTE: For each value in each sub array of win_combos we check for a winning position # for the computer.If there is none then we check if the human player has a # winning turn next, if so we place a 'O' in that position for x, y, z in winning_combos: if board[4] == " ": print("\nThe COMPUTER has placed a 'O' in position 5") update_board(board, 5, self.counter) return self.game_state(self.counter) elif board[x] == "O" and board[y] == "O" and board[z] == " ": print("\nThe COMPUTER has placed a 'O' in position " + str(z+1)) update_board(board, z+1, self.counter) return self.game_state(self.counter) elif board[x] == "X" and board[y] == "X" and board[z] == " ": print("\nThe COMPUTER has placed a 'O' in position " + str(z+1)) update_board(board, z+1, self.counter) return self.game_state(self.counter) # If non of the operations above work then get the computer to select a # random position on the board to place a 'O'. while True: move = random.randint(1, 9) if board[move-1] == " ": print("\nThe COMPUTER has placed a 'O' in position " + str(move)) update_board(board, move, self.counter) return self.game_state(self.counter) class Game(Player): def __init__(self,player1 ,player2, mode): self.player1 = player1 self.player2 = player2 self.mode = mode def instructions(self): """Method to output the instructions (depending on the gamemode) to the user""" os.system("cls") print(f"\tWELCOME TO {self.player1} VS {self.player2}!") print(f"""\nRULES: \n1. {self.player1} you're COUNTER 'X' and {self.player2} is COUNTER 'O'. 2. The first player to get 3 of their counters in a row, (up, down, across, or diagonally) is the winner. 3. When all 9 squares are full, the game is over. 4. If no player has 3 marks in a row, the game ends in a tie. 5. In this mode you also have the option to move an existing counter of your own!""") # def play_game(self): """Method which plays the oppropriate game mode depending on the selected game mode""" print_board(board) while True: print("\n -PLAYER ONE'S TURN (X)-") if player1.move_exisiting_piece() is None: player1.move() if player1.has_won: # If player 1 wins, reset the board and has_won values player1.reset() player2.reset() return None if self.mode == "PVP": # If the mode slected is PvP then execute player 2 move print("\n -PLAYER TWO'S TURN (O)-") if player2.move_exisiting_piece() is None: player2.move() if player2.has_won: # If player 2 wins, reset the board and has_won values player1.reset() player2.reset() return None elif self.mode == "PVC": # If the mode slected is PvP then execute computer move computer.cpu_move() if computer.has_won: # If computer wins, reset the board and has_won values player1.reset() computer.reset() return None if __name__ == "__main__": BOARD = [" " for move in range(9)] player1 = Player("X") player2 = Player("O") computer = Computer("O") pVp = Game("PLAYER 1", "PLAYER 2", "PVP") pVc = Game("PLAYER 1", "COMPUTER", "PVC") while True: game_selection() play_again() 
\$\endgroup\$
3
  • \$\begingroup\$So i just need to use doc strings in my classes, and also have 2 blank spaces between each class?\$\endgroup\$
    – Mj _
    CommentedJan 26, 2021 at 15:49
  • \$\begingroup\$Yes in general the code is pretty good, can be improved? Probably a bit but honestly if I try to improve the whole logic, it may instead be so clustered that is less readable (which is worst). Regarding the 2 spaces, this is mandatory however it is reccomended in the PEP 8 which is the writing convention for Python.\$\endgroup\$CommentedJan 26, 2021 at 15:56
  • \$\begingroup\$To be honest, I probably added 2 spaces unconsciously, is it because I study PEP 8? Nope, I never read it!. What I suggest you is to use Pycharm Comunity edition (is free ;) ) Why? Well Pycharm gives you some little error underline your code in case you don't follow the PEP 8 you can turn it off. but this is how I learn how to write pretty Good Python code, it is so annoyinh that you clean it up so now I do it Automaticallly. I suggest you to give it a try\$\endgroup\$CommentedJan 26, 2021 at 16:09

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.