0

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.

Fake Name
  • 813
  • 2
  • 9
  • 17
  • I have already rewritten this code and I am fairly certain that the sprites are the reason the boids behave strangely. How to I fix this? I am really frustrated! – Fake Name Jan 31 '16 at 20:20
  • use many `print()` to see what happens in code - print values in variables and text like `"i'm in else"` – furas Jan 31 '16 at 22:12
  • I tried that. I still do not know where the problem is coming from because it only appears when I use sprites instead of just blitting the images directly (which is when it works). – Fake Name Jan 31 '16 at 22:56
  • I didn't check code but it looks like you use the same values to change sprite position instead of different values. I would use `print()` to see position of all sprites and to see values which change sprites position.. – furas Jan 31 '16 at 23:13
  • I just finished checking. None of the values seem to repeat and a decent amount of the velocities during the first iteration are negative. – Fake Name Jan 31 '16 at 23:25
  • Also, the behavior functions are in a `for loop` and as a result get their parameters updated every iteration. – Fake Name Feb 01 '16 at 02:45
  • SO is place for questions with `code`+ `error message` so it is not good place for your questions. – furas Feb 02 '16 at 02:53
  • I edited my question. Thanks to you there is only the concept part left. Where should I put it then? – Fake Name Feb 02 '16 at 03:00
  • maybe http://gamedev.stackexchange.com or http://codereview.stackexchange.com – furas Feb 02 '16 at 03:02
  • Is there a way to migrate a question? – Fake Name Feb 02 '16 at 03:06
  • If I remove the first sub-question is it on topic? – Fake Name Feb 02 '16 at 03:09

1 Answers1

0

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.

furas
  • 134,197
  • 12
  • 106
  • 148
  • Thanks, but now the boids do the same thing but also move really fast. – Fake Name Feb 01 '16 at 03:56
  • I removed all `delay()` and set `tick(120)` 120FPS - it gives less than 10ms delay (1000ms/120FPS) – furas Feb 01 '16 at 04:53
  • That I understand. The problem is that all of the boids still go down to the lower right hand corner. – Fake Name Feb 01 '16 at 21:12
  • I run my code and every time they go to different direction. They move on all screen. – furas Feb 01 '16 at 21:23
  • For me, the boids start all spread out, but quickly go toward the lower right hand corner where they move normally except for staying close to the corner (every time they they go out a bit, they all turn back towards the corner). – Fake Name Feb 01 '16 at 21:38
  • I found problem - I run it on Python 3. I try it with Python 2 and get the same problem. And I found solution - in Python 2 result of dividing two integer numbers is rounded to integer value but if you divide integer by float you get correct result. Use float number in dividing - for example use `100.0` instead of `100` in `(avgX / 100)`. Now I think your original code can work properly without my modifications if you use float numbers. – furas Feb 01 '16 at 21:49
  • or use `from __future__ import division` before other imports - see in code in answer – furas Feb 01 '16 at 21:55
  • Thanks! That fixed it! As for how to add a predator, could you please just give me a basic idea of how I would do that? – Fake Name Feb 01 '16 at 22:11
  • Also, in the interest of improving, how on earth did you figure that out? – Fake Name Feb 01 '16 at 22:38
  • P.S. I wish I could upvote your answer, but I do not have enough reputation. – Fake Name Feb 01 '16 at 22:38
  • I never met `boids` before so I don't know how predator works and I can't help you. – furas Feb 02 '16 at 02:55
  • Ok, thanks for getting it to work! – Fake Name Feb 02 '16 at 03:00