python-2.7pygameboids

Python boids implementation


I am trying to implement a fairly basic boids simulation in python. My goal is to have a simulation with a basic predator prey setup. I found some pseudocode (can't post more than two links but it is the first result if you google boids pseudocode) and some code here and decided to give it a go. Because I wanted to add predators, I decided to give take the code I found modify it so that the boids (that will become prey) are sprites, and then go from there. However, I have run into this problem.

After I modified the code to use pygame sprites, all of the boids move to the lower right hand corner (the original code worked correctly).

My code (just clone the repo) is here(github). Has anyone ever run into the first issue? Does anyone have any ideas to solve it? As for question 2, could someone please explain how to do that?

Thank you and any help would be greatly appreciated.

P.S.

The behavior of the boids (their movement) appears to be working fine apart from the fact that they always go to the lower right hand corner.

P.P.S.

Thanks to furas the prey behave correctly now.

P.P.P.S.

As the debugging problem has been solved, the part of my question that remains involves an explanation, and I think should be on topic.


Solution

  • You have to differences in your code

    1. You use different velocity speed at start - in __init__ but this shouldn't do difference.

    2. You updates object in different moment.

    You move all preys (using Group update) at the same time - after all calculations.

    Original code moves every boid after its calculations so next boid use different data to calculate its move.

    I put prey.update() inside for loops and remove all_sprites_list.update()

    I organize code in a little different:

    #!/usr/bin/env python
    # Prey implementation in Python using PyGame
    
    from __future__ import division # required in Python 2.7
    
    import sys
    import pygame
    import random
    import math
    
    # === constants === (UPPER_CASE names)
    
    SCREEN_SIZE = SCREEN_WIDTH, SCREEN_HEIGHT = 800, 600
    
    BLACK = (0, 0, 0)
    
    MAX_VELOCITY = 10
    NUM_BOIDS = 50
    
    BORDER = 25
    
    # === classes === (CamelCase names for classes / lower_case names for method)
    
    class Prey(pygame.sprite.Sprite):
    
        def __init__(self, x, y):
            super(Prey, self).__init__()
    
            # Load image as sprite
            self.image = pygame.image.load("ressources/img/prey.png").convert()
    
            # Fetch the rectangle object that has the dimensions of the image
            self.rect = self.image.get_rect()
    
            # Coordinates
            self.rect.x = x
            self.rect.y = y
    
    #        self.velocityX = random.randint(-10, 10) / 10.0
    #        self.velocityY = random.randint(-10, 10) / 10.0
    
            self.velocityX = random.randint(1, 10) / 10.0
            self.velocityY = random.randint(1, 10) / 10.0
    
        def distance(self, prey):
            '''Return the distance from another prey'''
    
            distX = self.rect.x - prey.rect.x
            distY = self.rect.y - prey.rect.y
    
            return math.sqrt(distX * distX + distY * distY)
    
        def move_closer(self, prey_list):
            '''Move closer to a set of prey_list'''
    
            if len(prey_list) < 1:
                return
    
            # calculate the average distances from the other prey_list
            avgX = 0
            avgY = 0
            for prey in prey_list:
                if prey.rect.x == self.rect.x and prey.rect.y == self.rect.y:
                    continue
    
                avgX += (self.rect.x - prey.rect.x)
                avgY += (self.rect.y - prey.rect.y)
    
            avgX /= len(prey_list)
            avgY /= len(prey_list)
    
            # set our velocity towards the others
            distance = math.sqrt((avgX * avgX) + (avgY * avgY)) * -1.0
    
            self.velocityX -= (avgX / 100)
            self.velocityY -= (avgY / 100)
    
    
        def move_with(self, prey_list):
            '''Move with a set of prey_list'''
    
            if len(prey_list) < 1:
                return
    
            # calculate the average velocities of the other prey_list
            avgX = 0
            avgY = 0
    
            for prey in prey_list:
                avgX += prey.velocityX
                avgY += prey.velocityY
    
            avgX /= len(prey_list)
            avgY /= len(prey_list)
    
            # set our velocity towards the others
            self.velocityX += (avgX / 40)
            self.velocityY += (avgY / 40)
    
        def move_away(self, prey_list, minDistance):
            '''Move away from a set of prey_list. This avoids crowding'''
    
            if len(prey_list) < 1:
                return
    
            distanceX = 0
            distanceY = 0
            numClose = 0
    
            for prey in prey_list:
                distance = self.distance(prey)
    
                if  distance < minDistance:
                    numClose += 1
                    xdiff = (self.rect.x - prey.rect.x)
                    ydiff = (self.rect.y - prey.rect.y)
    
                    if xdiff >= 0:
                        xdiff = math.sqrt(minDistance) - xdiff
                    elif xdiff < 0:
                        xdiff = -math.sqrt(minDistance) - xdiff
    
                    if ydiff >= 0:
                        ydiff = math.sqrt(minDistance) - ydiff
                    elif ydiff < 0:
                        ydiff = -math.sqrt(minDistance) - ydiff
    
                    distanceX += xdiff
                    distanceY += ydiff
    
            if numClose == 0:
                return
    
            self.velocityX -= distanceX / 5
            self.velocityY -= distanceY / 5
    
        def update(self):
            '''Perform actual movement based on our velocity'''
    
            if abs(self.velocityX) > MAX_VELOCITY or abs(self.velocityY) > MAX_VELOCITY:
                scaleFactor = MAX_VELOCITY / max(abs(self.velocityX), abs(self.velocityY))
                self.velocityX *= scaleFactor
                self.velocityY *= scaleFactor
    
            self.rect.x += self.velocityX
            self.rect.y += self.velocityY
    
    # === main === (lower_case names)
    
    # --- init ---
    
    pygame.init()
    screen = pygame.display.set_mode(SCREEN_SIZE)
    #screen_rect = screen.get_rect()
    
    # --- objects ---
    
    # lists
    # This is a list of 'sprites.' Each block in the program is
    # added to this list. The list is managed by a class called 'Group.'
    prey_list = pygame.sprite.Group()
    
    # This is a list of every sprite. All blocks and the player block as well.
    all_sprites_list = pygame.sprite.Group()
    
    # create prey_list at random positions
    for i in range(NUM_BOIDS):
        prey = Prey(random.randint(0, SCREEN_WIDTH), random.randint(0, SCREEN_HEIGHT))
        # Add the prey to the list of objects
        prey_list.add(prey)
        all_sprites_list.add(prey)
    
    # --- mainloop ---
    
    clock = pygame.time.Clock()
    
    running = True
    
    while running:
    
        # --- events ---
    
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    running = False
    
        # --- updates ---
    
        for prey in prey_list:
            closeBoids = []
            for otherBoid in prey_list:
                if otherBoid == prey:
                    continue
                distance = prey.distance(otherBoid)
                if distance < 200:
                    closeBoids.append(otherBoid)
    
            prey.move_closer(closeBoids)
            prey.move_with(closeBoids)
            prey.move_away(closeBoids, 20)
    
            # ensure they stay within the screen space
            # if we roubound we can lose some of our velocity
            if prey.rect.x < BORDER and prey.velocityX < 0:
                prey.velocityX = -prey.velocityX * random.random()
            if prey.rect.x > SCREEN_WIDTH - BORDER and prey.velocityX > 0:
                prey.velocityX = -prey.velocityX * random.random()
            if prey.rect.y < BORDER and prey.velocityY < 0:
                prey.velocityY = -prey.velocityY * random.random()
            if prey.rect.y > SCREEN_HEIGHT - BORDER and prey.velocityY > 0:
                prey.velocityY = -prey.velocityY * random.random()
    
            prey.update()
    
        # Calls update() method on every sprite in the list
        #all_sprites_list.update()
    
        # --- draws ---
    
        screen.fill(BLACK)
    
        # Draw all the spites
        all_sprites_list.draw(screen)
    
        # Go ahead and update the screen with what we've drawn.
        pygame.display.flip()
    
        # Limit to 60 frames per second
        # Used to manage how fast the screen updates
        clock.tick(120)
    
    # --- the end ---
    pygame.quit()
    sys.exit()
    

    EDIT: real problem was dividing of two integer numbers in Python 2 which gives result rounded to integer number

    Solution:

    from __future__ import division
    

    Use this before other imports.