"""This file contains a collection of player classes for comparison with your own agent and example heuristic functions. """ from random import randint def null_score(game, player): """This heuristic presumes no knowledge for non-terminal states, and returns the same uninformative value for all other states. Parameters ---------- game : `isolation.Board` An instance of `isolation.Board` encoding the current state of the game (e.g., player locations and blocked cells). player : hashable One of the objects registered by the game object as a valid player. (i.e., `player` should be either game.__player_1__ or game.__player_2__). Returns ---------- float The heuristic value of the current game state. """ if game.is_loser(player): return float("-inf") if game.is_winner(player): return float("inf") return 0. def open_move_score(game, player): """The basic evaluation function described in lecture that outputs a score equal to the number of moves open for your computer player on the board. Parameters ---------- game : `isolation.Board` An instance of `isolation.Board` encoding the current state of the game (e.g., player locations and blocked cells). player : hashable One of the objects registered by the game object as a valid player. (i.e., `player` should be either game.__player_1__ or game.__player_2__). Returns ---------- float The heuristic value of the current game state """ if game.is_loser(player): return float("-inf") if game.is_winner(player): return float("inf") return float(len(game.get_legal_moves(player))) def improved_score(game, player): """The "Improved" evaluation function discussed in lecture that outputs a score equal to the difference in the number of moves available to the two players. Parameters ---------- game : `isolation.Board` An instance of `isolation.Board` encoding the current state of the game (e.g., player locations and blocked cells). player : hashable One of the objects registered by the game object as a valid player. (i.e., `player` should be either game.__player_1__ or game.__player_2__). Returns ---------- float The heuristic value of the current game state """ if game.is_loser(player): return float("-inf") if game.is_winner(player): return float("inf") own_moves = len(game.get_legal_moves(player)) opp_moves = len(game.get_legal_moves(game.get_opponent(player))) return float(own_moves - opp_moves) class RandomPlayer(): """Player that chooses a move randomly.""" def get_move(self, game, legal_moves, time_left): """Randomly select a move from the available legal moves. Parameters ---------- game : `isolation.Board` An instance of `isolation.Board` encoding the current state of the game (e.g., player locations and blocked cells). legal_moves : list<(int, int)> A list containing legal moves. Moves are encoded as tuples of pairs of ints defining the next (row, col) for the agent to occupy. time_left : callable A function that returns the number of milliseconds left in the current turn. Returning with any less than 0 ms remaining forfeits the game. Returns ---------- (int, int) A randomly selected legal move; may return (-1, -1) if there are no available legal moves. """ if not legal_moves: return (-1, -1) return legal_moves[randint(0, len(legal_moves) - 1)] class GreedyPlayer(): """Player that chooses next move to maximize heuristic score. This is equivalent to a minimax search agent with a search depth of one. """ def __init__(self, score_fn=open_move_score): self.score = score_fn def get_move(self, game, legal_moves, time_left): """Select the move from the available legal moves with the highest heuristic score. Parameters ---------- game : `isolation.Board` An instance of `isolation.Board` encoding the current state of the game (e.g., player locations and blocked cells). legal_moves : list<(int, int)> A list containing legal moves. Moves are encoded as tuples of pairs of ints defining the next (row, col) for the agent to occupy. time_left : callable A function that returns the number of milliseconds left in the current turn. Returning with any less than 0 ms remaining forfeits the game. Returns ---------- (int, int) The move in the legal moves list with the highest heuristic score for the current game state; may return (-1, -1) if there are no legal moves. """ if not legal_moves: return (-1, -1) _, move = max([(self.score(game.forecast_move(m), self), m) for m in legal_moves]) return move class HumanPlayer(): """Player that chooses a move according to user's input.""" def get_move(self, game, legal_moves, time_left): """ Select a move from the available legal moves based on user input at the terminal. ********************************************************************** NOTE: If testing with this player, remember to disable move timeout in the call to `Board.play()`. ********************************************************************** Parameters ---------- game : `isolation.Board` An instance of `isolation.Board` encoding the current state of the game (e.g., player locations and blocked cells). legal_moves : list<(int, int)> A list containing legal moves. Moves are encoded as tuples of pairs of ints defining the next (row, col) for the agent to occupy. time_left : callable A function that returns the number of milliseconds left in the current turn. Returning with any less than 0 ms remaining forfeits the game. Returns ---------- (int, int) The move in the legal moves list selected by the user through the terminal prompt; automatically return (-1, -1) if there are no legal moves """ if not legal_moves: return (-1, -1) print(('\t'.join(['[%d] %s' % (i, str(move)) for i, move in enumerate(legal_moves)]))) valid_choice = False while not valid_choice: try: index = int(input('Select move index:')) valid_choice = 0 <= index < len(legal_moves) if not valid_choice: print('Illegal move! Try again.') except ValueError: print('Invalid index! Try again.') return legal_moves[index] if __name__ == "__main__": from isolation import Board # create an isolation board (by default 7x7) player1 = RandomPlayer() player2 = GreedyPlayer() game = Board(player1, player2) # place player 1 on the board at row 2, column 3, then place player 2 on # the board at row 0, column 5; display the resulting board state. Note # that .apply_move() changes the calling object game.apply_move((2, 3)) game.apply_move((0, 5)) print(game.to_string()) # players take turns moving on the board, so player1 should be next to move assert(player1 == game.active_player) # get a list of the legal moves available to the active player print(game.get_legal_moves()) # get a successor of the current state by making a copy of the board and # applying a move. Notice that this does NOT change the calling object # (unlike .apply_move()). new_game = game.forecast_move((1, 1)) assert(new_game.to_string() != game.to_string()) print("\nOld state:\n{}".format(game.to_string())) print("\nNew state:\n{}".format(new_game.to_string())) # play the remainder of the game automatically -- outcome can be "illegal # move" or "timeout"; it should _always_ be "illegal move" in this example winner, history, outcome = game.play() print("\nWinner: {}\nOutcome: {}".format(winner, outcome)) print(game.to_string()) print("Move history:\n{!s}".format(history))