From 8aeb354776016decd479cd5e706819ec2bf9a0d7 Mon Sep 17 00:00:00 2001 From: Sverre Nystad Date: Mon, 11 Nov 2024 15:15:05 +0100 Subject: [PATCH 01/13] build: add only needed dependencies --- requirements.txt | 59 ++++++++++++------------------------------------ 1 file changed, 14 insertions(+), 45 deletions(-) diff --git a/requirements.txt b/requirements.txt index f0a2596..7ec813e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,46 +1,15 @@ -absl-py==2.1.0 -astunparse==1.6.3 -certifi==2024.8.30 -charset-normalizer==3.3.2 -colorama==0.4.6 -flake8==7.1.1 -flatbuffers==24.3.25 -gast==0.6.0 -google-pasta==0.2.0 -grpcio==1.66.1 -h5py==3.11.0 -idna==3.8 -iniconfig==2.0.0 -keras==3.5.0 -libclang==18.1.1 -Markdown==3.7 -markdown-it-py==3.0.0 -MarkupSafe==2.1.5 -mccabe==0.7.0 -mdurl==0.1.2 -ml-dtypes==0.4.0 -namex==0.0.8 -numpy==1.26.4 -opt-einsum==3.3.0 -optree==0.12.1 -packaging==24.1 -pluggy==1.5.0 -protobuf==4.25.4 -pycodestyle==2.12.1 -pyflakes==3.2.0 -pygame==2.6.0 -Pygments==2.18.0 -pytest==8.3.2 -requests==2.32.3 -rich==13.8.0 +cffi==1.17.1 +contourpy==1.3.0 +cycler==0.12.1 +fonttools==4.54.1 +kiwisolver==1.4.7 +matplotlib==3.9.2 +numpy==2.1.3 +packaging==24.2 +pillow==11.0.0 +pycparser==2.22 +pygame==2.6.1 +pymunk==6.9.0 +pyparsing==3.2.0 +python-dateutil==2.9.0.post0 six==1.16.0 -tensorboard==2.17.1 -tensorboard-data-server==0.7.2 -tensorflow==2.17.0 -tensorflow-cpu==2.17.0 -tensorflow-io-gcs-filesystem==0.31.0 -termcolor==2.4.0 -typing_extensions==4.12.2 -urllib3==2.2.2 -Werkzeug==3.0.4 -wrapt==1.16.0 From 786d3bb15525d293c71d4079757c752cb741654d Mon Sep 17 00:00:00 2001 From: tobiasfremming Date: Mon, 11 Nov 2024 15:25:42 +0100 Subject: [PATCH 02/13] refactor: put vision into creature --- main.py | 41 ++++++++----------- src/agent_parts/creature.py | 21 ++++++++-- src/agent_parts/vision.py | 82 +++++++++++++++++++++++++++++++++++++ src/environment.py | 80 +----------------------------------- 4 files changed, 117 insertions(+), 107 deletions(-) create mode 100644 src/agent_parts/vision.py diff --git a/main.py b/main.py index acbb481..2b218f6 100644 --- a/main.py +++ b/main.py @@ -4,6 +4,7 @@ import random import numpy as np import pygame +from src.agent_parts.vision import Vision from src.genetic_algoritm import GeneticAlgorithm import pymunk from pygame.locals import * @@ -33,7 +34,8 @@ 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) @@ -56,6 +58,7 @@ def create_creatures(amount, space): creatures.append(creature) + return creatures def create_population(population_size, creature: Creature): @@ -144,14 +147,11 @@ def main(): 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) @@ -163,23 +163,14 @@ def main(): 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) + 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 + + #creature_instance.render(screen) for creature in creatures: diff --git a/src/agent_parts/creature.py b/src/agent_parts/creature.py index f38b4e5..e93c65d 100644 --- a/src/agent_parts/creature.py +++ b/src/agent_parts/creature.py @@ -2,13 +2,18 @@ import pygame from src.agent_parts.limb import Limb from src.agent_parts.motorjoint import MotorJoint +from src.agent_parts.vision import Vision +from src.ground import Ground +from src.agent_parts.rectangle import Point + class Creature: limbs: list[Limb] motors: list[MotorJoint] + vision: Vision - def __init__(self, space): + def __init__(self, space, vision: Vision): """ Initialize a creature with an empty list of limbs and motors. @@ -18,6 +23,7 @@ def __init__(self, space): self.limbs = [] self.motors = [] self.relative_vectors = [] + self.vision = vision def add_limb(self, width: float, height: float, position: tuple[float,float], mass=1, color=(0, 255, 0)) -> Limb: """ @@ -34,6 +40,15 @@ def add_limb(self, width: float, height: float, position: tuple[float,float], ma self.limbs.append(limb) return limb + def update_vision(self, x: int, y: int): + """ + Update the vision position of the creature + Args: + x (int): x coordinate + y (int): y coordinate + """ + self.vision.update(x, y) + def start_dragging(self, dragged_limb: Limb): for limb in self.limbs: if limb != dragged_limb: @@ -91,7 +106,7 @@ def render(self, screen: pygame.display): limb.render(screen) for motor in self.motors: motor.render(screen, motor.pivot.a, motor.pivot.b) # Render motor joints - + self.vision.render_vision(screen) def get_joint_rates(self) -> list[float]: """Return the current rates of all motor joints.""" return [motor.motor.rate for motor in self.motors] @@ -122,4 +137,4 @@ def get_joint_positions(self) -> list[tuple[float,float]]: def get_limb_positions(self) -> list[tuple[float,float]]: return [(limb.body.position.x, limb.body.position.y) for limb in self.limbs] - \ No newline at end of file + diff --git a/src/agent_parts/vision.py b/src/agent_parts/vision.py new file mode 100644 index 0000000..ec0b966 --- /dev/null +++ b/src/agent_parts/vision.py @@ -0,0 +1,82 @@ +import pygame as pg +from src.agent_parts.rectangle import Point +from src.ground import Ground +from src.globals import BLACK, RED + + + +class Vision: + eye_position: Point + sight_width = 50 + x_offset = 50 + near_periphery: Point + far_periphery: Point + + def __init__(self, eye_position: Point): + self.eye_position = eye_position + self.near_periphery = Point(0, 0) + self.far_periphery = Point(0, 0) + + def update( + self, + eye_position: Point, + ground: Ground, + scroll_offset: int + ) -> None: + + self.eye_position = eye_position + x1 = eye_position.x + self.x_offset + x2 = x1 + self.sight_width + try: + y1=ground.get_y(x1+scroll_offset) + self.near_periphery = Point(x1, y1) + except: + pass + try: + y2=ground.get_y(x2+scroll_offset) + self.far_periphery = Point(x2, y2) + except: + pass + + def render_vision(self, screen): + pg.draw.circle( + screen, + BLACK, + (self.eye_position.x, self.eye_position.y), + 5, + 2 + ) + pg.draw.line( + screen, RED, (self.eye_position.x, self.eye_position.y), + (self.near_periphery.x, self.near_periphery.y), + 2 + ) + pg.draw.line( + screen, RED, (self.eye_position.x, self.eye_position.y), + (self.far_periphery.x, self.far_periphery.y), + 2 + ) + + def get_lower_periphery(self): + return self.near_periphery + + def get_upper_periphery(self): + return self.far_periphery + + def get_eye_position(self): + return self.eye_position + + def get_sight_width(self): + return self.sight_width + + def get_near_periphery(self) -> Point: + if self.near_periphery is None: + return Point(0, 0) + return self.near_periphery + + def get_far_periphery(self) -> Point: + if self.far_periphery is None: + return Point(0, 0) + return self.far_periphery + + diff --git a/src/environment.py b/src/environment.py index 243e209..8698dc6 100644 --- a/src/environment.py +++ b/src/environment.py @@ -32,7 +32,7 @@ def __init__(self, screen, space): self.ground: Ground = self.ground_factory(self.ground_type) self.starting_xx = 50 self.point = Point(self.starting_xx, 100) - self.vision: Vision = Vision(self.point) + self.offset = 0 self.offset_speed = 1 @@ -107,82 +107,4 @@ def draw_mark(surface, color, coord): pg.draw.circle(surface, color, coord, 3) -class Vision: - eye_position: Point - sight_width = 50 - x_offset = 50 - near_periphery: Point - far_periphery: Point - - def __init__(self, eye_position: Point): - self.eye_position = eye_position - self.near_periphery = Point(0, 0) - self.far_periphery = Point(0, 0) - - def update( - self, - screen: pg.display, - eye_position: Point, - ground: Ground, - scroll_offset: int - ) -> None: - - self.eye_position = eye_position - x1 = eye_position.x + self.x_offset - x2 = x1 + self.sight_width - try: - y1=ground.get_y(x1+scroll_offset) - self.near_periphery = Point(x1, y1) - except: - pass - try: - y2=ground.get_y(x2+scroll_offset) - self.far_periphery = Point(x2, y2) - except: - pass - - self.render_vision(screen) - - def render_vision(self, screen): - pg.draw.circle( - screen, - BLACK, - (self.eye_position.x, self.eye_position.y), - 5, - 2 - ) - pg.draw.line( - screen, RED, (self.eye_position.x, self.eye_position.y), - (self.near_periphery.x, self.near_periphery.y), - 2 - ) - pg.draw.line( - screen, RED, (self.eye_position.x, self.eye_position.y), - (self.far_periphery.x, self.far_periphery.y), - 2 - ) - - def get_lower_periphery(self): - return self.near_periphery - - def get_upper_periphery(self): - return self.far_periphery - - def get_eye_position(self): - return self.eye_position - - def get_sight_width(self): - return self.sight_width - - def get_near_periphery(self) -> Point: - if self.near_periphery is None: - return Point(0, 0) - return self.near_periphery - - def get_far_periphery(self) -> Point: - if self.far_periphery is None: - return Point(0, 0) - return self.far_periphery - - From fb1f7a8adfd862c917c39a31f2ddbc8107213f8e Mon Sep 17 00:00:00 2001 From: tobiasfremming Date: Mon, 11 Nov 2024 15:48:33 +0100 Subject: [PATCH 03/13] refactor: delete unused code --- main.py | 20 ++- src/agent_parts_old/__init__.py | 0 src/agent_parts_old/creature.py | 113 ----------------- src/agent_parts_old/joint.py | 116 ----------------- src/agent_parts_old/limb.py | 196 ----------------------------- src/agent_parts_old/pymunk_rect.py | 170 ------------------------- 6 files changed, 9 insertions(+), 606 deletions(-) delete mode 100644 src/agent_parts_old/__init__.py delete mode 100644 src/agent_parts_old/creature.py delete mode 100644 src/agent_parts_old/joint.py delete mode 100644 src/agent_parts_old/limb.py delete mode 100644 src/agent_parts_old/pymunk_rect.py diff --git a/main.py b/main.py index 2b218f6..b3e2b12 100644 --- a/main.py +++ b/main.py @@ -37,9 +37,13 @@ def create_creatures(amount, 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( @@ -58,9 +62,9 @@ def create_creatures(amount, space): 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 @@ -76,7 +80,6 @@ def create_population(population_size, creature: Creature): return population - def apply_mutations(self): """ Apply mutations to the genome based on defined mutation rates. @@ -100,11 +103,8 @@ def apply_mutations(self): self.mutate_nodes() - - def main(): - # Initialize Pygame and Pymunk pygame.init() screen_width, screen_height = SCREEN_WIDTH, SCREEN_HEIGHT @@ -124,7 +124,7 @@ def main(): environment.ground_type = GroundType.BASIC_GROUND #Population and creatures - population_size = 10 + population_size = 5 creatures: list[Creature] = create_creatures(population_size, space) creature_instance: Creature = creatures[0] population = create_population(population_size, creature_instance) @@ -170,8 +170,6 @@ def main(): environment.ground, environment.offset) # if perlin, offset = 0, if basic, offset = environment.offset - - #creature_instance.render(screen) for creature in creatures: creature.render(screen) diff --git a/src/agent_parts_old/__init__.py b/src/agent_parts_old/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/agent_parts_old/creature.py b/src/agent_parts_old/creature.py deleted file mode 100644 index 49daf5e..0000000 --- a/src/agent_parts_old/creature.py +++ /dev/null @@ -1,113 +0,0 @@ -from typing import List - -from src.environment import Environment -from src.agent_parts_old.limb import Limb -from src.agent_parts_old.joint import Joint -# from joint import Joint - -class Creature: - """ - A class representing a creature composed of limbs and joints in a given environment. - - Attributes: - ---------- - env : Environment - The environment in which the creature exists. - limblist : list[Limb] - A list of Limb objects representing the creature's limbs. - jointlist : list[Joint] - A list of Joint objects representing the creature's joints. - position : list[float, float] - The position of the creature represented as a list containing two floats [x, y]. - """ - - env: Environment - limblist: list[Limb] - #jointlist: list[Joint] - position: list[float, float] - - def __init__(self, env: Environment, limblist: list[Limb]): - #takes jointList as input - """ - Initializes the Creature object. - - Parameters: - ---------- - env : Environment - The environment where the creature is situated. - limblist : list[Limb] - A list of Limb objects which form the creature. - jointlist : list[Joint] - A list of Joint objects which connect the limbs of the creature. - - The creature's initial position is set to [200, 200]. - """ - self.env = env - self.limblist = limblist - #self.jointlist = jointlist - self.position = [200, 200] - self.relative_vectors = [] - - # def act(self, actions: list) -> None: - # """_summary_ Act on the environment based on the actions. This will rotate the joints. Creature physics must handle the rest. - - # Args: - # actions (list): list of rotations for each joint. - # """ - # for i in range(len(actions)): - # self.jointlist[i].rotate(actions[i]) - - def render(self, window): - """ - Renders the creature on a given window. - - Parameters: - ---------- - window : any - The graphical window where the creature will be rendered. - This can be any object capable of rendering the limbs and joints (e.g., a game screen or canvas). - - The function iterates through all the creature's limbs and joints and calls their respective render method. - """ - for limb in self.limblist: - limb.render(window) - # for joint in self.jointlist: - # joint.render(window) - - def updatePosition(self, x: float, y: float): - """ - Updates the creature's position and propagates the change to all its limbs. - - Parameters: - ---------- - x : float - The new x-coordinate of the creature. - y : float - The new y-coordinate of the creature. - - This method sets the new position of the creature and then updates the position of all limbs accordingly. - """ - self.position = [x, y] - for limb in self.limblist: - limb.updatePosition(x, y) - -def creature_factory(env: Environment, limblist: list[Limb]) -> Creature: - #, jointlist: list[Joint] - """ - Factory function for creating a Creature object. - - Parameters: - ---------- - env : Environment - The environment in which the creature will be created. - limblist : list[Limb] - A list of Limb objects to form the creature. - jointlist : list[Joint] - A list of Joint objects to connect the limbs of the creature. - - Returns: - ------- - Creature: - A new instance of the Creature class. - """ - return Creature(env, limblist) diff --git a/src/agent_parts_old/joint.py b/src/agent_parts_old/joint.py deleted file mode 100644 index a9ca5f9..0000000 --- a/src/agent_parts_old/joint.py +++ /dev/null @@ -1,116 +0,0 @@ -from src.render_object import RenderObject -import pygame as pg -from src.agent_parts_old.limb import Limb -from CrawlAI.src.agent_parts.rectangle import Point - - -class Joint(RenderObject): - - """ - The Joint class represents a connection between two limbs, providing a - point of articulation or rotation between them. The joint tracks the angle - of rotation, and the relative positions of the two limbs connected by it. - - Attributes: - angle (float): The current angle of the joint, representing the - rotation between the two limbs. - limb1 (Limb): The first limb connected to the joint. - limb2 (Limb): The second limb connected to the joint. - position1 (list[float, float]): The position of the - joint on the first limb. - position2 (list[float, float]): The position of the j - oint on the second limb. - """ - - angle: float - limbChild: Limb - point: Point - relative_point: Point - - def __init__(self, angle: float, point: Point): - """ - Initializes a Joint object. - - Args: - angle (float): The initial angle of the joint. - limb1 (Limb): The first limb to be connected to the joint. - limb2 (Limb): The second limb to be connected to the joint. - position1 (list[float, float]): The position on the first - limb where the joint is attached. - position2 (list[float, float]): The position on the second - limb where the joint is attached. - """ - self.angle = angle - self.limbChild = None - self.point = point - self.relative_point = None - - def get_position(self): - return self.point - - def set_position(self, point: Point): - self.point = point - - def render(self, window): - """ - This method is responsible for rendering the joint visually. - It is currently a placeholder method, and should be implemented in - subclasses or future revisions if visual representation is required. - """ - radius = min(window.get_width(), window.get_height())*0.005 - pg.draw.circle(window, (0, 0, 0), (self.point.x, self.point.y), radius) - if (self.limbChild is not None): - self.limbChild.render(window) - - def rotate(self, angle: float): - """ - Rotates the joint by a given angle, modifying the current joint angle. - - Args: - angle (float): The angle (in degrees or radians, based on - the system) by which the joint should be rotated. - """ - self.angle += angle - - def get_angle(self): - """ - Returns the current angle of the joint. - - Returns: - float: The current angle of the joint. - """ - return self.angle - - def set_angle(self, angle: float): - """ - Sets the angle of the joint to a specific value. - - Args: - angle (float): The new angle to set for the joint. - """ - self.angle = angle - - def set_relative_position(self, point: Point): - self.relative_point = point - - def get_relative_position(self): - return self.relative_point - - -def joint_factory(angle: float, point: Point) -> Joint: - """ - Factory function for creating a Joint object between two limbs. - - This function simplifies the creation of a Joint object between a - parent limb and a child limb. It returns a Joint instance, but the - specific positions and angles are assumed to be handled inside the - factory function (may need modification based on the actual usage). - - Args: - parent (Limb): The parent limb that will be connected to the joint. - child (Limb): The child limb that will be connected to the joint. - - Returns: - Joint: A new Joint instance connecting the parent and child limbs. - """ - return Joint(angle, point) \ No newline at end of file diff --git a/src/agent_parts_old/limb.py b/src/agent_parts_old/limb.py deleted file mode 100644 index 9688711..0000000 --- a/src/agent_parts_old/limb.py +++ /dev/null @@ -1,196 +0,0 @@ -from enum import Enum -import numpy as np - -from src.render_object import RenderObject -from CrawlAI.src.agent_parts.rectangle import Rectangle, Point -import math - - -class LimbType(Enum): - """ - Enum representing the different types of limbs a creature can have. - - Attributes: - ---------- - LIMB : int - Represents a standard limb (value 1). - HEAD : int - Represents the head of the creature (value 2). - FOOT : int - Represents the foot of the creature (value 3). - """ - LIMB = 1 - HEAD = 2 - FOOT = 3 - - -class Limb(RenderObject): - """ - A class representing a limb of a creature, - which is a subclass of RenderObject. - - Attributes: - ---------- - rect : Rectangle - A Rectangle object representing the shape and position of the limb. - damage_scale : float - The scale of damage this limb can take, determined by its type. - orientation : float - The orientation or angle of the limb in the environment. - limbType : LimbType - An instance of LimbType indicating the specific type of limb - (LIMB, HEAD, or FOOT). - """ - - rect: Rectangle - damage_scale: float - jointList: list[RenderObject] - - def __init__(self, - rect: Rectangle, - damage_scale: float, - limbType: LimbType - ): - """ - Initializes a Limb object. - - Parameters: - ---------- - rect : Rectangle - The rectangle representing the shape and size of the limb. - damage_scale : float - A value representing how much damage the limb can take. - limbType : LimbType - The type of the limb (LIMB, HEAD, or FOOT). - """ - self.rect = rect - self.damage_scale = damage_scale - self.limbType = limbType - self.jointList = [] - - def rotate(self, angle: float): - """ - Rotates the limb by a given angle. - - Parameters: - ---------- - angle : float - The angle by which to rotate the limb, - added to the current orientation. - """ - self.rect.rotateRectangle(angle) - for joint in self.jointList: - self.updateRenderObject(joint) - - def inherit_orientation(self, - parent_orientation: float, - limb_orientation: float): - """ - Inherits the orientation from the parent and adds - the limb's own orientation. - - Parameters: - ---------- - parent_orientation : float - The orientation of the parent limb or object. - limb_orientation : float - The local orientation of the limb, relative to the parent. - """ - # self.orientation = parent_orientation + limb_orientation - pass - - def updateRenderObject(self, renderObject: RenderObject): - - renderObject_relative_pos = renderObject.get_relative_position() - rect_pos = self.rect.get_position() - rect_angle = self.rect.get_angle() - - final_x, final_y = self.rect.rotatePointPoint( - rect_angle, - renderObject_relative_pos, - Point(rect_pos.x+1, rect_pos.y)) - - renderObject.set_position(Point(final_x, final_y)) - - def updatePosition(self, x: float, y: float): - """ - Updates the position of the limb by updating the - position of its rectangle. - - Parameters: - ---------- - x : float - The new x-coordinate of the limb. - y : float - The new y-coordinate of the limb. - """ - self.rect.updatePosition(x, y) - - def render(self, window): - """ - Renders the limb on the provided window. - - Parameters: - ---------- - window : any - The graphical window or surface where the limb will be rendered. - """ - self.rect.render(window) - for joint in self.jointList: - joint.render(window) - - def addJoint(self, joint: RenderObject): - if (self.rect.contains(joint.get_position())): - self.jointList.append(joint) - - joint_pos = joint.get_position() - rect_pos = self.rect.get_position() - rectangle_angle = self.rect.get_angle() - - angle, _ = self.rect.angle_between_vectors( - np.array([math.cos(rectangle_angle), - math.sin(rectangle_angle) - ]), - np.array([joint_pos.x-rect_pos.x, joint_pos.y-rect_pos.y])) - relative_x, relative_y = self.rect.rotatePointPoint( - angle, - joint_pos, - rect_pos - ) - joint.set_relative_position(Point(relative_x, relative_y)) - - -def limb_factory( - rect: Rectangle, - limbType: LimbType, - orientation: float - ) -> Limb: - """ - Factory function for creating a Limb object. - - Parameters: - ---------- - rect : Rectangle - A Rectangle object representing the limb's shape and position. - limbType : LimbType - The type of limb to be created (LIMB, HEAD, or FOOT). - orientation : float - The initial orientation angle of the limb. - - Returns: - ------- - Limb: - A new instance of the Limb class with the appropriate damage - scale based on the limb type. - """ - damage_scale: float = 0 - match limbType: - case 1: - damage_scale = 0.1 # Low damage scale for a standard limb - case 2: - damage_scale = 1.0 # High damage scale for the head - case 3: - damage_scale = 0.0 # No damage for the foot - case _: - return "Invalid LimbType" - return Limb(rect, damage_scale, limbType) diff --git a/src/agent_parts_old/pymunk_rect.py b/src/agent_parts_old/pymunk_rect.py deleted file mode 100644 index 96e2c09..0000000 --- a/src/agent_parts_old/pymunk_rect.py +++ /dev/null @@ -1,170 +0,0 @@ -import numpy as np -import pygame -import math -import pymunk # Importing Pymunk - -from CrawlAI.src.agent_parts.rectangle import Point - - -class Rectangle: - def __init__(self, - point: Point, - width: float, - height: float, - mass: float = 1.0 - ): - """ - Initializes a Rectangle object and its corresponding - Pymunk physics body. - - Parameters: - ---------- - point : Point - The initial position of the rectangle. - width : float - The width of the rectangle. - height : float - The height of the rectangle. - mass : float, optional - The mass of the rectangle for physics simulation. - """ - x, y = point.x, point.y - self.width = width - self.height = height - - self.poPoints = np.array([ - np.array([x, y]), - np.array([x + width, y]), - np.array([x + width, y + height]), - np.array([x, y + height]) - ]) - - # Calculate moment of inertia for a rectangle - moment = pymunk.moment_for_box(mass, (width, height)) - - # Create a dynamic Pymunk body - self.body = pymunk.Body(mass, moment) - self.body.position = x + width / 2, y + height / 2 - - # Define the polygon shape - vertices = [ - (-width / 2, -height / 2), - (width / 2, -height / 2), - (width / 2, height / 2), - (-width / 2, height / 2), - ] - self.shape = pymunk.Poly(self.body, vertices) - self.shape.friction = 0.7 - - def update_from_physics(self): - """ - Updates the rectangle's corner points based on the Pymunk - body's position and rotation. - """ - cx, cy = self.body.position # Center of the rectangle - angle = self.body.angle # Rotation angle of the rectangle - - # Calculate the rotated points around the center - cos_angle = math.cos(angle) - sin_angle = math.sin(angle) - half_width = self.width / 2 - half_height = self.height / 2 - - # New corner positions - self.poPoints[0] = np.array([ - cx - half_width * cos_angle + half_height * sin_angle, - cy - half_width * sin_angle - half_height * cos_angle - ]) - self.poPoints[1] = np.array([ - cx + half_width * cos_angle + half_height * sin_angle, - cy + half_width * sin_angle - half_height * cos_angle - ]) - self.poPoints[2] = np.array([ - cx + half_width * cos_angle - half_height * sin_angle, - cy + half_width * sin_angle + half_height * cos_angle - ]) - self.poPoints[3] = np.array([ - cx - half_width * cos_angle - half_height * sin_angle, - cy - half_width * sin_angle + half_height * cos_angle - ]) - - def apply_force(self, force, offset=(0, 0)): - """ - Applies a force to the Pymunk body. - - Parameters: - ---------- - force : tuple - The force to apply as (fx, fy). - offset : tuple - The point at which to apply the force, - relative to the center of the body. - """ - self.body.apply_force_at_local_point(force, offset) - - def apply_impulse(self, impulse, offset=(0, 0)): - """ - Applies an impulse to the Pymunk body. - - Parameters: - ---------- - impulse : tuple - The impulse to apply as (ix, iy). - offset : tuple - The point at which to apply the impulse, - relative to the center of the body. - """ - self.body.apply_impulse_at_local_point(impulse, offset) - - def render(self, window): - """ - Renders the rectangle on a given window using pygame. - - Parameters: - ---------- - window : any - The graphical window where the rectangle will be drawn. - """ - pygame.draw.polygon( - window, - (255, 255, 255), - [(p[0], p[1]) for p in self.poPoints] - ) - - def updatePosition(self, point: Point): - """ - Updates the position of the rectangle (manually) - by translating its corner points. - This method is not used in physics mode, - where the position is updated via Pymunk. - """ - x, y = point.x, point.y - translation = np.array([x, y]) - self.poPoints += translation - - -def rectangle_factory( - point: Point, - width: float, - height: float, - mass: float = 1.0): - """ - Factory function for creating a Rectangle object with physics. - - Parameters: - ---------- - point : Point - The initial position of the rectangle. - width : float - The width of the rectangle. - height : float - The height of the rectangle. - mass : float - The mass of the rectangle for physics simulation. - - Returns: - ------- - Rectangle: - A new instance of the Rectangle class. - """ - return Rectangle(point, width, height, mass) From afc142139f57f721c6170e95281304c36e492218 Mon Sep 17 00:00:00 2001 From: tobiasfremming Date: Mon, 11 Nov 2024 16:07:29 +0100 Subject: [PATCH 04/13] feat: death ray --- main.py | 8 ++++++-- src/agent.py | 4 ++-- src/environment.py | 24 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index b3e2b12..21a7c84 100644 --- a/main.py +++ b/main.py @@ -170,8 +170,12 @@ def main(): environment.ground, environment.offset) # if perlin, offset = 0, if basic, offset = environment.offset - #creature_instance.render(screen) - for creature in creatures: + if creature.limbs[0].body.position.y > SCREEN_HEIGHT: + # kill the creature + creatures.remove(creature) + if creature.limbs[0].body.position.x < environment.death_ray.get_x(): + # kill the creature + creatures.remove(creature) creature.render(screen) clock.tick(60) diff --git a/src/agent.py b/src/agent.py index 5c31f38..1ba7585 100644 --- a/src/agent.py +++ b/src/agent.py @@ -7,8 +7,8 @@ from src.environment import Environment from src.render_object import RenderObject -from src.agent_parts_old.limb import Limb, LimbType, limb_factory -from src.agent_parts_old.creature import Creature, creature_factory +from src.agent_parts.limb import Limb, LimbType, limb_factory +from src.agent_parts.creature import Creature, creature_factory class Agent(ABC): diff --git a/src/environment.py b/src/environment.py index 8698dc6..9a309bd 100644 --- a/src/environment.py +++ b/src/environment.py @@ -36,6 +36,7 @@ def __init__(self, screen, space): self.offset = 0 self.offset_speed = 1 + self.death_ray = DeathRay(20) def ground_factory(self, ground_type: GroundType) -> Ground: @@ -61,9 +62,12 @@ def update(self): self.ground.update(self.offset) # self.ground.move_segments(self.offset/100) self.starting_xx += 1 + self.death_ray.move(0.1) + def render(self): self.ground.render() + self.death_ray.render(self.screen) def run(self): @@ -106,5 +110,25 @@ def run(self): def draw_mark(surface, color, coord): pg.draw.circle(surface, color, coord, 3) +class DeathRay: + x: int + + def __init__(self, x: int): + self.x = x + + def update(self, x: int): + self.x = x + + def render(self, screen): + pg.draw.line(screen, RED, (self.x, 0), (self.x, SCREEN_HEIGHT), 2) + + def move(self, offset: int): + self.x += offset + + def get_x(self): + return self.x + + + From e3ea7df3f456d0d265f90ff62c717c388c88985d Mon Sep 17 00:00:00 2001 From: Sverre Nystad Date: Mon, 11 Nov 2024 16:29:07 +0100 Subject: [PATCH 05/13] style: format files --- src/genome.py | 56 +++++++++++++++++++-------------------- tests/test_NEATnetwork.py | 38 +++++++++++--------------- 2 files changed, 43 insertions(+), 51 deletions(-) diff --git a/src/genome.py b/src/genome.py index de88fe6..bed583d 100644 --- a/src/genome.py +++ b/src/genome.py @@ -1,6 +1,6 @@ import random from dataclasses import dataclass -from typing import List +from typing import List class Inovation: @@ -27,8 +27,7 @@ def _get_innovation_number(in_node, out_node): return Inovation._innovation_history[key] else: Inovation._global_innovation_counter += 1 - Inovation._innovation_history[key] =\ - Inovation._global_innovation_counter + Inovation._innovation_history[key] = Inovation._global_innovation_counter return Inovation._innovation_history[key] @@ -51,7 +50,7 @@ class Connection: def change_enable(self, status: bool): self.enabled = status - + class Genome: def __init__(self, genome_id: int, num_inputs: int, num_outputs: int): @@ -64,24 +63,25 @@ def __init__(self, genome_id: int, num_inputs: int, num_outputs: int): # Create input nodes for i in range(num_inputs): - self.nodes.append(Node(id=i, node_type='input')) + self.nodes.append(Node(id=i, node_type="input")) # Create output nodes for i in range(num_outputs): - self.nodes.append(Node(id=num_inputs + i, node_type='output')) + self.nodes.append(Node(id=num_inputs + i, node_type="output")) # Connect each input node to each output node with a random weight for input_node in range(num_inputs): for output_node in range(num_outputs): - self.connections.append(Connection( - in_node=input_node, - out_node=num_inputs + output_node, - weight=random.uniform(-1.0, 1.0), - innovation_number=Inovation.get_instance()._get_innovation_number( - input_node, - num_inputs + output_node + self.connections.append( + Connection( + in_node=input_node, + out_node=num_inputs + output_node, + weight=random.uniform(-1.0, 1.0), + innovation_number=Inovation.get_instance()._get_innovation_number( + input_node, num_inputs + output_node + ), ) - )) + ) def mutate_weights(self, delta: float): for conn in self.connections: @@ -100,9 +100,7 @@ def mutate_connections(self): out_node.id, weight, enabled, - Inovation.get_instance()._get_innovation_number( - in_node.id, - out_node.id) + Inovation.get_instance()._get_innovation_number(in_node.id, out_node.id), ) self.connections.append(new_conn) @@ -112,17 +110,19 @@ def mutate_nodes(self): con.change_enable(False) con1 = Connection( - con.in_node, - new_node.id, - 1.0, - True, - Inovation.get_instance()._get_innovation_number(con.in_node, new_node.id)) + con.in_node, + new_node.id, + 1.0, + True, + Inovation.get_instance()._get_innovation_number(con.in_node, new_node.id), + ) con2 = Connection( - new_node.id, - con.out_node, - con.weight, - True, - Inovation.get_instance()._get_innovation_number(new_node.id, con.out_node)) + new_node.id, + con.out_node, + con.weight, + True, + Inovation.get_instance()._get_innovation_number(new_node.id, con.out_node), + ) self.connections.extend([con1, con2]) self.nodes.append(new_node) @@ -137,4 +137,4 @@ def __eq__(self, other): return self.id == other.id def __lt__(self, other): - return self.fitness < other.fitness \ No newline at end of file + return self.fitness < other.fitness diff --git a/tests/test_NEATnetwork.py b/tests/test_NEATnetwork.py index d6c0edf..604d849 100644 --- a/tests/test_NEATnetwork.py +++ b/tests/test_NEATnetwork.py @@ -1,35 +1,27 @@ - - from ..src.genome import Node, Connection, Genome from src.NEATnetwork import NEATNetwork - def test_NEAT_network(): - node_1 = Node(1,"input") - node_2 = Node(2,"input") - node_3 = Node(3,"input") - node_4 = Node(4,"hidden") - node_5 = Node(5,"output") - - - nodes_list = [node_1,node_2,node_3,node_4,node_5] + node_1 = Node(1, "input") + node_2 = Node(2, "input") + node_3 = Node(3, "input") + node_4 = Node(4, "hidden") + node_5 = Node(5, "output") + nodes_list = [node_1, node_2, node_3, node_4, node_5] con_1 = Connection(node_1, node_4, 0.7, True, 1) - con_2 = Connection(node_2, node_4, 0.7, True, 2) - con_3 = Connection(node_3, node_4, 0.5, True, 3) - con_4 = Connection(node_2, node_5, 0.2, True, 4) - con_5 = Connection(node_5, node_4, 0.4, True, 5) - con_6 = Connection(node_1, node_5, 0.6, False, 6) - con_7 = Connection(node_1, node_5, 0.6, True, 11) - - - conns= [con_1,con_2,con_3,con_4,con_5,con_6,con_7] - + con_2 = Connection(node_2, node_4, 0.7, True, 2) + con_3 = Connection(node_3, node_4, 0.5, True, 3) + con_4 = Connection(node_2, node_5, 0.2, True, 4) + con_5 = Connection(node_5, node_4, 0.4, True, 5) + con_6 = Connection(node_1, node_5, 0.6, False, 6) + con_7 = Connection(node_1, node_5, 0.6, True, 11) - genome = Genome(id = 1,nodes = nodes_list,connections = conns) + conns = [con_1, con_2, con_3, con_4, con_5, con_6, con_7] + genome = Genome(id=1, nodes=nodes_list, connections=conns) - assert NEATNetwork(genome = genome) == 1.52 \ No newline at end of file + assert NEATNetwork(genome=genome) == 1.52 From b4c647513f7fd52063944da1df34e3da40150d68 Mon Sep 17 00:00:00 2001 From: Sverre Nystad Date: Mon, 11 Nov 2024 16:29:30 +0100 Subject: [PATCH 06/13] feat: create species --- src/species.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/species.py diff --git a/src/species.py b/src/species.py new file mode 100644 index 0000000..de0280d --- /dev/null +++ b/src/species.py @@ -0,0 +1,29 @@ +import random +from typing import List +from src.genome import Genome + + +class Species: + def __init__(self, species_id: int): + self.species_id = species_id + self.members: List[Genome] = [] + self.average_fitness: float = 0.0 + + def add_member(self, genome: Genome): + self.members.append(genome) + + def adjust_fitness(self): + total_fitness = sum(genome.fitness for genome in self.members) + for genome in self.members: + genome.adjusted_fitness = genome.fitness / len(self.members) + self.average_fitness = total_fitness / len(self.members) + + def select_parent(self): + total_adjusted_fitness = sum(genome.adjusted_fitness for genome in self.members) + pick = random.uniform(0, total_adjusted_fitness) + current = 0 + for genome in self.members: + current += genome.adjusted_fitness + if current > pick: + return genome + return random.choice(self.members) From 42e12f26b38059760559136b13e56b8c0a751d21 Mon Sep 17 00:00:00 2001 From: Sverre Nystad Date: Mon, 11 Nov 2024 16:31:17 +0100 Subject: [PATCH 07/13] style: format main --- main.py | 79 ++++++++++++++++++++++----------------------------------- 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/main.py b/main.py index 21a7c84..c091df0 100644 --- a/main.py +++ b/main.py @@ -21,12 +21,7 @@ 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 @@ -37,31 +32,20 @@ def create_creatures(amount, 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=3) + # 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 @@ -74,10 +58,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): @@ -89,7 +75,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 @@ -111,11 +96,11 @@ def main(): 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 @@ -123,7 +108,7 @@ def main(): environment: Environment = Environment(screen, space) environment.ground_type = GroundType.BASIC_GROUND - #Population and creatures + # Population and creatures population_size = 5 creatures: list[Creature] = create_creatures(population_size, space) creature_instance: Creature = creatures[0] @@ -131,7 +116,7 @@ def main(): neat_networks: list[NEATNetwork] = [] for genome in population: neat_networks.append(NEATNetwork(genome)) - + clock = pygame.time.Clock() vision_y = 100 vision_x = 0 @@ -142,20 +127,24 @@ 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() - + 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]) + 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) @@ -166,9 +155,8 @@ def main(): 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 + 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: # kill the creature @@ -177,18 +165,13 @@ def main(): # kill the creature creatures.remove(creature) creature.render(screen) - + clock.tick(60) pygame.display.flip() - pygame.quit() - - if __name__ == "__main__": main() - - From 1201351aa8f77af20ec52b021fda552227526310 Mon Sep 17 00:00:00 2001 From: Sverre Nystad Date: Mon, 11 Nov 2024 16:32:22 +0100 Subject: [PATCH 08/13] feat: Implement GeneticAlgorithm class for population evolution --- neat.py | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 neat.py diff --git a/neat.py b/neat.py new file mode 100644 index 0000000..bd2f801 --- /dev/null +++ b/neat.py @@ -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() From f0f1bb957fe13cb27f2a893e60f4e25e32568afc Mon Sep 17 00:00:00 2001 From: tobiasfremming Date: Mon, 11 Nov 2024 16:36:18 +0100 Subject: [PATCH 09/13] refactor: make death ray optional --- main.py | 9 ++++----- src/environment.py | 12 ++++++++---- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/main.py b/main.py index 21a7c84..13543ff 100644 --- a/main.py +++ b/main.py @@ -121,6 +121,7 @@ def main(): 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 @@ -171,18 +172,16 @@ def main(): environment.offset) # if perlin, offset = 0, if basic, offset = environment.offset if creature.limbs[0].body.position.y > SCREEN_HEIGHT: - # kill the creature - creatures.remove(creature) - if creature.limbs[0].body.position.x < environment.death_ray.get_x(): - # kill the creature 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() diff --git a/src/environment.py b/src/environment.py index 9a309bd..11338e4 100644 --- a/src/environment.py +++ b/src/environment.py @@ -25,6 +25,7 @@ class GroundType(Enum): class Environment(RenderObject): + def __init__(self, screen, space): self.screen = screen self.space = space @@ -32,10 +33,11 @@ def __init__(self, screen, space): self.ground: Ground = self.ground_factory(self.ground_type) self.starting_xx = 50 self.point = Point(self.starting_xx, 100) - - self.offset = 0 self.offset_speed = 1 + self.death_ray = None + + def activate_death_ray(self): self.death_ray = DeathRay(20) def ground_factory(self, ground_type: GroundType) -> Ground: @@ -62,12 +64,14 @@ def update(self): self.ground.update(self.offset) # self.ground.move_segments(self.offset/100) self.starting_xx += 1 - self.death_ray.move(0.1) + if self.death_ray is not None: + self.death_ray.move(0.1) def render(self): self.ground.render() - self.death_ray.render(self.screen) + if self.death_ray is not None: + self.death_ray.render(self.screen) def run(self): From d3f9c8d26b578b10841359ad95faa075428b1ead Mon Sep 17 00:00:00 2001 From: tobiasfremming Date: Mon, 11 Nov 2024 16:46:33 +0100 Subject: [PATCH 10/13] clean: nasty prints --- src/agent_parts/creature.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/agent_parts/creature.py b/src/agent_parts/creature.py index e93c65d..b7f2d06 100644 --- a/src/agent_parts/creature.py +++ b/src/agent_parts/creature.py @@ -91,7 +91,6 @@ def add_motor(self, limb_a: Limb, limb_b: Limb, anchor_a: tuple[float,float], an if abs(global_a[0] - global_b[0]) < tolerance and abs(global_a[1] - global_b[1]) < tolerance: motor = MotorJoint(self.space, limb_a.body, limb_b.body, anchor_a, anchor_b, rate) self.motors.append(motor) - print("add_motor: true") return motor def local_to_global(self, limb: Limb, point: tuple[float, float]) -> tuple[float, float]|None: From cad267bafc34e2e5c4f5f7607121b14736af51c5 Mon Sep 17 00:00:00 2001 From: tobiasfremming Date: Mon, 11 Nov 2024 17:06:10 +0100 Subject: [PATCH 11/13] fix: fix some minor stuff --- agent_parts_main.py | 7 ++++--- main.py | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/agent_parts_main.py b/agent_parts_main.py index 9871e1f..37f1ce7 100644 --- a/agent_parts_main.py +++ b/agent_parts_main.py @@ -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 @@ -108,8 +109,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 diff --git a/main.py b/main.py index 9fe39d8..d07ab4b 100644 --- a/main.py +++ b/main.py @@ -111,7 +111,8 @@ def main(): # Population and creatures population_size = 5 - creatures: list[Creature] = create_creatures(population_size, space) + 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] = [] From 7e2720256a3561b16b77b1315d66e0dbd1ef0786 Mon Sep 17 00:00:00 2001 From: tobiasfremming Date: Mon, 11 Nov 2024 17:12:16 +0100 Subject: [PATCH 12/13] refactor: refactor button function in create creature mode --- agent_parts_main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agent_parts_main.py b/agent_parts_main.py index 37f1ce7..7fd4ee1 100644 --- a/agent_parts_main.py +++ b/agent_parts_main.py @@ -62,7 +62,7 @@ def handle_physics(): def make_limb(): nonlocal make_limb_mode make_limb_mode = not make_limb_mode - make_motorjoint_mode = False + limb_button = Button( text="Add limb", @@ -81,7 +81,7 @@ def make_limb(): def add_motorjoint(): nonlocal make_motorjoint_mode make_motorjoint_mode = not make_motorjoint_mode - make_limb_mode = False + motorjoint_button = Button( text="Add joint", From 91fa131c09ae149298cc99770c0b3251ec674118 Mon Sep 17 00:00:00 2001 From: tobiasfremming Date: Mon, 11 Nov 2024 17:13:06 +0100 Subject: [PATCH 13/13] style: lint --- main.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index d07ab4b..ec73e5d 100644 --- a/main.py +++ b/main.py @@ -4,11 +4,11 @@ import random import numpy as np import pygame -from src.agent_parts.vision import Vision -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 @@ -17,8 +17,6 @@ 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