1

I am making a Covid simulator and need my balls to move around randomly. However, I want them to stay moving in the first random direction that the chose, and only chnage direction if it hits another cell or hits the wall.

My current code is as follow:

import random
import pygame

# --- constants --- (UPPER_CASE_NAMES)

GREEN1 = (0, 255, 0)  # Healthy cells
RED = (255, 0, 0)  # Infected cells
GREEN2 = (0, 100, 0)  # Healthy cells not susecptible
BLACK = (0, 0, 0)  # Dead cells
WHITE = (255, 255, 255)

BACKGROUND_COLOR = (225, 198, 153)

SCREEN_SIZE = (800, 800)


# --- classes --- (CamelCaseNames)

# class keeep only one cell so it should has name `Cell` instead of `Cells`

class Cell(pygame.sprite.Sprite):

    def __init__(self, color, speed, width, height):
        super().__init__()

        self.color = color
        self.speed = speed

        self.image = pygame.Surface([width, height])
        self.image.fill(WHITE)
        self.image.set_colorkey(WHITE)

        self.radius = width // 2  # 25
        center = [width // 2, height // 2]
        pygame.draw.circle(self.image, self.color, center, self.radius, width=0)

        self.rect = self.image.get_rect()
        self.rect.x = random.randint(0, 400)
        self.rect.y = random.randint(50, 700)

        self.pos = pygame.math.Vector2(self.rect.center)
        self.dir = pygame.math.Vector2(1, 0).rotate(random.randrange(360))

    def update(self):
        self.pos += self.dir * self.speed

        if self.pos.x - self.radius < 0 or self.pos.x + self.radius > SCREEN_SIZE[0]:
            self.dir.x *= -1
        if self.pos.y - self.radius < 0 or self.pos.y + self.radius > SCREEN_SIZE[1]:
            self.dir.y *= -1

        for other_cell in all_cells:
            if all_cells != self:
                distance_vec = self.pos - other_cell.pos
                if 0 < distance_vec.length_squared() < (self.radius * 2) ** 2:
                    self.dir.reflect_ip(distance_vec)
                    other_cell.dir.reflect_ip(distance_vec)

        self.rect.centerx = round(self.pos.x)
        self.rect.centery = round(self.pos.y)


# --- functions --- (lower_case_names)

# empty

# --- main --- (lower_case_names)

pygame.init()

screen = pygame.display.set_mode(SCREEN_SIZE)
pygame.display.set_caption("Covid-19 Simualtion")

speed = [0.5, -0.5]

# - objects -

all_cells = pygame.sprite.Group()  # PEP8: lower_case_name

for _ in range(5):
    cell = Cell(GREEN1, 5, 10, 10)  # PEP8: lower_case_name
    all_cells.add(cell)

# - loop -

clock = pygame.time.Clock()

end = False
while not end:

    # - events -

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            end = True

    # - upadates (without draws) -

    all_cells.update()

    # - draws (without updates) -

    screen.fill(BACKGROUND_COLOR)
    pygame.draw.rect(screen, BLACK, (0, 50, 400, 700), 3)

    all_cells.draw(screen)

    pygame.display.flip()
    clock.tick(30)  # to use less CPU

# - end

pygame.quit()  # some system may need it to close window

I was wondering if there was a way to do this, really grateful for any help. Thanks in advance. I tried copy the first answer below into my pycharm but it didnt work, so now i have put my full code up as opposed to one section in case it is needed. Sorry for not putting it all before

1 Answer 1

1

I recommend to use pygame.math.Vector2. Add the attributes pos and dir(position and direction) to the class Cell. Set the position with the center of the rect attribute and generate a vector with a random direction:

class Cell(pygame.sprite.Sprite):
    def __init__(self, color, speed, width, height):
        # [...]

        self.pos = pygame.math.Vector2(self.rect.center)
        self.dir = pygame.math.Vector2(1, 0).rotate(random.randrange(360))

Change the position in t he method update. Add the product of the direction vector dir and the speed to the position (pos). Update the position of the rectangle by rounding (round) the position vector:

class Cell(pygame.sprite.Sprite):
    # [...]

    def update(self):
        self.pos += self.dir * self.speed
        self.rect.centerx = round(self.pos.x)
        self.rect.centery = round(self.pos.y)

See How to make ball bounce off wall with Pygame? and apply the suggestions to your code:

class Cell(pygame.sprite.Sprite):
    # [...]

    def update(self):
        self.pos += self.dir * self.speed

        if self.pos.x - self.radius < 0:
            self.pos.x = self.radius
            self.dir.x = abs(self.dir.x)
        elif self.pos.x + self.radius > 400:
            self.pos.x = 400 - self.radius
            self.dir.x = -abs(self.dir.x)
        if self.pos.y - self.radius < 50:
            self.pos.y = 50 + self.radius
            self.dir.y = abs(self.dir.y)
        elif self.pos.y + self.radius > 700:
            self.pos.y = 700 - self.radius
            self.dir.y = -abs(self.dir.y) 

        self.rect.centerx = round(self.pos.x)
        self.rect.centery = round(self.pos.y)

See Pygame how to let balls collide. Test to see if the cells collide and reflect the direction vectors when a collision is detected:

class Cell(pygame.sprite.Sprite):
    # [...]

    def update(self):
        self.pos += self.dir * self.speed

        # [...] 

        for other_cell in all_cells:
            if all_cells != self:
                distance_vec = self.pos - other_cell.pos
                if 0 < distance_vec.length_squared() < (self.radius*2) ** 2:
                    self.dir.reflect_ip(distance_vec)
                    other_cell.dir.reflect_ip(distance_vec)

        self.rect.centerx = round(self.pos.x)
        self.rect.centery = round(self.pos.y)

See also Collision and Intersection - Circle and circle


Complete example:

import random
import pygame

class Particle(pygame.sprite.Sprite):
    def __init__(self, hue, pos, radius, dir, vel):
        super().__init__()
        self.pos = pygame.math.Vector2(pos)
        self.dir = pygame.math.Vector2(dir)
        self.vel = vel
        self.radius = radius
        self.rect = pygame.Rect(round(self.pos.x - radius), round(self.pos.y - radius), radius*2, radius*2)
        self.image = pygame.Surface((radius*2, radius*2))
        self.changeColor(hue)

    def changeColor(self, hue):
        self.hue = hue
        color = pygame.Color(0)
        color.hsla = (self.hue, 100, 50, 100)
        self.image.set_colorkey((0, 0, 0))
        self.image.fill(0)
        pygame.draw.circle(self.image, color, (self.radius, self.radius), self.radius)

    def move(self):
        self.pos += self.dir * self.vel

    def update(self, border_rect):

        if self.pos.x - self.radius < border_rect.left:
            self.pos.x = border_rect.left + self.radius
            self.dir.x = abs(self.dir.x)
        elif self.pos.x + self.radius > border_rect.right:
            self.pos.x = border_rect.right - self.radius
            self.dir.x = -abs(self.dir.x)
        if self.pos.y - self.radius < border_rect.top:
            self.pos.y = border_rect.top + self.radius
            self.dir.y = abs(self.dir.y)
        elif self.pos.y + self.radius > border_rect.bottom:
            self.pos.y = border_rect.bottom - self.radius
            self.dir.y = -abs(self.dir.y) 

        self.rect = self.image.get_rect(center = (round(self.pos.x), round(self.pos.y)))

pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
rect_area = window.get_rect().inflate(-40, -40)

all_particles = pygame.sprite.Group()
radius, velocity = 5, 1
pos_rect = rect_area.inflate(-radius * 2, -radius * 2)

run = True
while run:
    clock.tick(40)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    if len(all_particles.sprites()) < 100:
        hue = random.randrange(360)
        x = random.randrange(pos_rect.left, pos_rect.right)
        y = random.randrange(pos_rect.top, pos_rect.bottom)
        dir = pygame.math.Vector2(1, 0).rotate(random.randrange(360))
        particle = Particle(hue, (x, y), radius, dir, velocity)
        if not pygame.sprite.spritecollide(particle, all_particles, False, collided = pygame.sprite.collide_circle):
            all_particles.add(particle)

    for particle in all_particles:
        particle.move()

    particle_list = all_particles.sprites()
    for i, particle_1 in enumerate(particle_list):
        for particle_2 in particle_list[i:]:
            distance_vec = particle_1.pos - particle_2.pos
            if 0 < distance_vec.length_squared() < (particle_1.radius + particle_2.radius) ** 2:
                particle_1.dir.reflect_ip(distance_vec)
                particle_2.dir.reflect_ip(distance_vec)
                if abs(particle_1.hue - particle_2.hue) <= 180:
                    hue = (particle_1.hue + particle_2.hue) // 2
                else:
                    hue = (particle_1.hue + particle_2.hue + 360) // 2 % 360
                particle_1.changeColor(hue)
                particle_2.changeColor(hue)
                break

    all_particles.update(rect_area)

    window.fill(0)
    pygame.draw.rect(window, (255, 0, 0), rect_area, 3)
    all_particles.draw(window)
    pygame.display.flip()

pygame.quit()
exit()
Sign up to request clarification or add additional context in comments.

6 Comments

Ive made these chnages that you said but it hasnt worked?
i think i may be placing everything in the wrong order
would it be possible for me to send the code and you maybe add it where it should be?
@IsmailHussain I based my answer on the answer of to your previous question drawing objects on pygame. I've add the complete example to the answer.
@IsmailHussain Stack Overflow is not a code writing service. Anyway, I've changed the answer.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.