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