示例#1
0
class Agent(Block):

    color_palette = choice([NETLOGO_PRIMARY_COLORS, PYGAME_COLORS])

    half_patch_pixel = pairs.Pixel_xy((HALF_PATCH_SIZE(), HALF_PATCH_SIZE()))

    id = 0

    key_step_done = True

    some_agent_changed = False

    def __init__(self,
                 center_pixel=None,
                 color=None,
                 scale=1.4,
                 shape_name='netlogo_figure'):
        # Can't make this a default value because pairs.CENTER_PIXEL()
        # isn't defined when the default values are compiled
        if center_pixel is None:
            center_pixel = pairs.center_pixel()

        if color is None:
            # Select a color at random from the color_palette
            color = choice(Agent.color_palette)[1]

        super().__init__(center_pixel, color)

        self.scale = scale

        self.shape_name = shape_name
        self.base_image = self.create_base_image()

        self.id = Agent.id
        Agent.id += 1

        World.agents.add(self)
        self.current_patch().add_agent(self)

        self.animation_target = None

        # Agents are created with a random heading and a velocity of 0.
        # In NetLogo, agents do not have a speed or velocity attribute. They have a heading attribute.
        # They move by a given amount (forward(amount)) in the heading direction.
        # After each forward() action, the agent is no longer moving. (But it retains its heading.)
        self.heading = randint(0, 359)
        self.velocity = Velocity.velocity_00

    def __str__(self):
        class_name = utils.get_class_name(self)
        return f'{class_name}-{self.id}{tuple(self.center_pixel.round())}'

    def agents_in_radius(self, distance):
        qualifying_agents = [
            agent for agent in World.agents
            if agent is not self and self.distance_to(agent) < distance
        ]
        return qualifying_agents

    def all_links(self):
        return [
            lnk for lnk in World.links if self in (lnk.agent_1, lnk.agent_2)
        ]

    def average_of_headings(self, agent_set, fn):
        """
        fn extracts a heading from an agent. This function returns the average of those headings.
        Cannot be static because fn may refer to self.
        agent_set may not be all the agents. So it must be passed as an argument.
        """
        # dx and dy are the x and y components of traveling one unit in the heading direction.
        dx = mean([utils.dx(fn(agent)) for agent in agent_set])
        dy = mean([utils.dy(fn(agent)) for agent in agent_set])
        return utils.dxdy_to_heading(dx, dy, default_heading=self.heading)

    def bounce_off_screen_edge(self, dxdy):
        """
       Bounce agent off the screen edges. dxdv is the current agent velocity.
       If the agent should bounce, change it as needed.
       """
        # The center pixel of this agent.
        current_center_pixel = self.center_pixel
        # Where the agent will be it if moves by dxdy.
        next_center_pixel = current_center_pixel + dxdy
        # The patch's row_col or next_center_pixel. Is that off the screen? If so, the agent should bounce.
        next_row_col = next_center_pixel.pixel_to_row_col()
        if next_row_col.row < 0 or gui.PATCH_ROWS <= next_row_col.row:
            dxdy = Velocity((dxdy.dx, dxdy.dy * (-1)))
        if next_row_col.col < 0 or gui.PATCH_COLS <= next_row_col.col:
            dxdy = Velocity((dxdy.dx * (-1), dxdy.dy))

        return dxdy

    def create_base_image(self):
        base_image = self.create_blank_base_image()

        factor = self.scale * PATCH_SIZE
        if self.shape_name in SHAPES:
            # Instead of using pygame's smoothscale to scale the image, scale the polygon instead.
            scaled_shape = [(v[0] * factor, v[1] * factor)
                            for v in SHAPES[self.shape_name]]
            pg.draw.polygon(base_image, self.color, scaled_shape, 0)
        return base_image

    def create_blank_base_image(self):

        # Give the agent a larger Surface (by sqrt(2)) to work with since it may rotate.
        surface_size = XY((self.rect.width, self.rect.height)) * SQRT_2
        blank_base_image = Surface(surface_size)

        # This sets the rectangle to be transparent.
        # Otherwise it would be black and would cover nearby agents.
        # Even though it's a method of Surface, it can also take a Surface parameter.
        # If the Surface parameter is not given, PyCharm complains.
        # noinspection PyArgumentList
        blank_base_image = blank_base_image.convert_alpha()
        blank_base_image.fill((0, 0, 0, 0))
        return blank_base_image

    def current_patch(self) -> Patch:
        row_col: RowCol = (self.center_pixel).pixel_to_row_col()
        patch = World.patches_array[row_col.row, row_col.col]
        return patch

    def delete(self):
        self.current_patch().remove_agent(self)
        World.agents.remove(self)
        World.links -= {lnk for lnk in World.links if lnk.includes(self)}

    def distance_to(self, other):
        dist = self.distance_to_pixel(other.center_pixel)
        # wrap = not gui_get('Bounce?')
        # dist = (self.center_pixel).distance_to(other.center_pixel, wrap)
        return dist

    def distance_to_pixel(self, pxl):
        dist = (self.center_pixel).distance_to(pxl)
        return dist

    def draw(self, shape_name=None):
        # No point in rotating circles or nodes. Only rotate SHAPES.
        if self.shape_name in SHAPES:
            self.image = pgt.rotate(self.base_image, -self.heading)
            self.rect = self.image.get_rect(center=self.center_pixel)
        super().draw(shape_name=self.shape_name)

    def face_xy(self, xy: Pixel_xy):
        new_heading = (self.center_pixel).heading_toward(xy)
        self.set_heading(new_heading)

    def forward(self, speed=1):
        velocity = heading_and_speed_to_velocity(self.heading, speed)
        self.set_velocity(velocity)
        self.move_by_velocity()

    def heading_toward(self, target):
        """ The heading required to face the target """
        from_pixel = self.center_pixel
        to_pixel = target.center_pixel
        return from_pixel.heading_toward(to_pixel)

    def in_links(self):
        return [
            lnk for lnk in World.links if lnk.directed and lnk.agent_2 is self
        ]

    def lnk_nbrs(self):
        """
        Return a list of links from this node and the nodes to which they attach.
        """
        lns = [(lnk, lnk.other_side()) for lnk in World.links
               if lnk.includes(self)]
        return lns

    def move_by_dxdy(self, dxdy: Velocity):
        """
        Move to self.center_pixel + (dx, dy)
        """
        # noinspection PyTypeChecker
        new_center_pixel_unwrapped: Pixel_xy = self.center_pixel + dxdy
        # Wrap around the grid of pixels.
        new_center_pixel_wrapped = new_center_pixel_unwrapped.wrap()
        self.move_to_xy(new_center_pixel_wrapped)

    def move_by_velocity(self):
        if gui_get('Bounce?'):
            new_velocity = self.bounce_off_screen_edge(self.velocity)
            if self.velocity != new_velocity:
                self.set_velocity(new_velocity)
        self.move_by_dxdy(self.velocity)

    def move_agent(self, delta: Velocity):
        Agent.some_agent_changed = True
        (capped_x, capped_y) = delta.cap_abs_value(1)

        # Note that x and y have been defined to be getters for center pixels (x, y).
        new_center_pixel = Pixel_xy((self.x + capped_x, self.y + capped_y))
        self.move_to_xy(new_center_pixel)

    def move_to_patch(self, patch):
        self.move_to_xy(patch.center_pixel)

    def move_to_xy(self, xy: Pixel_xy):
        """
        Remove this agent from the list of agents at its current patch.
        Move this agent to its new patch with center_pixel xy.
        Add this agent to the list of agents in its new patch.
        """
        current_patch: Patch = self.current_patch()
        current_patch.remove_agent(self)
        self.set_center_pixel(xy)
        new_patch = self.current_patch()
        new_patch.add_agent(self)

    def out_links(self):
        return [
            lnk for lnk in World.links if lnk.directed and lnk.agent_1 is self
        ]

    @staticmethod
    def run_an_animation_step():
        Agent.key_step_done = True
        visited_agents = set()
        for node in World.agents:
            if node.animation_target and node not in visited_agents:
                visited_agents.add(node)
                node.take_animation_step()

    def set_center_pixel(self, xy: Pixel_xy):
        self.center_pixel: Pixel_xy = xy.wrap()
        # Set the center point of this agent's rectangle.
        self.rect.center = (self.center_pixel - Agent.half_patch_pixel).round()

    def set_color(self, color):
        self.color = color
        self.base_image = self.create_base_image()

    def set_heading(self, heading):
        # Keep heading as an int in range(360)
        self.heading = int(round(heading))

    def set_target_by_dxdy(self, velocity):
        self.animation_target = self.center_pixel + velocity

    # noinspection PyTypeChecker
    def take_animation_step(self):
        if not self.animation_target:
            return

        delta = self.animation_target - self.center_pixel
        if abs(delta.x) > 0 or abs(delta.y) > 0:
            Agent.key_step_done = False
        self.move_agent(delta)

        if abs(self.distance_to_pixel(self.animation_target)) < 0.5:
            self.move_to_xy(self.animation_target)
            self.animation_target = None

    def turn_left(self, delta_angles):
        self.turn_right(-delta_angles)

    def turn_right(self, delta_angles):
        self.set_heading(utils.normalize_360(self.heading + delta_angles))

    def set_velocity(self, velocity):
        self.velocity = velocity
        self.face_xy(self.center_pixel + velocity)

    def undirected_links(self):
        return [lnk for lnk in self.all_links() if not lnk.directed]

    @property
    def x_y(self):
        return self.center_pixel.round().as_tuple()

    @property
    def x(self):
        return self.center_pixel.x

    @property
    def y(self):
        return self.center_pixel.y
示例#2
0
class Agent(Block):

    color_palette = choice([NETLOGO_PRIMARY_COLORS, PYGAME_COLORS])

    half_patch_pixel = pairs.Pixel_xy((HALF_PATCH_SIZE(), HALF_PATCH_SIZE()))

    id = 0

    SQRT_2 = sqrt(2)

    def __init__(self,
                 center_pixel=None,
                 color=None,
                 scale=1.4,
                 shape=SHAPES['netlogo_figure']):
        # Can't make this a default value because pairs.CENTER_PIXEL() isn't defined
        # when the default values are compiled
        if center_pixel is None:
            center_pixel = pairs.center_pixel()

        if color is None:
            # Select a color at random from the color_palette
            color = choice(Agent.color_palette)[1]

        super().__init__(center_pixel, color)

        self.scale = scale
        self.shape = shape
        self.base_image = self.create_base_image()

        self.id = Agent.id
        Agent.id += 1
        self.label = None
        World.agents.add(self)
        self.current_patch().add_agent(self)
        self.heading = randint(0, 359)
        self.speed = 1
        # To keep PyCharm happy.
        self.velocity = Velocity.velocity_00

    def __str__(self):
        class_name = utils.get_class_name(self)
        return f'{class_name}-{self.id}{tuple(self.center_pixel.round())}'

    def agents_in_radius(self, distance):
        qualifying_agents = [
            agent for agent in World.agents
            if agent is not self and self.distance_to(agent) < distance
        ]
        return qualifying_agents

    def all_links(self):
        return [
            lnk for lnk in World.links if self in (lnk.agent_1, lnk.agent_2)
        ]

    def average_of_headings(self, agent_set, fn):
        """
        fn extracts a heading from an agent. This function returns the average of those headings.
        Cannot be static because fn may refer to self.
        agent_set may not be all the agents. So it must be passed as an argument.
        """
        # dx and dy are the x and y components of traveling one unit in the heading direction.
        dx = mean([utils.dx(fn(agent)) for agent in agent_set])
        dy = mean([utils.dy(fn(agent)) for agent in agent_set])
        return utils.dxdy_to_heading(dx, dy, default_heading=self.heading)

    def bounce_off_screen_edge(self, dxdy):
        """
       Bounce agent off the screen edges. dxdv is the current agent velocity.
       If the agent should bounce, change it as needed.
       """
        # The center pixel of this agent.
        current_center_pixel = self.center_pixel
        # Where the agent will be it if moves by dxdy.
        next_center_pixel = current_center_pixel + dxdy
        # The patch's row_col or next_center_pixel. Is that off the screen? If so, the agent should bounce.
        next_row_col = next_center_pixel.pixel_to_row_col()
        if next_row_col.row < 0 or gui.PATCH_ROWS <= next_row_col.row:
            dxdy = Velocity((dxdy.dx, dxdy.dy * (-1)))
        if next_row_col.col < 0 or gui.PATCH_COLS <= next_row_col.col:
            dxdy = Velocity((dxdy.dx * (-1), dxdy.dy))

        return dxdy

    def create_base_image(self):
        base_image = self.create_blank_base_image()

        # Instead of using pygame's smoothscale to scale the image, scale the polygon instead.
        factor = self.scale * PATCH_SIZE
        scaled_shape = [(v[0] * factor, v[1] * factor) for v in self.shape]
        pg.draw.polygon(base_image, self.color, scaled_shape)
        return base_image

    def create_blank_base_image(self):
        # Give the agent a larger Surface (by sqrt(2)) to work with since it may rotate.
        blank_base_image = Surface(
            (self.rect.w * Agent.SQRT_2, self.rect.h * Agent.SQRT_2))
        # This sets the rectangle to be transparent.
        # Otherwise it would be black and would cover nearby agents.
        # Even though it's a method of Surface, it can also take a Surface parameter.
        # If the Surface parameter is not given, PyCharm complains.
        # noinspection PyArgumentList
        blank_base_image = blank_base_image.convert_alpha()
        blank_base_image.fill((0, 0, 0, 0))
        return blank_base_image

    def current_patch(self) -> Patch:
        row_col: RowCol = (self.center_pixel).pixel_to_row_col()
        patch = World.patches_array[row_col.row, row_col.col]
        return patch

    def distance_to(self, other):
        wrap = not SimEngine.gui_get('Bounce?')
        dist = (self.center_pixel).distance_to(other.center_pixel, wrap)
        return dist

    def draw(self):
        self.image = pgt.rotate(self.base_image, -self.heading)
        self.rect = self.image.get_rect(center=self.center_pixel)
        super().draw()

    def face_xy(self, xy: Pixel_xy):
        new_heading = (self.center_pixel).heading_toward(xy)
        self.set_heading(new_heading)

    def forward(self, speed=None):
        if speed is None:
            speed = self.speed
        dxdy = pairs.heading_to_dxdy(self.heading) * speed
        self.move_by_dxdy(dxdy)

    def heading_toward(self, target):
        """ The heading required to face the target """
        from_pixel = self.center_pixel
        to_pixel = target.center_pixel
        return from_pixel.heading_toward(to_pixel)

    def in_links(self):
        return [
            lnk for lnk in World.links if lnk.directed and lnk.agent_2 is self
        ]

    def move_by_dxdy(self, dxdy: Velocity):
        """
        Move to self.center_pixel + (dx, dy)
        """
        if SimEngine.gui_get('Bounce?'):
            new_dxdy = self.bounce_off_screen_edge(dxdy)
            if dxdy is self.velocity:
                self.set_velocity(new_dxdy)
            dxdy = new_dxdy
        new_center_pixel_unwrapped = self.center_pixel + dxdy
        # Wrap around the grid of pixels.
        new_center_pixel_wrapped = new_center_pixel_unwrapped.wrap()
        self.move_to_xy(new_center_pixel_wrapped)

    def move_by_velocity(self):
        self.move_by_dxdy(self.velocity)

    def move_to_patch(self, patch):
        self.move_to_xy(patch.center_pixel)

    def move_to_xy(self, xy: Pixel_xy):
        """
        Remove this agent from the list of agents at its current patch.
        Move this agent to its new patch with center_pixel xy.
        Add this agent to the list of agents in its new patch.
        """
        current_patch: Patch = self.current_patch()
        current_patch.remove_agent(self)
        self.set_center_pixel(xy)
        new_patch = self.current_patch()
        new_patch.add_agent(self)

    def out_links(self):
        return [
            lnk for lnk in World.links if lnk.directed and lnk.agent_1 is self
        ]

    def set_center_pixel(self, xy: Pixel_xy):
        self.center_pixel: Pixel_xy = xy.wrap()
        # Set the center point of this agent's rectangle.
        self.rect.center = (self.center_pixel - Agent.half_patch_pixel).round()

    def set_color(self, color):
        self.color = color
        self.base_image = self.create_base_image()

    def set_heading(self, heading):
        # Keep heading an int in range(360)
        self.heading = int(round(heading))

    def turn_left(self, delta_angles):
        self.turn_right(-delta_angles)

    def turn_right(self, delta_angles):
        self.set_heading(utils.normalize_360(self.heading + delta_angles))

    def set_velocity(self, velocity):
        self.velocity = velocity
        self.face_xy(self.center_pixel + velocity)

    def undirected_links(self):
        return [lnk for lnk in self.all_links() if not lnk.directed]