Пример #1
0
    def __init__(self, name=None, strategy=RandomStrategy()):
        """

        :param str name: the name of the Santorini player.
        :param Strategy strategy: the strategy the player employs.
        :return:
        :rtype: None
        """
        # TODO: Discuss / Document for code walk.
        # Should we have the player store a board object and call the set_board() method within Player,
        # or should we have a Player.set_board() function that simply reassigns self.board to a board object that's
        # already had set_board called on it?
        # Pranav's opinion: I think we should do the first option, because it ensures the clients of player don't have
        # to know about the Board.set_board() function, which is almost a helper, since it doesn't have core
        # functionality that's exposed to users. May as well abstract that using a Player function. If we do option 2,
        # we'll also have to write an extra contract to make sure a board that's been passed in has had set_board()
        # called on it, which will be overly complicated.
        if not name:
            self.name = "LocalPlayer{}".format(Player.COUNT)
        elif not isinstance(name, str):
            raise ContractViolation("Name must be a string!")
        else:
            self.name = name
        if not isinstance(strategy, BaseStrategy):
            raise ContractViolation("Strategy must implement BaseStrategy interface!")
        self.board = Board()
        self.strategy = strategy
        self.color = None
        self.registered = False  # shadow state
        Player.COUNT += 1
Пример #2
0
    def place(self, board, color):
        """
        Returns the worker placements for the player.

        CONTRACT:
         - Can only be called after Player.register() has been called.
         - Must be called before Player.play().
         - Cannot be called more than once.
         - `board` must be a valid initial board (a board where all cell's heights are 0, and no workers of `self.color`
         are present any cell.

        :param list board: an instance of Board (refer to documentation of Board class).
        :param str color: A color (as defined in the documentation of Referee).
        :return: `list` of [position1, position2] denoting the position of the player's 1st and 2nd worker respectively.
        See `position`, `worker` in documentation of Board.py.
        :rtype: list
        """
        if not self.registered:
            raise ContractViolation("Function must be called after player.register()!")
        if self.color:
            raise ContractViolation("Cannot call Player.place() again until game ends!")
        if not RuleChecker.is_valid_color(color):
            raise ContractViolation("Invalid color provided: {}".format(color))
        self.color = color
        if not RuleChecker.is_legal_initial_board(board, self.color):
            raise ContractViolation("Invalid initial board provided: {}".format(board))
        self.board.set_board(board)
        # TODO: potential contract needed to ensure set_board is called at start of every turn for player
        return self.strategy.get_placements(self.board, self.color)
Пример #3
0
 def play(self, board):
     if not self.color:
         raise ContractViolation(
             "Function must be called after player.place()!")
     if not RuleChecker.is_legal_board(board):
         raise ContractViolation("Invalid board provided: {}".format(board))
     if not self._check_board(board):
         raise IllegalPlay("Player provided with a cheating board.")
     print("cheater checking...")  # debug
     return super().play(board)
Пример #4
0
    def is_winning_play(board, worker, directions):
        """
        Takes in a play (as specified above) and checks if it's a winning play.

        This entails checking if the `worker` is moving up to height 3, or if the play blocks the opposition player from
        making any moves or builds.

        CONTRACT:
         - `[worker, directions]` must be a legal play.

        :param Board board:
        :param string worker:
        :param list directions:
        :return:
        :rtype: bool
        """
        if not RuleChecker.is_valid_worker(worker):
            raise ContractViolation("Invalid (or no) worker provided.")
        if not all(map(RuleChecker.is_valid_direction, directions)):
            raise ContractViolation("Invalid (or no) directions provided.")
        if not RuleChecker.is_legal_play(board, worker, directions):
            raise ContractViolation(
                "Illegal play passed into is_winning_play: {}".format(
                    [worker, directions]))

        color = worker[:-1]
        available_colors = list(RuleChecker.COLORS)
        available_colors.remove(color)
        opp_color = available_colors[0]

        if len(
                directions
        ) == 1:  # must be winning, since one direction is only legal in the winning case
            return True
        else:
            opp_cannot_play = False
            move_dir, build_dir = directions

            # simulate play
            board.move(worker, move_dir)
            board.build(worker, build_dir)

            # commented out to avoid circular imports
            # opposition_player_legal_plays = Strategy.get_legal_plays(board, opp_color)

            # if not opposition_player_legal_plays:
            #     opp_cannot_play = True

            # undo play
            board.undo_build(worker, build_dir)
            board.move(worker, board.get_opposite_direction(move_dir))

            return opp_cannot_play
Пример #5
0
    def is_legal_play(board, worker, directions):
        """
        :param Board board:
        :param string worker:
        :param list directions:
        :return: `True` if play is legal, `False` otherwise
        :rtype: bool
        """
        build_dir = None
        if len(directions) == 1:
            move_dir = directions[0]
        elif len(directions) == 2:
            move_dir, build_dir = directions
        else:
            raise ContractViolation("Too many/few directions provided.")

        if RuleChecker.is_valid_move(board, worker, move_dir):
            if RuleChecker.is_winning_move(board, worker, move_dir):
                if build_dir is None:  # checking for win
                    return True
                else:
                    return False
            elif build_dir is None:
                return False
            board.move(worker, move_dir)
            if RuleChecker.is_valid_build(board, worker, build_dir):
                return_val = True
            else:
                return_val = False
            board.move(worker,
                       board.get_opposite_direction(move_dir))  # undo the move
            return return_val
        else:
            return False
Пример #6
0
    def play(self, board):
        """
        Returns the strategized play a player wants to execute on a given turn.

        :param list board:
        :return: a play (as defined above)
        :rtype: list
        """
        if not self.color:
            raise ContractViolation("Function must be called after player.place()!")
        if not RuleChecker.is_legal_board(board):
            raise ContractViolation("Invalid board provided: {}".format(board))
        self.board.set_board(board)
        play = self.strategy.get_play(self.board, self.color)
        print("sending play", play)  # debug
        return play
Пример #7
0
    def move(self, worker, direction):
        """
        Moves the specified worker from it's cell to the cell adjacent to the worker's existing position in the
        specified direction, if all of the following are true:
         - the cell being moved to exists
         - the cell being moved to is not occupied
         - the height of the cell being moved to is not 4
         - the height of the cell being moved to - the height of the worker's cell <= 1

        A move entails:
        - editing the specified worker's cell from `[height, worker]` to `height`
        - editing the adjacent cell from it's `height` to `[height, worker]`.

        :param string worker: a worker (as defined above).
        :param string direction: a direction (as defined above).
        :return: a board (as specified above) edited to reflect the build. Nothing if move is invalid.
        :rtype: list, void
        """
        if not RuleChecker.is_valid_worker(
                worker) or not RuleChecker.is_valid_direction(direction):
            raise ContractViolation(
                "Invalid (or no) worker / direction provided.")
        worker_row, worker_col, worker_height = self.get_worker_position(
            worker)
        adj_cell_row, adj_cell_col = self._get_adj_cell(
            worker_row, worker_col, direction)
        adj_cell_height = self.board[adj_cell_row][adj_cell_col]
        self.board[adj_cell_row][adj_cell_col] = [adj_cell_height, worker]
        self.board[worker_row][worker_col] = worker_height
        self.worker_positions[worker] = (adj_cell_row, adj_cell_col,
                                         adj_cell_height)
        return self.board
Пример #8
0
    def undo_build(self, worker, direction):
        """
        Decreases the height of the cell adjacent to the worker's position in the specified direction by 1, if all of
        the following are true:
         - the cell being destroyed on exists
         - the cell being destroyed on is not occupied
         - the height of the cell being built on is not 0.

        Note: should not be used outside the `Strategy` component.

        :param string worker: a worker (as defined above).
        :param string direction: a direction (as defined above).
        :return: a board (as specified above) edited to reflect the undoing of the build. Nothing if move is invalid.
        :rtype: list, void
        """
        if not RuleChecker.is_valid_worker(
                worker) or not RuleChecker.is_valid_direction(direction):
            raise ContractViolation(
                "Invalid (or no) worker / direction provided.")
        worker_row, worker_col, worker_height = self.get_worker_position(
            worker)
        adj_cell_row, adj_cell_col = self._get_adj_cell(
            worker_row, worker_col, direction)
        self.board[adj_cell_row][adj_cell_col] -= 1
        return self.board
Пример #9
0
    def _update_board_with_play(self, play):
        """
        Updates the board state with the play and returns `True` if the given play resulted in a win. `False` otherwise.

        CONTRACT:
         - Can only be called after _update_board_with_placements has been called for every player.

        :param list play: a play (as defined above)
        :return: Boolean indicating whether the play resulted in the player winning on that turn
        :rtype: bool
        """
        if not RuleChecker.is_valid_play(play):
            raise ContractViolation("Play not in correct format.")
        worker, directions = play
        if (worker[:-1] != RuleChecker.COLORS[self.turn] or
                not RuleChecker.is_legal_play(self.board, worker, directions)):
            raise IllegalPlay("Illegal play made by {player}: {play}".format(
                player=self.players[self.turn].get_name(), play=play))

        if len(directions) == 1:
            return True

        move_dir, build_dir = directions
        self.board.move(worker, move_dir)
        self.board.build(worker, build_dir)

        return False
Пример #10
0
 def _examine_for_error(
     response
 ):  # TODO: this method makes assumptions about how the Player operates. refactor
     if response == "InvalidCommand":
         raise InvalidCommand()
     if response == "IllegalPlay":
         raise IllegalPlay()
     if response == "ContractViolation":
         raise ContractViolation()
Пример #11
0
 def is_valid_build(board, worker, direction):
     if not RuleChecker.is_valid_worker(
             worker) or not RuleChecker.is_valid_direction(direction):
         raise ContractViolation(
             "Invalid (or no) worker / direction provided.")
     adj_cell_height = board.get_height(worker, direction)
     return (board.neighboring_cell_exists(worker, direction)
             and not board.is_occupied(worker, direction)
             and adj_cell_height != 4)
Пример #12
0
    def get_worker_position(self, worker):
        """
        Returns the position of the given worker and the height at that position as one tuple of
        the form `(row, col, height)` by iterating through the the cells in `self.board`.

        :param string worker: a worker (as defined above).
        :return: the position of the given worker (as specified above) and the height at that position as one tuple of
        the form `(row, col, height)`.
        :rtype: tuple of ints
        """
        if not RuleChecker.is_valid_worker(worker):
            raise ContractViolation(
                "Invalid worker provided: {}".format(worker))
        # Note: would use a dictionary for O(1) access if the board wasn't being reset with every command.
        if worker in self.worker_positions:
            return self.worker_positions[worker]
        else:
            raise ContractViolation(
                "Worker does not exist in worker_dictionary!")
Пример #13
0
    def get_name(self):
        """

        :return:
        :rtype: str
        """
        if not self.name:
            raise ContractViolation(
                "ProxyPlayer.register() must be called before get_name()!")
        return self.name
Пример #14
0
    def is_legal_initial_board(board, color):
        """
        Checks the validity of an initial board.

        :param list board: A board (as defined in the documentation of Board).
        :param string color: A color (as defined in the documentation of Referee).
        :return: 'True' if the board is a valid initial board, else 'False'.
        :rtype: bool
        """
        if not RuleChecker.is_valid_color(color):
            raise ContractViolation("Invalid color provided: {}".format(color))
        unset_workers = [color + "1", color + "2"]
        return RuleChecker.is_legal_board(board, unset_workers, 0)
Пример #15
0
    def get_dimensions(self):
        """
        CONTRACT:
         - cannot be called before set_board() has been called.

        :return: The dimensions of the board member variable in format (num_rows, num_cols)
        :rtype: tuple
        """
        if not self.board:
            raise ContractViolation(
                "Cannot get dimensions if Board.board member variable has not been set!"
            )
        return len(self.board), len(self.board[0])
Пример #16
0
    def register(self):
        """
        Returns the name of the player.

        CONTRACT:
         - Must be the first function to be called after Player is instantiated.
         - Cannot be called more than once.

        :return: the name of the player
        :rtype: str
        """
        if self.registered:
            raise ContractViolation("Cannot call Player.register() more than once!")
        self.registered = True
        return self.name
Пример #17
0
    def place_worker(self, row, col, worker):
        """
        Places a worker at the cell at position (row, col)

        :param int row: `row` in a position (as defined above).
        :param int col: `col` in a position (as defined above).
        :param string worker: a worker (as defined above).
        :rtype: void
        """
        if not RuleChecker.is_valid_worker(worker):
            raise ContractViolation(
                "Invalid worker provided: {}".format(worker))
        if self.has_worker(row, col):
            raise IllegalPlay("Cannot place worker in occupied cell!")
        height = self.board[row][col]
        self.board[row][col] = [height, worker]
        self.worker_positions[worker] = (row, col, height)
Пример #18
0
    def notify(self, winner_name):
        """
        Notifies the player with the winner of the Santorini game.

        CONTRACT:
         - can only be called once per game
         - must be the last function to be called by an object that implements PlayerInterface.

        :param str winner_name: Name of the winner of the Santorini game
        :return: An acknowledgement string of "OK"
        :rtype: str
        """
        if not self.registered:
            raise ContractViolation("Player.notify() cannot be called before register!")
        # resetting interaction protocol contracts for future games
        self.board = Board()
        self.color = None
        print("{} has won the game!".format(winner_name))  # debug
        print("------------------------------------------------")  # debug
        return "OK"
Пример #19
0
    def neighboring_cell_exists(self, worker, direction):
        """
        Checks if the cell adjacent to the worker's position in the specified direction exists.

        A cell exists if it's position is within the bounds of the board, i.e. it is a valid position as defined above.

        :param string worker: a worker (as defined above).
        :param string direction: a direction (as defined above).
        :return: `True` if cell adjacent in the specified direction exists, else `False`.
        :rtype: bool
        """
        if not RuleChecker.is_valid_worker(
                worker) or not RuleChecker.is_valid_direction(direction):
            raise ContractViolation(
                "Invalid (or no) worker / direction provided.")
        worker_row, worker_col, worker_height = self.get_worker_position(
            worker)
        adj_cell_row, adj_cell_col = Board._get_adj_cell(
            worker_row, worker_col, direction)
        return 0 <= adj_cell_row < len(self.board) and 0 <= adj_cell_col < len(
            self.board[0])
Пример #20
0
    def is_occupied(self, worker, direction):
        """
        Checks if the cell adjacent to the worker's position in the specified direction is occupied.

        A cell is occupied if it is a `list` of [height, worker].

        :param string worker: a worker (as defined above).
        :param string direction: a direction (as defined above).
        :return: `True` if the cell adjacent to the worker's position in the specified direction exists and is occupied,
        `False`, if cell is unoccupied. Behaviour unspecified if cell adjacent cell doesn't exist.
        :rtype: bool, void
        """
        if not RuleChecker.is_valid_worker(
                worker) or not RuleChecker.is_valid_direction(direction):
            raise ContractViolation(
                "Invalid (or no) worker / direction provided.")
        if self.neighboring_cell_exists(worker, direction):
            worker_row, worker_col, cell_height = self.get_worker_position(
                worker)
            adj_cell_row, adj_cell_col = self._get_adj_cell(
                worker_row, worker_col, direction)
            return self.has_worker(adj_cell_row, adj_cell_col)
Пример #21
0
    def _update_board_with_placements(self, placements):
        """
        Updates the board with the given placements.

        CONTRACT:
         - Can only be called after _register_player has been called for every player.
         - Can only be called once per distinct player per game (two players).

        :param list placements: a list of placements (as defined above)
        :return:
        :rtype: void
        """
        if not RuleChecker.is_valid_placement(placements):
            raise ContractViolation("Placements not in correct format.")
        for placement in placements:
            if not RuleChecker.is_legal_placement(self.board, placement):
                raise IllegalPlay(
                    "Invalid placement position given: {}".format(placement))
        for worker_num, placement in enumerate(placements, 1):
            row, col = placement
            worker = RuleChecker.COLORS[self.turn] + str(worker_num)
            self.board.place_worker(row, col, worker)
Пример #22
0
    def get_legal_plays(board, color):
        """
        Returns a list of all possible legal plays for players of the given color.

        :param Board board: an instance of Board (refer to documentation of Board class).
        :param str color: color (as defined above)
        :return: a `list` of legal plays (as defined above)
        :rtype: list
        """
        if not RuleChecker.is_valid_color(color):
            raise ContractViolation("Invalid color given: {}".format(color))

        legal_plays = []
        players = [str(color + "1"), str(color + "2")]
        player_movable_directions = [[], []]

        # Valid Move directions
        for direc in RuleChecker.DIRECTIONS:
            for i, player in enumerate(players):
                if RuleChecker.is_valid_move(board, player, direc):
                    player_movable_directions[i].append(direc)

        # Constructing all possible legal plays
        for i, player in enumerate(players):
            for move_dir in player_movable_directions[i]:
                if RuleChecker.is_winning_move(board, player, move_dir):
                    legal_plays.append([player, [move_dir]])

                else:
                    board.move(player, move_dir)
                    for build_dir in RuleChecker.DIRECTIONS:
                        if RuleChecker.is_valid_build(board, player,
                                                      build_dir):
                            legal_plays.append([player, [move_dir, build_dir]])
                    opp_dir = board.get_opposite_direction(move_dir)
                    board.move(player, opp_dir)  # undoing the move

        return legal_plays
Пример #23
0
    def get_height(self, worker, direction):
        """
        Returns the height of the cell adjacent to the worker's position in the specified direction.

        :param string worker: a worker (as defined above).
        :param string direction: a direction (as defined above).
        :return: the height of the cell adjacent to the worker's position in the specified direction.
        :rtype: int
        """
        if not RuleChecker.is_valid_worker(
                worker) or not RuleChecker.is_valid_direction(direction):
            raise ContractViolation(
                "Invalid (or no) worker / direction provided.")
        if self.neighboring_cell_exists(worker, direction):
            worker_row, worker_col, worker_height = self.get_worker_position(
                worker)
            adj_cell_row, adj_cell_col = Board._get_adj_cell(
                worker_row, worker_col, direction)
            cell = self.board[adj_cell_row][adj_cell_col]
            if isinstance(cell, list):
                return cell[0]
            else:
                return cell
Пример #24
0
 def __init__(self, num_looks_ahead=1):
     if not isinstance(num_looks_ahead, int) or num_looks_ahead < 1:
         raise ContractViolation(
             "num_looks_ahead must be a positive integer! Given: {}".format(
                 num_looks_ahead))
     self.num_looks_ahead = num_looks_ahead
Пример #25
0
    def get_plays(board, color, num_look_ahead):  # TODO - make private method
        """
        Returns a list of all possible legal plays that cannot not result in the opposing player winning within the next
        `num_look_ahead` moves.

        CONTRACT:
         - `num_look_ahead` must be >= 1

        :param Board board: an instance of Board (refer to documentation of Board class).
        :param str color: color (as defined above)
        :param int num_look_ahead: number of moves to look ahead by
        :return: a `list` of legal plays (as defined above)
        :rtype: `list`
        """
        if not RuleChecker.is_valid_color(color):
            raise ContractViolation("Invalid color given: {}".format(color))

        available_colors = list(RuleChecker.COLORS)
        available_colors.remove(color)
        opp_color = available_colors[0]

        result_plays = []

        for play in NLooksAheadStrategy.get_legal_plays(board, color):
            # avoid circular import
            # if RuleChecker.is_winning_play(board, *play):
            #     result_plays.append(play)
            # print("checking", play)  # debug
            if len(play[1]) == 1:
                result_plays.append(play)
            else:
                opposition_win = False
                worker = play[0]
                move_dir, build_dir = play[1]

                # player play
                board.move(worker, move_dir)
                board.build(worker, build_dir)

                opp_legal_plays = NLooksAheadStrategy.get_legal_plays(
                    board, opp_color)
                if any(len(opp_play[1]) == 1 for opp_play in
                       opp_legal_plays):  # try and prune search
                    opposition_win = True
                else:
                    for opp_play in opp_legal_plays:
                        # avoid circular import
                        # if RuleChecker.is_winning_play(board, *opp_play):
                        #     opposition_win = True
                        #     break
                        if len(opp_play[1]) == 1:
                            opposition_win = True
                            break
                        elif num_look_ahead > 1:
                            opp_worker = opp_play[0]
                            opp_move_dir, opp_build_dir = opp_play[1]

                            # opposition play
                            board.move(opp_worker, opp_move_dir)
                            board.build(opp_worker, opp_build_dir)

                            opposition_win = NLooksAheadStrategy._loses_in_n_moves(
                                board, color, num_look_ahead - 1)

                            # undoing opposition play
                            board.undo_build(opp_worker, opp_build_dir)
                            board.move(
                                opp_worker,
                                board.get_opposite_direction(opp_move_dir))

                            if opposition_win:
                                break

                # undoing player play
                board.undo_build(worker, build_dir)
                board.move(worker, board.get_opposite_direction(move_dir))

                if not opposition_win:
                    result_plays.append(play)

        return result_plays
Пример #26
0
 def is_winning_move(board, worker, direction):
     if not RuleChecker.is_valid_worker(
             worker) or not RuleChecker.is_valid_direction(direction):
         raise ContractViolation(
             "Invalid (or no) worker / direction provided.")
     return board.get_height(worker, direction) == 3