| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 | 
							- """
 
- This file contains the `Board` class, which implements the rules for the
 
- game Isolation as described in lecture, modified so that the players move
 
- like knights in chess rather than queens.
 
- You MAY use and modify this class, however ALL function signatures must
 
- remain compatible with the defaults provided, and none of your changes will
 
- be available to project reviewers.
 
- """
 
- import random
 
- import timeit
 
- from copy import copy
 
- TIME_LIMIT_MILLIS = 150
 
- class Board(object):
 
-     """Implement a model for the game Isolation assuming each player moves like
 
-     a knight in chess.
 
-     Parameters
 
-     ----------
 
-     player_1 : object
 
-         An object with a get_move() function. This is the only function
 
-         directly called by the Board class for each player.
 
-     player_2 : object
 
-         An object with a get_move() function. This is the only function
 
-         directly called by the Board class for each player.
 
-     width : int (optional)
 
-         The number of columns that the board should have.
 
-     height : int (optional)
 
-         The number of rows that the board should have.
 
-     """
 
-     BLANK = 0
 
-     NOT_MOVED = None
 
-     def __init__(self, player_1, player_2, width=7, height=7):
 
-         self.width = width
 
-         self.height = height
 
-         self.move_count = 0
 
-         self._player_1 = player_1
 
-         self._player_2 = player_2
 
-         self._active_player = player_1
 
-         self._inactive_player = player_2
 
-         # The last 3 entries of the board state includes initiative (0 for
 
-         # player 1, 1 for player 2) player 2 last move, and player 1 last move
 
-         self._board_state = [Board.BLANK] * (width * height + 3)
 
-         self._board_state[-1] = Board.NOT_MOVED
 
-         self._board_state[-2] = Board.NOT_MOVED
 
-     def hash(self):
 
-         return str(self._board_state).__hash__()
 
-     @property
 
-     def active_player(self):
 
-         """The object registered as the player holding initiative in the
 
-         current game state.
 
-         """
 
-         return self._active_player
 
-     @property
 
-     def inactive_player(self):
 
-         """The object registered as the player in waiting for the current
 
-         game state.
 
-         """
 
-         return self._inactive_player
 
-     def get_opponent(self, player):
 
-         """Return the opponent of the supplied player.
 
-         Parameters
 
-         ----------
 
-         player : object
 
-             An object registered as a player in the current game. Raises an
 
-             error if the supplied object is not registered as a player in
 
-             this game.
 
-         Returns
 
-         -------
 
-         object
 
-             The opponent of the input player object.
 
-         """
 
-         if player == self._active_player:
 
-             return self._inactive_player
 
-         elif player == self._inactive_player:
 
-             return self._active_player
 
-         raise RuntimeError("`player` must be an object registered as a player in the current game.")
 
-     def copy(self):
 
-         """ Return a deep copy of the current board. """
 
-         new_board = Board(self._player_1, self._player_2, width=self.width, height=self.height)
 
-         new_board.move_count = self.move_count
 
-         new_board._active_player = self._active_player
 
-         new_board._inactive_player = self._inactive_player
 
-         new_board._board_state = copy(self._board_state)
 
-         return new_board
 
-     def forecast_move(self, move):
 
-         """Return a deep copy of the current game with an input move applied to
 
-         advance the game one ply.
 
-         Parameters
 
-         ----------
 
-         move : (int, int)
 
-             A coordinate pair (row, column) indicating the next position for
 
-             the active player on the board.
 
-         Returns
 
-         -------
 
-         isolation.Board
 
-             A deep copy of the board with the input move applied.
 
-         """
 
-         new_board = self.copy()
 
-         new_board.apply_move(move)
 
-         return new_board
 
-     def move_is_legal(self, move):
 
-         """Test whether a move is legal in the current game state.
 
-         Parameters
 
-         ----------
 
-         move : (int, int)
 
-             A coordinate pair (row, column) indicating the next position for
 
-             the active player on the board.
 
-         Returns
 
-         -------
 
-         bool
 
-             Returns True if the move is legal, False otherwise
 
-         """
 
-         idx = move[0] + move[1] * self.height
 
-         return (0 <= move[0] < self.height and 0 <= move[1] < self.width and
 
-                 self._board_state[idx] == Board.BLANK)
 
-     def get_blank_spaces(self):
 
-         """Return a list of the locations that are still available on the board.
 
-         """
 
-         return [(i, j) for j in range(self.width) for i in range(self.height)
 
-                 if self._board_state[i + j * self.height] == Board.BLANK]
 
-     def get_player_location(self, player):
 
-         """Find the current location of the specified player on the board.
 
-         Parameters
 
-         ----------
 
-         player : object
 
-             An object registered as a player in the current game.
 
-         Returns
 
-         -------
 
-         (int, int) or None
 
-             The coordinate pair (row, column) of the input player, or None
 
-             if the player has not moved.
 
-         """
 
-         if player == self._player_1:
 
-             if self._board_state[-1] == Board.NOT_MOVED:
 
-                 return Board.NOT_MOVED
 
-             idx = self._board_state[-1]
 
-         elif player == self._player_2:
 
-             if self._board_state[-2] == Board.NOT_MOVED:
 
-                 return Board.NOT_MOVED
 
-             idx = self._board_state[-2]
 
-         else:
 
-             raise RuntimeError(
 
-                 "Invalid player in get_player_location: {}".format(player))
 
-         w = idx // self.height
 
-         h = idx % self.height
 
-         return (h, w)
 
-     def get_legal_moves(self, player=None):
 
-         """Return the list of all legal moves for the specified player.
 
-         Parameters
 
-         ----------
 
-         player : object (optional)
 
-             An object registered as a player in the current game. If None,
 
-             return the legal moves for the active player on the board.
 
-         Returns
 
-         -------
 
-         list<(int, int)>
 
-             The list of coordinate pairs (row, column) of all legal moves
 
-             for the player constrained by the current game state.
 
-         """
 
-         if player is None:
 
-             player = self.active_player
 
-         return self.get_moves(self.get_player_location(player))
 
-     def apply_move(self, move):
 
-         """Move the active player to a specified location.
 
-         Parameters
 
-         ----------
 
-         move : (int, int)
 
-             A coordinate pair (row, column) indicating the next position for
 
-             the active player on the board.
 
-         """
 
-         idx = move[0] + move[1] * self.height
 
-         last_move_idx = int(self.active_player == self._player_2) + 1
 
-         self._board_state[-last_move_idx] = idx
 
-         self._board_state[idx] = 1
 
-         self._board_state[-3] ^= 1
 
-         self._active_player, self._inactive_player = self._inactive_player, self._active_player
 
-         self.move_count += 1
 
-     def is_winner(self, player):
 
-         """ Test whether the specified player has won the game. """
 
-         return player == self._inactive_player and not self.get_legal_moves(self._active_player)
 
-     def is_loser(self, player):
 
-         """ Test whether the specified player has lost the game. """
 
-         return player == self._active_player and not self.get_legal_moves(self._active_player)
 
-     def utility(self, player):
 
-         """Returns the utility of the current game state from the perspective
 
-         of the specified player.
 
-                     /  +infinity,   "player" wins
 
-         utility =  |   -infinity,   "player" loses
 
-                     \          0,    otherwise
 
-         Parameters
 
-         ----------
 
-         player : object (optional)
 
-             An object registered as a player in the current game. If None,
 
-             return the utility for the active player on the board.
 
-         Returns
 
-         ----------
 
-         float
 
-             The utility value of the current game state for the specified
 
-             player. The game has a utility of +inf if the player has won,
 
-             a value of -inf if the player has lost, and a value of 0
 
-             otherwise.
 
-         """
 
-         if not self.get_legal_moves(self._active_player):
 
-             if player == self._inactive_player:
 
-                 return float("inf")
 
-             if player == self._active_player:
 
-                 return float("-inf")
 
-         return 0.
 
-     def get_moves(self, loc):
 
-         """Generate the list of possible moves for an L-shaped motion (like a
 
-         knight in chess).
 
-         """
 
-         if loc == Board.NOT_MOVED:
 
-             return self.get_blank_spaces()
 
-         r, c = loc
 
-         directions = [(-2, -1), (-2, 1), (-1, -2), (-1, 2),
 
-                       (1, -2), (1, 2), (2, -1), (2, 1)]
 
-         valid_moves = [(r + dr, c + dc) for dr, dc in directions
 
-                        if self.move_is_legal((r + dr, c + dc))]
 
-         random.shuffle(valid_moves)
 
-         return valid_moves
 
-     def print_board(self):
 
-         """DEPRECATED - use Board.to_string()"""
 
-         return self.to_string()
 
-     def to_string(self, symbols=['1', '2']):
 
-         """Generate a string representation of the current game state, marking
 
-         the location of each player and indicating which cells have been
 
-         blocked, and which remain open.
 
-         """
 
-         p1_loc = self._board_state[-1]
 
-         p2_loc = self._board_state[-2]
 
-         col_margin = len(str(self.height - 1)) + 1
 
-         prefix = "{:<" + "{}".format(col_margin) + "}"
 
-         offset = " " * (col_margin + 3)
 
-         out = offset + '   '.join(map(str, range(self.width))) + '\n\r'
 
-         for i in range(self.height):
 
-             out += prefix.format(i) + ' | '
 
-             for j in range(self.width):
 
-                 idx = i + j * self.height
 
-                 if not self._board_state[idx]:
 
-                     out += ' '
 
-                 elif p1_loc == idx:
 
-                     out += symbols[0]
 
-                 elif p2_loc == idx:
 
-                     out += symbols[1]
 
-                 else:
 
-                     out += '-'
 
-                 out += ' | '
 
-             out += '\n\r'
 
-         return out
 
-     def play(self, time_limit=TIME_LIMIT_MILLIS):
 
-         """Execute a match between the players by alternately soliciting them
 
-         to select a move and applying it in the game.
 
-         Parameters
 
-         ----------
 
-         time_limit : numeric (optional)
 
-             The maximum number of milliseconds to allow before timeout
 
-             during each turn.
 
-         Returns
 
-         ----------
 
-         (player, list<[(int, int),]>, str)
 
-             Return multiple including the winning player, the complete game
 
-             move history, and a string indicating the reason for losing
 
-             (e.g., timeout or invalid move).
 
-         """
 
-         move_history = []
 
-         time_millis = lambda: 1000 * timeit.default_timer()
 
-         while True:
 
-             legal_player_moves = self.get_legal_moves()
 
-             game_copy = self.copy()
 
-             move_start = time_millis()
 
-             time_left = lambda : time_limit - (time_millis() - move_start)
 
-             curr_move = self._active_player.get_move(
 
-                 game_copy, legal_player_moves, time_left)
 
-             move_end = time_left()
 
-             if curr_move is None:
 
-                 curr_move = Board.NOT_MOVED
 
-             if move_end < 0:
 
-                 return self._inactive_player, move_history, "timeout"
 
-             if curr_move not in legal_player_moves:
 
-                 if len(legal_player_moves) > 0:
 
-                     return self._inactive_player, move_history, "forfeit"
 
-                 return self._inactive_player, move_history, "illegal move"
 
-             move_history.append(list(curr_move))
 
-             self.apply_move(curr_move)
 
 
  |