Ejemplo n.º 1
0
 def test_longest_path_to_tail(self):
     m = Map(6, 6)
     m.create_food(Pos(4, 4))
     s = Snake(m, Direc.RIGHT,
               [Pos(1, 3), Pos(1, 2), Pos(1, 1)],
               [PointType.HEAD_R, PointType.BODY_HOR, PointType.BODY_HOR])
     solver = PathSolver(s)
     # noinspection PyUnusedLocal
     act_path = solver.longest_path_to_tail()
     act_path = solver.longest_path_to_tail()  # Check idempotency
     expect_path = [
         Direc.RIGHT, Direc.DOWN, Direc.DOWN, Direc.DOWN, Direc.LEFT,
         Direc.LEFT, Direc.LEFT, Direc.UP, Direc.RIGHT, Direc.RIGHT,
         Direc.UP, Direc.LEFT, Direc.LEFT, Direc.UP
     ]
     assert m.point(s.tail()).type == PointType.BODY_HOR
     assert len(act_path) == len(expect_path)
     for i, direc in enumerate(act_path):
         assert direc == expect_path[i]
     # Empty path because the tail has not been 'emptied' yet,
     # so it is not a valid position to be added to the queue.
     # This means that after all the evaluations, None of them check the snake tail
     # Because it is not valid, according to the function, since
     # it is part of the body.
     # Therefore, we pass the path finders through a function called path_to
     # to remove the 'tail' at that location so it can be evaluated.
     assert not solver.longest_path_to(s.tail())
Ejemplo n.º 2
0
 def __init__(self, snake, shortcuts=True):
     if snake.map.num_rows % 2 != 0 or snake.map.num_cols % 2 != 0:
         raise ValueError('num_rows and num_cols must be even.')
     super().__init__(snake)
     self.__shortcuts = shortcuts
     self.__path_solver = PathSolver(snake)
     self.__table = [[_TableCell() for _ in range(snake.map.num_cols)] for _ in range(snake.map.num_rows)]
     self.__build_cycle()
Ejemplo n.º 3
0
class GreedySolver(BaseSolver):
    """    A greedy little snake that only seeks to eat food. smh.    """
    def __init__(self, snake):
        super().__init__(snake)
        self.__path_solver = PathSolver(snake)

    def next_direc(self):
        """
        Get the next direction to move in.
        :return: A direction of type Direc.
        """
        # Clone the snake.
        s_copy, m_copy = self.snake.copy()
        # Step 1: Get the path to the food. If path 1 exists, move to step 2.
        # Otherwise, move to step 4.
        self.__path_solver.snake = self.snake  # That's my snake you're looking at!
        path_to_food = self.__path_solver.shortest_path_to_food()
        if path_to_food:
            # Step 2: Make a virtual snake to eat the food along the path.
            s_copy.move_path(path_to_food)
            if m_copy.is_full():
                return path_to_food[0]
            # Step 3: Calculate the longest path from head to tail after eating food.
            # If that longest path exists, then move along that path.
            # Otherwise, go to step 4.
            self.__path_solver.snake = s_copy
            path_to_tail = self.__path_solver.longest_path_to_tail()
            if len(path_to_tail) > 1:
                return path_to_food[0]

        # Step 4: Calculate the longest path from head to tail.
        # Remember, there is no path to the food right now that will guarantee our survival.
        # Therefore, we do one full loop, and then check again.
        # If that path exists, then move along that path.
        # Else, move to step 5.
        self.__path_solver.snake = self.snake
        path_to_tail = self.__path_solver.longest_path_to_tail()
        if len(path_to_tail) > 1:
            return path_to_tail[0]

        # Step 5: RUN AWAY! No, seriously, get as far away as you can from the food.
        head = self.snake.head()
        direc, max_dist = self.snake.direc, -1
        for adj in head.all_adj():
            if self.map.is_safe(adj):
                dist = Pos.manhattan_distance(adj, self.map.food)
                if dist > max_dist:
                    max_dist = dist
                    direc = head.direction_to(adj)
        return direc
Ejemplo n.º 4
0
def test_shortest():
    m = Map(7, 7)
    m.create_food(Pos(5, 5))
    s = Snake(m, Direc.RIGHT,
              [Pos(2, 3), Pos(2, 2), Pos(2, 1)],
              [PointType.HEAD_R, PointType.BODY_HOR, PointType.BODY_HOR])
    solver = PathSolver(s)
    act_path = solver.shortest_path_to_food()
    act_path = solver.shortest_path_to_food()  # Check idempotence
    expect_path = [
        Direc.RIGHT, Direc.RIGHT, Direc.DOWN, Direc.DOWN, Direc.DOWN
    ]
    assert len(act_path) == len(expect_path)
    for i, direc in enumerate(act_path):
        assert direc == expect_path[i]
    assert solver.table[5][1].dist == 5
    assert solver.table[5][1].dist == solver.table[5][5].dist
    # Empty path
    assert not solver.shortest_path_to(s.tail())
Ejemplo n.º 5
0
def test_longest():
    m = Map(6, 6)
    m.create_food(Pos(4, 4))
    s = Snake(m, Direc.RIGHT,
              [Pos(1, 3), Pos(1, 2), Pos(1, 1)],
              [PointType.HEAD_R, PointType.BODY_HOR, PointType.BODY_HOR])
    solver = PathSolver(s)
    act_path = solver.longest_path_to_tail()
    act_path = solver.longest_path_to_tail()  # Check idempotence
    expect_path = [
        Direc.RIGHT, Direc.DOWN, Direc.DOWN, Direc.DOWN, Direc.LEFT,
        Direc.LEFT, Direc.LEFT, Direc.UP, Direc.RIGHT, Direc.RIGHT, Direc.UP,
        Direc.LEFT, Direc.LEFT, Direc.UP
    ]
    assert m.point(s.tail()).type == PointType.BODY_HOR
    assert len(act_path) == len(expect_path)
    for i, direc in enumerate(act_path):
        assert direc == expect_path[i]
    # Empty path
    assert not solver.longest_path_to(s.tail())
Ejemplo n.º 6
0
 def test_shortest_path_to_food(self):
     m = Map(7, 7)
     m.create_food(Pos(5, 5))
     s = Snake(m, Direc.RIGHT,
               [Pos(2, 3), Pos(2, 2), Pos(2, 1)],
               [PointType.HEAD_R, PointType.BODY_HOR, PointType.BODY_HOR])
     solver = PathSolver(s)
     # noinspection PyUnusedLocal
     act_path = solver.shortest_path_to_food()
     act_path = solver.shortest_path_to_food()  # Check idempotency
     # Idempotency is pretty much checking for side effects.
     # You're checking whether or not the algorithm will produce the same result if it is run multiple times.
     # We do this multiple times to verify that the table has been properly reset.
     expect_path = [
         Direc.RIGHT, Direc.RIGHT, Direc.DOWN, Direc.DOWN, Direc.DOWN
     ]
     assert len(act_path) == len(expect_path)
     for i, direc in enumerate(act_path):
         assert direc == expect_path[i]
     assert solver.table[5][1].dist == 5
     assert solver.table[5][1].dist == solver.table[5][5].dist
     # Empty path
     assert not solver.shortest_path_to(s.tail())
Ejemplo n.º 7
0
 def __init__(self, snake):
     super().__init__(snake)
     self.__path_solver = PathSolver(snake)
Ejemplo n.º 8
0
class HamiltonSolver(BaseSolver):
    """
    A snake called Hamilton. It goes around in big, big circles.
    """

    def __init__(self, snake, shortcuts=True):
        if snake.map.num_rows % 2 != 0 or snake.map.num_cols % 2 != 0:
            raise ValueError('num_rows and num_cols must be even.')
        super().__init__(snake)
        self.__shortcuts = shortcuts
        self.__path_solver = PathSolver(snake)
        self.__table = [[_TableCell() for _ in range(snake.map.num_cols)] for _ in range(snake.map.num_rows)]
        self.__build_cycle()

    @property
    def table(self):
        """
        :return: The poor table cells, all bunched up and forced to be numbers and strings.
        """
        return self.__table

    def next_direc(self):
        """
        Gets the next direction, taking shortcuts if it won't disrupt the cycle.
        :return: The next direction to go in of type Direc.
        """
        head = self.snake.head()
        nxt_direc = self.__table[head.x][head.y].direc
        # We should take shortcuts if the snake isn't too long, to speed up gameplay.
        if self.__shortcuts and self.snake.len() < 0.5 * self.map.capacity:
            path = self.__path_solver.shortest_path_to_food()
            # Check if there is a path from the head to the food.
            if path:
                tail, nxt, food = self.snake.tail(), head.adj(path[0]), self.map.food
                tail_idx = self.__table[tail.x][tail.y].idx  # Get the location of the tail on the hamiltonian cycle.
                head_idx = self.__table[head.x][head.y].idx  # Get the location of the head on the hamiltonian cycle.
                nxt_idx = self.__table[nxt.x][nxt.y].idx
                # Get the location of the next move,
                # if Hamilton were to follow the shortest path to the food.
                food_idx = self.__table[food.x][food.y].idx  # Get the location of the food on the hamiltonian cycle.
                if not (len(path) == 1 and abs(food_idx - tail_idx) == 1):
                    # Make sure that either the path does not lead to instant death.
                    # This is because if it takes exactly one move to get to the food,
                    # and the tail is next on the hamiltonian cycle,
                    # the tail will "freeze" and so the head, which will continue to follow the hamiltonian cycle
                    # (remember, it does not have any self-preserving capabilities like the greedy solver),
                    # will smash the tail and will result in certain death.
                    head_idx_rel = self.__relative_dst(tail_idx, head_idx, self.map.capacity)
                    nxt_idx_rel = self.__relative_dst(tail_idx, nxt_idx, self.map.capacity)
                    food_idx_rel = self.__relative_dst(tail_idx, food_idx, self.map.capacity)
                    if head_idx_rel < nxt_idx_rel <= food_idx_rel:  # Average 1786.16 for 100 tests.
                        # if head_idx < nxt_idx <= food_idx:  # Fails quite often. Still trying to figure out why.
                        # If the next move suggested by the bfs solver would jump the head closer to the food,
                        # and wouldn't overshoot the food, then go ahead.
                        nxt_direc = path[0]
        return nxt_direc

    def __build_cycle(self):
        """
        Build a hamiltonian cycle on the map.
        0 is the head, and capacity of the map is the end of the cycle.
        :return: Void.
        """
        path = self.__path_solver.longest_path_to_tail()
        # The longest path to the tail, assuming that the map is square and even,
        # guarantees that the path will pass through every single point on the map.
        cur, cnt = self.snake.head(), 0
        for direc in path:
            self.__table[cur.x][cur.y].idx = cnt
            self.__table[cur.x][cur.y].direc = direc
            cur = cur.adj(direc)
            cnt += 1
        tail = self.snake.tail()
        self.__table[tail.x][tail.y].idx = cnt
        self.__table[tail.x][tail.y].direc = self.snake.direc
        # With this we have a cycle that's composed of the whole grid.
        # It might seem a little boring, but eh.

    @staticmethod
    def __relative_dst(tail, x, size):
        """
        Gets the distance from tail to x on the hamiltonian cycle.
        :param tail: The position of the tail on the hamiltonian cycle of type Integer.
        :param x: An index on the hamiltonian cycle of type integer.
        :param size: The length of the cycle. This is so that if the tail is ahead of x at some point,
                     we can establish the total number of steps needed to get from
                     tail to x following the hamiltonian cycle.
        :return: Relative distance from the tail to the location of type Integer.
        """
        if tail > x:
            x += size
        return x - tail