NextGenC commited on
Commit
28ac2ea
·
verified ·
1 Parent(s): 934db11

Upload 6 files

Browse files
Files changed (6) hide show
  1. agent_coev.py +239 -0
  2. environment.py +350 -0
  3. main_coev.py +293 -0
  4. neat_config_v4.txt +98 -0
  5. settings.py +91 -0
  6. 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))