Source code for logic.reproduction

from collections import defaultdict, Counter
from core.organism import Producer, Consumer
import random

REPRO_COOLDOWN_BY_LEVEL = {
    "primary": 2,
    "secondary": 6,
    "tertiary": 8,
    "omnivore": 5,
    "unknown": 5
}

REPRO_ENERGY_THRESHOLD_BY_LEVEL = {
    "primary": 60,
    "secondary": 80,
    "tertiary": 90,
    "omnivore": 70,
    "unknown": 80
}

REPRO_RATIOS = {
    "Rabbit": 2,   # 1 Rabbit -> 2 Carrot
    "Fox": 1       # 1 Fox -> 1 Rabbit
}

[docs]def reproduce(organisms, grid_size, step_counter): """ Handles the reproduction logic for organisms in the simulation. This function spawns new Producer or Consumer organisms under various conditions such as energy thresholds, trophic level, proximity, and availability of prey. Parameters: organisms (list): List of organism objects (Producers or Consumers). grid_size (int): Size of the simulation grid. step_counter (int): Current simulation step used for cooldown and timing logic. Returns: list: List of newly spawned organism objects. """ new_organisms = [] species_by_pos = defaultdict(list) for org in organisms: if org.alive: species_by_pos[(org.x, org.y)].append(org) live_counts = Counter([o.species for o in organisms if o.alive]) food_sources = defaultdict(int) if hasattr(reproduce, "_foodweb") and reproduce._foodweb: fw = reproduce._foodweb for species in live_counts: prey_species = fw.get_prey(species) total_prey = sum(live_counts[prey] for prey in prey_species) food_sources[species] = total_prey # --- Producer Respawn --- if step_counter % 30 == 0: producer_species = set(o.species for o in organisms if isinstance(o, Producer)) producers_alive = any(isinstance(o, Producer) and o.alive for o in organisms) if not producers_alive: for species in producer_species: for _ in range(3): new_x = random.randint(0, grid_size - 1) new_y = random.randint(0, grid_size - 1) if any(o.x == new_x and o.y == new_y and o.alive for o in organisms): continue if hasattr(reproduce, "_terrain") and reproduce._terrain.is_blocked(new_x, new_y): continue new_organisms.append(Producer(species, new_x, new_y)) else: for org in organisms: if isinstance(org, Producer) and org.alive: dx, dy = random.choice([(1, 0), (-1, 0), (0, 1), (0, -1)]) new_x = min(grid_size - 1, max(0, org.x + dx)) new_y = min(grid_size - 1, max(0, org.y + dy)) if any(o.x == new_x and o.y == new_y and o.alive for o in organisms): continue if hasattr(reproduce, "_terrain") and ( reproduce._terrain.is_blocked(new_x, new_y) or reproduce._terrain.is_water(new_x, new_y) or reproduce._terrain.is_shelter(new_x, new_y)): continue new_organisms.append(Producer(org.species, new_x, new_y)) # --- Consumer on same cell --- for pos, orgs in species_by_pos.items(): groups = defaultdict(list) for o in orgs: if isinstance(o, Consumer): groups[o.species].append(o) for species, members in groups.items(): if len(members) < 2: continue if not all(m.energy >= REPRO_ENERGY_THRESHOLD_BY_LEVEL.get(m.trophic_level, 70) for m in members): continue prey_available = food_sources.get(species, 0) current = live_counts[species] required_ratio = REPRO_RATIOS.get(species, 1) if prey_available / required_ratio < current: continue last = reproduce._last_repro.get((species, pos), -999) cooldown = REPRO_COOLDOWN_BY_LEVEL.get(members[0].trophic_level, 5) if step_counter - last >= cooldown: new_organisms.append(Consumer(species, pos[0], pos[1], trophic_level=members[0].trophic_level)) reproduce._last_repro[(species, pos)] = step_counter # --- Consumer proximity based (all consumers including primary) --- paired = set() consumers = [o for o in organisms if isinstance(o, Consumer)] for i, o1 in enumerate(consumers): for o2 in consumers[i+1:]: if o1.species != o2.species or id(o1) in paired or id(o2) in paired: continue if abs(o1.x - o2.x) + abs(o1.y - o2.y) == 2: if o1.energy >= 40 and o2.energy >= 40: avg_x = (o1.x + o2.x) // 2 avg_y = (o1.y + o2.y) // 2 if hasattr(reproduce, "_terrain") and ( reproduce._terrain.is_blocked(avg_x, avg_y) or reproduce._terrain.is_water(avg_x, avg_y)): continue prey_available = food_sources.get(o1.species, 0) predatorcount = live_counts.get(o1.species, 0) current = live_counts[o1.species] required_ratio = REPRO_RATIOS.get(o1.species, 1) if prey_available <= predatorcount*2: continue if prey_available / required_ratio < current: if random.random() > 0.7: continue pos = (avg_x, avg_y) last = reproduce._last_repro.get((o1.species, pos), -999) cooldown = REPRO_COOLDOWN_BY_LEVEL.get(o1.trophic_level, 5) if step_counter - last >= cooldown: new_organisms.append(Consumer(o1.species, avg_x, avg_y, trophic_level=o1.trophic_level)) reproduce._last_repro[(o1.species, pos)] = step_counter paired.add(id(o1)) paired.add(id(o2)) # --- Primary 1 remaining fallback to shelter --- if hasattr(reproduce, "_foodweb") and reproduce._foodweb: fw = reproduce._foodweb for species, count in live_counts.items(): if count == 1 and fw.get_trophic_level(species) == "primary": last = reproduce._last_primary_respawn.get(species, -999) if step_counter - last >= 2: if hasattr(reproduce, "_terrain"): for (x, y), t in reproduce._terrain.map.items(): if t == "shelter": new_organisms.append(Consumer(species, x, y, trophic_level="primary")) reproduce._last_primary_respawn[species] = step_counter break return new_organisms
reproduce._last_repro = {} reproduce._last_primary_respawn = {} reproduce._foodweb = None reproduce._terrain = None