# Name: Rushil Umaretiya
# Date: 1/18/21

import random

class RandomBot:
   def __init__(self):
      self.yellow = "O"
      self.red = "X"
      self.directions = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]]
      self.opposite_color = {self.red: self.yellow, self.yellow: self.red}
      self.x_max = None
      self.y_max = None

   def __str__(self):
      return "Random Bot"

   def best_strategy(self, board, color):
      # returns best move
      self.x_max = len(board)
      self.y_max = len(board[0])
      if color == "#ffff00":
         color = "O"
      else:
         color = "X"
      
      best_move = random.choice(self.find_moves(board, color))

      return best_move, 0

   def find_moves(self, board, color):
      moves_found = []
      for col in range(self.x_max):
         for row in reversed(range(self.y_max)):
               if board[col][row] == '.':
                  moves_found.append([col, row])
                  break
      return moves_found

class SmartBot:

   def __init__(self):
      self.yellow = "O"
      self.red = "X"
      self.directions = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]]
      self.opposite_color = {self.red: self.yellow, self.yellow: self.red}
      self.x_max = None
      self.y_max = None
      self.color = None

   def __str__(self):
      return "Smart Bot"

   def best_strategy(self, board, color):
    # returns best move
      self.x_max = len(board)
      self.y_max = len(board[0])

      if color == "#ffff00":
         color = "O"
      else:
         color = "X"
      self.color = color

      sd = 4
      v, best_move = self.alphabeta(board, color, sd, -9999999999, 9999999999)   # returns state

      return best_move, v
      
   def alphabeta(self, board, color, search_depth, alpha, beta, last_move = -1):
      terminal_test = self.terminal_test(board, color)
      if search_depth <= 0 or terminal_test:
         if terminal_test:
            if color == self.color:
               return 99999999, last_move
            else:
               return -99999999, last_move
         heuristic = self.evaluate(board, color)
         return heuristic, 0

      if search_depth % 2 == 0:
         v = -9999999999
         result = None
         for move in self.find_moves(board, color):
            max_val, max_state = self.alphabeta(self.make_move(board, color, move), self.opposite_color[color], search_depth - 1, alpha, beta, move)
            if max_val > v:
               v = max_val
               result = move
            if v > beta:
               return v, result
            alpha = max(alpha, v)
         return v, result
      else:
         v = 9999999999
         result = None
         for move in self.find_moves(board, color):
            min_val, min_state = self.alphabeta(self.make_move(board, color, move), self.opposite_color[color], search_depth - 1, alpha, beta, move)
            if min_val < v:
               v = min_val
               result = move
            if v < alpha:
               return v, result
            beta = min(beta, v)
         return v, result

   def minimax(self, board, color, search_depth):
    # returns best "value"
      return 1

   def negamax(self, board, color, search_depth):
    # returns best "value"
      return 1

   def make_move(self, board, color, move):
      my_board = [row[:] for row in board]

      my_board[move[0]][move[1]] = color
      
      return my_board

   def terminal_test(self, board, color):
      for col in range(len(board)):
         for row in range(len(board[col])):
            if board[col][row] == color:
               for direction in self.directions:
                  x_pos = col
                  y_pos = row
                  row_count = 0
                  while 0 <= x_pos < self.x_max and 0 <= y_pos < self.y_max:
                     if board[x_pos][y_pos] == color:
                        row_count += 1
                        if row_count == 4: return True
                     else:
                        break
                     x_pos += direction[0]
                     y_pos += direction[1]
      return False

   def evaluate(self, board, color):
      heuristic = 0
      
      # Center Column
      center_array = [i for i in board[self.x_max // 2]]
      center_heuristic = center_array.count(color)
      heuristic += center_heuristic * 3

      # Columns
      for col in range(self.x_max):
         col_array = [i for i in board[col]]
         for row in range(self.y_max - 3):
            array = col_array[row : row + 4]
            heuristic += self.evaluate_array(array, color)
      
      # Rows
      for row in range(self.y_max):
         row_array = [i[row] for i in board]
         for col in range(self.x_max - 3):
            array = row_array[col : col + 4]
            heuristic += self.evaluate_array(array, color)
      
      # Diagonals
      for col in range(self.x_max - 3):
         for row in range(self.y_max - 3):
            array = [board[col + i][row + 1] for i in range(4)]
            heuristic += self.evaluate_array(array, color)
      
      for col in range(self.x_max - 3):
         for row in range(self.y_max - 3):
            array = [board[col + 3 - i][row + i] for i in range(4)]
            heuristic += self.evaluate_array(array, color)
      
      return heuristic

   def evaluate_array(self, array, color):
      heuristic = 0
      opposite_color = self.opposite_color[color]

      if array.count(color) == 4:
         heuristic += 999
      elif array.count(color) == 3 and array.count('.') == 1:
         heuristic += 5
      elif array.count(color) == 2 and array.count('.') == 2:
         heuristic += 2
      
      if array.count(opposite_color) == 3 and array.count('.') == 1:
         heuristic -= 100
      
      return heuristic

   def find_moves(self, board, color):
      moves_found = []
      for col in range(self.x_max):
         for row in reversed(range(self.y_max)):
               if board[col][row] == '.':
                  moves_found.append([col, row])
                  break
      return moves_found