Upload 6 files
Browse files- agent_coev.py +239 -0
- environment.py +350 -0
- main_coev.py +293 -0
- neat_config_v4.txt +98 -0
- settings.py +91 -0
- vector.py +92 -0
agent_coev.py
ADDED
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# swarm_mind_v4/agent_coev.py
|
2 |
+
import pygame
|
3 |
+
import numpy as np
|
4 |
+
import random
|
5 |
+
import math
|
6 |
+
import neat # NEAT kütüphanesi
|
7 |
+
from vector import Vector2D
|
8 |
+
# V4 Environment'ı import ediyoruz
|
9 |
+
from environment import Environment, FoodSource
|
10 |
+
import settings as s
|
11 |
+
|
12 |
+
class AgentCoEv:
|
13 |
+
"""
|
14 |
+
Ko-evrim simülasyonundaki bir ajan. NEAT tarafından evrimleştirilen bir
|
15 |
+
sinir ağı tarafından kontrol edilir ve kendi kolonisine aittir.
|
16 |
+
"""
|
17 |
+
def __init__(self, genome, config, environment: Environment, colony_id: int):
|
18 |
+
"""Ajanı genom, config, ortam ve koloni ID ile başlatır."""
|
19 |
+
self.genome = genome
|
20 |
+
self.config = config
|
21 |
+
self.environment = environment
|
22 |
+
self.colony_id = colony_id # Kendi kolonisi (0: Kırmızı, 1: Mavi)
|
23 |
+
|
24 |
+
# Genomdan sinir ağını oluştur
|
25 |
+
self.net = neat.nn.FeedForwardNetwork.create(genome, config)
|
26 |
+
|
27 |
+
# Fiziksel özellikler
|
28 |
+
self.max_speed = s.MAX_SPEED
|
29 |
+
self.max_force = s.MAX_FORCE
|
30 |
+
self.size = s.AGENT_SIZE
|
31 |
+
|
32 |
+
# Sensör parametreleri
|
33 |
+
self.pheromone_sense_dist = s.NN_PHEROMONE_SENSE_DIST
|
34 |
+
self.pheromone_sense_angles = s.NN_PHEROMONE_SENSE_ANGLES
|
35 |
+
self.food_sense_radius = s.NN_FOOD_SENSE_RADIUS
|
36 |
+
self.agent_sense_radius = s.NN_AGENT_SENSE_RADIUS
|
37 |
+
|
38 |
+
# Başlangıç durumunu ayarlamak için reset() çağır
|
39 |
+
self.reset()
|
40 |
+
|
41 |
+
|
42 |
+
def reset(self):
|
43 |
+
"""Ajanın durumunu başlangıç koşullarına sıfırlar."""
|
44 |
+
# Başlangıç pozisyonu (kendi yuvasının yakınında)
|
45 |
+
my_nest_pos = self.environment.get_nest_position(self.colony_id)
|
46 |
+
angle = random.uniform(0, 2 * math.pi)
|
47 |
+
start_offset_np = np.array([math.cos(angle), math.sin(angle)]) * s.NEST_RADIUS * 1.2
|
48 |
+
start_pos_np = my_nest_pos + start_offset_np
|
49 |
+
self.position = Vector2D(np.clip(start_pos_np[0], 0, s.SCREEN_WIDTH),
|
50 |
+
np.clip(start_pos_np[1], 0, s.SCREEN_HEIGHT))
|
51 |
+
|
52 |
+
self.velocity = Vector2D.random_vector() * (self.max_speed / 2)
|
53 |
+
self.acceleration = Vector2D(0, 0)
|
54 |
+
|
55 |
+
# Durum
|
56 |
+
self.has_food = False
|
57 |
+
# Renk kolonisine göre ayarlanır
|
58 |
+
self.color = s.COLOR_AGENT_RED_SEEKING if self.colony_id == s.COLONY_ID_RED else s.COLOR_AGENT_BLUE_SEEKING
|
59 |
+
|
60 |
+
# Fitness takibi için sayaç
|
61 |
+
self.food_collected_count = 0
|
62 |
+
|
63 |
+
|
64 |
+
def _get_inputs(self, all_agents: list) -> list[float]:
|
65 |
+
"""Sinir ağı için girdileri toplar, normalize eder."""
|
66 |
+
inputs = []
|
67 |
+
current_pos_np = np.array([self.position.x, self.position.y])
|
68 |
+
|
69 |
+
# --- Feromon Girdileri (Kendi Kolonisinin, Normalleştirilmiş 0-1) ---
|
70 |
+
current_heading = self.velocity.heading()
|
71 |
+
sample_points_v = []
|
72 |
+
for angle_offset in self.pheromone_sense_angles:
|
73 |
+
angle = current_heading + angle_offset
|
74 |
+
point = self.position + Vector2D(math.cos(angle), math.sin(angle)) * self.pheromone_sense_dist
|
75 |
+
sample_points_v.append(point)
|
76 |
+
sample_points_np = [np.array([p.x, p.y]) for p in sample_points_v]
|
77 |
+
|
78 |
+
# Kendi kolonisine ait 'home' ve 'food' feromonlarını al
|
79 |
+
home_pheromones = self.environment.sense_pheromones_at(self.colony_id, 'home', sample_points_np)
|
80 |
+
food_pheromones = self.environment.sense_pheromones_at(self.colony_id, 'food', sample_points_np)
|
81 |
+
|
82 |
+
inputs.extend(home_pheromones / s.PHEROMONE_MAX_STRENGTH) # 3 girdi
|
83 |
+
inputs.extend(food_pheromones / s.PHEROMONE_MAX_STRENGTH) # 3 girdi
|
84 |
+
|
85 |
+
# --- Bias Girdisi ---
|
86 |
+
inputs.append(1.0) # 1 girdi
|
87 |
+
|
88 |
+
# --- Durum Girdisi ---
|
89 |
+
inputs.append(1.0 if self.has_food else 0.0) # 1 girdi
|
90 |
+
|
91 |
+
# --- Hız Girdisi (Normalleştirilmiş -1 ile 1 arası) ---
|
92 |
+
norm_vel_x = np.clip(self.velocity.x / self.max_speed, -1.0, 1.0)
|
93 |
+
norm_vel_y = np.clip(self.velocity.y / self.max_speed, -1.0, 1.0)
|
94 |
+
inputs.extend([norm_vel_x, norm_vel_y]) # 2 girdi
|
95 |
+
|
96 |
+
# --- Yuva Yönü Girdisi (Kendi Yuvasına, Normalleştirilmiş X, Y) ---
|
97 |
+
my_nest_pos = self.environment.get_nest_position(self.colony_id)
|
98 |
+
nest_vector = my_nest_pos - current_pos_np
|
99 |
+
dist_to_nest_sq = np.sum(nest_vector**2)
|
100 |
+
if dist_to_nest_sq > 1e-6:
|
101 |
+
nest_direction = nest_vector / np.sqrt(dist_to_nest_sq)
|
102 |
+
else:
|
103 |
+
nest_direction = np.zeros(2, dtype=np.float32)
|
104 |
+
inputs.extend(nest_direction) # 2 girdi
|
105 |
+
|
106 |
+
# --- Yem Girdileri (Yön X, Y normalleştirilmiş; Mesafe normalleştirilmiş) ---
|
107 |
+
# Yem kaynakları ortak olduğu için environment'dan direkt alınır
|
108 |
+
closest_food_pos, dist_sq_to_food, food_direction = self.environment.get_closest_food(current_pos_np)
|
109 |
+
if closest_food_pos is not None and dist_sq_to_food < self.food_sense_radius**2 :
|
110 |
+
inputs.extend(food_direction) # 2 girdi
|
111 |
+
normalized_dist = np.sqrt(dist_sq_to_food) / self.food_sense_radius
|
112 |
+
inputs.append(np.clip(normalized_dist, 0.0, 1.0)) # 1 girdi
|
113 |
+
else:
|
114 |
+
inputs.extend([0.0, 0.0]) # Yön yok
|
115 |
+
inputs.append(1.0) # Mesafe maksimum
|
116 |
+
|
117 |
+
# --- Yakındaki Ajan Yoğunluğu Girdisi (Normalleştirilmiş 0-1) ---
|
118 |
+
# Şimdilik dost/düşman ayrımı yapmıyoruz, toplam yoğunluk
|
119 |
+
nearby_count = 0
|
120 |
+
for other_agent in all_agents:
|
121 |
+
# Ajan listesi artık karışık kolonilerden oluşacak
|
122 |
+
if other_agent is self: continue
|
123 |
+
# Vector2D ile mesafe kontrolü
|
124 |
+
dist_sq = self.position.distance_squared(other_agent.position)
|
125 |
+
if dist_sq < self.agent_sense_radius**2:
|
126 |
+
nearby_count += 1
|
127 |
+
# Normalleştirme (örn: 15 komşu max yoğunluk)
|
128 |
+
density = np.clip(nearby_count / (s.TOTAL_AGENTS / 3.0), 0.0, 1.0) # Toplam ajan sayısına göre oranla
|
129 |
+
inputs.append(density) # 1 girdi
|
130 |
+
|
131 |
+
# Toplam girdi sayısı config ile eşleşmeli (16)
|
132 |
+
assert len(inputs) == s.num_inputs, f"Hata: Girdi sayısı ({len(inputs)}) config ile eşleşmiyor ({s.num_inputs})"
|
133 |
+
return inputs
|
134 |
+
|
135 |
+
|
136 |
+
def update(self, all_agents: list):
|
137 |
+
"""Ajanın durumunu sinir ağına ve ortama göre günceller."""
|
138 |
+
# 1. Girdileri Al
|
139 |
+
nn_inputs = self._get_inputs(all_agents)
|
140 |
+
|
141 |
+
# 2. Sinir Ağını Aktive Et
|
142 |
+
nn_outputs = self.net.activate(nn_inputs)
|
143 |
+
|
144 |
+
# 3. Çıktıları Yorumla
|
145 |
+
steer_x = nn_outputs[0] * self.max_force
|
146 |
+
steer_y = nn_outputs[1] * self.max_force
|
147 |
+
steering_force = Vector2D(steer_x, steer_y)
|
148 |
+
deposit_signal = (nn_outputs[2] + 1.0) / 2.0 # tanh için (0,1) aralığı
|
149 |
+
should_deposit_home = deposit_signal > s.NN_OUTPUT_DEPOSIT_THRESHOLD
|
150 |
+
|
151 |
+
# 4. Yönlendirme Kuvvetini Uygula
|
152 |
+
self.apply_force(steering_force)
|
153 |
+
|
154 |
+
# --- Fizik Güncellemesi ---
|
155 |
+
self.velocity += self.acceleration
|
156 |
+
self.velocity.limit(self.max_speed)
|
157 |
+
self.position += self.velocity
|
158 |
+
self.acceleration *= 0
|
159 |
+
|
160 |
+
# --- Ortamla Etkileşim ---
|
161 |
+
current_pos_np = np.array([self.position.x, self.position.y])
|
162 |
+
|
163 |
+
# a. Yuvada mı kontrol et (Yem bırakma - KENDİ YUVASI)
|
164 |
+
if self.has_food and self.environment.is_at_nest(self.colony_id, current_pos_np):
|
165 |
+
self.has_food = False
|
166 |
+
self.food_collected_count += 1
|
167 |
+
# Rengi güncelle
|
168 |
+
self.color = s.COLOR_AGENT_RED_SEEKING if self.colony_id == s.COLONY_ID_RED else s.COLOR_AGENT_BLUE_SEEKING
|
169 |
+
|
170 |
+
# b. Yemde mi kontrol et (Yem alma)
|
171 |
+
if not self.has_food:
|
172 |
+
active_foods = self.environment.get_food_sources()
|
173 |
+
for food_source in active_foods:
|
174 |
+
dist_sq = np.sum((food_source.position - current_pos_np)**2)
|
175 |
+
if dist_sq < (food_source.radius + self.size)**2:
|
176 |
+
if food_source.take():
|
177 |
+
self.has_food = True
|
178 |
+
# Rengi güncelle
|
179 |
+
self.color = s.COLOR_AGENT_RED_RETURNING if self.colony_id == s.COLONY_ID_RED else s.COLOR_AGENT_BLUE_RETURNING
|
180 |
+
break # Yem alındı
|
181 |
+
|
182 |
+
# c. Feromon Bırakma (KENDİ KOLONİSİNİN 'home' izi)
|
183 |
+
if should_deposit_home and self.has_food:
|
184 |
+
if random.random() < 0.8:
|
185 |
+
self.environment.deposit_pheromone(self.colony_id, 'home', current_pos_np, s.PHEROMONE_DEPOSIT_VALUE)
|
186 |
+
|
187 |
+
# --- Kenarlar ve Engeller ---
|
188 |
+
self.edges()
|
189 |
+
self._handle_obstacle_collisions()
|
190 |
+
|
191 |
+
|
192 |
+
def _handle_obstacle_collisions(self):
|
193 |
+
"""Engellerle çarpışmayı kontrol eder ve iter (V3'ten aynı)."""
|
194 |
+
agent_pos_np = np.array([self.position.x, self.position.y])
|
195 |
+
for obs in self.environment.obstacles:
|
196 |
+
dist_vec_np = agent_pos_np - obs.position
|
197 |
+
dist_sq = np.sum(dist_vec_np**2)
|
198 |
+
min_dist = obs.radius + self.size * 1.5
|
199 |
+
if dist_sq < min_dist**2 and dist_sq > 1e-6:
|
200 |
+
distance = np.sqrt(dist_sq)
|
201 |
+
penetration = min_dist - distance
|
202 |
+
away_force_dir = dist_vec_np / distance
|
203 |
+
force_magnitude = penetration * self.max_force * 5
|
204 |
+
away_force = Vector2D(away_force_dir[0], away_force_dir[1]) * force_magnitude
|
205 |
+
self.apply_force(away_force)
|
206 |
+
self.velocity *= 0.95
|
207 |
+
|
208 |
+
|
209 |
+
def apply_force(self, force: Vector2D):
|
210 |
+
"""İvmeye kuvvet ekler (Aynı)."""
|
211 |
+
self.acceleration += force
|
212 |
+
|
213 |
+
def edges(self):
|
214 |
+
"""Ekran kenarlarından sekme (Aynı)."""
|
215 |
+
margin = 5
|
216 |
+
turn_force = Vector2D(0,0)
|
217 |
+
apply_turn = False
|
218 |
+
pos = self.position; vel = self.velocity; max_s = self.max_speed; max_f = self.max_force * 3
|
219 |
+
|
220 |
+
if pos.x < margin: turn_force.x = (max_s - vel.x); apply_turn=True
|
221 |
+
elif pos.x > s.SCREEN_WIDTH - margin: turn_force.x = (-max_s - vel.x); apply_turn=True
|
222 |
+
if pos.y < margin: turn_force.y = (max_s - vel.y); apply_turn=True
|
223 |
+
elif pos.y > s.SCREEN_HEIGHT - margin: turn_force.y = (-max_s - vel.y); apply_turn=True
|
224 |
+
|
225 |
+
if apply_turn:
|
226 |
+
turn_force.limit(max_f)
|
227 |
+
self.apply_force(turn_force)
|
228 |
+
|
229 |
+
def draw(self, screen):
|
230 |
+
"""Ajanı kolonisine uygun renkte çizer."""
|
231 |
+
# Rengi __init__ ve update içinde ayarladık
|
232 |
+
current_color = self.color
|
233 |
+
pos_int = (int(self.position.x), int(self.position.y))
|
234 |
+
radius = int(self.size)
|
235 |
+
|
236 |
+
# Basit daire çizimi
|
237 |
+
pygame.draw.circle(screen, current_color, pos_int, radius)
|
238 |
+
if self.has_food: # Yem taşıyorsa içine beyaz daire
|
239 |
+
pygame.draw.circle(screen, (255,255,255), pos_int, int(radius * 0.5))
|
environment.py
ADDED
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# swarm_mind_v4/environment.py
|
2 |
+
import pygame
|
3 |
+
import numpy as np
|
4 |
+
import random
|
5 |
+
import settings as s # Ayarları 's' takma adıyla içeri aktar
|
6 |
+
|
7 |
+
class FoodSource:
|
8 |
+
"""Basit bir yem kaynağı sınıfı (V3'ten aynı)."""
|
9 |
+
def __init__(self, position, initial_amount, radius):
|
10 |
+
self.position = np.array(position, dtype=np.float32)
|
11 |
+
self.amount = initial_amount
|
12 |
+
self.radius = radius
|
13 |
+
self.color = s.COLOR_FOOD
|
14 |
+
|
15 |
+
def take(self) -> bool:
|
16 |
+
if self.amount > 0:
|
17 |
+
self.amount -= 1
|
18 |
+
return True
|
19 |
+
return False
|
20 |
+
|
21 |
+
def is_empty(self) -> bool:
|
22 |
+
return self.amount <= 0
|
23 |
+
|
24 |
+
def draw(self, screen):
|
25 |
+
if self.amount > 0: # Sadece içinde yem varsa çiz
|
26 |
+
brightness = max(0.2, min(1.0, self.amount / s.FOOD_INITIAL_AMOUNT)) # Min parlaklık ekle
|
27 |
+
color = (int(self.color[0] * brightness),
|
28 |
+
int(self.color[1] * brightness),
|
29 |
+
int(self.color[2] * brightness))
|
30 |
+
pygame.draw.circle(screen, color, self.position.astype(int), self.radius)
|
31 |
+
|
32 |
+
# V3/V4: Engel Sınıfı
|
33 |
+
class Obstacle:
|
34 |
+
"""Basit dairesel bir engeli temsil eder."""
|
35 |
+
def __init__(self, position, radius):
|
36 |
+
self.position = np.array(position, dtype=np.float32)
|
37 |
+
self.radius = radius
|
38 |
+
self.color = s.COLOR_OBSTACLE
|
39 |
+
|
40 |
+
def draw(self, screen):
|
41 |
+
pygame.draw.circle(screen, self.color, self.position.astype(int), int(self.radius))
|
42 |
+
|
43 |
+
class Environment:
|
44 |
+
"""
|
45 |
+
Simülasyon ortamını yönetir (V4: İki Koloni, Ayrı Feromonlar, Dinamik Yem, Engeller).
|
46 |
+
"""
|
47 |
+
def __init__(self, width, height):
|
48 |
+
self.width = width
|
49 |
+
self.height = height
|
50 |
+
self.grid_res = s.PHEROMONE_RESOLUTION
|
51 |
+
self.grid_width = width // self.grid_res
|
52 |
+
self.grid_height = height // self.grid_res
|
53 |
+
|
54 |
+
# V4: Koloniye özel feromon ızgaraları
|
55 |
+
self.home_pheromone_grids = {
|
56 |
+
s.COLONY_ID_RED: np.zeros((self.grid_width, self.grid_height), dtype=np.float32),
|
57 |
+
s.COLONY_ID_BLUE: np.zeros((self.grid_width, self.grid_height), dtype=np.float32)
|
58 |
+
}
|
59 |
+
self.food_pheromone_grids = {
|
60 |
+
s.COLONY_ID_RED: np.zeros((self.grid_width, self.grid_height), dtype=np.float32),
|
61 |
+
s.COLONY_ID_BLUE: np.zeros((self.grid_width, self.grid_height), dtype=np.float32)
|
62 |
+
}
|
63 |
+
|
64 |
+
# V4: İki Yuva
|
65 |
+
self.nest_positions = {
|
66 |
+
s.COLONY_ID_RED: s.NEST_POS_RED.astype(np.float32),
|
67 |
+
s.COLONY_ID_BLUE: s.NEST_POS_BLUE.astype(np.float32)
|
68 |
+
}
|
69 |
+
self.nest_radius = s.NEST_RADIUS
|
70 |
+
self.nest_colors = {
|
71 |
+
s.COLONY_ID_RED: s.COLOR_NEST_RED,
|
72 |
+
s.COLONY_ID_BLUE: s.COLOR_NEST_BLUE
|
73 |
+
}
|
74 |
+
|
75 |
+
# Dinamik Yem Kaynakları
|
76 |
+
self.food_sources = []
|
77 |
+
|
78 |
+
# Engeller
|
79 |
+
self.obstacles = self._create_obstacles()
|
80 |
+
|
81 |
+
# Başlangıç yemleri
|
82 |
+
for _ in range(s.MAX_FOOD_SOURCES // 2):
|
83 |
+
self._try_spawn_food()
|
84 |
+
|
85 |
+
# Feromonları çizmek için yüzey
|
86 |
+
self.pheromone_surface = pygame.Surface((self.width, self.height), pygame.SRCALPHA)
|
87 |
+
|
88 |
+
def _create_obstacles(self):
|
89 |
+
"""Ortama rastgele engeller yerleştirir."""
|
90 |
+
obstacles = []
|
91 |
+
attempts = 0
|
92 |
+
max_attempts = s.NUM_OBSTACLES * 15
|
93 |
+
|
94 |
+
while len(obstacles) < s.NUM_OBSTACLES and attempts < max_attempts:
|
95 |
+
attempts += 1
|
96 |
+
radius = random.uniform(s.OBSTACLE_MIN_RADIUS, s.OBSTACLE_MAX_RADIUS)
|
97 |
+
margin = radius + 20
|
98 |
+
pos = np.array([random.uniform(margin, self.width - margin),
|
99 |
+
random.uniform(margin, self.height - margin)], dtype=np.float32)
|
100 |
+
|
101 |
+
# Yuvalarla çarpışıyor mu?
|
102 |
+
nest_collision = False
|
103 |
+
for nest_pos in self.nest_positions.values():
|
104 |
+
if np.linalg.norm(pos - nest_pos) < self.nest_radius + radius + 15:
|
105 |
+
nest_collision = True
|
106 |
+
break
|
107 |
+
if nest_collision: continue
|
108 |
+
|
109 |
+
# Diğer engellerle çarpışıyor mu?
|
110 |
+
obstacle_collision = False
|
111 |
+
for obs in obstacles:
|
112 |
+
if np.linalg.norm(pos - obs.position) < obs.radius + radius + 10:
|
113 |
+
obstacle_collision = True
|
114 |
+
break
|
115 |
+
if not obstacle_collision:
|
116 |
+
obstacles.append(Obstacle(pos, radius))
|
117 |
+
|
118 |
+
print(f"Created {len(obstacles)} obstacles.")
|
119 |
+
return obstacles
|
120 |
+
|
121 |
+
def _try_spawn_food(self):
|
122 |
+
"""Ortama yeni bir yem kaynağı eklemeyi dener."""
|
123 |
+
if len(self.food_sources) >= s.MAX_FOOD_SOURCES: return
|
124 |
+
|
125 |
+
attempts = 0
|
126 |
+
max_attempts = 30
|
127 |
+
while attempts < max_attempts:
|
128 |
+
attempts += 1
|
129 |
+
margin = s.FOOD_RADIUS + 15
|
130 |
+
pos = np.array([random.uniform(margin, self.width - margin),
|
131 |
+
random.uniform(margin, self.height - margin)], dtype=np.float32)
|
132 |
+
|
133 |
+
# Yuvalarla çarpışıyor mu?
|
134 |
+
nest_collision = False
|
135 |
+
for nest_pos in self.nest_positions.values():
|
136 |
+
if np.linalg.norm(pos - nest_pos) < self.nest_radius + s.FOOD_RADIUS + 25:
|
137 |
+
nest_collision = True
|
138 |
+
break
|
139 |
+
if nest_collision: continue
|
140 |
+
|
141 |
+
# Engellerle çarpışıyor mu?
|
142 |
+
obstacle_collision = False
|
143 |
+
for obs in self.obstacles:
|
144 |
+
if np.linalg.norm(pos - obs.position) < obs.radius + s.FOOD_RADIUS + 15:
|
145 |
+
obstacle_collision = True
|
146 |
+
break
|
147 |
+
if obstacle_collision: continue
|
148 |
+
|
149 |
+
# Diğer yem kaynaklarına çok yakın mı?
|
150 |
+
food_collision = False
|
151 |
+
for fs in self.food_sources:
|
152 |
+
if np.linalg.norm(pos - fs.position) < s.FOOD_RADIUS * 5:
|
153 |
+
food_collision = True
|
154 |
+
break
|
155 |
+
if food_collision: continue
|
156 |
+
|
157 |
+
# Uygun yer bulundu
|
158 |
+
self.food_sources.append(FoodSource(pos, s.FOOD_INITIAL_AMOUNT, s.FOOD_RADIUS))
|
159 |
+
return
|
160 |
+
|
161 |
+
def update(self):
|
162 |
+
"""Ortamı her adımda günceller."""
|
163 |
+
self._update_pheromones()
|
164 |
+
|
165 |
+
# Yem Kaynaklarını Yönet
|
166 |
+
if s.FOOD_DEPLETION_REMOVAL:
|
167 |
+
self.food_sources = [fs for fs in self.food_sources if not fs.is_empty()]
|
168 |
+
if random.random() < s.FOOD_SPAWN_RATE:
|
169 |
+
self._try_spawn_food()
|
170 |
+
|
171 |
+
def world_to_grid(self, world_pos: np.ndarray) -> tuple[int, int]:
|
172 |
+
"""Dünya koordinatlarını ızgara indekslerine dönüştürür."""
|
173 |
+
gx = int(world_pos[0] / self.grid_res)
|
174 |
+
gy = int(world_pos[1] / self.grid_res)
|
175 |
+
gx = np.clip(gx, 0, self.grid_width - 1)
|
176 |
+
gy = np.clip(gy, 0, self.grid_height - 1)
|
177 |
+
return gx, gy
|
178 |
+
|
179 |
+
def deposit_pheromone(self, colony_id: int, pheromone_type: str, world_pos: np.ndarray, amount: float):
|
180 |
+
"""Belirtilen konuma, belirtilen koloninin feromonunu bırakır."""
|
181 |
+
gx, gy = self.world_to_grid(world_pos)
|
182 |
+
# Doğru ızgarayı seç
|
183 |
+
if pheromone_type == 'home':
|
184 |
+
grid = self.home_pheromone_grids.get(colony_id) # .get() ile None dönebilir
|
185 |
+
elif pheromone_type == 'food':
|
186 |
+
grid = self.food_pheromone_grids.get(colony_id)
|
187 |
+
else:
|
188 |
+
grid = None # Bilinmeyen tür veya hatalı colony_id
|
189 |
+
|
190 |
+
if grid is not None:
|
191 |
+
grid[gx, gy] += amount
|
192 |
+
grid[gx, gy] = np.clip(grid[gx, gy], 0, s.PHEROMONE_MAX_STRENGTH)
|
193 |
+
|
194 |
+
def _diffuse_and_evaporate(self, grid: np.ndarray) -> np.ndarray:
|
195 |
+
"""Tek bir feromon ızgarasını günceller."""
|
196 |
+
padded_grid = np.pad(grid, 1, mode='constant')
|
197 |
+
center_weight = 1.0 - (s.PHEROMONE_DIFFUSION_RATE * 8)
|
198 |
+
neighbor_weight = s.PHEROMONE_DIFFUSION_RATE
|
199 |
+
# Komşuların ağırlıklı toplamı (daha okunabilir)
|
200 |
+
neighbors_sum = (padded_grid[:-2, 1:-1] + padded_grid[2:, 1:-1] + # Yukarı/Aşağı
|
201 |
+
padded_grid[1:-1, :-2] + padded_grid[1:-1, 2:] + # Sol/Sağ
|
202 |
+
padded_grid[:-2, :-2] + padded_grid[:-2, 2:] + # SolÜst/SağÜst
|
203 |
+
padded_grid[2:, :-2] + padded_grid[2:, 2:]) # SolAlt/SağAlt
|
204 |
+
diffused = (padded_grid[1:-1, 1:-1] * center_weight + neighbors_sum * neighbor_weight)
|
205 |
+
evaporated = diffused * (1.0 - s.PHEROMONE_EVAPORATION_RATE)
|
206 |
+
return np.clip(evaporated, 0, s.PHEROMONE_MAX_STRENGTH)
|
207 |
+
|
208 |
+
def _update_pheromones(self):
|
209 |
+
"""Tüm feromon ızgaralarını günceller."""
|
210 |
+
for colony_id in self.home_pheromone_grids:
|
211 |
+
self.home_pheromone_grids[colony_id] = self._diffuse_and_evaporate(self.home_pheromone_grids[colony_id])
|
212 |
+
self.food_pheromone_grids[colony_id] = self._diffuse_and_evaporate(self.food_pheromone_grids[colony_id])
|
213 |
+
|
214 |
+
def get_pheromone_strength(self, colony_id: int, pheromone_type: str, world_pos: np.ndarray) -> float:
|
215 |
+
"""Belirtilen koloninin, belirtilen türdeki feromon gücünü alır."""
|
216 |
+
gx, gy = self.world_to_grid(world_pos)
|
217 |
+
if pheromone_type == 'home':
|
218 |
+
grid = self.home_pheromone_grids.get(colony_id)
|
219 |
+
elif pheromone_type == 'food':
|
220 |
+
grid = self.food_pheromone_grids.get(colony_id)
|
221 |
+
else: return 0.0
|
222 |
+
|
223 |
+
return grid[gx, gy] if grid is not None else 0.0
|
224 |
+
|
225 |
+
def sense_pheromones_at(self, colony_id: int, pheromone_type: str, sample_points: list[np.ndarray]) -> np.ndarray:
|
226 |
+
"""Verilen noktalardaki belirtilen koloniye ait feromon yoğunluklarını döndürür."""
|
227 |
+
strengths = []
|
228 |
+
# Doğru ızgarayı seç
|
229 |
+
if pheromone_type == 'home':
|
230 |
+
grid = self.home_pheromone_grids.get(colony_id)
|
231 |
+
elif pheromone_type == 'food':
|
232 |
+
grid = self.food_pheromone_grids.get(colony_id)
|
233 |
+
else: grid = None
|
234 |
+
|
235 |
+
if grid is None: return np.zeros(len(sample_points), dtype=np.float32)
|
236 |
+
|
237 |
+
for point in sample_points:
|
238 |
+
gx, gy = self.world_to_grid(point)
|
239 |
+
strengths.append(grid[gx, gy])
|
240 |
+
return np.array(strengths, dtype=np.float32)
|
241 |
+
|
242 |
+
def get_food_sources(self) -> list[FoodSource]:
|
243 |
+
"""Aktif yem kaynaklarının listesi."""
|
244 |
+
return [fs for fs in self.food_sources if not fs.is_empty()]
|
245 |
+
|
246 |
+
# === Bu metot environment.py içinde olmalı ===
|
247 |
+
def get_closest_food(self, world_pos: np.ndarray) -> tuple[np.ndarray | None, float, np.ndarray]:
|
248 |
+
"""Verilen konuma en yakın aktif yem kaynağını bulur.
|
249 |
+
Döndürülenler: (kaynak_pozisyonu, uzaklık_karesi, yön_vektörü_normalize)
|
250 |
+
Eğer kaynak yoksa: (None, float('inf'), np.zeros(2))
|
251 |
+
"""
|
252 |
+
closest_fs = None
|
253 |
+
min_dist_sq = float('inf')
|
254 |
+
active_foods = self.get_food_sources() # Aktif olanları al
|
255 |
+
|
256 |
+
for fs in active_foods:
|
257 |
+
dist_sq = np.sum((fs.position - world_pos)**2)
|
258 |
+
if dist_sq < min_dist_sq:
|
259 |
+
min_dist_sq = dist_sq
|
260 |
+
closest_fs = fs
|
261 |
+
|
262 |
+
if closest_fs:
|
263 |
+
direction_vec = closest_fs.position - world_pos
|
264 |
+
dist = np.sqrt(min_dist_sq)
|
265 |
+
if dist > 1e-6:
|
266 |
+
normalized_direction = direction_vec / dist
|
267 |
+
else:
|
268 |
+
normalized_direction = np.zeros(2, dtype=np.float32)
|
269 |
+
return closest_fs.position, min_dist_sq, normalized_direction
|
270 |
+
else:
|
271 |
+
return None, float('inf'), np.zeros(2, dtype=np.float32)
|
272 |
+
# ============================================
|
273 |
+
|
274 |
+
def get_nest_position(self, colony_id: int) -> np.ndarray:
|
275 |
+
"""Belirtilen koloninin yuva pozisyonunu döndürür."""
|
276 |
+
return self.nest_positions.get(colony_id) # .get() ile None dönebilir
|
277 |
+
|
278 |
+
def is_at_nest(self, colony_id: int, world_pos: np.ndarray) -> bool:
|
279 |
+
"""Pozisyonun belirtilen koloninin yuvasında olup olmadığını kontrol eder."""
|
280 |
+
nest_pos = self.nest_positions.get(colony_id)
|
281 |
+
if nest_pos is None: return False
|
282 |
+
return np.linalg.norm(world_pos - nest_pos) < self.nest_radius
|
283 |
+
|
284 |
+
def check_obstacle_collision(self, world_pos: np.ndarray, radius: float) -> bool:
|
285 |
+
"""Engelle çarpışma kontrolü."""
|
286 |
+
for obs in self.obstacles:
|
287 |
+
if np.linalg.norm(world_pos - obs.position) < obs.radius + radius:
|
288 |
+
return True
|
289 |
+
return False
|
290 |
+
|
291 |
+
def draw(self, screen):
|
292 |
+
"""Ortamın tüm bileşenlerini çizer."""
|
293 |
+
if s.DEBUG_DRAW_PHEROMONES:
|
294 |
+
self._draw_pheromones(screen)
|
295 |
+
if s.DEBUG_DRAW_NESTS:
|
296 |
+
self._draw_nests(screen)
|
297 |
+
if s.DEBUG_DRAW_FOOD_LOCATIONS:
|
298 |
+
self._draw_food_sources(screen)
|
299 |
+
if s.DEBUG_DRAW_OBSTACLES:
|
300 |
+
self._draw_obstacles(screen)
|
301 |
+
|
302 |
+
def _draw_pheromones(self, screen):
|
303 |
+
"""Her iki koloninin feromonlarını ayrı renklerle çizer."""
|
304 |
+
self.pheromone_surface.fill((0, 0, 0, 0))
|
305 |
+
res = self.grid_res
|
306 |
+
pheromone_colors = {
|
307 |
+
'home_red': s.COLOR_PHEROMONE_HOME_RED,
|
308 |
+
'home_blue': s.COLOR_PHEROMONE_HOME_BLUE,
|
309 |
+
'food_red': (*s.COLOR_PHEROMONE_HOME_RED[:3], s.COLOR_PHEROMONE_HOME_RED[3] // 2), # Daha soluk
|
310 |
+
'food_blue': (*s.COLOR_PHEROMONE_HOME_BLUE[:3], s.COLOR_PHEROMONE_HOME_BLUE[3] // 2) # Daha soluk
|
311 |
+
}
|
312 |
+
grids_to_draw = {
|
313 |
+
'home_red': self.home_pheromone_grids.get(s.COLONY_ID_RED),
|
314 |
+
'home_blue': self.home_pheromone_grids.get(s.COLONY_ID_BLUE),
|
315 |
+
'food_red': self.food_pheromone_grids.get(s.COLONY_ID_RED),
|
316 |
+
'food_blue': self.food_pheromone_grids.get(s.COLONY_ID_BLUE)
|
317 |
+
}
|
318 |
+
|
319 |
+
for p_type, grid in grids_to_draw.items():
|
320 |
+
if grid is None: continue # Izgara yoksa atla
|
321 |
+
color_info = pheromone_colors[p_type]
|
322 |
+
for x in range(self.grid_width):
|
323 |
+
for y in range(self.grid_height):
|
324 |
+
strength = grid[x, y]
|
325 |
+
if strength > 0.01:
|
326 |
+
alpha = int(np.clip(strength / s.PHEROMONE_MAX_STRENGTH, 0, 1) * color_info[3])
|
327 |
+
color = (*color_info[:3], alpha)
|
328 |
+
rect = pygame.Rect(x * res, y * res, res, res)
|
329 |
+
pygame.draw.rect(self.pheromone_surface, color, rect)
|
330 |
+
|
331 |
+
screen.blit(self.pheromone_surface, (0, 0))
|
332 |
+
|
333 |
+
|
334 |
+
def _draw_nests(self, screen):
|
335 |
+
"""Her iki yuvayı da çizer."""
|
336 |
+
for colony_id, nest_pos in self.nest_positions.items():
|
337 |
+
color = self.nest_colors.get(colony_id, (128,128,128)) # Hata durumunda gri
|
338 |
+
pygame.draw.circle(screen, color, nest_pos.astype(int), self.nest_radius)
|
339 |
+
pygame.draw.circle(screen, (color[0]//2, color[1]//2, color[2]//2),
|
340 |
+
nest_pos.astype(int), self.nest_radius - 3)
|
341 |
+
|
342 |
+
def _draw_food_sources(self, screen):
|
343 |
+
"""Aktif yem kaynaklarını çizer."""
|
344 |
+
for fs in self.food_sources:
|
345 |
+
fs.draw(screen)
|
346 |
+
|
347 |
+
def _draw_obstacles(self, screen):
|
348 |
+
"""Engelleri çizer."""
|
349 |
+
for obs in self.obstacles:
|
350 |
+
obs.draw(screen)
|
main_coev.py
ADDED
@@ -0,0 +1,293 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# swarm_mind_v4/main_coev.py
|
2 |
+
import pygame
|
3 |
+
import sys
|
4 |
+
import os
|
5 |
+
import neat # NEAT kütüphanesi
|
6 |
+
import numpy as np
|
7 |
+
import pickle # Genomları kaydetmek/yüklemek için
|
8 |
+
import random
|
9 |
+
import time # Zaman ölçümü için
|
10 |
+
import settings as s
|
11 |
+
from environment import Environment # V4 Environment
|
12 |
+
from agent_coev import AgentCoEv # V4 Agent
|
13 |
+
|
14 |
+
# --- Tek Bir Eşleşmeyi Simüle Etme Fonksiyonu ---
|
15 |
+
def eval_simulation(genome_red, config_red, genome_blue, config_blue):
|
16 |
+
"""
|
17 |
+
Bir kırmızı ve bir mavi genom arasındaki tek bir maçı simüle eder.
|
18 |
+
Her iki koloninin topladığı yem miktarını döndürür.
|
19 |
+
"""
|
20 |
+
# Ağları oluştur
|
21 |
+
net_red = neat.nn.FeedForwardNetwork.create(genome_red, config_red)
|
22 |
+
net_blue = neat.nn.FeedForwardNetwork.create(genome_blue, config_blue)
|
23 |
+
|
24 |
+
# Ortamı oluştur
|
25 |
+
environment = Environment(s.SCREEN_WIDTH, s.SCREEN_HEIGHT)
|
26 |
+
|
27 |
+
# Ajanları oluştur (her koloni kendi ağını kullanır)
|
28 |
+
agents_red = [AgentCoEv(genome_red, config_red, environment, s.COLONY_ID_RED) for _ in range(s.NUM_AGENTS_PER_COLONY)]
|
29 |
+
agents_blue = [AgentCoEv(genome_blue, config_blue, environment, s.COLONY_ID_BLUE) for _ in range(s.NUM_AGENTS_PER_COLONY)]
|
30 |
+
all_agents = agents_red + agents_blue
|
31 |
+
|
32 |
+
# --- Simülasyon Döngüsü (Görselleştirmesiz) ---
|
33 |
+
for step in range(s.SIMULATION_STEPS_PER_GEN):
|
34 |
+
environment.update()
|
35 |
+
# Ajanları rastgele sırada güncellemek yanlılığı azaltabilir
|
36 |
+
random.shuffle(all_agents)
|
37 |
+
for agent in all_agents:
|
38 |
+
# update metodu artık tüm ajan listesini alıyor
|
39 |
+
agent.update(all_agents)
|
40 |
+
|
41 |
+
# --- Sonuçları Hesapla ---
|
42 |
+
food_red = sum(agent.food_collected_count for agent in agents_red)
|
43 |
+
food_blue = sum(agent.food_collected_count for agent in agents_blue)
|
44 |
+
|
45 |
+
return food_red, food_blue
|
46 |
+
|
47 |
+
# --- Ko-evrim Sürecini Başlatma Fonksiyonu ---
|
48 |
+
def run_coev(config_file):
|
49 |
+
"""
|
50 |
+
İki popülasyon için NEAT ko-evrim sürecini yönetir.
|
51 |
+
"""
|
52 |
+
# NEAT yapılandırmasını yükle
|
53 |
+
config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
|
54 |
+
neat.DefaultSpeciesSet, neat.DefaultStagnation,
|
55 |
+
config_file)
|
56 |
+
|
57 |
+
# --- Popülasyonları Oluştur veya Yükle ---
|
58 |
+
checkpoint_dir_red = 'swarm_mind_v4/checkpoints_red'
|
59 |
+
checkpoint_dir_blue = 'swarm_mind_v4/checkpoints_blue'
|
60 |
+
os.makedirs(checkpoint_dir_red, exist_ok=True)
|
61 |
+
os.makedirs(checkpoint_dir_blue, exist_ok=True)
|
62 |
+
|
63 |
+
try:
|
64 |
+
p_red = neat.Checkpointer.restore_checkpoint(os.path.join(checkpoint_dir_red, 'neat-checkpoint-'))
|
65 |
+
print("Kırmızı popülasyon checkpoint'ten yüklendi.")
|
66 |
+
except Exception:
|
67 |
+
print("Kırmızı popülasyon checkpoint bulunamadı, yeni oluşturuluyor.")
|
68 |
+
p_red = neat.Population(config)
|
69 |
+
|
70 |
+
try:
|
71 |
+
p_blue = neat.Checkpointer.restore_checkpoint(os.path.join(checkpoint_dir_blue, 'neat-checkpoint-'))
|
72 |
+
print("Mavi popülasyon checkpoint'ten yüklendi.")
|
73 |
+
except Exception:
|
74 |
+
print("Mavi popülasyon checkpoint bulunamadı, yeni oluşturuluyor.")
|
75 |
+
p_blue = neat.Population(config)
|
76 |
+
|
77 |
+
# --- Raporlayıcıları Ekle ---
|
78 |
+
# Kırmızı Popülasyon
|
79 |
+
p_red.add_reporter(neat.StdOutReporter(True))
|
80 |
+
stats_red = neat.StatisticsReporter()
|
81 |
+
p_red.add_reporter(stats_red)
|
82 |
+
p_red.add_reporter(neat.Checkpointer(generation_interval=5, filename_prefix=os.path.join(checkpoint_dir_red, 'neat-checkpoint-')))
|
83 |
+
# Mavi Popülasyon
|
84 |
+
# İkinci StdOutReporter'ı eklemeyebiliriz, çıktılar karışmasın diye
|
85 |
+
# p_blue.add_reporter(neat.StdOutReporter(True)) # Veya sadece mavi için ayrı bir prefix ile
|
86 |
+
stats_blue = neat.StatisticsReporter()
|
87 |
+
p_blue.add_reporter(stats_blue)
|
88 |
+
p_blue.add_reporter(neat.Checkpointer(generation_interval=5, filename_prefix=os.path.join(checkpoint_dir_blue, 'neat-checkpoint-')))
|
89 |
+
|
90 |
+
# --- Özel Nesil Döngüsü ---
|
91 |
+
for generation in range(s.NUM_GENERATIONS):
|
92 |
+
start_time = time.time()
|
93 |
+
print(f"\n****** Ko-Evrim Nesil {generation} Başladı ******")
|
94 |
+
|
95 |
+
# Mevcut neslin genomlarını al (sözlük olarak: {genome_id: genome})
|
96 |
+
genomes_red_dict = p_red.population
|
97 |
+
genomes_blue_dict = p_blue.population
|
98 |
+
# Liste olarak da alabiliriz (eşleşme için daha kolay olabilir)
|
99 |
+
genomes_red_list = list(genomes_red_dict.items())
|
100 |
+
genomes_blue_list = list(genomes_blue_dict.items())
|
101 |
+
|
102 |
+
# Her genomun bu nesildeki maç skorlarını saklamak için
|
103 |
+
# Anahtar: genome_id, Değer: [(kendi_skoru, rakip_skoru), ...] listesi
|
104 |
+
genome_scores_red = {gid: [] for gid, _ in genomes_red_list}
|
105 |
+
genome_scores_blue = {gid: [] for gid, _ in genomes_blue_list}
|
106 |
+
|
107 |
+
# --- Eşleştirme ve Değerlendirme ---
|
108 |
+
eval_count = 0
|
109 |
+
# Her kırmızı genomu, rastgele K mavi genoma karşı test et
|
110 |
+
for gid_r, genome_r in genomes_red_list:
|
111 |
+
# Rastgele K rakip seç (eğer mavi popülasyon K'dan küçükse hepsiyle eşleşir)
|
112 |
+
num_opponents = min(s.NUM_OPPONENTS_PER_EVAL, len(genomes_blue_list))
|
113 |
+
opponents = random.sample(genomes_blue_list, num_opponents)
|
114 |
+
|
115 |
+
for gid_b, genome_b in opponents:
|
116 |
+
# Simülasyonu çalıştır
|
117 |
+
food_r, food_b = eval_simulation(genome_r, config, genome_b, config)
|
118 |
+
eval_count += 1
|
119 |
+
|
120 |
+
# Sonuçları her iki genom için de kaydet
|
121 |
+
genome_scores_red[gid_r].append((food_r, food_b))
|
122 |
+
genome_scores_blue[gid_b].append((food_b, food_r)) # Rakibin skorunu kendi skoru olarak kaydet
|
123 |
+
|
124 |
+
print(f"Nesil {generation}: {eval_count} eşleşme değerlendirildi.")
|
125 |
+
|
126 |
+
# --- Fitness Hesaplama ve Atama ---
|
127 |
+
# Kırmızı genomlar için
|
128 |
+
for gid, genome in genomes_red_dict.items():
|
129 |
+
scores = genome_scores_red[gid]
|
130 |
+
if not scores: # Eğer hiç maç yapmadıysa (popülasyon çok küçükse olabilir)
|
131 |
+
genome.fitness = 0.0
|
132 |
+
continue
|
133 |
+
avg_my_food = np.mean([s[0] for s in scores])
|
134 |
+
avg_opp_food = np.mean([s[1] for s in scores])
|
135 |
+
|
136 |
+
if s.FITNESS_METHOD == 'competitive':
|
137 |
+
genome.fitness = avg_my_food - avg_opp_food
|
138 |
+
else: # 'absolute'
|
139 |
+
genome.fitness = avg_my_food
|
140 |
+
|
141 |
+
# Mavi genomlar için
|
142 |
+
for gid, genome in genomes_blue_dict.items():
|
143 |
+
scores = genome_scores_blue[gid]
|
144 |
+
if not scores:
|
145 |
+
genome.fitness = 0.0
|
146 |
+
continue
|
147 |
+
avg_my_food = np.mean([s[0] for s in scores])
|
148 |
+
avg_opp_food = np.mean([s[1] for s in scores])
|
149 |
+
|
150 |
+
if s.FITNESS_METHOD == 'competitive':
|
151 |
+
genome.fitness = avg_my_food - avg_opp_food
|
152 |
+
else: # 'absolute'
|
153 |
+
genome.fitness = avg_my_food
|
154 |
+
|
155 |
+
# --- NEAT Üreme ve Raporlama Adımları (Manuel) ---
|
156 |
+
# Raporlayıcıları bilgilendir ve sonraki nesli oluştur
|
157 |
+
best_genome_red = max(genomes_red_dict.values(), key=lambda g: g.fitness)
|
158 |
+
best_genome_blue = max(genomes_blue_dict.values(), key=lambda g: g.fitness)
|
159 |
+
|
160 |
+
p_red.reporters.post_evaluate(config, genomes_red_dict, p_red.species, best_genome_red)
|
161 |
+
p_blue.reporters.post_evaluate(config, genomes_blue_dict, p_blue.species, best_genome_blue)
|
162 |
+
|
163 |
+
p_red.reporters.end_generation(config, genomes_red_dict, p_red.species)
|
164 |
+
p_blue.reporters.end_generation(config, genomes_blue_dict, p_blue.species)
|
165 |
+
|
166 |
+
# Sonraki nesilleri oluştur
|
167 |
+
p_red.population = p_red.reproduction.reproduce(config, p_red.species, config.pop_size, generation)
|
168 |
+
p_blue.population = p_blue.reproduction.reproduce(config, p_blue.species, config.pop_size, generation)
|
169 |
+
|
170 |
+
# Yeni nesil için türleri ayarla (checkpoint'ten sonra gerekli olabilir)
|
171 |
+
if not p_red.species or not p_blue.species:
|
172 |
+
p_red.species = config.species_set_type(config, p_red.reporters)
|
173 |
+
p_blue.species = config.species_set_type(config, p_blue.reporters)
|
174 |
+
p_red.species.speciate(config, p_red.population, generation)
|
175 |
+
p_blue.species.speciate(config, p_blue.population, generation)
|
176 |
+
|
177 |
+
# Raporlayıcıları yeni nesil için başlat
|
178 |
+
p_red.reporters.start_generation(generation + 1)
|
179 |
+
p_blue.reporters.start_generation(generation + 1)
|
180 |
+
|
181 |
+
end_time = time.time()
|
182 |
+
print(f"Nesil {generation} tamamlandı. Süre: {end_time - start_time:.2f} saniye")
|
183 |
+
|
184 |
+
|
185 |
+
# --- Evrim Sonrası ---
|
186 |
+
print('\nKo-Evrim tamamlandı.')
|
187 |
+
|
188 |
+
# En iyi genomları bul (popülasyonlar artık bir sonraki nesli içeriyor olabilir,
|
189 |
+
# istatistiklerden veya kaydedilenlerden almak daha güvenli olabilir)
|
190 |
+
# Şimdilik popülasyon içindeki en iyiyi varsayalım (dikkatli olunmalı)
|
191 |
+
try:
|
192 |
+
winner_red = max(p_red.population.values(), key=lambda g: g.fitness if g.fitness is not None else -float('inf'))
|
193 |
+
winner_blue = max(p_blue.population.values(), key=lambda g: g.fitness if g.fitness is not None else -float('inf'))
|
194 |
+
|
195 |
+
print('\nEn İyi Kırmızı Genom:')
|
196 |
+
print(winner_red)
|
197 |
+
print('\nEn İyi Mavi Genom:')
|
198 |
+
print(winner_blue)
|
199 |
+
|
200 |
+
# En iyi genomları kaydet
|
201 |
+
os.makedirs('swarm_mind_v4/best_genomes', exist_ok=True)
|
202 |
+
with open('swarm_mind_v4/best_genomes/winner_red.pkl', 'wb') as f:
|
203 |
+
pickle.dump(winner_red, f)
|
204 |
+
with open('swarm_mind_v4/best_genomes/winner_blue.pkl', 'wb') as f:
|
205 |
+
pickle.dump(winner_blue, f)
|
206 |
+
print("En iyi genomlar 'best_genomes' klasörüne kaydedildi.")
|
207 |
+
|
208 |
+
# İsteğe bağlı görselleştirme
|
209 |
+
if s.VISUALIZE_BEST_GENOMES:
|
210 |
+
visualize_simulation(winner_red, config, winner_blue, config)
|
211 |
+
|
212 |
+
except Exception as e:
|
213 |
+
print(f"\nEvrim sonrası hata (En iyi genom bulunamadı veya kaydedilemedi): {e}")
|
214 |
+
|
215 |
+
|
216 |
+
# --- Görselleştirme Fonksiyonu (V4 için güncellendi) ---
|
217 |
+
def visualize_simulation(genome_red, config_red, genome_blue, config_blue):
|
218 |
+
"""
|
219 |
+
İki rakip genomun davranışını Pygame ile görselleştirir.
|
220 |
+
"""
|
221 |
+
print("\nEn iyi Kırmızı ve Mavi genomların maçı görselleştiriliyor...")
|
222 |
+
pygame.init()
|
223 |
+
screen = pygame.display.set_mode((s.SCREEN_WIDTH, s.SCREEN_HEIGHT))
|
224 |
+
pygame.display.set_caption(f"{s.WINDOW_TITLE} - Best Genomes Match")
|
225 |
+
clock = pygame.time.Clock()
|
226 |
+
|
227 |
+
environment = Environment(s.SCREEN_WIDTH, s.SCREEN_HEIGHT)
|
228 |
+
net_red = neat.nn.FeedForwardNetwork.create(genome_red, config_red)
|
229 |
+
net_blue = neat.nn.FeedForwardNetwork.create(genome_blue, config_blue)
|
230 |
+
agents_red = [AgentCoEv(genome_red, config_red, environment, s.COLONY_ID_RED) for _ in range(s.NUM_AGENTS_PER_COLONY)]
|
231 |
+
agents_blue = [AgentCoEv(genome_blue, config_blue, environment, s.COLONY_ID_BLUE) for _ in range(s.NUM_AGENTS_PER_COLONY)]
|
232 |
+
all_agents = agents_red + agents_blue
|
233 |
+
|
234 |
+
running = True
|
235 |
+
sim_step = 0
|
236 |
+
max_vis_steps = s.SIMULATION_STEPS_PER_GEN * 2 # Görselleştirmeyi biraz daha uzun tutalım
|
237 |
+
while running and sim_step < max_vis_steps:
|
238 |
+
for event in pygame.event.get():
|
239 |
+
if event.type == pygame.QUIT: running = False; break
|
240 |
+
if event.type == pygame.KEYDOWN:
|
241 |
+
if event.key == pygame.K_p: s.DEBUG_DRAW_PHEROMONES = not s.DEBUG_DRAW_PHEROMONES
|
242 |
+
if event.key == pygame.K_ESCAPE: running = False; break
|
243 |
+
if not running: break
|
244 |
+
|
245 |
+
environment.update()
|
246 |
+
random.shuffle(all_agents)
|
247 |
+
for agent in all_agents:
|
248 |
+
agent.update(all_agents)
|
249 |
+
|
250 |
+
screen.fill(s.COLOR_BACKGROUND)
|
251 |
+
environment.draw(screen)
|
252 |
+
for agent in all_agents:
|
253 |
+
agent.draw(screen)
|
254 |
+
|
255 |
+
# Bilgi metinleri
|
256 |
+
font = pygame.font.SysFont(None, 24)
|
257 |
+
food_r = sum(a.food_collected_count for a in agents_red)
|
258 |
+
food_b = sum(a.food_collected_count for a in agents_blue)
|
259 |
+
info_text = font.render(f"Adım: {sim_step}/{max_vis_steps} | Kırmızı Yem: {food_r} | Mavi Yem: {food_b}", True, (255, 255, 255))
|
260 |
+
screen.blit(info_text, (10, 10))
|
261 |
+
|
262 |
+
pygame.display.flip()
|
263 |
+
clock.tick(s.VISUALIZATION_FPS)
|
264 |
+
sim_step += 1
|
265 |
+
|
266 |
+
pygame.quit()
|
267 |
+
print("Görselleştirme tamamlandı.")
|
268 |
+
|
269 |
+
|
270 |
+
# --- Ana Çalıştırma Bloğu ---
|
271 |
+
if __name__ == '__main__':
|
272 |
+
local_dir = os.path.dirname(__file__)
|
273 |
+
config_path = os.path.join(local_dir, 'neat_config_v4.txt')
|
274 |
+
|
275 |
+
if not os.path.exists(config_path):
|
276 |
+
print(f"HATA: NEAT config dosyası bulunamadı: {config_path}")
|
277 |
+
sys.exit(1)
|
278 |
+
|
279 |
+
print("SwarmMind V4.0 - Co-evolutionary Competition başlatılıyor...")
|
280 |
+
print(f"NEAT Yapılandırması: {config_path}")
|
281 |
+
print(f"Jenerasyon Sayısı: {s.NUM_GENERATIONS}")
|
282 |
+
print(f"Popülasyon Büyüklüğü/Koloni: (config dosyasında belirtilir)")
|
283 |
+
print(f"Simülasyon Adım Sayısı/Eşleşme: {s.SIMULATION_STEPS_PER_GEN}")
|
284 |
+
print(f"Ajan Sayısı/Koloni: {s.NUM_AGENTS_PER_COLONY}")
|
285 |
+
print(f"Rakip Sayısı/Değerlendirme: {s.NUM_OPPONENTS_PER_EVAL}")
|
286 |
+
print(f"Fitness Yöntemi: {s.FITNESS_METHOD}")
|
287 |
+
|
288 |
+
# Gerekli klasörleri oluştur
|
289 |
+
os.makedirs('swarm_mind_v4/checkpoints_red', exist_ok=True)
|
290 |
+
os.makedirs('swarm_mind_v4/checkpoints_blue', exist_ok=True)
|
291 |
+
os.makedirs('swarm_mind_v4/best_genomes', exist_ok=True)
|
292 |
+
|
293 |
+
run_coev(config_path)
|
neat_config_v4.txt
ADDED
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# swarm_mind_v4/neat_config_v4.txt
|
2 |
+
|
3 |
+
[NEAT]
|
4 |
+
fitness_criterion = max
|
5 |
+
# Rekabetçi fitness negatif olabileceği için threshold dikkatli ayarlanmalı
|
6 |
+
# Hedef (rekabetçi) fitness (bu değere ulaşılırsa evrim durur)
|
7 |
+
fitness_threshold = 5000
|
8 |
+
# Popülasyon büyüklüğü (Hız için düşük)
|
9 |
+
pop_size = 30
|
10 |
+
reset_on_extinction = False
|
11 |
+
|
12 |
+
[DefaultGenome]
|
13 |
+
# Girdi (16 adet - V3 ile aynı, şimdilik)
|
14 |
+
num_inputs = 16
|
15 |
+
# Çıktı (3 adet - V3 ile aynı)
|
16 |
+
num_outputs = 3
|
17 |
+
# Başlangıçta gizli nöron yok
|
18 |
+
num_hidden = 0
|
19 |
+
|
20 |
+
# --- Diğer Genom Parametreleri ---
|
21 |
+
# Varsayılan aktivasyon fonksiyonu
|
22 |
+
activation_default = tanh
|
23 |
+
activation_mutate_rate = 0.1
|
24 |
+
activation_options = tanh sigmoid relu clamped gaussian
|
25 |
+
|
26 |
+
aggregation_default = sum
|
27 |
+
aggregation_mutate_rate = 0.0
|
28 |
+
aggregation_options = sum product min max mean median
|
29 |
+
|
30 |
+
bias_init_mean = 0.0
|
31 |
+
bias_init_stdev = 1.0
|
32 |
+
bias_max_value = 10.0
|
33 |
+
bias_min_value = -10.0
|
34 |
+
# Bias mutasyonunun ne kadar güçlü olacağı
|
35 |
+
bias_mutate_power = 0.5
|
36 |
+
# Bias değerlerinin mutasyona uğrama oranı
|
37 |
+
bias_mutate_rate = 0.7
|
38 |
+
# Bias değerinin tamamen değişme oranı
|
39 |
+
bias_replace_rate = 0.1
|
40 |
+
|
41 |
+
compatibility_disjoint_coefficient = 1.0
|
42 |
+
compatibility_weight_coefficient = 0.5
|
43 |
+
|
44 |
+
# Yeni bağlantı ekleme olasılığı
|
45 |
+
conn_add_prob = 0.1
|
46 |
+
# Bağlantı silme olasılığı
|
47 |
+
conn_delete_prob = 0.05
|
48 |
+
|
49 |
+
enabled_default = True
|
50 |
+
enabled_mutate_rate = 0.01
|
51 |
+
|
52 |
+
# Geri beslemesiz ağ (FeedForward)
|
53 |
+
feed_forward = True
|
54 |
+
# Başlangıç bağlantı tipi (full_direct: tüm girdiler çıktılara bağlı)
|
55 |
+
initial_connection = full_direct
|
56 |
+
|
57 |
+
# Yeni nöron ekleme olasılığı
|
58 |
+
node_add_prob = 0.05
|
59 |
+
# Nöron silme olasılığı
|
60 |
+
node_delete_prob = 0.02
|
61 |
+
|
62 |
+
response_init_mean = 1.0
|
63 |
+
response_init_stdev = 0.0
|
64 |
+
response_max_value = 10.0
|
65 |
+
response_min_value = -10.0
|
66 |
+
response_mutate_power = 0.0
|
67 |
+
response_mutate_rate = 0.0
|
68 |
+
response_replace_rate = 0.0
|
69 |
+
|
70 |
+
weight_init_mean = 0.0
|
71 |
+
weight_init_stdev = 1.0
|
72 |
+
weight_max_value = 10.0
|
73 |
+
weight_min_value = -10.0
|
74 |
+
# Ağırlık mutasyonunun ne kadar güçlü olacağı
|
75 |
+
weight_mutate_power = 0.5
|
76 |
+
# Ağırlıkların mutasyona uğrama oranı
|
77 |
+
weight_mutate_rate = 0.8
|
78 |
+
# Ağırlıkların tamamen değişme oranı
|
79 |
+
weight_replace_rate = 0.1
|
80 |
+
|
81 |
+
|
82 |
+
[DefaultSpeciesSet]
|
83 |
+
# Tür uyumluluk eşiği
|
84 |
+
compatibility_threshold = 3.0
|
85 |
+
|
86 |
+
[DefaultStagnation]
|
87 |
+
# Türün fitness'ı en iyi bireye göre mi ortalamaya göre mi?
|
88 |
+
species_fitness_func = max
|
89 |
+
# Bir tür kaç nesil gelişmezse yok sayılır
|
90 |
+
max_stagnation = 25
|
91 |
+
# Her türden kaç en iyi birey korunur
|
92 |
+
species_elitism = 2
|
93 |
+
|
94 |
+
[DefaultReproduction]
|
95 |
+
# Popülasyonun kaç en iyi bireyi doğrudan sonraki nesle geçer
|
96 |
+
elitism = 2
|
97 |
+
# Tür içindeki bireylerin ne kadarının üremeye katılabileceği
|
98 |
+
survival_threshold = 0.2
|
settings.py
ADDED
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# swarm_mind_v4/settings.py
|
2 |
+
import pygame
|
3 |
+
import numpy as np
|
4 |
+
import random
|
5 |
+
|
6 |
+
# --- Temel Ekran ve Simülasyon Ayarları ---
|
7 |
+
SCREEN_WIDTH = 1280
|
8 |
+
SCREEN_HEIGHT = 720
|
9 |
+
FPS = 60 # Görselleştirme FPS'i
|
10 |
+
WINDOW_TITLE = "SwarmMind V4.0 - Co-evolutionary Competition"
|
11 |
+
SIMULATION_STEPS_PER_GEN = 800 # Her eşli değerlendirme için adım sayısı (Hız için düşük)
|
12 |
+
NUM_GENERATIONS = 100 # Toplam evrimleştirilecek nesil sayısı
|
13 |
+
|
14 |
+
# --- Koloni Ayarları ---
|
15 |
+
COLONY_ID_RED = 0
|
16 |
+
COLONY_ID_BLUE = 1
|
17 |
+
NUM_AGENTS_PER_COLONY = 15 # Koloni başına ajan sayısı (Hız için düşük)
|
18 |
+
TOTAL_AGENTS = NUM_AGENTS_PER_COLONY * 2
|
19 |
+
|
20 |
+
# --- Renkler ---
|
21 |
+
COLOR_BACKGROUND = (10, 10, 30)
|
22 |
+
# Kırmızı Koloni
|
23 |
+
COLOR_AGENT_RED_SEEKING = (255, 100, 100)
|
24 |
+
COLOR_AGENT_RED_RETURNING = (255, 180, 180)
|
25 |
+
COLOR_PHEROMONE_HOME_RED = (200, 0, 0, 150) # Kırmızı yuva izi
|
26 |
+
# Mavi Koloni
|
27 |
+
COLOR_AGENT_BLUE_SEEKING = (100, 100, 255)
|
28 |
+
COLOR_AGENT_BLUE_RETURNING = (180, 180, 255)
|
29 |
+
COLOR_PHEROMONE_HOME_BLUE = (0, 0, 200, 150) # Mavi yuva izi
|
30 |
+
# Ortak Renkler
|
31 |
+
COLOR_NEST_RED = (255, 50, 0) # Kırmızı yuva
|
32 |
+
COLOR_NEST_BLUE = (0, 50, 255) # Mavi yuva
|
33 |
+
COLOR_FOOD = (50, 255, 50) # Yem rengi (Yeşil)
|
34 |
+
COLOR_OBSTACLE = (100, 100, 100)
|
35 |
+
|
36 |
+
# --- Ajan Ayarları (Temel Fizik) ---
|
37 |
+
AGENT_SIZE = 7
|
38 |
+
MAX_SPEED = 3.5
|
39 |
+
MAX_FORCE = 0.2
|
40 |
+
|
41 |
+
# --- Ortam Ayarları (V4 - İki Yuva, Dinamik Yem) ---
|
42 |
+
# Yuvaları ekranın iki yanına yerleştirelim
|
43 |
+
NEST_POS_RED = np.array([100, SCREEN_HEIGHT / 2], dtype=np.float32)
|
44 |
+
NEST_POS_BLUE = np.array([SCREEN_WIDTH - 100, SCREEN_HEIGHT / 2], dtype=np.float32)
|
45 |
+
NEST_RADIUS = 30
|
46 |
+
|
47 |
+
# Yem Kaynakları (Dinamik)
|
48 |
+
MAX_FOOD_SOURCES = 10
|
49 |
+
FOOD_RADIUS = 10
|
50 |
+
FOOD_INITIAL_AMOUNT = 40
|
51 |
+
FOOD_SPAWN_RATE = 0.015
|
52 |
+
# Yem bitince kaynak ortamdan silinsin mi?
|
53 |
+
FOOD_DEPLETION_REMOVAL = True # <--- EKLENEN SATIR
|
54 |
+
|
55 |
+
# Engeller
|
56 |
+
NUM_OBSTACLES = 6
|
57 |
+
OBSTACLE_MIN_RADIUS = 15
|
58 |
+
OBSTACLE_MAX_RADIUS = 45
|
59 |
+
|
60 |
+
# Feromon Ayarları
|
61 |
+
PHEROMONE_RESOLUTION = 15
|
62 |
+
GRID_WIDTH = SCREEN_WIDTH // PHEROMONE_RESOLUTION
|
63 |
+
GRID_HEIGHT = SCREEN_HEIGHT // PHEROMONE_RESOLUTION
|
64 |
+
PHEROMONE_MAX_STRENGTH = 1.0
|
65 |
+
PHEROMONE_DEPOSIT_VALUE = 0.9
|
66 |
+
PHEROMONE_EVAPORATION_RATE = 0.010
|
67 |
+
PHEROMONE_DIFFUSION_RATE = 0.05
|
68 |
+
|
69 |
+
# --- NN Girdi/Çıktı Ayarları (V3 ile aynı, şimdilik) ---
|
70 |
+
# Dikkat: Bu değerler neat_config_v4.txt içindeki num_inputs ile eşleşmeli!
|
71 |
+
# Şu anki girdiler: Home Pher (3), Food Pher (3), Bias (1), Carrying (1), Vel (2), Nest Dir (2), Food Dir (2), Food Dist (1), Agent Density (1) = 16
|
72 |
+
num_inputs = 16 # Config dosyasıyla tutarlılık için buraya da ekleyelim (assert'te kullanılabilir)
|
73 |
+
NN_PHEROMONE_SENSE_DIST = AGENT_SIZE * 4
|
74 |
+
NN_PHEROMONE_SENSE_ANGLES = [-np.pi / 3, 0, np.pi / 3] # Sol, Orta, Sağ
|
75 |
+
NN_FOOD_SENSE_RADIUS = 150
|
76 |
+
NN_AGENT_SENSE_RADIUS = 60
|
77 |
+
NN_OUTPUT_DEPOSIT_THRESHOLD = 0.6
|
78 |
+
|
79 |
+
# --- Ko-evrim Ayarları ---
|
80 |
+
# Fitness Hesaplama Yöntemi: 'absolute' (sadece kendi yemi), 'competitive' (kendi - rakip)
|
81 |
+
FITNESS_METHOD = 'competitive'
|
82 |
+
# Her genomu kaç rakip genoma karşı test edelim?
|
83 |
+
NUM_OPPONENTS_PER_EVAL = 5
|
84 |
+
|
85 |
+
# --- Görselleştirme ve Debug ---
|
86 |
+
VISUALIZE_BEST_GENOMES = True
|
87 |
+
VISUALIZATION_FPS = 30
|
88 |
+
DEBUG_DRAW_PHEROMONES = False
|
89 |
+
DEBUG_DRAW_FOOD_LOCATIONS = True
|
90 |
+
DEBUG_DRAW_NESTS = True
|
91 |
+
DEBUG_DRAW_OBSTACLES = True
|
vector.py
ADDED
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# swarm_mind_v1/vector.py
|
2 |
+
import math
|
3 |
+
import random
|
4 |
+
|
5 |
+
class Vector2D:
|
6 |
+
"""2 Boyutlu Vektör Sınıfı."""
|
7 |
+
def __init__(self, x=0.0, y=0.0):
|
8 |
+
self.x = x
|
9 |
+
self.y = y
|
10 |
+
|
11 |
+
def __str__(self):
|
12 |
+
"""Vektörün string temsilini döndürür."""
|
13 |
+
return f"Vector2D({self.x:.2f}, {self.y:.2f})"
|
14 |
+
|
15 |
+
def __add__(self, other):
|
16 |
+
"""Vektör toplama (+) operatörü."""
|
17 |
+
return Vector2D(self.x + other.x, self.y + other.y)
|
18 |
+
|
19 |
+
def __sub__(self, other):
|
20 |
+
"""Vektör çıkarma (-) operatörü."""
|
21 |
+
return Vector2D(self.x - other.x, self.y - other.y)
|
22 |
+
|
23 |
+
def __mul__(self, scalar):
|
24 |
+
"""Skaler ile çarpma (*) operatörü."""
|
25 |
+
return Vector2D(self.x * scalar, self.y * scalar)
|
26 |
+
|
27 |
+
def __truediv__(self, scalar):
|
28 |
+
"""Skaler ile bölme (/) operatörü."""
|
29 |
+
if scalar == 0:
|
30 |
+
return Vector2D() # Sıfıra bölme hatasını önle
|
31 |
+
return Vector2D(self.x / scalar, self.y / scalar)
|
32 |
+
|
33 |
+
def magnitude_squared(self):
|
34 |
+
"""Vektörün büyüklüğünün karesini döndürür (sqrt daha yavaştır)."""
|
35 |
+
return self.x * self.x + self.y * self.y
|
36 |
+
|
37 |
+
def magnitude(self):
|
38 |
+
"""Vektörün büyüklüğünü (uzunluğunu) döndürür."""
|
39 |
+
mag_sq = self.magnitude_squared()
|
40 |
+
if mag_sq == 0:
|
41 |
+
return 0.0
|
42 |
+
return math.sqrt(mag_sq)
|
43 |
+
|
44 |
+
def normalize(self):
|
45 |
+
"""Vektörü birim vektöre dönüştürür (büyüklüğü 1 yapar)."""
|
46 |
+
mag = self.magnitude()
|
47 |
+
if mag > 0:
|
48 |
+
self.x /= mag
|
49 |
+
self.y /= mag
|
50 |
+
return self # Zincirleme için kendini döndür
|
51 |
+
|
52 |
+
def get_normalized(self):
|
53 |
+
"""Vektörün normalize edilmiş bir kopyasını döndürür."""
|
54 |
+
mag = self.magnitude()
|
55 |
+
if mag == 0:
|
56 |
+
return Vector2D()
|
57 |
+
return Vector2D(self.x / mag, self.y / mag)
|
58 |
+
|
59 |
+
def limit(self, max_magnitude):
|
60 |
+
"""Vektörün büyüklüğünü verilen maksimum değerle sınırlar."""
|
61 |
+
if self.magnitude_squared() > max_magnitude * max_magnitude:
|
62 |
+
self.normalize()
|
63 |
+
self.x *= max_magnitude
|
64 |
+
self.y *= max_magnitude
|
65 |
+
return self # Zincirleme için kendini döndür
|
66 |
+
|
67 |
+
def distance_squared(self, other):
|
68 |
+
"""İki vektör arasındaki mesafenin karesini döndürür."""
|
69 |
+
dx = self.x - other.x
|
70 |
+
dy = self.y - other.y
|
71 |
+
return dx * dx + dy * dy
|
72 |
+
|
73 |
+
def distance(self, other):
|
74 |
+
"""İki vektör arasındaki mesafeyi döndürür."""
|
75 |
+
return math.sqrt(self.distance_squared(other))
|
76 |
+
|
77 |
+
def set_magnitude(self, magnitude):
|
78 |
+
"""Vektörün büyüklüğünü ayarlar."""
|
79 |
+
self.normalize()
|
80 |
+
self.x *= magnitude
|
81 |
+
self.y *= magnitude
|
82 |
+
return self
|
83 |
+
|
84 |
+
def heading(self):
|
85 |
+
"""Vektörün açısını (radyan cinsinden) döndürür."""
|
86 |
+
return math.atan2(self.y, self.x)
|
87 |
+
|
88 |
+
@staticmethod
|
89 |
+
def random_vector():
|
90 |
+
"""Rastgele bir yönü olan birim vektör oluşturur."""
|
91 |
+
angle = random.uniform(0, 2 * math.pi)
|
92 |
+
return Vector2D(math.cos(angle), math.sin(angle))
|