I am attempting to simulate a very simplified version of evolution, but when I increase the number of animals, the code slows down to a virtually unusable point (200+ animals). I believe it is because I am using a for each loop within a for each loop, however my attempts to use other methods have failed.
KEY PARTS – action function in the animal class
create function
import pygame as pg
import random
import math
class Setup:
def __init__(self):
self.width = 800
self.height = 800
self.clock = pg.time.Clock()
self.fps = 60
self.screen = pg.display.set_mode((self.width, self.height))
self.run = True
def events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.run = False
def update(self):
self.screen.fill("black")
self.clock.tick(self.fps)
class Food:
def __init__(self):
self.width = 2
self.height = 2
self.colour = ("green")
self.x = random.randint(0, setup.width)
self.y = random.randint(0, setup.height)
def draw(self):
self.food_rect = pg.Rect(self.x, self.y, self.width, self.height)
pg.draw.rect(setup.screen, self.colour, self.food_rect)
class Animal:
def __init__(self, aggression, energy_efficiency, mate, generation): #rate each from a score between 0 and 1 based on parents
self.aggression = aggression
self.energy_efficiency = energy_efficiency
self.mate = mate
self.food = 1000
self.health = 1000
self.energy = 1000
self.mate_attraction = random.randint(1, 10) # each animal can choose to change their att by 1 point each survived day (between 0 and 10) NOT ADDED YET
self.hunting = random.randint(1, 10)
self.hiding = random.randint(1, 10)
self.gathering_food = random.randint(1, 10)
self.width = 5
self.height = 5
self.x = random.randint(0, 800)
self.y = random.randint(0, 800)
self.colour = ("white")
self.can_move_frames = 60
self.generation = generation
self.time_since_action = 0
self.vel = 1
self.y_move = 0
self.x_move = 0
self.range = 50
def update_att(self):
self.time_since_action += 1
self.energy -= 1 - (2 * self.energy_efficiency)
self.food -= 2 - (self.energy_efficiency)
self.health -= 0.5 # age
if self.food <= 0:
self.energy -= 10 - (2 * self.energy_efficiency)
elif self.food >= 500 and self.energy < 1000:
self.energy += 10
if self.energy <= 0:
self.health -= 10
atts = [self.aggression, self.energy_efficiency, self.mate]
atts = sorted(atts)
if atts[-1] == self.aggression:
self.colour = "red"
elif atts[-1] == self.energy_efficiency:
self.colour = "blue"
else:
self.colour = "yellow"
def draw(self):
self.animal_rect = pg.Rect(self.x, self.y, self.width, self.height)
pg.draw.rect(setup.screen, self.colour, self.animal_rect)
def actions(self, animals):
partner = "none"
for a in animals:
if a.aggression != self.aggression and a.energy_efficiency != self.energy_efficiency and a.mate != self.mate:
if math.dist((self.x, self.y), (a.x, a.y)) < self.range:
if partner != "none":
if a.mate_attraction > partner.mate_attraction:
partner = a
else:
partner = a
mutation = random.randint(-10, 10) / 100
self.child_aggression = (self.aggression + partner.aggression) / 2 + mutation
self.child_energy_efficiency = (self.energy_efficiency + partner.energy_efficiency) / 2 + mutation
self.child_mate = (self.mate + partner.mate) / 2 + mutation
self.child_generation = self.generation + 1
if random.randint(0, 10) <= self.mate_attraction and random.randint(0, 10) <= (self.mate * 10) and self.generation == a.generation and self.time_since_action >= 300:
self.time_since_action = 0
return True
if random.randint(0, 10) <= (self.aggression * 10) and random.randint(1, 10) >= a.hiding and self.time_since_action >= 300:
self.time_since_action = 0
if random.randint(1, 2) == 1:
a.health -= 10000
self.food += self.hunting * 25
else:
self.health -= 10000
a.food += a.hunting * 25
def move(self):
if self.can_move_frames >= random.randint(45, 75):
if self.hunting > self.hiding:
best_att = self.hunting / 10
else:
best_att = self.hiding / 10
self.x_move = random.randint(-200 * best_att, 200 * best_att)
self.y_move = random.randint(-200 * best_att, 200 * best_att)
self.can_move_frames = 0
if random.randint(1, 3) == 1:
self.has_mated = False
self.has_attacked = False
angle = math.atan2((self.y + self.y_move) - self.y, (self.x + self.x_move) - self.x)
x_vel = math.cos(angle) * self.vel
y_vel = math.sin(angle) * self.vel
if self.x + x_vel > setup.width - self.width:
x_vel = setup.width - self.width - self.x
elif self.x + x_vel < 0:
x_vel = -self.x
if self.y + y_vel > setup.height - self.height:
y_vel = setup.height - self.height - self.y
elif self.y + y_vel < 0:
y_vel = -self.y
self.x += x_vel
self.y += y_vel
self.can_move_frames += 1
def eat(self, f):
if math.dist((self.x, self.y), (f.x, f.y)) < self.range:
self.food += self.gathering_food * 25
return True
animals = []
food = []
food_spawn = 0
show_stats = 0
for x in range(100):
animal = Animal(random.random(), random.random(), random.random(), 0)
animals.append(animal)
def create(animals, food, food_spawn, show_stats):
if show_stats >= 60:
calculate_statistics(animals, food)
show_stats = 0
if food_spawn >= 10:
new_food = Food()
food.append(new_food)
food_spawn = 0
food_spawn += 1
show_stats += 1
for f in food:
f.draw()
for a in animals:
if a.health > 0:
for f in food:
if a.eat(f):
food.remove(f)
a.move()
a.draw()
a.update_att()
if a.actions(animals):
new_animal = Animal(a.child_aggression, a.child_energy_efficiency, a.child_mate, a.child_generation)
animals.append(new_animal)
else:
animals.remove(a)
return animals, food, food_spawn, show_stats
def calculate_statistics(animals, food):
average_aggression = 0
average_energy_efficiency = 0
average_mate = 0
highest_generation = 0
for a in animals:
average_aggression += a.aggression
average_energy_efficiency += a.energy_efficiency
average_mate += a.mate
if a.generation > highest_generation:
highest_generation = a.generation
if len(animals) > 0:
average_aggression /= len(animals)
average_energy_efficiency /= len(animals)
average_mate /= len(animals)
print(f"ANIMALS: {len(animals)} AGGRESSION: {round(average_aggression, 2)} ENERGY: {round(average_energy_efficiency, 2)} MATE: {round(average_mate, 2)} FOOD: {len(food)} GEN: {round(highest_generation, 2)}")
setup = Setup()
while setup.run:
setup.events()
setup.update()
animals, food, food_spawn, show_stats = create(animals, food, food_spawn, show_stats)
pg.display.update()
pg.quit()
I tried using numpy to iterate through the list of animals and calculate the distances between the points, however i realised that the points are no longer connected to the animal object so i scrapped that idea. I also tried using map() in the create() function however it did not run the actions function. Basically i need a substitute for – for a in animals – that is much quicker and will allow me to use larger population sizes.
Thomas Dixon is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.