Simulate Tic-Tac-Toe game in Python Simulate a tic-tac-toe game

Requirements 


A player is picked at random as a first player. 
The two players plays tic tac toe game. 
The game ends when the board is full or either one of the player wins. 
Return the board status and result after end of the game. 



  Player Class


class Player:
    def __init__(self, name, symbol):
        self.name = name
        self.symbol = symbol

    def pick_available_grid(self, available_position):
        random_index = random.choice(available_position.keys())
        return random_index



  Board Class


class Board:
    def __init__(self):
        self.grid = [None]*9
        self.available_position = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0}

    def update_board(self, index, symbol):
        self.grid[index-1] = symbol
        del self.available_position[index]

    def check_if_board_full(self):
        len(self.available_position) == 0

    def check_for_win_condition(self,symbol):
        if (self.grid[0]==symbol and self.grid[1]==symbol and self.grid[2]==symbol):
            return True
        if (self.grid[3]==symbol and self.grid[4]==symbol and self.grid[5]==symbol):
            return True
        if (self.grid[6]==symbol and self.grid[7]==symbol and self.grid[8]==symbol):
            return True

        #vertical grid check
        if (self.grid[0]==symbol and self.grid[3]==symbol and self.grid[6]==symbol):
            return True
        if (self.grid[1]==symbol and self.grid[4]==symbol and self.grid[7]==symbol):
            return True
        if (self.grid[2]==symbol and self.grid[5]==symbol and self.grid[8]==symbol):
            return True

        #diagonal grid check
        if (self.grid[0]==symbol and self.grid[4]==symbol and self.grid[8]==symbol):
            return True
        if (self.grid[2]==symbol and self.grid[4]==symbol and self.grid[6]==symbol):
            return True



  Game Class


class Game:
    global SYMBOL
    SYMBOL = ['X', 'O']

    def __init__(self):
        self.board = Board()
        self.players = []
        self.result = None

    def set_players(self):
        player1 = Player('akanksha', SYMBOL[0])
        player2 = Player('akanksha', SYMBOL[1])
        self.players.append(player1)
        self.players.append(player2)

    def set_current_player(self, current_player=None):
        current_player = self.players[1] if current_player == self.players[0] else self.players[0]
        return current_player

    def set_result(self):
        if self.board.check_for_win_condition(self.players[0].symbol):
            self.result = self.players[0]
        elif self.board.check_for_win_condition(self.players[1].symbol):
            self.result = self.players[1]
        elif self.board.check_if_board_full():
            self.result = 'Draw'
        return self.result


    def play_game(self):
        current_player = self.set_current_player()
        while(not self.set_result()):
            available_position = self.board.available_position
            grid_position = current_player.pick_available_grid(available_position)
            self.board.update_board(grid_position, current_player.symbol)
            current_player = self.set_current_player(current_player)

    def end_game(self):
        print(self.board.grid)
        if self.result == 'Draw':
            print('This game is a draw')
        else:
            print('player', self.result.name, "with symbol", self.result.symbol, "wins")

    def start_game(self):
        self.set_players()
        self.play_game()
        self.set_result()
        self.end_game()



  Main Program


class MainProgram:
    def main():
        print("Welcome to TicTacToe Game")
        game = Game()
        game.start_game()

    main()


I am new to oops, how can I improve this?

Questions:


Should I create a separate class for symbol?
Should check_for_win_condition be included in Game instead of Board class?
Should self.available_position be used or it should be method which computes available cells from grid
Honestly, for the symbol, I'd just go with using uppercase characters (X and O) since the pieces don't really have any special functionality at this stage, and it's good to keep some simplicity with earlier projects.

I'd put the win condition checking into the game class. If you want to reuse the board class in the future, it's a good idea for you to avoid having any implementation-specific code (i.e. functionality that's specific to Tic-Tac-Toe) in the class - when you're dealing with Object Orientied Programming, you have to consider SOLID principles.

In terms of self.available_positions, I'd forego it entirely. You already have a structure which keeps track of the board, so adding a second structure to keep track of available positions is largely redundant - because one is just the inverse of the other (i.e. self.available_positions are the null elements in self.grid), and duplication is wasting memory and can lead to error (it's also good practice to have a single source of truth).

In terms of code, it's a good start, but I have a few suggested modifications for the Board class:

class Board:
    def __init__(self):
        self.grid = {1: '', 2: '', 3: '', 4: '', 5: '', 6: '', 7: '', 8: '', 9: }
        # Board starts with 9 available positions, and we'll decrement it by one.
        # I've changed to this from the self.available_position dictionary because you were
        # essentially maintaining two structures to maintain the progress of the game, 
        # and having a single integer that we decrement is easier.
        self.remaining_turns = 9

    def update_board(self, index, symbol):
        self.grid[index-1] = symbol
        # Decrease the number of available turns after placing a piece.
        self.remaining_turns -= 1
        # Probably worth checking for 0 remaining turns after performing this decrement.    

    def check_for_win_condition(self,symbol):
        # This if statement will be entered if any of the functions return true

        if check_diagonal(symbol) or check_horizontal(symbol) or check_vertical(symbol):
            return True

    def check_horizontal(self, symbol):
        # 0,3,6 refer to the starting elements in your vertical grid check, the additions
        # refer to the offsets to get to the other elements in the line
        for element in [0, 3, 6]:
            if self.grid[element]==self.grid[element+1]==self.grid[element+2]==symbol:
                return True

    def check_vertical(self, symbol):
        # 0,1,2 refer to the starting elements in your vertical grid check, the additions
        # refer to the offsets to get to the other elements in the line
        for element in [0, 1, 2]:
            if self.grid[element]==self.grid[element+3]==self.grid[element+6]==symbol:
                return True

    def check_diagonal(self, symbol):
        # You can chain equality checks
        if (self.grid[0]==self.grid[4]==self.grid[8]==symbol) or (self.grid[2]==self.grid[4]==self.grid[6]==symbol):
            return True


It's just something I typed up quickly, and I haven't had the opportunity to test it yet. My biggest issue was with the check_for_win_condition function - you were repeating a lot of the same checks (just altering the element reference for the grid).

In terms of language choice; Python is great - it's versatile, and it's intuitive - however, go with Python 3. Python 2 is end of life in 2020 - which means it will no longer have support from the Python developers/maintainers, but Python 3 is in active development, and it has a number of improvements and new features.