def get_heuristic_value(board: Board):
        """
        Given a board, calculates and returns its rating based on heuristics.
        """
        # Get a list of all squares with white pieces and a list of squares with
        # black pieces.
        white_squares: List[Square] = board.get_player_squares(
            PlayerColor.WHITE)
        black_squares: List[Square] = board.get_player_squares(
            PlayerColor.BLACK)

        # If there are any black pieces, calculate the sum of all white pieces'
        # manhattan displacement to the first black piece in the list. This
        # piece will remain consistent until it is dead. This fixes the issue of
        # white pieces, when separated from the black pieces, not being able to
        # find their way to the black pieces easily.
        manhattan_dist_sum: int = 0
        if (len(black_squares) > 0):
            black_square: Square = black_squares[0]
            for white_square in white_squares:
                displacement: Pos2D = (black_square.pos - white_square.pos)
                manhattan_dist_sum += abs(displacement.x) + abs(displacement.y)

        # Calculate the number of white and black pieces. This is a very
        # important heuristic that will help prioritize preserving white's own
        # pieces and killing the enemy's black pieces.
        num_white_pieces: int = len(white_squares)
        num_black_pieces: int = len(black_squares)

        # Return the heuristic rating by using the appropriate weights.
        return round(
            IDSAgent._WHITE_WEIGHT * num_white_pieces -
            IDSAgent._BLACK_WEIGHT * num_black_pieces -
            IDSAgent._DIST_WEIGHT * manhattan_dist_sum,
            IDSAgent._RATING_NUM_ROUNDING)
    def get_heuristic_value(board: Board, player: PlayerColor,
                            parameters: List[float]):
        """
        Given a board, calculates and returns its rating based on heuristics.
        """

        player_squares: List[Square] = board.get_player_squares(player)
        opponent_squares: List[Square] = board.get_player_squares(
            player.opposite())

        # -- Num pieces --
        # Calculate the number of white and black pieces. This is a very
        # important heuristic that will help prioritize preserving white's own
        # pieces and killing the enemy's black pieces.
        num_own_pieces: int = len(player_squares)
        num_opponent_pieces: int = len(opponent_squares)

        # -- Mobility --
        # Calculate the mobility for both white and black i.e. the number of
        # possible moves they can make.
        own_mobility: int = board.get_num_moves(player)
        opponent_mobility: int = board.get_num_moves(player.opposite())

        # -- Cohesiveness --
        own_total_distance: int = 0
        opponent_total_distance: int = 0

        displacement: Pos2D
        for idx, square in enumerate(player_squares):
            for square2 in player_squares[idx + 1:]:
                displacement = square.pos - square2.pos
                own_total_distance += abs(displacement.x) + abs(displacement.y)

        for idx, square in enumerate(opponent_squares):
            for square2 in opponent_squares[idx + 1:]:
                displacement = square.pos - square2.pos
                opponent_total_distance += abs(displacement.x) + abs(
                    displacement.y)

        own_avg_allied_distance: float = own_total_distance / (num_own_pieces +
                                                               1)
        opponent_avg_allied_distance: float = opponent_total_distance / (
            num_opponent_pieces + 1)

        # -- Centrality --
        own_total_distance: int = 0
        opponent_total_distance: int = 0

        x_displacement: float
        y_displacement: float
        for square in player_squares:
            x_displacement = 3.5 - square.pos.x
            y_displacement = 3.5 - square.pos.y
            own_total_distance += abs(x_displacement) + abs(y_displacement)

        for square in opponent_squares:
            x_displacement = 3.5 - square.pos.x
            y_displacement = 3.5 - square.pos.y
            opponent_total_distance += abs(x_displacement) + abs(
                y_displacement)

        own_avg_center_distance: float = own_total_distance / (num_own_pieces +
                                                               1)
        opponent_avg_center_distance: float = opponent_total_distance / (
            num_opponent_pieces + 1)

        # Calculate the heuristic score/rating.
        rounded_heuristic_score: float = round(
            parameters[0] * num_own_pieces +
            parameters[1] * num_opponent_pieces +
            parameters[2] * own_mobility + parameters[3] * opponent_mobility +
            parameters[4] * own_avg_allied_distance +
            parameters[5] * opponent_avg_allied_distance +
            parameters[6] * own_avg_center_distance +
            parameters[7] * opponent_avg_center_distance,
            Player._RATING_NUM_ROUNDING)

        # Return the score as is or negate, depending on the player.
        # For white, return as is. For black, negate.
        return rounded_heuristic_score if player == PlayerColor.WHITE \
            else -rounded_heuristic_score