I have created a simple simulation to show evolution. It works through a simuple window that contains many squares representing single-celled organisms. The screen looks like this:
The single-celled organisms (dubbed amoebae for conciseness) move around randomly. If they collide with another amoebae they produce an offspring. However, to prevent them reproducing infinitely I introduced an age measure. Amoebae must attain a certain age before they reproduce and once they do their age is reset to 1.
Now for the evolution part. As you can see, the amoebae are different colours. This represents the 'gene' that is passed down to offspring through reproduction (there is a chance of mutation controlled by a constant called maturingSpeed, which I set very high to increase the speed of evolution). It's called maturingSpeed and it controls the speed at which the amoebae age, which means that amoebae that have a higher maturingSpeed with reproduce faster and pass on their gene. In this way, they should gradually evolve through natural selection so all of the amoebae have a very high maturingSpeed. A high maturingSpeed translates to a brighter colour on the screen.
There is one other thing I should mention, which is the life countdown on each amoeba. It starts out at 10000 and ticks down by one each time the amoeba is updated. This is to gradually kill off the old amoebae, also increasing the rate of evolution and making it more lifelike.
My problem is that before the amoebae all evolve to get a high maturingSpeed (the highest I've had is around 65%), they become too numerous and the simulation starts slowing down as it struggles to load them all. I need a method to make the amoebae die off faster as more of them are produced. I have tried to cull them if they are above a certain number, or increase their countdown rate based on the number of amoebae however all of these methods cause them to eventually stop reproducing and die off for some reason. I have deleted these sections from my code now because they didn't work but I could add them again if needed.
My source code:
import pygame
import random
import time
import itertools
from pygame.locals import (
QUIT
)
pygame.init()
SCREEN_WIDTH = 500
SCREEN_HEIGHT = 500
screen = pygame.display.set_mode([500, 500])
amoebas = pygame.sprite.Group()
all_sprites = pygame.sprite.Group()
idList = []
mutationConstant = 254
class Amoeba(pygame.sprite.Sprite):
id_iter = itertools.count()
def __init__(self, maturingSpeed, x, y):
super(Amoeba, self).__init__()
self.id = 'amoeba' str(next(Amoeba.id_iter))
idList.append(self.id)
self.surf = pygame.Surface((10,10))
if maturingSpeed <= 0:
maturingSpeed = 1
elif maturingSpeed >= 255:
maturingSpeed = 254
print(maturingSpeed)
self.surf.fill((maturingSpeed, 0, 0))
self.rect = self.surf.get_rect(
center=(
x,
y,
)
)
self.speed = 2
self.age = 1
self.maturingSpeed = int(maturingSpeed)
self.life = 9999
def update(self):
if self.rect.left <= 0:
direction = 1
elif self.rect.right >= SCREEN_WIDTH:
direction = 2
elif self.rect.top <= 0:
direction = 3
elif self.rect.bottom >= SCREEN_HEIGHT:
direction = 4
else:
direction = random.randint(1, 4)
if direction == 1:
self.rect.move_ip(self.speed, 0)
elif direction == 2:
self.rect.move_ip(-self.speed, 0)
elif direction == 3:
self.rect.move_ip(0, self.speed)
elif direction == 4:
self.rect.move_ip(0, -self.speed)
self.life = self.life - 1
if self.life <= 0:
self.kill()
modMaturingSpeed = self.maturingSpeed / 1240
self.age = self.age (1 * modMaturingSpeed)
@classmethod
def collide(cls):
global collisionSuccess
collisionSuccess = False
global posList
posList = [[amoeba.rect.left, amoeba.rect.bottom] for amoeba in amoebas]
length = len(posList)
for i in range(length):
for amoeba in amoebas:
if amoeba.id == str(idList[i]):
ageOne = getattr(amoeba, 'age')
for h in range(i 1, length):
for amoeba in amoebas:
if amoeba.id == str(idList[h]):
ageTwo = getattr(amoeba, 'age')
OneX = int(posList[i][0])
OneY = int(posList[i][1])
TwoX = int(posList[h][0])
TwoY = int(posList[h][1])
if ageOne >= 100 and ageTwo >= 100:
if (OneX < TwoX 10 and OneX 10 > TwoX
and OneY < TwoY 10 and 10 OneY > TwoY):
for amoeba in amoebas:
if amoeba.id == str(idList[i]):
setattr(amoeba, 'age', 1)
pOMSinitial = int(getattr(amoeba, 'maturingSpeed'))
for amoeba in amoebas:
if amoeba.id == str(idList[h]):
setattr(amoeba, 'age', 1)
pTMSinitial = int(getattr(amoeba, 'maturingSpeed'))
locationX = OneX random.randint(-10, 10)
locationY = OneY random.randint(-10, 10)
if pOMSinitial >= pTMSinitial:
pOMSfinal = pOMSinitial mutationConstant
pTMSfinal = pTMSinitial - mutationConstant
newMaturingSpeed = random.randint(pTMSfinal, pOMSfinal)
else:
pOMSfinal = pOMSinitial - mutationConstant
pTMSfinal = pTMSinitial mutationConstant
newMaturingSpeed = random.randint(pOMSfinal, pTMSfinal)
collisionSuccess = True
return cls(newMaturingSpeed, locationX, locationY)
screen.fill((255, 255, 255))
for i in range(15):
amoebaname = Amoeba(random.randint(100, 150), random.randint(0, SCREEN_WIDTH), random.randint(0, SCREEN_HEIGHT))
amoebas.add(amoebaname)
all_sprites.add(amoebaname)
p = 0
while True:
ageArray = [amoeba.age for amoeba in amoebas]
if p == 1000:
print(amoebas)
five = 0
four = 0
three = 0
two = 0
one = 0
for amoeba in amoebas:
if amoeba.maturingSpeed >= 200:
five = five 1
elif amoeba.maturingSpeed >=150:
four = four 1
elif amoeba.maturingSpeed >= 100:
three = three 1
elif amoeba.maturingSpeed >= 50:
two = two 1
else:
one = one 1
total = one two three four five
DivFive = five / total
DivFour = four / total
DivThree = three / total
DivTwo = two / total
DivOne = one / total
print(DivFive, DivFour, DivThree, DivTwo, DivOne)
p = 0
else:
p = p 1
time.sleep(0.0000001)
screen.fill((255, 255, 255))
for event in pygame.event.get():
if event.type == QUIT:
break
amoebas.update()
amoebaname = Amoeba.collide()
if collisionSuccess == True:
amoebas.add(amoebaname)
all_sprites.add(amoebaname)
for entity in all_sprites:
screen.blit(entity.surf, entity.rect)
pygame.display.flip()
pygame.quit()
CodePudding user response:
Too many nested loops and unneeded data structures. I did some cleanup and it's faster now. And it seems that the mutation constant was far to high. I changed the value from 254
to 25
.
import pygame
import random
import time
import itertools
from pygame.locals import (
QUIT
)
SCREEN_WIDTH = 500
SCREEN_HEIGHT = 500
MUTATION_CONSTANT = 25
pygame.init()
screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT])
amoebas = pygame.sprite.Group()
class Amoeba(pygame.sprite.Sprite):
id_iter = itertools.count()
def __init__(self, maturing_speed, x, y):
super().__init__()
self.id = 'amoeba' str(next(Amoeba.id_iter))
self.surf = pygame.Surface((10, 10))
self.maturing_speed = min(max(maturing_speed, 1), 254)
self.surf.fill((self.maturing_speed, 0, 0))
self.rect = self.surf.get_rect(center=(x, y,))
self.speed = 2
self.age = 1
self.life = 9999
def update(self):
if self.rect.left <= 0:
direction = 1
elif self.rect.right >= SCREEN_WIDTH:
direction = 2
elif self.rect.top <= 0:
direction = 3
elif self.rect.bottom >= SCREEN_HEIGHT:
direction = 4
else:
direction = random.randint(1, 4)
if direction == 1:
self.rect.move_ip(self.speed, 0)
elif direction == 2:
self.rect.move_ip(-self.speed, 0)
elif direction == 3:
self.rect.move_ip(0, self.speed)
elif direction == 4:
self.rect.move_ip(0, -self.speed)
self.life = self.life - 1
if self.life <= 0:
self.kill()
self.age = self.age (1 * self.maturing_speed / 1240)
@classmethod
def collide(cls):
for amoeba_1, amoeba_2 in itertools.combinations(amoebas, 2):
if amoeba_1.age >= 100 and amoeba_2.age >= 100 and (
pygame.sprite.collide_rect(amoeba_1, amoeba_2)
):
amoeba_1.age = 1
amoeba_2.age = 1
location_x = amoeba_1.rect.left random.randint(-10, 10)
location_y = amoeba_1.rect.bottom random.randint(-10, 10)
speed_low = min(amoeba_1.maturing_speed, amoeba_2.maturing_speed) - MUTATION_CONSTANT
speed_high = max(amoeba_1.maturing_speed, amoeba_2.maturing_speed) MUTATION_CONSTANT
new_maturing_speed = random.randint(speed_low, speed_high)
return cls(new_maturing_speed, location_x, location_y)
return None
def main():
screen.fill((255, 255, 255))
for i in range(25):
amoeba = Amoeba(random.randint(100, 150), random.randint(0, SCREEN_WIDTH), random.randint(0, SCREEN_HEIGHT))
amoebas.add(amoeba)
step_counter = 0
while True:
step_counter = 1
if step_counter % 100 == 0:
print(step_counter, amoebas)
five = 0
four = 0
three = 0
two = 0
one = 0
for amoeba in amoebas:
if amoeba.maturing_speed >= 200:
five = five 1
elif amoeba.maturing_speed >= 150:
four = four 1
elif amoeba.maturing_speed >= 100:
three = three 1
elif amoeba.maturing_speed >= 50:
two = two 1
else:
one = one 1
total = one two three four five
print(f'{five/total:.4f} {four/total:.4f} {three/total:.4f} {two/total:.4f} {one/total:.4f}')
time.sleep(0.0000001)
screen.fill((255, 255, 255))
for event in pygame.event.get():
if event.type == QUIT:
break
amoebas.update()
amoeba = Amoeba.collide()
if amoeba:
amoebas.add(amoeba)
for amoeba in amoebas:
screen.blit(amoeba.surf, amoeba.rect)
pygame.display.flip()
pygame.quit()
if __name__ == '__main__':
main()