# Name: Rushil Umaretiya
# Date: 1/8/2021

import random

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

   def best_strategy(self, board, color):
      # returns best move
      self.x_max = len(board)
      self.y_max = len(board[0])
      if color == "#000000":
         color = "@"
      else:
         color = "O"
      
      random_move, flipped_stones = random.choice(list(self.find_moves(board, color).items()))

      return (random_move // self.x_max, random_move % self.y_max), len(flipped_stones)

   def stones_left(self, board):
      left = 0
      for i in range(board):
         for j in range(board[i]):
            if board[i][j] == '.': left += 1
      
      return left

   def find_moves(self, board, color):
    moves_found = {}
    for i in range(len(board)):
        for j in range(len(board[i])):
            flipped_stones = self.find_flipped(board, i, j, color)
            if len(flipped_stones) > 0:
                moves_found.update({i * self.y_max + j: flipped_stones})
    return moves_found

   def find_flipped(self, board, x, y, color):
      if board[x][y] != ".":
         return []
      
      if color == self.black:
         color = "@"
      else:
         color = "O"
      
      flipped_stones = []

      for incr in self.directions:
         temp_flip = []
         x_pos = x + incr[0]
         y_pos = y + incr[1]
         while 0 <= x_pos < self.x_max and 0 <= y_pos < self.y_max:
               if board[x_pos][y_pos] == ".":
                  break
               if board[x_pos][y_pos] == color:
                  flipped_stones += temp_flip
                  break
               temp_flip.append([x_pos, y_pos])
               x_pos += incr[0]
               y_pos += incr[1]

      return flipped_stones

class Best_AI_bot:

   def __init__(self):
      self.logging = True
      self.white = "#ffffff" # "O"
      self.black = "#000000" # "@"
      self.directions = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]]
      self.opposite_color = {self.black: self.white, self.white: self.black}
      self.x_max = None
      self.y_max = None
      self.color = None
      self.heuristic_table = [
         [999,-3,2,2,2,2,-3,999],
         [-3,-4,-1,-1,-1,-1,-4,-3],
         [2,-1,1,0,0,1,-1,2],
         [2,-1,0,1,1,0,-1,2],
         [2,-1,0,1,1,0,-1,2],
         [2,-1,1,0,0,1,-1,2],
         [-3,-4,-1,-1,-1,-1,-4,-3],
         [999, -3, 2, 2, 2, 2, -3, 999],
      ]

   def best_strategy(self, board, color):
      self.x_max = len(board)
      self.y_max = len(board[0])
      self.color = color
      
      stones_left = self.stones_left(board)
      if stones_left > 32:
         sd = 4
      elif stones_left > 10:
         sd = 6
      else:
         sd = 8
      
      v, best_move = self.alphabeta(board, color, search_depth=sd, alpha=-9999999999, beta=9999999999)   # returns state

      return (best_move // self.x_max, best_move % self.y_max), 0

   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 alphabeta(self, board, color, search_depth, alpha, beta, last_move=-1):
      terminal_test = self.terminal_test(board)
      if search_depth <= 0 or terminal_test:
         if terminal_test:
            return self.evaluate(board, self.color, last_move) * 1000000, last_move
         return self.evaluate(board, self.color, last_move), 0

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

   def terminal_test(self, board):
      return self.stones_left(board) == 0

   def stones_left(self, board):
      left = 0
      for i in range(len(board)):
         for j in range(len(board[i])):
            if board[i][j] == '.': left += 1
      
      return left

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

      if color == self.black:
         color = "@"
      else:
         color = "O"
      
      my_board[move // self.x_max][move % self.y_max] = color
      for flip in flipped:
         my_board[flip[0]][flip[1]] = color
      
      return my_board

   def evaluate(self, board, color, last_move):
      score = self.score(board, color)
      if last_move != -1:
         heuristic = self.heuristic_table[last_move // self.x_max][last_move % self.y_max]
         return score * heuristic
      else:
         return score



   def score(self, board, color):
      if color == self.black:
         color = "@"
      else:
         color = "O"

      score = 0
      for i in range(len(board)):
         for j in range(len(board[i])):
            if board[i][j] == color:
               score += 1
            elif board[i][j] != '.':
               score -= 1

      return score
      

   def find_moves(self, board, color):
      moves_found = {}
      for i in range(len(board)):
         for j in range(len(board[i])):
            flipped_stones = self.find_flipped(board, i, j, color)
            if len(flipped_stones) > 0:
                  moves_found.update({i * self.y_max + j: flipped_stones})
      return moves_found

   def find_flipped(self, board, x, y, color):
      if board[x][y] != ".":
         return []
      
      if color == self.black:
         color = "@"
      else:
         color = "O"
      
      flipped_stones = []

      for incr in self.directions:
         temp_flip = []
         x_pos = x + incr[0]
         y_pos = y + incr[1]
         while 0 <= x_pos < self.x_max and 0 <= y_pos < self.y_max:
               if board[x_pos][y_pos] == ".":
                  break
               if board[x_pos][y_pos] == color:
                  flipped_stones += temp_flip
                  break
               temp_flip.append([x_pos, y_pos])
               x_pos += incr[0]
               y_pos += incr[1]

      return flipped_stones