Python - Genetic Algorithm mutation function is not working

I created an AI in python/pygame but even after spending hours of debugging, I could not find why the individuals(dots) are not getting mutated. After few generations, all the individuals just overlap each other and follow the same exact path. But after mutation they should move a little bit differently.

Here is what a population size of 10 looks like after every 2-3 generations…

Image 1 Image 2 Image 3

As you can see, just after few generations they just overlap and all the individuals in the population move together, following exact same path! We need mutations!!!

I would be really grateful to you if you could find any mistake. Thank!

I saw the code from: https://www.youtube.com/watch?v=BOZfhUcNiqk&t and tried to make it in python. Here’s my code

import pygame, random
import numpy as np

pygame.init()
width = 800
height = 600
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("The Dots")

FPS = 30
clock = pygame.time.Clock()
gameExit = False

grey = [30, 30, 30]
white = [255, 255, 255]
black = [0, 0, 0]
red = [255, 0, 0]

goal = [400, 10]

class Dot():
    def __init__(self):
        self.x = int(width/2)
        self.y = int(height - 150)
        self.r = 3
        self.c = black
        self.xVel = self.yVel = 0
        self.xAcc = 0
        self.yAcc = 0
        self.dead = False
        self.steps = 0
        self.reached = False
        self.brain = Brain(200)

    def show(self):
        pygame.draw.circle(screen, self.c, [int(self.x), int(self.y)], self.r)

    def update(self):
        if (self.x >= width or self.x <= 0 or self.y >= height or self.y <= 0):
            self.dead = True
        elif (np.sqrt((self.x-goal[0])**2 + (self.y-goal[1])**2) < 5):
            self.reached = True
        if not self.dead and not self.reached:
            if len(self.brain.directions) > self.steps:
                self.xAcc = self.brain.directions[self.steps][0]
                self.yAcc = self.brain.directions[self.steps][1]
                self.steps += 1

                self.xVel += self.xAcc
                self.yVel += self.yAcc
                if self.xVel > 5:
                    self.xVel = 5
                if self.yVel > 5:
                    self.yVel = 5
                self.x += self.xVel
                self.y += self.yVel
            else: self.dead = True

    def calculateFitness(self):
        distToGoal = np.sqrt((self.x-goal[0])**2 + (self.y-goal[1])**2)
        self.fitness = 1/(distToGoal**2)
        return self.fitness

    def getChild(self):
        child = Dot()
        child.brain = self.brain
        return child

class Brain():
    def __init__(self, size):
        self.size = size
        self.directions = []
        self.randomize()

    def randomize(self):
        self.directions.append((np.random.normal(size=(self.size, 2))).tolist())
        self.directions = self.directions[0]

    def mutate(self):
        for i in self.directions:
            rand = random.random()
            if rand < 1:
                i = np.random.normal(size=(1, 2)).tolist()[0]

class Population():
    def __init__(self, size):
        self.size = size
        self.dots = []
        self.fitnessSum = 0

        for i in range(self.size):
            self.dots.append(Dot())

    def show(self):
        for i in self.dots:
            i.show()

    def update(self):
        for i in self.dots:
            i.update()

    def calculateFitness(self):
        for i in self.dots:
            i.calculateFitness()

    def allDead(self):
        for i in self.dots:
            if not i.dead and not i.reached:
                return False
        return True

    def calculateFitnessSum(self):
        self.fitnessSum = 0
        for i in self.dots:
            self.fitnessSum += i.fitness

    def SelectParent(self):
        rand = random.uniform(0, self.fitnessSum)
        runningSum = 0
        for i in self.dots:
            runningSum += i.fitness
            if runningSum > rand:
                return i

    def naturalSelection(self):
        newDots = []
        self.calculateFitnessSum()
        for i in self.dots:
            parent = self.SelectParent()
            newDots.append(parent.getChild())

        self.dots = newDots

    def mutate(self):
        for i in self.dots:
            i.brain.mutate()

test = Population(100)

while not gameExit:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            gameExit = True
    screen.fill(white)

    if test.allDead():
        #Genetic Algorithm
        test.calculateFitness()
        test.naturalSelection()
        test.mutate()

    else:
        test.update()
        test.show()

    pygame.draw.circle(screen, red, goal, 4)
    clock.tick(FPS)
    pygame.display.update()
pygame.quit()

Thanks for any help!