Example #1
0
    def get_move(self, game_state, possible_moves):
        self.clock = time.time()
        self.time_for_current_move = self.time_remaining_in_round / self.turns_remaining_in_round - 0.05
        if len(possible_moves) == 1:
            return possible_moves[0]

        # best_move = possible_moves[0]
        # next_state = copy.deepcopy(game_state)
        # next_state.perform_move(best_move[0], best_move[1])
        # # Choosing an arbitrary move
        # # Get the best move according the utility function
        # for move in possible_moves:
        #     new_state = copy.deepcopy(game_state)
        #     new_state.perform_move(move[0], move[1])
        #     if self.utility(new_state) > self.utility(next_state):
        #         next_state = new_state
        #         best_move = move
        best_move = possible_moves[0]
        min_max = MiniMaxAlgorithm(self.utility, self.color, self.no_more_time,
                                   None)
        self.depth = 1
        while self.no_more_time() is False:
            min_max_val = min_max.search(game_state, self.depth, True)[1]
            best_move = min_max_val if min_max_val is not None else best_move
            self.depth += 1
        if self.turns_remaining_in_round == 1:
            self.turns_remaining_in_round = self.k
            self.time_remaining_in_round = self.time_per_k_turns
        else:
            self.turns_remaining_in_round -= 1
            self.time_remaining_in_round -= (time.time() - self.clock)

        return best_move
Example #2
0
    def iterative_deepening(self, state):
        mini_max = MiniMaxAlgorithm(self.utility, self.color, self.no_more_time, False)
        depth = 1
        optimal_move = None
        while True:
            _, move = mini_max.search(state, depth, True)
            if move is None:
                break
            optimal_move = move
            depth += 1

        return optimal_move
Example #3
0
    def __init__(self, setup_time, player_color, time_per_k_turns, k):
        AbstractPlayer.__init__(self, setup_time, player_color, time_per_k_turns, k)

        self.clock = time.time()

        # We are simply providing (remaining time / remaining turns) for each turn in round.
        # Taking a spare time of 0.05 seconds.
        self.turns_remaining_in_round = self.k
        self.time_remaining_in_round = self.time_per_k_turns
        self.time_for_current_move = self.time_remaining_in_round / self.turns_remaining_in_round - 0.05

        self.simple = simplePlayer(setup_time, player_color, time_per_k_turns, k)
        self.algorithm = MiniMaxAlgorithm(utility=self.simple.utility, my_color=self.color,
                                          no_more_time=self.no_more_time, selective_deepening=self.selective_deeping)
Example #4
0
    def __init__(self, setup_time, player_color, time_per_k_turns, k):
        abstract.AbstractPlayer.__init__(self, setup_time, player_color, \
                time_per_k_turns, k)
        self.clock = time.time()

        # We are simply providing (remaining time / remaining turns) for each \
        #       turn in round.
        # Taking a spare time of 0.05 seconds.
        self.turns_remaining_in_round = self.k
        self.time_remaining_in_round = self.time_per_k_turns
        self.time_for_current_move = self.time_remaining_in_round / \
                self.turns_remaining_in_round - 0.05

        self.min_max_algorithm = MiniMaxAlgorithm(self.utility, \
                self.color, self.no_more_time, None)

        # for performence
        self.max_steps_left = 62
Example #5
0
    def get_move(self, game_state, possible_moves):
        self.clock = time.time()
        self.time_for_current_move = self.time_remaining_in_round / self.turns_remaining_in_round - 0.05
        if len(possible_moves) == 1:
            print("min max : only one choice")
            return possible_moves[0]

        best_move = possible_moves[0]
        next_state = copy.deepcopy(game_state)
        next_state.perform_move(best_move[0], best_move[1])
        best_util = self.utility(next_state)

        min_max = MiniMaxAlgorithm(self.utility, self.color, self.no_more_time,
                                   self.selective_deepening_criterion)
        i = 1
        depth = 0
        while not self.no_more_time():
            curr_best_util, curr_best_move = min_max.search(
                game_state, i, True)
            depth += 1
            if curr_best_util > best_util:
                best_move = curr_best_move
                best_util = curr_best_util
            i += 1

        # Not sure if we need these lines,compied them from simple_player, code works with / without them - does not
        # understand their propose
        # TODO: check if needed or not
        if self.turns_remaining_in_round == 1:
            self.turns_remaining_in_round = self.k
            self.time_remaining_in_round = self.time_per_k_turns
        else:
            self.turns_remaining_in_round -= 1
            self.time_remaining_in_round -= (time.time() - self.clock)
        #print("min_max depth : ", depth)
        return best_move
Example #6
0
    def __init__(self, setup_time, player_color, time_per_k_turns, k):
        abstract.AbstractPlayer.__init__(self, setup_time, player_color,
                                         time_per_k_turns, k)
        self.clock = time.time()

        # We are simply providing (remaining time / remaining turns) for each turn in round.
        # Taking a spare time of 0.05 seconds.
        self.turns_remaining_in_round = self.k
        self.time_remaining_in_round = self.time_per_k_turns
        self.time_for_current_move = self.time_remaining_in_round / self.turns_remaining_in_round - 0.05
        miniMax = MiniMaxAlgorithm(self.utilityBetter, player_color,
                                   self.no_more_time, False)
        self.search = miniMax.search

        # divide the board into 5 categories and score them from best to worst
        cells1Type = [2, 3, 4, 5]
        cells2TypeA = [1, 6]
        cells2TypeB = [2, 3, 4, 5]
        cells3TypeA = [0, 7]
        cells3TypeB = [2, 3, 4, 5]
        cells4Type = [0, 1, 6]
        cells5Type = [0, 7]
        # initiate the scoring matrix
        self.scoreMat = np.zeros((8, 8))
        for i in cells2TypeA:
            for j in cells2TypeB:
                self.scoreMat[i][j] = Player.SCORE_PERIMETER_BORDER
                self.scoreMat[j][i] = Player.SCORE_PERIMETER_BORDER
        for i in cells3TypeA:
            for j in cells3TypeB:
                self.scoreMat[i][j] = Player.SCORE_BORDER
                self.scoreMat[j][i] = Player.SCORE_BORDER
        for i in cells4Type:
            for j in cells4Type:
                self.scoreMat[i][j] = Player.SCORE_PERIMETER_CORNER
        for i in cells5Type:
            for j in cells5Type:
                self.scoreMat[i][j] = Player.SCORE_CORNER
Example #7
0
class Player(AbstractPlayer):
    def __init__(self, setup_time, player_color, time_per_k_turns, k):
        AbstractPlayer.__init__(self, setup_time, player_color, time_per_k_turns, k)

        self.clock = time.time()

        # We are simply providing (remaining time / remaining turns) for each turn in round.
        # Taking a spare time of 0.05 seconds.
        self.turns_remaining_in_round = self.k
        self.time_remaining_in_round = self.time_per_k_turns
        self.time_for_current_move = self.time_remaining_in_round / self.turns_remaining_in_round - 0.05

        self.simple = simplePlayer(setup_time, player_color, time_per_k_turns, k)
        self.algorithm = MiniMaxAlgorithm(utility=self.simple.utility, my_color=self.color,
                                          no_more_time=self.no_more_time, selective_deepening=self.selective_deeping)

    def __repr__(self):
        return '{} {}'.format(abstract.AbstractPlayer.__repr__(self), '- min_max_player')

    def no_more_time(self):
        time_passed = (time.time() - self.clock)
        self.clock = time.time()
        self.time_for_current_move -= time_passed
        self.time_remaining_in_round -= time_passed
        if self.time_for_current_move <= 0.05 or self.time_remaining_in_round <= 0.05:
            return True
        return False

    def time_for_step(self):
        return (self.split_time_equally(PERCENTAGE_OF_TIME_TO_SPLIT_EQUALLY * self.time_remaining_in_round) + \
               self.split_time_not_equally(PERCENTAGE_OF_TIME_TO_SPLIT_NOT_EQUALLY * self.time_remaining_in_round))*0.97

    def split_time_equally(self, time_remaining):
        return time_remaining/self.turns_remaining_in_round

    def split_time_not_equally(self, time_remaining):
        sum_of_remaining_turns = sum(range(self.turns_remaining_in_round + 1))
        return time_remaining * (self.turns_remaining_in_round / sum_of_remaining_turns)

    def selective_deeping(self, state):
        sensitive_spots = [(0, 1), (1, 0), (1, 1), (0, 6), (1, 6), (1, 7), (6, 0), (6, 1),
                                   (7, 1), (7, 6), (6, 7), (6, 6)]
        corners = [(0, 0), (0, 7), (7, 0), (7, 7)]
        for move in state.get_possible_moves():
            if move in sensitive_spots:
                return True
            if move in corners:
                return True
        return False

    def get_move(self, game_state, possible_moves):
        depth = 0
        self.time_for_current_move = self.time_for_step()
        self.clock = time.time()

        best_move = None
        max_value = 0
        searched_all_tree = False
        while not self.no_more_time() and not searched_all_tree:
            depth += 1
            [value, move, searched_all_tree] = self.algorithm.search(game_state, depth, True)
            if best_move is None or value > max_value:
                max_value = value
                best_move = move
            if searched_all_tree:
                break

        if self.turns_remaining_in_round == 1:
            self.turns_remaining_in_round = self.k
            self.time_remaining_in_round = self.time_per_k_turns
        else:
            self.turns_remaining_in_round -= 1

        return best_move if best_move is not None else possible_moves[0]
Example #8
0
    def __init__(self, setup_time, player_color, time_per_k_turns, k):
        abstract.AbstractPlayer.__init__(self, setup_time, player_color, \
                time_per_k_turns, k)
        self.clock = time.time()

        # We are simply providing (remaining time / remaining turns) for each \
        #       turn in round.
        # Taking a spare time of 0.05 seconds.
        self.turns_remaining_in_round = self.k
        self.time_remaining_in_round = self.time_per_k_turns
        self.time_for_current_move = self.time_remaining_in_round / \
                self.turns_remaining_in_round - 0.05

        # chose get_move function according to time_per_k_turns:
        # time_per_k_turns < 3 --> get_move_min_max
        # time_per_k_turns > 3 --> get_move_alpha_beta
        if time_per_k_turns < 3:
            self.get_move_chosen = self.get_move_min_max
            self.min_max_algorithm = MiniMaxAlgorithm(self.utility, \
                    self.color, self.no_more_time, None)
        else:
            self.get_move_chosen = self.get_move_alpha_beta
            self.alpha_beta_algorithm = MiniMaxWithAlphaBetaPruning(self.utility, \
                    self.color, self.no_more_time, None)

        # keep the game-moves as a string
        self.moves = ''
        self.last_state = GameState()

        # chose board configuration
        if self.color == O_PLAYER:
            # will be set after opponent first move
            self.book2reality = None
            self.reality2book = None
        else:
            self.book2reality = self.book2reality1
            self.reality2book = self.reality2book1

        # for performence
        self.max_steps_left = 62

        # create opening book
        opening_book = {}
        f = open("best_70_opens.gam", 'r')
        # FIXME: remove error check before submmision
        if not f:
            print("cannot open file")
            sys.exit(1)

        # assume we play first and update otherwise
        first_move_index = 0
        if self.color == O_PLAYER:
            first_move_index = 1

        for line in f:
            tmp = line.split(' ')[1]
            tmp = re.split(r'[+-]', tmp)
            moves = ''.join(tmp)
            for i in range(first_move_index, 10, 2):
                tmp = re.split('r[+-]', moves)
                key = moves[0:2 * i]
                if key not in opening_book:
                    opening_book[key] = moves[2 * i:2 * i + 2]
        f.close()
        self.opening_book = opening_book
Example #9
0
class Player(abstract.AbstractPlayer):
    def __init__(self, setup_time, player_color, time_per_k_turns, k):
        abstract.AbstractPlayer.__init__(self, setup_time, player_color, \
                time_per_k_turns, k)
        self.clock = time.time()

        # We are simply providing (remaining time / remaining turns) for each \
        #       turn in round.
        # Taking a spare time of 0.05 seconds.
        self.turns_remaining_in_round = self.k
        self.time_remaining_in_round = self.time_per_k_turns
        self.time_for_current_move = self.time_remaining_in_round / \
                self.turns_remaining_in_round - 0.05

        # chose get_move function according to time_per_k_turns:
        # time_per_k_turns < 3 --> get_move_min_max
        # time_per_k_turns > 3 --> get_move_alpha_beta
        if time_per_k_turns < 3:
            self.get_move_chosen = self.get_move_min_max
            self.min_max_algorithm = MiniMaxAlgorithm(self.utility, \
                    self.color, self.no_more_time, None)
        else:
            self.get_move_chosen = self.get_move_alpha_beta
            self.alpha_beta_algorithm = MiniMaxWithAlphaBetaPruning(self.utility, \
                    self.color, self.no_more_time, None)

        # keep the game-moves as a string
        self.moves = ''
        self.last_state = GameState()

        # chose board configuration
        if self.color == O_PLAYER:
            # will be set after opponent first move
            self.book2reality = None
            self.reality2book = None
        else:
            self.book2reality = self.book2reality1
            self.reality2book = self.reality2book1

        # for performence
        self.max_steps_left = 62

        # create opening book
        opening_book = {}
        f = open("best_70_opens.gam", 'r')
        # FIXME: remove error check before submmision
        if not f:
            print("cannot open file")
            sys.exit(1)

        # assume we play first and update otherwise
        first_move_index = 0
        if self.color == O_PLAYER:
            first_move_index = 1

        for line in f:
            tmp = line.split(' ')[1]
            tmp = re.split(r'[+-]', tmp)
            moves = ''.join(tmp)
            for i in range(first_move_index, 10, 2):
                tmp = re.split('r[+-]', moves)
                key = moves[0:2 * i]
                if key not in opening_book:
                    opening_book[key] = moves[2 * i:2 * i + 2]
        f.close()
        self.opening_book = opening_book

    def get_move(self, game_state, possible_moves):
        self.clock = time.time()
        self.time_for_current_move = self.time_remaining_in_round / \
                self.turns_remaining_in_round - 0.05

        self.max_steps_left -= 2

        # discover last move done by opponent
        opponent_move_str_format = ''
        for x in range(BOARD_COLS):
            for y in range(BOARD_ROWS):
                if self.last_state.board[x][
                        y] == EM and game_state.board[x][y] != EM:
                    opponent_move_str_format = TO_LETTER[str(x + 1)] + str(y +
                                                                           1)

        # chose the board_configuration - firs opponent move became 'd3' in
        # oppening book
        if self.book2reality == None:
            if opponent_move_str_format == 'd6':
                self.book2reality = self.book2reality1
                self.reality2book = self.reality2book1
            elif opponent_move_str_format == 'e3':
                self.book2reality = self.book2reality2
                self.reality2book = self.reality2book2
            elif opponent_move_str_format == 'c5':
                self.book2reality = self.book2reality3
                self.reality2book = self.reality2book3
            elif opponent_move_str_format == 'f4':
                self.book2reality = self.book2reality4
                self.reality2book = self.reality2book4
            else:
                raise ImpossibleBoardTransform

        # append opponent move to self.moves - this will represent the key.
        # if we play first we will append '' to '' (nothing will hapen)
        self.moves += self.reality2book(opponent_move_str_format)

        # this case is handled in run_game.py
        assert (len(possible_moves) != 0)

        best_move = self.get_move_chosen(game_state, possible_moves)

        to_be_append = TO_LETTER[str(best_move[0] + 1)] + str(best_move[1] + 1)
        self.moves += self.reality2book(to_be_append)

        if self.turns_remaining_in_round == 1:
            self.turns_remaining_in_round = self.k
            self.time_remaining_in_round = self.time_per_k_turns
        else:
            self.turns_remaining_in_round -= 1
            self.time_remaining_in_round -= (time.time() - self.clock)

        return best_move

    def get_move_alpha_beta(self, game_state, possible_moves):

        # find the best move
        if len(possible_moves) == 1:
            best_move = possible_moves[0]
        else:
            # check if next move can be determined from oppening book
            oppening_book_res = self.opening_move()
            if oppening_book_res != None:
                best_move = oppening_book_res
            else:
                curr_depth = 1

                # there is maximum max_steps_left steps in the game
                last_move = None
                while curr_depth < self.max_steps_left:
                    try:
                        _, best_move = self.alpha_beta_algorithm.search( \
                                game_state, curr_depth, -INFINITY*2, INFINITY*2, True)

                        if last_move != best_move:
                            game_state_copy = copy.deepcopy(game_state)
                            game_state_copy.perform_move(
                                best_move[0], best_move[1])
                            self.last_state = game_state_copy

                        last_move = best_move
                        curr_depth += 1
                    except ExceededTimeError:
                        break

        return best_move

    def get_move_min_max(self, game_state, possible_moves):

        # find the best move
        if len(possible_moves) == 1:
            best_move = possible_moves[0]
        else:
            # check if next move can be determined from oppening book
            oppening_book_res = self.opening_move()
            if oppening_book_res != None:
                best_move = oppening_book_res
            else:
                curr_depth = 1

                # there is maximum max_steps_left steps in the game
                last_move = None
                while curr_depth < self.max_steps_left:
                    try:
                        _, best_move = self.min_max_algorithm.search( \
                                game_state, curr_depth, True)

                        if last_move != best_move:
                            game_state_copy = copy.deepcopy(game_state)
                            game_state_copy.perform_move(
                                best_move[0], best_move[1])
                            self.last_state = game_state_copy

                        last_move = best_move
                        curr_depth += 1
                    except ExceededTimeError:
                        break

        return best_move

    def opening_move(self):

        # check if we can find the next move in the oppening book
        if self.moves not in self.opening_book:
            return None

        res = self.book2reality(self.opening_book[self.moves])
        return [int(TO_DIGIT[res[0]]) - 1, int(res[1]) - 1]

    def book2reality1(self, str_move):
        old_digit = str_move[1]
        old_letter = str_move[0]
        new_digit = str(BOARD_ROWS - int(old_digit) + 1)
        new_letter = old_letter
        return new_letter + new_digit

    def reality2book1(self, str_move):
        if str_move == '':
            return ''
        return self.book2reality1(str_move)

    def book2reality2(self, str_move):
        old_digit = str_move[1]
        old_letter = str_move[0]
        new_digit = old_digit
        new_letter = REVERSE_LETTER[old_letter]
        return new_letter + new_digit

    def reality2book2(self, str_move):
        if str_move == '':
            return ''
        return self.book2reality2(str_move)

    def book2reality3(self, str_move):
        old_digit = str_move[1]
        old_letter = str_move[0]
        new_digit = TO_LETTER[old_digit]
        new_letter = TO_DIGIT[REVERSE_LETTER[old_letter]]
        return new_digit + new_letter

    def reality2book3(self, str_move):
        if str_move == '':
            return ''
        return self.book2reality4(str_move)

    def book2reality4(self, str_move):
        old_digit = str_move[1]
        old_letter = str_move[0]
        new_digit = REVERSE_LETTER[TO_LETTER[old_digit]]
        new_letter = TO_DIGIT[old_letter]
        return new_digit + new_letter

    def reality2book4(self, str_move):
        if str_move == '':
            return ''
        return self.book2reality3(str_move)
#------------------------------------------------------------------------------

    def __is_stable(self, state, J, I):

        # a corner is always stable
        if (I == 0 or I == BOARD_ROWS) and (J == 0 or J == BOARD_COLS):
            return True

        elif I == 0 or I == BOARD_ROWS - 1:
            res1 = True
            res2 = True
            for j in range(J):
                if state.board[j][I] == OPPONENT_COLOR[self.color]:
                    res1 = False
                    break
            for j in range(J, BOARD_COLS):
                if state.board[j][I] == OPPONENT_COLOR[self.color]:
                    res2 = False
                    break
            return res1 or res2

        elif J == 0 or J == BOARD_COLS - 1:
            res1 = True
            res2 = True
            for i in range(I):
                if state.board[J][i] == OPPONENT_COLOR[self.color]:
                    res1 = False
                    break
            for i in range(I, BOARD_ROWS):
                if state.board[J][i] == OPPONENT_COLOR[self.color]:
                    res2 = False
                    break
            return res1 or res2

        # don't let this method be applied on a non-edge index
        else:
            raise NonEdgeIndex

    def __score_utility(self, state):
        CORNER_FAC = 100
        STABLE_EDGE_FAC = 20
        EDGE_FAC = 5
        INTER_FAC = 1

        my_u = 0
        op_u = 0
        for x in range(BOARD_COLS):
            for y in range(BOARD_ROWS):

                # is a corner
                if (x == 0 or x == BOARD_COLS - 1) and (y == 0 or y
                                                        == BOARD_ROWS - 1):
                    if state.board[x][y] == self.color:
                        my_u += CORNER_FAC
                    elif state.board[x][y] == OPPONENT_COLOR[self.color]:
                        op_u += CORNER_FAC

                # is an edge
                elif x == 0 or x == BOARD_COLS - 1 or y == 0 or y == BOARD_ROWS - 1:

                    # is stable
                    if self.__is_stable(state, x, y):
                        if state.board[x][y] == self.color:
                            my_u += STABLE_EDGE_FAC
                        elif state.board[x][y] == OPPONENT_COLOR[self.color]:
                            op_u += STABLE_EDGE_FAC

                    # is not stable
                    else:
                        if state.board[x][y] == self.color:
                            my_u += EDGE_FAC
                        elif state.board[x][y] == OPPONENT_COLOR[self.color]:
                            op_u -= EDGE_FAC

                # is internal
                else:
                    if state.board[x][y] == self.color:
                        my_u += INTER_FAC
                    elif state.board[x][y] == OPPONENT_COLOR[self.color]:
                        op_u -= INTER_FAC

        if my_u == 0:
            # I have no tools left
            return -INFINITY
        elif op_u == 0:
            # The opponent has no tools left
            return INFINITY
        else:
            return my_u - op_u

    def __mobility_utility(self, state):
        MOBILITY_FAC = 1

        # when this function is called the current player is already the
        # opponent so we will change it to our player, check the value and
        # return it as it was
        op_moves = len(state.get_possible_moves())
        state.curr_player = OPPONENT_COLOR[state.curr_player]
        my_moves = len(state.get_possible_moves())
        state.curr_player = OPPONENT_COLOR[state.curr_player]

        return (my_moves - op_moves) * MOBILITY_FAC

    def utility(self, state):
        assert (len(state.get_possible_moves()) != 0)

        mobility_fac = 1
        score_fac = 1

        mobility_res = self.__mobility_utility(state)
        score_res = self.__score_utility(state)

        return mobility_res * mobility_fac + score_res * score_fac

#------------------------------------------------------------------------------

    def selective_deepening_criterion(self, state):
        # Simple player does not selectively deepen into certain nodes.
        return False

    def no_more_time(self):
        return (time.time() - self.clock) >= self.time_for_current_move

    def __repr__(self):
        return '{} {}'.format(abstract.AbstractPlayer.__repr__(self),
                              'competition')
root2.set_right(2)
root2.left.set_left(5)
root2.left.set_right(4)
root2.right.set_left(7)
root2.right.set_right(6)
root2.left.left.set_left(9)
root2.left.left.set_right(8)
root2.left.right.set_left(11)
root2.left.right.set_right(10)
root2.right.left.set_left(13)
root2.right.left.set_right(12)
root2.right.right.set_left(15)
root2.right.right.set_right(14)

# chek min-max without time consideration
mma = MiniMaxAlgorithm(utility, 'X', no_more_time, None)
res1 = mma.search(root, 10, True)
res2 = mma.search(root, 10, False)
res3 = mma.search(root, 2, True)
res4 = mma.search(root, 2, False)

asssert(res1[0] == 13)
asssert(res2[0] == 10)
asssert(res3[0] == 6)
asssert(res4[0] == 5)

res1 = mma.search(root2, 10, True)
res2 = mma.search(root2, 10, False)
res3 = mma.search(root2, 2, True)
res4 = mma.search(root2, 2, False)
Example #11
0
class Player(abstract.AbstractPlayer):
    def __init__(self, setup_time, player_color, time_per_k_turns, k):
        abstract.AbstractPlayer.__init__(self, setup_time, player_color, \
                time_per_k_turns, k)
        self.clock = time.time()

        # We are simply providing (remaining time / remaining turns) for each \
        #       turn in round.
        # Taking a spare time of 0.05 seconds.
        self.turns_remaining_in_round = self.k
        self.time_remaining_in_round = self.time_per_k_turns
        self.time_for_current_move = self.time_remaining_in_round / \
                self.turns_remaining_in_round - 0.05

        self.min_max_algorithm = MiniMaxAlgorithm(self.utility, \
                self.color, self.no_more_time, None)

        # for performence
        self.max_steps_left = 62

    def get_move(self, game_state, possible_moves):
        self.clock = time.time()
        self.time_for_current_move = self.time_remaining_in_round / \
                self.turns_remaining_in_round - 0.05

        self.max_steps_left -= 2

        if len(possible_moves) == 1:
            best_move = possible_moves[0]
        else:
            curr_depth = 1

            # there is maximum max_steps_left steps in the game
            while curr_depth < self.max_steps_left:
                try:
                    _, best_move = self.min_max_algorithm.search( \
                            game_state, curr_depth, True)
                    curr_depth += 1
                except ExceededTimeError:
                    break

        if self.turns_remaining_in_round == 1:
            self.turns_remaining_in_round = self.k
            self.time_remaining_in_round = self.time_per_k_turns
        else:
            self.turns_remaining_in_round -= 1
            self.time_remaining_in_round -= (time.time() - self.clock)

        return best_move

    def utility(self, state):
        if len(state.get_possible_moves()) == 0:
            return INFINITY if state.curr_player != self.color else -INFINITY

        my_u = 0
        op_u = 0
        for x in range(BOARD_COLS):
            for y in range(BOARD_ROWS):
                if state.board[x][y] == self.color:
                    my_u += 1
                if state.board[x][y] == OPPONENT_COLOR[self.color]:
                    op_u += 1

        if my_u == 0:
            # I have no tools left
            return -INFINITY
        elif op_u == 0:
            # The opponent has no tools left
            return INFINITY
        else:
            return my_u - op_u

    def selective_deepening_criterion(self, state):
        # Simple player does not selectively deepen into certain nodes.
        return False

    def no_more_time(self):
        return (time.time() - self.clock) >= self.time_for_current_move

    def __repr__(self):
        return '{} {}'.format(abstract.AbstractPlayer.__repr__(self),
                              'min_max')