def setUp(self): players = [{ "color": "blue", "score": 0, "places": [] }, { "color": "yellow", "score": 0, "places": [] }, { "color": "red", "score": 0, "places": [] }] tiles = [[1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1]] self.state = State(players, tiles) self.state.place_penguin((4, 4)) self.state.place_penguin((4, 3)) self.state.place_penguin((4, 2)) self.state.place_penguin((3, 4)) self.state.place_penguin((3, 3)) self.state.place_penguin((3, 2)) self.state.place_penguin((2, 4)) self.state.place_penguin((2, 3)) self.state.place_penguin((2, 2)) self.tree = GameTreeNode(self.state)
def test_place_penguin(self): players = [{ "color": "blue", "score": 0, "places": [] }, { "color": "yellow", "score": 0, "places": [] }] tiles = [[1, 0, 1, 0, 1], [1, 0, 1, 0, 1], [1, 0, 1, 0, 1], [1, 0, 1, 0, 1], [1, 0, 1, 0, 1]] self.state = State(players, tiles) placement = place_penguin(self.state) self.assertEqual(placement, [(0, 0)]) self.state = GameTreeNode(self.state).generate_successor(placement) placement = place_penguin(self.state) self.assertEqual(placement, [(0, 2)]) self.state = GameTreeNode(self.state).generate_successor(placement) placement = place_penguin(self.state) self.assertEqual(placement, [(0, 4)]) self.state = GameTreeNode(self.state).generate_successor(placement) placement = place_penguin(self.state) self.assertEqual(placement, [(1, 0)]) self.state = GameTreeNode(self.state).generate_successor(placement) placement = place_penguin(self.state) self.assertEqual(placement, [(1, 2)])
def test_get_optimal_action_game_over(self): players = [{ "color": "blue", "score": 0, "places": [(0, 0), (0, 2), (1, 0), (1, 2)] }, { "color": "yellow", "score": 0, "places": [(0, 1), (0, 3), (1, 1), (1, 3)] }] tiles = [[1, 1, 1, 1], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]] self.state = State(players, tiles) node = GameTreeNode(self.state) self.assertEqual(get_optimal_action(node), None) players = [{ "color": "blue", "score": 0, "places": [(0, 0)] }, { "color": "yellow", "score": 0, "places": [] }] tiles = [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] self.state = State(players, tiles) node = GameTreeNode(self.state) self.assertEqual(get_optimal_action(node), None)
def generate_successor(self, action): """ generate_successor - returns the next state for the referee given that the action was valid. Returns None otherwise. Parameters: action - An action is a list containing either: - a single tuple [(row,col)] representing the row and col coordinates of the players next penguin placement one the board - two tuples [(origin-row, origin-col),(dest-row,dest-col)] where the first tuple represents the current position of the penguin and the second tuple represents the destination of the move. Output -> Either A State represnting the new state after the given action has taken placement Or None if the action is an invalid move. """ node = GameTreeNode(self.state) return node.generate_successor(action)
def get_minimum_score(self, state: State, max_depth: int, player_idx: int): """ get_minimum_score: Helper function for minimax that gets the min score of all possible moves from the given state, looking forward the specified number of turns. Params: state: State - A state in which no more penguins will be placed. max_depth: int - An integer representing the number of turns that minimax will look forward to calculate the optimal reward player_idx: int - An integer representing the turn index corresponding to the maximizing agent in the State representation Output -> An integer representing the min reward for the given state after a certain number of turns (max_depth) """ min_score = float("inf") node = GameTreeNode(state) for action in node.check_available_actions(): min_score = min( self.minimax(node.generate_successor(action), max_depth, player_idx), min_score) return min_score
def get_optimal_action(self, node: GameTreeNode): # TODO: Make this actually use node.generate_tree() so it caches data! """ get_optimal_action: Runs a minimax search on the provided GameTreeNode to the provided depth "d to determine the move with the optimal "reward" (player score) for the current player after D moves, assuming that all other players will play with the intent to minimize the current player's score. This function takes in a GameTreeNode and evaluates the "reward" for each available action with a recursive minimax evaluation function. This method returns an Action, which is a list of tuples in the form [(w, x), (y, z)], where the first pair of coordinates represents the current position of the penguin and the second pair of coordinates represents the intended destination. In the case of a tiebreaker, a tiebreaker method gets called that will prioritize by origin row coord, origin column coord, destination row coord, and destination column coord in that order. Params: node: GameTreeNode - The initial node, to which no more players will be added. This node should contain a state with game phase PLAYING. Output -> A list of tuples in the form [(a, b), (c, d)] representing the optimal action. (a, b) represents the origin and (c, d) represents the destination. If the game is over, then this method returns None. """ if not node or node.get_state().game_over(): return None max_score = float("-inf") optimal_actions = [] idx = node.get_state().get_turn_idx() for action in node.check_available_actions(): successor_state = node.generate_successor(action) successor_score = self.minimax(successor_state, self.depth - 1, idx) if successor_score > max_score: max_score = successor_score optimal_actions = [action] elif successor_score == max_score: optimal_actions.append(action) if len(optimal_actions) > 1: return self.tiebreak(optimal_actions)[0] return optimal_actions[0] if len(optimal_actions) > 0 else False
def test_generate_successor_placement(self): players = [{ "color": "blue", "score": 0, "places": [] }, { "color": "yellow", "score": 0, "places": [] }, { "color": "red", "score": 0, "places": [] }] tiles = [[1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1]] state = State(players, tiles) node = GameTreeNode(state) successor = node.generate_successor([(0, 0)]) node = GameTreeNode(successor) successor = node.generate_successor([(0, 1)]) node = GameTreeNode(successor) successor = node.generate_successor([(0, 2)]) players = [{ "color": "blue", "score": 0, "places": [[0, 0]] }, { "color": "yellow", "score": 0, "places": [[0, 1]] }, { "color": "red", "score": 0, "places": [[0, 2]] }] tiles = [[1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1]] expected_state = State(players, tiles, turn_index=0) self.assertEqual(successor, expected_state)
def get_move(self, state: State, actions: list): return self.get_optimal_action(GameTreeNode(state))
class TestGameTreeNode(unittest.TestCase): def __init__(self, *args, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) self.state = None def setUp(self): players = [{ "color": "blue", "score": 0, "places": [] }, { "color": "yellow", "score": 0, "places": [] }, { "color": "red", "score": 0, "places": [] }] tiles = [[1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1]] self.state = State(players, tiles) self.state.place_penguin((4, 4)) self.state.place_penguin((4, 3)) self.state.place_penguin((4, 2)) self.state.place_penguin((3, 4)) self.state.place_penguin((3, 3)) self.state.place_penguin((3, 2)) self.state.place_penguin((2, 4)) self.state.place_penguin((2, 3)) self.state.place_penguin((2, 2)) self.tree = GameTreeNode(self.state) def test_check_valid_move(self): self.assertTrue(self.tree.check_valid_move((2, 4), (0, 4))) self.assertFalse(self.tree.check_valid_move((3, 4), (0, 4))) def test_generate_successor(self): successor = self.tree.generate_successor([(2, 4), (0, 4)]) players = [{ "color": "blue", "score": 1, "places": [(4, 4), (3, 4), (0, 4)] }, { "color": "yellow", "score": 0, "places": [(4, 3), (3, 3), (2, 3)] }, { "color": "red", "score": 0, "places": [(4, 2), (3, 2), (2, 2)] }] tiles = [[1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 0], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1]] expected_state = State(players, tiles, turn_index=1) self.assertEqual(successor, expected_state) def test_generate_successor_placement(self): players = [{ "color": "blue", "score": 0, "places": [] }, { "color": "yellow", "score": 0, "places": [] }, { "color": "red", "score": 0, "places": [] }] tiles = [[1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1]] state = State(players, tiles) node = GameTreeNode(state) successor = node.generate_successor([(0, 0)]) node = GameTreeNode(successor) successor = node.generate_successor([(0, 1)]) node = GameTreeNode(successor) successor = node.generate_successor([(0, 2)]) players = [{ "color": "blue", "score": 0, "places": [[0, 0]] }, { "color": "yellow", "score": 0, "places": [[0, 1]] }, { "color": "red", "score": 0, "places": [[0, 2]] }] tiles = [[1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1]] expected_state = State(players, tiles, turn_index=0) self.assertEqual(successor, expected_state)