python 3.x – Game in Python3 using OOP

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()
```