| |
|
| | import pygame
|
| | import numpy as np
|
| | import random
|
| | import settings as s
|
| |
|
| | class FoodSource:
|
| | """Basit bir yem kaynağı sınıfı (V3'ten aynı)."""
|
| | def __init__(self, position, initial_amount, radius):
|
| | self.position = np.array(position, dtype=np.float32)
|
| | self.amount = initial_amount
|
| | self.radius = radius
|
| | self.color = s.COLOR_FOOD
|
| |
|
| | def take(self) -> bool:
|
| | if self.amount > 0:
|
| | self.amount -= 1
|
| | return True
|
| | return False
|
| |
|
| | def is_empty(self) -> bool:
|
| | return self.amount <= 0
|
| |
|
| | def draw(self, screen):
|
| | if self.amount > 0:
|
| | brightness = max(0.2, min(1.0, self.amount / s.FOOD_INITIAL_AMOUNT))
|
| | color = (int(self.color[0] * brightness),
|
| | int(self.color[1] * brightness),
|
| | int(self.color[2] * brightness))
|
| | pygame.draw.circle(screen, color, self.position.astype(int), self.radius)
|
| |
|
| |
|
| | class Obstacle:
|
| | """Basit dairesel bir engeli temsil eder."""
|
| | def __init__(self, position, radius):
|
| | self.position = np.array(position, dtype=np.float32)
|
| | self.radius = radius
|
| | self.color = s.COLOR_OBSTACLE
|
| |
|
| | def draw(self, screen):
|
| | pygame.draw.circle(screen, self.color, self.position.astype(int), int(self.radius))
|
| |
|
| | class Environment:
|
| | """
|
| | Simülasyon ortamını yönetir (V4: İki Koloni, Ayrı Feromonlar, Dinamik Yem, Engeller).
|
| | """
|
| | def __init__(self, width, height):
|
| | self.width = width
|
| | self.height = height
|
| | self.grid_res = s.PHEROMONE_RESOLUTION
|
| | self.grid_width = width // self.grid_res
|
| | self.grid_height = height // self.grid_res
|
| |
|
| |
|
| | self.home_pheromone_grids = {
|
| | s.COLONY_ID_RED: np.zeros((self.grid_width, self.grid_height), dtype=np.float32),
|
| | s.COLONY_ID_BLUE: np.zeros((self.grid_width, self.grid_height), dtype=np.float32)
|
| | }
|
| | self.food_pheromone_grids = {
|
| | s.COLONY_ID_RED: np.zeros((self.grid_width, self.grid_height), dtype=np.float32),
|
| | s.COLONY_ID_BLUE: np.zeros((self.grid_width, self.grid_height), dtype=np.float32)
|
| | }
|
| |
|
| |
|
| | self.nest_positions = {
|
| | s.COLONY_ID_RED: s.NEST_POS_RED.astype(np.float32),
|
| | s.COLONY_ID_BLUE: s.NEST_POS_BLUE.astype(np.float32)
|
| | }
|
| | self.nest_radius = s.NEST_RADIUS
|
| | self.nest_colors = {
|
| | s.COLONY_ID_RED: s.COLOR_NEST_RED,
|
| | s.COLONY_ID_BLUE: s.COLOR_NEST_BLUE
|
| | }
|
| |
|
| |
|
| | self.food_sources = []
|
| |
|
| |
|
| | self.obstacles = self._create_obstacles()
|
| |
|
| |
|
| | for _ in range(s.MAX_FOOD_SOURCES // 2):
|
| | self._try_spawn_food()
|
| |
|
| |
|
| | self.pheromone_surface = pygame.Surface((self.width, self.height), pygame.SRCALPHA)
|
| |
|
| | def _create_obstacles(self):
|
| | """Ortama rastgele engeller yerleştirir."""
|
| | obstacles = []
|
| | attempts = 0
|
| | max_attempts = s.NUM_OBSTACLES * 15
|
| |
|
| | while len(obstacles) < s.NUM_OBSTACLES and attempts < max_attempts:
|
| | attempts += 1
|
| | radius = random.uniform(s.OBSTACLE_MIN_RADIUS, s.OBSTACLE_MAX_RADIUS)
|
| | margin = radius + 20
|
| | pos = np.array([random.uniform(margin, self.width - margin),
|
| | random.uniform(margin, self.height - margin)], dtype=np.float32)
|
| |
|
| |
|
| | nest_collision = False
|
| | for nest_pos in self.nest_positions.values():
|
| | if np.linalg.norm(pos - nest_pos) < self.nest_radius + radius + 15:
|
| | nest_collision = True
|
| | break
|
| | if nest_collision: continue
|
| |
|
| |
|
| | obstacle_collision = False
|
| | for obs in obstacles:
|
| | if np.linalg.norm(pos - obs.position) < obs.radius + radius + 10:
|
| | obstacle_collision = True
|
| | break
|
| | if not obstacle_collision:
|
| | obstacles.append(Obstacle(pos, radius))
|
| |
|
| | print(f"Created {len(obstacles)} obstacles.")
|
| | return obstacles
|
| |
|
| | def _try_spawn_food(self):
|
| | """Ortama yeni bir yem kaynağı eklemeyi dener."""
|
| | if len(self.food_sources) >= s.MAX_FOOD_SOURCES: return
|
| |
|
| | attempts = 0
|
| | max_attempts = 30
|
| | while attempts < max_attempts:
|
| | attempts += 1
|
| | margin = s.FOOD_RADIUS + 15
|
| | pos = np.array([random.uniform(margin, self.width - margin),
|
| | random.uniform(margin, self.height - margin)], dtype=np.float32)
|
| |
|
| |
|
| | nest_collision = False
|
| | for nest_pos in self.nest_positions.values():
|
| | if np.linalg.norm(pos - nest_pos) < self.nest_radius + s.FOOD_RADIUS + 25:
|
| | nest_collision = True
|
| | break
|
| | if nest_collision: continue
|
| |
|
| |
|
| | obstacle_collision = False
|
| | for obs in self.obstacles:
|
| | if np.linalg.norm(pos - obs.position) < obs.radius + s.FOOD_RADIUS + 15:
|
| | obstacle_collision = True
|
| | break
|
| | if obstacle_collision: continue
|
| |
|
| |
|
| | food_collision = False
|
| | for fs in self.food_sources:
|
| | if np.linalg.norm(pos - fs.position) < s.FOOD_RADIUS * 5:
|
| | food_collision = True
|
| | break
|
| | if food_collision: continue
|
| |
|
| |
|
| | self.food_sources.append(FoodSource(pos, s.FOOD_INITIAL_AMOUNT, s.FOOD_RADIUS))
|
| | return
|
| |
|
| | def update(self):
|
| | """Ortamı her adımda günceller."""
|
| | self._update_pheromones()
|
| |
|
| |
|
| | if s.FOOD_DEPLETION_REMOVAL:
|
| | self.food_sources = [fs for fs in self.food_sources if not fs.is_empty()]
|
| | if random.random() < s.FOOD_SPAWN_RATE:
|
| | self._try_spawn_food()
|
| |
|
| | def world_to_grid(self, world_pos: np.ndarray) -> tuple[int, int]:
|
| | """Dünya koordinatlarını ızgara indekslerine dönüştürür."""
|
| | gx = int(world_pos[0] / self.grid_res)
|
| | gy = int(world_pos[1] / self.grid_res)
|
| | gx = np.clip(gx, 0, self.grid_width - 1)
|
| | gy = np.clip(gy, 0, self.grid_height - 1)
|
| | return gx, gy
|
| |
|
| | def deposit_pheromone(self, colony_id: int, pheromone_type: str, world_pos: np.ndarray, amount: float):
|
| | """Belirtilen konuma, belirtilen koloninin feromonunu bırakır."""
|
| | gx, gy = self.world_to_grid(world_pos)
|
| |
|
| | if pheromone_type == 'home':
|
| | grid = self.home_pheromone_grids.get(colony_id)
|
| | elif pheromone_type == 'food':
|
| | grid = self.food_pheromone_grids.get(colony_id)
|
| | else:
|
| | grid = None
|
| |
|
| | if grid is not None:
|
| | grid[gx, gy] += amount
|
| | grid[gx, gy] = np.clip(grid[gx, gy], 0, s.PHEROMONE_MAX_STRENGTH)
|
| |
|
| | def _diffuse_and_evaporate(self, grid: np.ndarray) -> np.ndarray:
|
| | """Tek bir feromon ızgarasını günceller."""
|
| | padded_grid = np.pad(grid, 1, mode='constant')
|
| | center_weight = 1.0 - (s.PHEROMONE_DIFFUSION_RATE * 8)
|
| | neighbor_weight = s.PHEROMONE_DIFFUSION_RATE
|
| |
|
| | neighbors_sum = (padded_grid[:-2, 1:-1] + padded_grid[2:, 1:-1] +
|
| | padded_grid[1:-1, :-2] + padded_grid[1:-1, 2:] +
|
| | padded_grid[:-2, :-2] + padded_grid[:-2, 2:] +
|
| | padded_grid[2:, :-2] + padded_grid[2:, 2:])
|
| | diffused = (padded_grid[1:-1, 1:-1] * center_weight + neighbors_sum * neighbor_weight)
|
| | evaporated = diffused * (1.0 - s.PHEROMONE_EVAPORATION_RATE)
|
| | return np.clip(evaporated, 0, s.PHEROMONE_MAX_STRENGTH)
|
| |
|
| | def _update_pheromones(self):
|
| | """Tüm feromon ızgaralarını günceller."""
|
| | for colony_id in self.home_pheromone_grids:
|
| | self.home_pheromone_grids[colony_id] = self._diffuse_and_evaporate(self.home_pheromone_grids[colony_id])
|
| | self.food_pheromone_grids[colony_id] = self._diffuse_and_evaporate(self.food_pheromone_grids[colony_id])
|
| |
|
| | def get_pheromone_strength(self, colony_id: int, pheromone_type: str, world_pos: np.ndarray) -> float:
|
| | """Belirtilen koloninin, belirtilen türdeki feromon gücünü alır."""
|
| | gx, gy = self.world_to_grid(world_pos)
|
| | if pheromone_type == 'home':
|
| | grid = self.home_pheromone_grids.get(colony_id)
|
| | elif pheromone_type == 'food':
|
| | grid = self.food_pheromone_grids.get(colony_id)
|
| | else: return 0.0
|
| |
|
| | return grid[gx, gy] if grid is not None else 0.0
|
| |
|
| | def sense_pheromones_at(self, colony_id: int, pheromone_type: str, sample_points: list[np.ndarray]) -> np.ndarray:
|
| | """Verilen noktalardaki belirtilen koloniye ait feromon yoğunluklarını döndürür."""
|
| | strengths = []
|
| |
|
| | if pheromone_type == 'home':
|
| | grid = self.home_pheromone_grids.get(colony_id)
|
| | elif pheromone_type == 'food':
|
| | grid = self.food_pheromone_grids.get(colony_id)
|
| | else: grid = None
|
| |
|
| | if grid is None: return np.zeros(len(sample_points), dtype=np.float32)
|
| |
|
| | for point in sample_points:
|
| | gx, gy = self.world_to_grid(point)
|
| | strengths.append(grid[gx, gy])
|
| | return np.array(strengths, dtype=np.float32)
|
| |
|
| | def get_food_sources(self) -> list[FoodSource]:
|
| | """Aktif yem kaynaklarının listesi."""
|
| | return [fs for fs in self.food_sources if not fs.is_empty()]
|
| |
|
| |
|
| | def get_closest_food(self, world_pos: np.ndarray) -> tuple[np.ndarray | None, float, np.ndarray]:
|
| | """Verilen konuma en yakın aktif yem kaynağını bulur.
|
| | Döndürülenler: (kaynak_pozisyonu, uzaklık_karesi, yön_vektörü_normalize)
|
| | Eğer kaynak yoksa: (None, float('inf'), np.zeros(2))
|
| | """
|
| | closest_fs = None
|
| | min_dist_sq = float('inf')
|
| | active_foods = self.get_food_sources()
|
| |
|
| | for fs in active_foods:
|
| | dist_sq = np.sum((fs.position - world_pos)**2)
|
| | if dist_sq < min_dist_sq:
|
| | min_dist_sq = dist_sq
|
| | closest_fs = fs
|
| |
|
| | if closest_fs:
|
| | direction_vec = closest_fs.position - world_pos
|
| | dist = np.sqrt(min_dist_sq)
|
| | if dist > 1e-6:
|
| | normalized_direction = direction_vec / dist
|
| | else:
|
| | normalized_direction = np.zeros(2, dtype=np.float32)
|
| | return closest_fs.position, min_dist_sq, normalized_direction
|
| | else:
|
| | return None, float('inf'), np.zeros(2, dtype=np.float32)
|
| |
|
| |
|
| | def get_nest_position(self, colony_id: int) -> np.ndarray:
|
| | """Belirtilen koloninin yuva pozisyonunu döndürür."""
|
| | return self.nest_positions.get(colony_id)
|
| |
|
| | def is_at_nest(self, colony_id: int, world_pos: np.ndarray) -> bool:
|
| | """Pozisyonun belirtilen koloninin yuvasında olup olmadığını kontrol eder."""
|
| | nest_pos = self.nest_positions.get(colony_id)
|
| | if nest_pos is None: return False
|
| | return np.linalg.norm(world_pos - nest_pos) < self.nest_radius
|
| |
|
| | def check_obstacle_collision(self, world_pos: np.ndarray, radius: float) -> bool:
|
| | """Engelle çarpışma kontrolü."""
|
| | for obs in self.obstacles:
|
| | if np.linalg.norm(world_pos - obs.position) < obs.radius + radius:
|
| | return True
|
| | return False
|
| |
|
| | def draw(self, screen):
|
| | """Ortamın tüm bileşenlerini çizer."""
|
| | if s.DEBUG_DRAW_PHEROMONES:
|
| | self._draw_pheromones(screen)
|
| | if s.DEBUG_DRAW_NESTS:
|
| | self._draw_nests(screen)
|
| | if s.DEBUG_DRAW_FOOD_LOCATIONS:
|
| | self._draw_food_sources(screen)
|
| | if s.DEBUG_DRAW_OBSTACLES:
|
| | self._draw_obstacles(screen)
|
| |
|
| | def _draw_pheromones(self, screen):
|
| | """Her iki koloninin feromonlarını ayrı renklerle çizer."""
|
| | self.pheromone_surface.fill((0, 0, 0, 0))
|
| | res = self.grid_res
|
| | pheromone_colors = {
|
| | 'home_red': s.COLOR_PHEROMONE_HOME_RED,
|
| | 'home_blue': s.COLOR_PHEROMONE_HOME_BLUE,
|
| | 'food_red': (*s.COLOR_PHEROMONE_HOME_RED[:3], s.COLOR_PHEROMONE_HOME_RED[3] // 2),
|
| | 'food_blue': (*s.COLOR_PHEROMONE_HOME_BLUE[:3], s.COLOR_PHEROMONE_HOME_BLUE[3] // 2)
|
| | }
|
| | grids_to_draw = {
|
| | 'home_red': self.home_pheromone_grids.get(s.COLONY_ID_RED),
|
| | 'home_blue': self.home_pheromone_grids.get(s.COLONY_ID_BLUE),
|
| | 'food_red': self.food_pheromone_grids.get(s.COLONY_ID_RED),
|
| | 'food_blue': self.food_pheromone_grids.get(s.COLONY_ID_BLUE)
|
| | }
|
| |
|
| | for p_type, grid in grids_to_draw.items():
|
| | if grid is None: continue
|
| | color_info = pheromone_colors[p_type]
|
| | for x in range(self.grid_width):
|
| | for y in range(self.grid_height):
|
| | strength = grid[x, y]
|
| | if strength > 0.01:
|
| | alpha = int(np.clip(strength / s.PHEROMONE_MAX_STRENGTH, 0, 1) * color_info[3])
|
| | color = (*color_info[:3], alpha)
|
| | rect = pygame.Rect(x * res, y * res, res, res)
|
| | pygame.draw.rect(self.pheromone_surface, color, rect)
|
| |
|
| | screen.blit(self.pheromone_surface, (0, 0))
|
| |
|
| |
|
| | def _draw_nests(self, screen):
|
| | """Her iki yuvayı da çizer."""
|
| | for colony_id, nest_pos in self.nest_positions.items():
|
| | color = self.nest_colors.get(colony_id, (128,128,128))
|
| | pygame.draw.circle(screen, color, nest_pos.astype(int), self.nest_radius)
|
| | pygame.draw.circle(screen, (color[0]//2, color[1]//2, color[2]//2),
|
| | nest_pos.astype(int), self.nest_radius - 3)
|
| |
|
| | def _draw_food_sources(self, screen):
|
| | """Aktif yem kaynaklarını çizer."""
|
| | for fs in self.food_sources:
|
| | fs.draw(screen)
|
| |
|
| | def _draw_obstacles(self, screen):
|
| | """Engelleri çizer."""
|
| | for obs in self.obstacles:
|
| | obs.draw(screen) |