Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
window:
width: 1200
height: 1000
height: 900
panel:
width: 700
height: 700
x: 100
y: 50
chart:
height: 200
height: 100
grid:
cell_size: 25
mating:
min_age_percent: 0.2
min_energy_level: 0.75
max_range: 5
mutation_chance: 0.05
mutation_multiply_border: 0.2
min_age_percent: 0.10
min_energy_level: 0.50
max_range: 10
mutation_chance: 0.08
mutation_multiply_border: 0.25
mutation_addding_border: 0.05
8 changes: 4 additions & 4 deletions src/agent.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Agent module containing the Agent class and related utilities for simulation."""
from __future__ import annotations
from food import FoodSource
import math
import random
import pygame
Expand Down Expand Up @@ -109,7 +108,7 @@ def __init__(self, position: tuple, environment : Environment,/, decision_matrix
self.max_energy = (10.0 + _sqrt_scale(self.body_points["energy"], 2.0)) * 2
self.base_speed = 0.5 + _sqrt_scale(self.body_points["speed"], 0.2)
self.attack_power = 0.5 + _sqrt_scale(self.body_points["attack"], 0.06)
self.max_age = int(200 + _sqrt_scale(self.body_points["lifespan"], 14.0)) * 2
self.max_age = int((300 + _sqrt_scale(self.body_points["lifespan"], 18.0)) * 2.5)
self.sight = 70.0 + _sqrt_scale(self.body_points["sight"], 6.0)
self.agility = 30.0 + _sqrt_scale(self.body_points["agility"], 2.0)

Expand Down Expand Up @@ -333,7 +332,7 @@ def _move(self, turn: float, intensity: float, speed_modifier: float = 1.0):
new_y = self.y - math.sin(rad) * sp
self._apply_bounds(new_x, new_y, new_angle)

cost = 0.02 + 0.06 * inten
cost = 0.01 + 0.05 * inten
self.energy = max(0.0, self.energy - cost)

def _idle(self):
Expand Down Expand Up @@ -372,7 +371,8 @@ def update(self, speed_modifier):
return

if self._inputs_override is None:
inputs = self.sense(self.environment.food_sources, self.environment.get_agents())
nearby_agents = self.environment.get_nearby_agents(self, self.sight * 2)
inputs = self.sense(self.environment.food_sources, nearby_agents)
else:
inputs = self._inputs_override

Expand Down
10 changes: 5 additions & 5 deletions src/area.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ class Area(Enum):
"display_name": "Plains",
"agent_speed_modifier": 1.0,
"food_regen_modifier": 1.2,
"expansion_chance": 0.003,
"expansion_chance": 0.010,
"color": (60, 120, 60),
"max_food_sources": 5}
"max_food_sources": 8}
FERTILE_VALLEY = {"id": 2,
"display_name": "Fertile Valley",
"agent_speed_modifier": 0.9,
"food_regen_modifier": 1.5,
"expansion_chance": 0.001,
"expansion_chance": 0.007,
"color": (80, 160, 80),
"max_food_sources": 5}
"max_food_sources": 7}
DESERT = {"id": 3,
"display_name":
"Desert",
Expand All @@ -28,7 +28,7 @@ class Area(Enum):
"display_name": "Berry Corner",
"agent_speed_modifier": 1.0,
"food_regen_modifier": 1.2,
"expansion_chance": 0.007,
"expansion_chance": 0.012,
"color": (180, 100, 255),
"max_food_sources": 5}

Expand Down
36 changes: 35 additions & 1 deletion src/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ def __init__(self, grid_width: int, grid_height: int, pixel_width: int, pixel_he

self.data_lock = threading.Lock()

self._spatial_cell_size = 100.0
self._spatial_grid = {}

self.simulation_thread = threading.Thread(target=self._simulation_loop)
self.simulation_thread.start()

Expand Down Expand Up @@ -167,8 +170,37 @@ def _spawn_initial_agents(self):
agent = Agent((pos_x, pos_y), self)
self.agents.append(agent)

def _build_spatial_grid(self):
"""Build spatial hash grid for fast neighbor queries."""
self._spatial_grid.clear()
for agent in self.agents:
if agent.is_alive():
cell_x = int(agent.x // self._spatial_cell_size)
cell_y = int(agent.y // self._spatial_cell_size)
if (cell_x, cell_y) not in self._spatial_grid:
self._spatial_grid[(cell_x, cell_y)] = []
self._spatial_grid[(cell_x, cell_y)].append(agent)

def get_nearby_agents(self, agent, radius: float) -> List[Agent]:
"""Get agents within radius using spatial partitioning (O(1) instead of O(n))."""
cell_x = int(agent.x // self._spatial_cell_size)
cell_y = int(agent.y // self._spatial_cell_size)

# Check how many cells we need to search based on radius
cells_to_check = int(radius // self._spatial_cell_size) + 1

nearby = []
for dx in range(-cells_to_check, cells_to_check + 1):
for dy in range(-cells_to_check, cells_to_check + 1):
cell_key = (cell_x + dx, cell_y + dy)
if cell_key in self._spatial_grid:
nearby.extend(self._spatial_grid[cell_key])

return nearby

def set_speed(self, value: float):
self.sim_speed = max(0.1, value)
with self.data_lock:
self.sim_speed = max(0.1, value)

def _simulation_loop(self):
while self.running:
Expand Down Expand Up @@ -220,6 +252,8 @@ def _simulation_loop(self):
food_to_keep.append(food)
self.food_items = food_to_keep

self._build_spatial_grid()

new_agents = []
for agent in self.agents:
area = self._get_agent_area(agent)
Expand Down
35 changes: 28 additions & 7 deletions src/mating.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,39 @@
from random import choice, randint, random, uniform

class Mating:
"""Handles reproduction logic for agents including mate selection and genome crossover."""

def __init__(self, parent: Agent):
self.parent : Agent = parent

def mate(self):
"""Attempt mating if constraints are met. Creates offspring with blended genetics."""
if not self._checking_constrains():
return
close_agents = self._get_nearby_agents()
if not close_agents:
return
matrix, vector = self.get_new_genome(choice(close_agents))
new_agent = Agent((self.parent.x, self.parent.y), self.parent.environment, decision_matrix= matrix, genome= vector, species = self.parent.group_id)
energy_level = self.parent.energy / self.parent.max_energy
new_agent.energy = new_agent.max_energy * energy_level / 2
self.parent.energy /= 2
self.parent.environment.create_agent(new_agent)


def _get_nearby_agents(self):
def _get_nearby_agents(self) -> typing.List[Agent]:
"""Find nearby agents of the same group within mating range."""
close_agents: typing.List[Agent] = []
for agent in self.parent.environment.get_agents():
if dist2(self.parent.x, self.parent.y, agent.x, agent.y) < config.MAX_RANGE and agent.group_id == self.parent.group_id:
close_agents.append(agent)
return close_agents

def _checking_constrains(self):
def _checking_constrains(self) -> bool:
"""Check if parent meets mating requirements (age and energy thresholds)."""
return not( self.parent.age < config.MIN_AGE_PERCENT * self.parent.max_age or self.parent.energy < config.MIN_ENERGY_LEVEL * self.parent.max_energy)

def get_new_genome(self, second : Agent):
def get_new_genome(self, second : Agent) -> typing.Tuple[typing.List[typing.List[float]], typing.Dict[str, int]]:
"""Generate offspring genome by crossing over parent genomes and applying mutations."""
matrix = self.create_new_decision_matrix(second)
vector = self.create_new_genome_vector(second)

Expand Down Expand Up @@ -62,11 +69,25 @@ def get_new_genome(self, second : Agent):

return matrix, vector

def create_new_decision_matrix(self, second : Agent):
def create_new_decision_matrix(self, second : Agent) -> typing.List[typing.List[float]]:
"""Perform two-point crossover on neural network weights."""
output = []
crossover_points = [randint(1, len(self.parent.weights[0])-1) for _ in range(2)]
crossover_points.sort()

for i in range(len(self.parent.weights)):
number = randint(0, len(self.parent.weights[0]))
output.append(self.parent.weights[i][:number] + second.weights[i][number:])
row = []
last_point = 0
parent = self.parent

for cp in crossover_points:
row.extend(parent.weights[i][last_point:cp])
parent = second if parent == self.parent else self.parent
last_point = cp

row.extend(parent.weights[i][last_point:])
output.append(row)

return output

def create_new_genome_vector(self, second : Agent):
Expand Down
1 change: 0 additions & 1 deletion src/run.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Main application entry point for the simulation."""
import pygame
import pygame_gui
from pygame_gui.elements import UIButton, UIPanel
import config
from environment import Environment
from area import Area
Expand Down
Loading