import os
import numpy as np

class Game:
    def __init__(self):
        """Initialize a new Tic-Tac-Toe game with an empty game board."""
        self.game_arr = np.array([
                            [" ", " ", " "],
                            [" ", " ", " "],
                            [" ", " ", " "]
                         ])
        
    def update(self, row, column, new_val):
        """
        Update the game board with the player's move at the specified position.

        Parameters:
            row (int): The row index of the move.
            column (int): The column index of the move.
            new_val (str): The value of the player's move ("X" or "O").

        Raises:
            ValueError: If the new_val is not a valid move ("X" or "O").
        """
        valid = ["X", "x", "O", "o"]
        if new_val in valid:
            self.game_arr[row][column] = new_val
            self.make_grid(self.game_arr)
        else:
            print("Invalid Value!!")

    
    def make_grid(self,arr):
        """
        Display the current Tic-Tac-Toe game board.

        Parameters:
            arr (numpy.array): The 3x3 numpy array representing the game board.
        """
        print(f"""
{arr[0][0]} | {arr[0][1]} | {arr[0][2]}
----------
{arr[1][0]} | {arr[1][1]} | {arr[1][2]}
----------
{arr[2][0]} | {arr[2][1]} | {arr[2][2]}
""")

    def get_current_game(self):
        """
        Get the current state of the Tic-Tac-Toe game board.

        Returns:
            numpy.array: The 3x3 numpy array representing the game board.
        """
        return self.game_arr 
      
    def check_win(self, arr):
        """
        Check if there is a winning combination on the game board.

        Parameters:
            arr (numpy.array): The 3x3 numpy array representing the game board.

        Returns:
            list or False: If there is a winning combination, returns [True, "X"/"O"].
                           Otherwise, returns False.
        """
        res_r = self.check_row(arr)
        if not res_r:
            res_c = self.check_col(arr)
            if not res_c:
                res_d = self.check_diag(arr)
                if not res_d:
                    return False
                else:
                    return [True,res_d[1]]
            else:
                return [True,res_c[1]]
        else:
            return [True,res_r[1]]

    
    def check_row(self, arr):
        """
        Check if any row on the game board has a winning combination.

        Parameters:
            arr (numpy.array): The 3x3 numpy array representing the game board.

        Returns:
            list or False: If there is a winning row, returns [True, "X"/"O"].
                           Otherwise, returns False.
        """
        # This shouldn't be hard-coded, but
        # I am doing it to save time
        for i in arr:
            if i[0] == i[1] == i[2] != " ":
                return [True,i[0]]
        return False
        
    def check_col(self, arr):
        """
        Check if any column on the game board has a winning combination.

        Parameters:
            arr (numpy.array): The 3x3 numpy array representing the game board.

        Returns:
            list or False: If there is a winning column, returns [True, "X"/"O"].
                           Otherwise, returns False.
        """
        # This shouldn't be hard-coded, but
        # I am doing it to save time
        for i in range(3):  # Assuming it's a 3x3 grid
            if arr[0][i] == arr[1][i] == arr[2][i] != " ":
                return [True, arr[0][i]]
        return False
    
    def check_diag(self, arr):
        """
        Check if any diagonal on the game board has a winning combination.

        Parameters:
            arr (numpy.array): The 3x3 numpy array representing the game board.

        Returns:
            list or False: If there is a winning diagonal, returns [True, "X"/"O"].
                           Otherwise, returns False.
        """
        # This shouldn't be hard-coded, but
        # I am doing it to save time
        if arr[0][0] == arr[1][1] == arr[2][2] != " ":
            return [True,arr[0][0]]
        elif arr[0][2] == arr[1][1] == arr[2][0] != " ":
            return [True,arr[0][2]]
        else:
            return False
        
    def check_tie(self, arr) -> bool:
        """
        Check if the game has ended in a tie (draw).

        Parameters:
            arr (numpy.array): The 3x3 numpy array representing the game board.

        Returns:
            bool: True if the game is a tie, False otherwise.
        """
        if " " in arr:
            return False
        else:
            return True
        
    def game_details(self):
        print("Enter your choice according to this: ")
        print("1. Top Left")
        print("2. Top Center")
        print("3. Top Right")
        print("4. Center Left")
        print("5. Center Center")
        print("6. Center Right")
        print("7. Bottom Left")
        print("8. Bottom Center")
        print("9. Bottom Right")


        
g = Game()
win = False
map_dict = {
            1:[0,0], 2:[0,1], 3:[0,2],
            4:[1,0], 5:[1,1], 6:[1,2],
            7:[2,0], 8:[2,1], 9:[2,2],
        }
players = {"O":"Player1", "X":"Player2"}
choices = []

while not win:
    g.game_details()
    g.make_grid(g.get_current_game())
    # getting input from player 1
    player1 = int(input("Player 1 Enter the Choice(1-9): "))
    # Checking if it's already taken
    while player1 in choices:
        print("Make another choice! It's already Taken!")
        player1 = int(input("Player 2 Enter the Choice(1-9): "))
    # updating according to the player1's choice
    g.update(map_dict[player1][0], map_dict[player1][1], "O")
    # Checking for tie
    if g.check_tie(g.get_current_game()):
        os.system('cls' if os.name == 'nt' else 'clear')
        g.make_grid(g.get_current_game())
        print("It's a tie!")
        break
    # checking if player 1 won
    check = g.check_win(g.get_current_game())
    try:
        if check[0]==True:
            os.system('cls' if os.name == 'nt' else 'clear')
            g.make_grid(g.get_current_game())
            print(f"{players[check[1]]} wins")
            win=True
            break
    except Exception:
        pass

    g.make_grid(g.get_current_game())
    choices.append(player1)
    # getting input from player 2
    player2 = int(input("Player 2 Enter the Choice(1-9): "))
    # Checking if it's already taken
    while player2 in choices:
        print("Make another choice! It's already Taken!")
        player2 = int(input("Player 2 Enter the Choice(1-9): "))
    # updating according to the player2's choice
    g.update(map_dict[player2][0], map_dict[player2][1], "X")
    # Checking for tie
    if g.check_tie(g.get_current_game()):
        os.system('cls' if os.name == 'nt' else 'clear')
        g.make_grid(g.get_current_game())
        print("It's a tie!")
        break
    # checking if player 2 won
    check2 = g.check_win(g.get_current_game())
    try:
        if check2[0]==True:
            os.system('cls' if os.name == 'nt' else 'clear')
            g.make_grid(g.get_current_game())
            print(f"{players[check2[1]]} wins")
            win=True
            break
    except Exception:
        pass
    g.make_grid(g.get_current_game())
    choices.append(player2)