Skip to content

Commit

Permalink
refactor: pull changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Nathaniavm committed Nov 11, 2024
2 parents 7a765fe + 91fa131 commit a31eb3c
Show file tree
Hide file tree
Showing 16 changed files with 368 additions and 853 deletions.
7 changes: 4 additions & 3 deletions agent_parts_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
from src.interface import Button, Interface
from src.agent_parts.limb import Limb
from src.ground import *

from src.agent_parts.vision import Vision
from src.agent_parts.rectangle import Point
from src.agent_parts.creature import Creature

#NOTE_TO_MYSELF: When add limb is clicked it doesn't go away when unpaused
Expand Down Expand Up @@ -105,8 +106,8 @@ def add_motorjoint():
environment = Environment(screen, space)
environment.ground_type = GroundType.BASIC_GROUND


creature = Creature(space)
vision: Vision = Vision(Point(0,0))
creature = Creature(space, vision)

# Add limbs to the creature, placing them above the ground
#limb1 = creature.add_limb(100, 20, (300, 100), mass=1) # Positioned above the ground
Expand Down
127 changes: 51 additions & 76 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
import random
import numpy as np
import pygame
from src.genetic_algoritm import GeneticAlgorithm
import pymunk
from pygame.locals import *

from src.agent_parts.vision import Vision
from src.genetic_algoritm import GeneticAlgorithm
from src.agent_parts.limb import Limb
from src.genome import Genome
from src.globals import SCREEN_WIDTH, SCREEN_HEIGHT
Expand All @@ -16,48 +17,36 @@
from src.interface import Button, Interface
from src.agent_parts.limb import Limb
from src.agent_parts.rectangle import Point


from src.agent_parts.rectangle import Point
from src.genetic_algoritm import GeneticAlgorithm
from src.globals import (
FONT_SIZE,
SEGMENT_WIDTH,
BLACK,
RED
)
from src.globals import FONT_SIZE, SEGMENT_WIDTH, BLACK, RED
from src.agent_parts.creature import Creature
from src.NEATnetwork import NEATNetwork


def create_creatures(amount, space):
creatures = []
for i in range(amount):
creature: Creature = Creature(space)
vision = Vision(Point(0, 0))
creature: Creature = Creature(space, vision)
# Add limbs to the creature, placing them above the ground
limb1 = creature.add_limb(100, 60, (300, 100), mass=1)
limb2 = creature.add_limb(100, 20, (350, 100), mass=1)
limb3 = creature.add_limb(110, 60, (400, 100), mass=5)
# limb1 = creature.add_limb(100, 60, (300, 100), mass=1)
# limb2 = creature.add_limb(100, 20, (350, 100), mass=3)
# limb3 = creature.add_limb(110, 60, (400, 100), mass=5)

limb1 = creature.add_limb(100, 20, (300, 300), mass=1)
limb2 = creature.add_limb(100, 20, (350, 300), mass=3)
limb3 = creature.add_limb(80, 40, (400, 300), mass=5)

# Add a motor between limbs
creature.add_motor(
limb1,
limb2,
(50, 0),
(-25, 0),
rate=0, tolerance=30)
creature.add_motor(
limb2,
limb3,
(37, 0),
(-23, 0),
rate=0,
tolerance=50)

creature.add_motor(limb1, limb2, (50, 0), (-25, 0), rate=0, tolerance=30)
creature.add_motor(limb2, limb3, (37, 0), (-23, 0), rate=0, tolerance=50)

creatures.append(creature)

return creatures


def create_population(population_size, creature: Creature):
amount_of_joints = creature.get_amount_of_joints()
amount_of_out_nodes = amount_of_joints
Expand All @@ -67,11 +56,12 @@ def create_population(population_size, creature: Creature):
amount_of_in_nodes += amount_of_joints
# limb positions
amount_of_in_nodes += creature.get_amount_of_limb() * 2

population = GeneticAlgorithm.initialize_population(population_size, amount_of_in_nodes, amount_of_out_nodes)

return population
population = GeneticAlgorithm.initialize_population(
population_size, amount_of_in_nodes, amount_of_out_nodes
)

return population


def apply_mutations(self):
Expand All @@ -83,7 +73,6 @@ def apply_mutations(self):
MUTATION_RATE_CONNECTION = 0.05 # 5% chance to add a new connection
MUTATION_RATE_NODE = 0.03 # 3% chance to add a new node


# Mutate weights with a certain probability
if random.random() < MUTATION_RATE_WEIGHT:
self.mutate_weights(delta=0.1) # Adjust delta as needed
Expand All @@ -97,38 +86,37 @@ def apply_mutations(self):
self.mutate_nodes()




def main():


# Initialize Pygame and Pymunk
pygame.init()
screen_width, screen_height = SCREEN_WIDTH, SCREEN_HEIGHT
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Pymunk Rectangle Physics")
interface = Interface()

# Track whether physics is on or off
physics_on = False
physics_value = 0

# Set up the Pymunk space
space = pymunk.Space()
space.gravity = (0, 981) # Gravity pointing downward

environment: Environment = Environment(screen, space)
environment.activate_death_ray()
environment.ground_type = GroundType.BASIC_GROUND

#Population and creatures
population_size = 10
creatures: list[Creature] = create_creatures(population_size, space)
# Population and creatures
population_size = 5
creature_population: list[Creature] = create_creatures(population_size, space)
creatures = creature_population.copy()
creature_instance: Creature = creatures[0]
population = create_population(population_size, creature_instance)
neat_networks: list[NEATNetwork] = []
for genome in population:
neat_networks.append(NEATNetwork(genome))

clock = pygame.time.Clock()
vision_y = 100
vision_x = 0
Expand All @@ -139,63 +127,50 @@ def main():
running = False
interface.handle_events(event)

space.step(1/60.0)
space.step(1 / 60.0)
screen.fill((135, 206, 235))
environment.update()
environment.render()


# TODO: vision should be part of a creature, and not environment
inputs = np.array([environment.vision.get_near_periphery().x,
environment.vision.get_near_periphery().y,
environment.vision.get_far_periphery().x,
environment.vision.get_far_periphery().y])

for index, creature in enumerate(creatures):
inputs = np.array(
[
creature.vision.get_near_periphery().x,
creature.vision.get_near_periphery().y,
creature.vision.get_far_periphery().x,
creature.vision.get_far_periphery().y,
]
)
network = population[index]
for joint_rate in creature.get_joint_rates():
inputs = np.append(inputs, joint_rate)

for limb in creature.limbs:
inputs = np.append(inputs, limb.body.position.x)
inputs = np.append(inputs, limb.body.position.y)

outputs = neat_networks[index].forward(inputs)
creature.set_joint_rates(outputs)

vision_y = round(creature_instance.limbs[0].body.position.y)
vision_x = round(creature_instance.limbs[0].body.position.x)

match environment.ground_type:
case GroundType.BASIC_GROUND:
environment.vision.update(
environment.screen,
Point(vision_x, vision_y),
environment.ground,
environment.offset)

case GroundType.PERLIN:
environment.vision.update(
environment.screen,
Point(vision_x, vision_y),
environment.ground,
0)

#creature_instance.render(screen)
for creature in creatures:
vision_y = round(creature.limbs[0].body.position.y)
vision_x = round(creature.limbs[0].body.position.x)
creature.vision.update(
Point(vision_x, vision_y), environment.ground, environment.offset
) # if perlin, offset = 0, if basic, offset = environment.offset

if creature.limbs[0].body.position.y > SCREEN_HEIGHT:
creatures.remove(creature)
if environment.death_ray is not None:
if creature.limbs[0].body.position.x < environment.death_ray.get_x():
creatures.remove(creature)
creature.render(screen)

clock.tick(60)

pygame.display.flip()


pygame.quit()




if __name__ == "__main__":
main()


98 changes: 98 additions & 0 deletions neat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import random
from typing import List, Tuple

from src.genome import Genome, Innovation
from src.species import Species


class GeneticAlgorithm:
def __init__(self, population_size: int, num_inputs: int, num_outputs: int):
self.population_size = population_size
self.num_inputs = num_inputs
self.num_outputs = num_outputs
self.population: List[Genome] = []
self.species_list: List[Species] = []
self.innovation = Innovation.get_instance()
self.genome_id_counter = 0
self.species_id_counter = 0

self.initialize_population()

def initialize_population(self):
for _ in range(self.population_size):
genome = Genome(
genome_id=self.genome_id_counter,
num_inputs=self.num_inputs,
num_outputs=self.num_outputs,
)
self.population.append(genome)
self.genome_id_counter += 1

def evaluate_fitness(self, evaluate_function):
for genome in self.population:
genome.fitness = evaluate_function(genome)

def speciate(self, compatibility_threshold: float):
self.species_list = []
for genome in self.population:
found_species = False
for species in self.species_list:
representative = species.members[0]
distance = genome.compute_compatibility_distance(representative)
if distance < compatibility_threshold:
species.add_member(genome)
genome.species = species.species_id
found_species = True
break
if not found_species:
new_species = Species(self.species_id_counter)
new_species.add_member(genome)
genome.species = new_species.species_id
self.species_list.append(new_species)
self.species_id_counter += 1

def adjust_fitness(self):
for species in self.species_list:
species.adjust_fitness()

def reproduce(self):
new_population = []
total_average_fitness = sum(
species.average_fitness for species in self.species_list
)
for species in self.species_list:
offspring_count = int(
(species.average_fitness / total_average_fitness) * self.population_size
)
for _ in range(offspring_count):
parent1 = species.select_parent()
if random.random() < 0.25:
# Mutation without crossover
child = parent1.copy()
child.mutate()
else:
parent2 = species.select_parent()
child = parent1.crossover(parent2)
child.mutate()
child.genome_id = self.genome_id_counter
self.genome_id_counter += 1
new_population.append(child)
# If we don't have enough offspring due to rounding, fill up the population
while len(new_population) < self.population_size:
parent = random.choice(self.population)
child = parent.copy()
child.mutate()
child.genome_id = self.genome_id_counter
self.genome_id_counter += 1
new_population.append(child)
self.population = new_population

def evolve(
self, generations: int, evaluate_function, compatibility_threshold: float
):
for generation in range(generations):
print(f"Generation {generation+1}")
self.evaluate_fitness(evaluate_function)
self.speciate(compatibility_threshold)
self.adjust_fitness()
self.reproduce()
Loading

0 comments on commit a31eb3c

Please sign in to comment.