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

Ted Klein Bergman
  • 9,146
  • 4
  • 29
  • 50

1 Answers1

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()
Rabbid76
  • 202,892
  • 27
  • 131
  • 174