turns-00052.parquet:23737
385fb0c11c8b5a7dffa2492d
turn 1/1o1-mini-2024-09-12GermanGermany9361 words
degenerate_repetitionAbsentFinal dense release
USER
Ich nenne in dieser nachricht 3 skripte mit jeweils einer etwas unterschiedlichen version der selben anwendung, einer sandbox ameisen kolonie simulation welche nach möglichst einfachen, aber auch realistischen grundregeln welche möglichst ohne feste hardcoded inhalte auskommt und möglichst immer auf reale gegebenheiten basieren bzw diese nachempfinden sollen. Prüfe welche inhalte aus welchen skripten die besten und weitesten entwickelten sind und erstelle ein vollumfassendes und den vorgaben entsprechendes skript dieser anwendung. Nutze dafür aus allen skripten die besten umsetzungen und inhalte und verbessere bei bedarf auch inhalte und umsetzungen, um den anforderungen an diese anwendung besser zu erfüllen. Sorge dabei übrigens auch dafür das die umsetzung der beine zum einen 2 dimensional umgesetzt wird, zum anderen das die körper und die beine wirklich authentisch sind und entsprechend authentische form besitzen. Zudem das die beine unabhängig voneinander authentisch bewegt werden, auf eine wenig aufwendige und wenig rechenintensive aber ansehnliche weise. Was durch korrektes aussehen der beinform und dessen strecken bei entfernung und dem platzieren sowie dem verweilen der beinenden, bis zu einer maximalen distanz zur position des bein beginns am körper, auf der umgebungs oberfläche erzeugt werden kann.
Skript1:
import random
import numpy as np
from ursina import *
from opensimplex import OpenSimplex
import math
import time
# -----------------------
# Umgebungseinstellungen
# -----------------------
ENV_SIZE = (50, 50, 20) # Breite, Höhe, Tiefe
VOXEL_SIZE = 1
INITIAL_WORKERS = 50
INITIAL_SOLDIERS = 10
FOOD_SPAWN_RATE = 0.005 # Rate, mit der Nahrung erscheint
PHEROMONE_EVAPORATION_RATE = 0.01 # Rate, mit der Pheromone verdunsten
PHEROMONE_DIFFUSION_RATE = 0.1 # Rate, mit der Pheromone diffundieren
PHEROMONE_DEPOSIT_AMOUNT = 10.0 # Menge an Pheromon, die eine Ameise ablegt
PHEROMONE_MAX = 100.0 # Maximaler Pheromonwert
PHEROMONE_THRESHOLD = 1.0 # Mindeststärke für Einfluss
# Voxeltypen
AIR = 0
SOIL = 1
FOOD = 2
NEST = 3
STONE = 4
PLANT = 5
DEBRIS = 6 # Abgetragenes Material
# Pheromonarten
FOOD_PHEROMONE = 0
HOME_PHEROMONE = 1
DANGER_PHEROMONE = 2
# Simulationseinstellungen
DAY_LENGTH = 1000
current_time_step = 0
# Darstellungseinstellungen
ANT_COLORS = {
'WORKER': color.red,
'SOLDIER': color.blue,
'QUEEN': color.yellow
}
PHEROMONE_COLORS = {
FOOD_PHEROMONE: color.green,
HOME_PHEROMONE: color.cyan,
DANGER_PHEROMONE: color.red
}
ANT_SPEED = 0.1
# Ameisenkaste
CASTES = ['WORKER', 'SOLDIER', 'QUEEN']
ANT_LIFESPAN = 5000
# -----------------------
# Klassen
# -----------------------
class Voxel(Entity):
def __init__(self, position=(0, 0, 0), voxel_type=AIR):
super().__init__(
parent=scene,
position=position,
model='cube' if voxel_type != AIR else None,
color=self.get_color(voxel_type),
scale=VOXEL_SIZE
)
self.type = voxel_type
self.pheromones = np.zeros(3) # [FOOD_PHEROMONE, HOME_PHEROMONE, DANGER_PHEROMONE]
def get_color(self, voxel_type):
colors = {
SOIL: color.rgb(139, 69, 19), # Braun
FOOD: color.yellow,
NEST: color.rgb(255, 165, 0), # Orange für Nest
STONE: color.gray,
PLANT: color.green,
DEBRIS: color.rgb(210, 180, 140), # Hellbraun für abgetragenes Material
AIR: color.clear
}
return colors.get(voxel_type, color.clear)
def evaporate_pheromones(self):
self.pheromones *= (1 - PHEROMONE_EVAPORATION_RATE)
self.pheromones = np.clip(self.pheromones, 0, PHEROMONE_MAX)
def diffuse_pheromones(self, neighbors):
for pheromone_type in range(len(self.pheromones)):
if neighbors:
diffusion_amount = self.pheromones[pheromone_type] * PHEROMONE_DIFFUSION_RATE / len(neighbors)
for neighbor in neighbors:
neighbor.pheromones[pheromone_type] += diffusion_amount
self.pheromones[pheromone_type] *= (1 - PHEROMONE_DIFFUSION_RATE)
else:
# Keine Nachbarn, also keine Diffusion
pass
class Environment:
def __init__(self):
self.simplex = OpenSimplex(seed=random.randint(0, 10000))
self.grid = np.empty(ENV_SIZE, dtype=object)
self.initialize_environment()
def initialize_environment(self):
for x in range(ENV_SIZE[0]):
for y in range(ENV_SIZE[1]):
# Terrainhöhe basierend auf Simplex-Noise
noise_value = self.simplex.noise2(x / 10, y / 10)
height = int((noise_value + 1) * (ENV_SIZE[2] // 2))
for z in range(ENV_SIZE[2]):
voxel_type = SOIL if z < height else AIR
self.grid[x][y][z] = Voxel(position=(x, y, z), voxel_type=voxel_type)
# Hindernisse und Pflanzen hinzufügen
for _ in range(100):
x = random.randint(0, ENV_SIZE[0] - 1)
y = random.randint(0, ENV_SIZE[1] - 1)
z = self.get_surface_z(x, y)
if z < ENV_SIZE[2]:
voxel_type = STONE if random.random() < 0.5 else PLANT
self.grid[x][y][z] = Voxel(position=(x, y, z), voxel_type=voxel_type)
# Nest positionieren
nest_x, nest_y = ENV_SIZE[0] // 2, ENV_SIZE[1] // 2
nest_z = self.get_surface_z(nest_x, nest_y) - 1
self.grid[nest_x][nest_y][nest_z] = Voxel(position=(nest_x, nest_y, nest_z), voxel_type=NEST)
def get_surface_z(self, x, y):
for z in range(ENV_SIZE[2] - 1, -1, -1):
if self.grid[x][y][z].type != AIR:
return z + 1 if z + 1 < ENV_SIZE[2] else z
return 0
def update_pheromones(self):
# Erste Diffusionsschritt
for x in range(ENV_SIZE[0]):
for y in range(ENV_SIZE[1]):
for z in range(ENV_SIZE[2]):
voxel = self.grid[x][y][z]
if voxel.type == AIR:
neighbors = self.get_neighbors(x, y, z)
if neighbors:
voxel.diffuse_pheromones(neighbors)
# Zweite Verdunstungsschritt
for x in range(ENV_SIZE[0]):
for y in range(ENV_SIZE[1]):
for z in range(ENV_SIZE[2]):
self.grid[x][y][z].evaporate_pheromones()
def get_neighbors(self, x, y, z):
neighbors = []
for dx in [-1, 0, 1]:
nx = x + dx
if 0 <= nx < ENV_SIZE[0]:
for dy in [-1, 0, 1]:
ny = y + dy
if 0 <= ny < ENV_SIZE[1]:
for dz in [-1, 0, 1]:
nz = z + dz
if 0 <= nz < ENV_SIZE[2]:
if dx == 0 and dy == 0 and dz == 0:
continue
neighbor = self.grid[nx][ny][nz]
if neighbor.type == AIR:
neighbors.append(neighbor)
return neighbors
def spawn_food(self):
if random.random() < FOOD_SPAWN_RATE:
x = random.randint(0, ENV_SIZE[0] - 1)
y = random.randint(0, ENV_SIZE[1] - 1)
z = self.get_surface_z(x, y)
if z < ENV_SIZE[2] and self.grid[x][y][z].type == AIR:
self.grid[x][y][z] = Voxel(position=(x, y, z), voxel_type=FOOD)
class AntSensorySystem:
def __init__(self):
self.range = 1 # Wahrnehmungsreichweite
def perceive(self, ant):
pheromone_levels = np.zeros(3)
x, y, z = int(ant.position.x), int(ant.position.y), int(ant.position.z)
for dx in range(-self.range, self.range + 1):
nx = x + dx
if 0 <= nx < ENV_SIZE[0]:
for dy in range(-self.range, self.range + 1):
ny = y + dy
if 0 <= ny < ENV_SIZE[1]:
for dz in range(-self.range, self.range + 1):
nz = z + dz
if 0 <= nz < ENV_SIZE[2]:
neighbor = ant.environment.grid[nx][ny][nz]
pheromone_levels += neighbor.pheromones
return pheromone_levels
def detect_food(self, ant):
x, y, z = int(ant.position.x), int(ant.position.y), int(ant.position.z)
for dx in range(-self.range, self.range + 1):
nx = x + dx
if 0 <= nx < ENV_SIZE[0]:
for dy in range(-self.range, self.range + 1):
ny = y + dy
if 0 <= ny < ENV_SIZE[1]:
for dz in range(-self.range, self.range + 1):
nz = z + dz
if 0 <= nz < ENV_SIZE[2]:
voxel = ant.environment.grid[nx][ny][nz]
if voxel.type == FOOD:
return True, Vec3(dx, dy, dz)
return False, None
class Leg(Entity):
def __init__(self, ant, index):
super().__init__(
parent=ant,
model='cylinder',
color=color.black,
scale=(0.05, 0.5, 0.05),
origin_y=0.5
)
self.ant = ant
self.index = index
self.update_leg()
def calculate_end_pos(self):
# Berechnung der Endposition des Beins entsprechend der Ameisenbewegung
angle = self.ant.rotation_y + (self.index % 3 - 1) * 30 + (self.index // 3) * 180
rad = math.radians(angle)
offset_x = math.cos(rad) * 0.3
offset_y = math.sin(rad) * 0.3
end_pos = Vec3(self.ant.position.x + offset_x, self.ant.position.y + offset_y, self.ant.position.z - 0.2)
return end_pos
def update_leg(self):
self.position = self.ant.position
self.look_at(self.calculate_end_pos(), 'up')
class Ant(Entity):
def __init__(self, position, colony, environment, caste='WORKER'):
super().__init__(
parent=scene,
position=position,
model=None,
color=ANT_COLORS.get(caste, color.white),
scale=0.2
)
self.caste = caste
self.colony = colony
self.environment = environment
self.has_food = False
self.has_soil = False # Ob die Ameise Erde trägt
self.energy = 100
self.age = 0
self.max_age = ANT_LIFESPAN
self.direction = self.random_direction()
self.memory = []
self.state = 'FORAGING' # Mögliche Zustände: FORAGING, DIGGING, DEPOSITING
self.sensory_system = AntSensorySystem()
self.legs = [Leg(self, i) for i in range(6)] # 6 Beine
self.generate_body()
def generate_body(self):
# Kopf, Thorax und Abdomen mit den angegebenen Dimensionen
self.head = Entity(parent=self, model='sphere', color=self.color,
scale=Vec3(0.4, 0.4, 0.4), position=Vec3(0, 0, 0.3))
self.thorax = Entity(parent=self, model='sphere', color=self.color,
scale=Vec3(0.5, 0.5, 0.5), position=Vec3(0, 0, 0))
self.abdomen = Entity(parent=self, model='sphere', color=self.color,
scale=Vec3(0.6, 0.6, 0.6), position=Vec3(0, 0, -0.4))
def random_direction(self):
angle = random.uniform(0, 360)
return Vec3(math.cos(math.radians(angle)), math.sin(math.radians(angle)), 0)
def perceive(self):
return self.sensory_system.perceive(self)
def decide(self):
if self.state == 'FORAGING':
self.decide_foraging()
elif self.state == 'DEPOSITING':
self.decide_depositing()
def decide_foraging(self):
pheromones = self.perceive()
food_pheromone = pheromones[FOOD_PHEROMONE]
home_pheromone = pheromones[HOME_PHEROMONE]
if self.has_food:
# Auf dem Weg zurück zum Nest
if home_pheromone > PHEROMONE_THRESHOLD and random.random() < 0.9:
self.follow_pheromone(HOME_PHEROMONE)
else:
self.direction = self.random_direction()
else:
if food_pheromone > PHEROMONE_THRESHOLD and random.random() < 0.9:
self.follow_pheromone(FOOD_PHEROMONE)
else:
self.direction = self.random_direction()
def decide_depositing(self):
if self.has_food:
x, y, z = int(self.position.x), int(self.position.y), int(self.position.z)
voxel = self.environment.grid[x][y][z]
if voxel.type == NEST:
self.has_food = False
self.colony.food_storage += 1
self.energy += 50
self.state = 'FORAGING'
def follow_pheromone(self, pheromone_type):
x, y, z = int(self.position.x), int(self.position.y), int(self.position.z)
max_pheromone = -1
best_direction = None
for dx in [-1, 0, 1]:
nx = x + dx
if 0 <= nx < ENV_SIZE[0]:
for dy in [-1, 0, 1]:
ny = y + dy
if 0 <= ny < ENV_SIZE[1]:
for dz in [-1, 0, 1]:
nz = z + dz
if 0 <= nz < ENV_SIZE[2]:
neighbor = self.environment.grid[nx][ny][nz]
pheromone_level = neighbor.pheromones[pheromone_type]
if pheromone_level > max_pheromone:
max_pheromone = pheromone_level
best_direction = Vec3(dx, dy, dz)
if best_direction:
self.direction = best_direction + Vec3(random.uniform(-0.1, 0.1), random.uniform(-0.1, 0.1), 0)
else:
self.direction = self.random_direction()
def move(self):
if self.direction.length() == 0:
self.direction = self.random_direction()
self.direction = self.direction.normalized()
new_position = self.position + self.direction * ANT_SPEED
x, y, z = int(new_position.x), int(new_position.y), int(new_position.z)
if 0 <= x < ENV_SIZE[0] and 0 <= y < ENV_SIZE[1] and 0 <= z < ENV_SIZE[2]:
voxel = self.environment.grid[x][y][z]
if voxel.type in [AIR, FOOD, NEST]:
self.position = new_position
self.energy -= 0.1
else:
self.direction = self.random_direction()
def act(self):
x, y, z = int(self.position.x), int(self.position.y), int(self.position.z)
voxel = self.environment.grid[x][y][z]
if self.state == 'FORAGING':
if not self.has_food and voxel.type == FOOD:
self.has_food = True
voxel.type = AIR
voxel.model = None
voxel.color = color.clear
self.state = 'DEPOSITING'
elif self.state == 'DEPOSITING':
self.decide_depositing()
def deposit_pheromones(self):
x, y, z = int(self.position.x), int(self.position.y), int(self.position.z)
voxel = self.environment.grid[x][y][z]
if self.has_food:
voxel.pheromones[HOME_PHEROMONE] += PHEROMONE_DEPOSIT_AMOUNT
else:
voxel.pheromones[FOOD_PHEROMONE] += PHEROMONE_DEPOSIT_AMOUNT
def update(self):
self.age += 1
if self.age > self.max_age or self.energy <= 0:
self.die()
return
self.decide()
self.move()
self.act()
self.deposit_pheromones()
# Aktualisiere die Beine
for leg in self.legs:
leg.update_leg()
def die(self):
self.colony.remove_ant(self)
destroy(self)
class QueenAnt(Entity):
def __init__(self, position, colony):
super().__init__(
parent=scene,
position=position,
model='sphere',
color=ANT_COLORS['QUEEN'],
scale=0.6
)
self.colony = colony
self.laying_timer = 500 # Zeit bis zum nächsten Ei
def update(self):
self.laying_timer -= 1
if self.laying_timer <= 0:
if self.colony.food_storage >= 20:
self.colony.food_storage -= 20
self.lay_egg()
self.laying_timer = 500
def lay_egg(self):
egg = Egg(position=self.position, colony=self.colony)
self.colony.add_egg(egg)
class Egg(Entity):
def __init__(self, position, colony):
super().__init__(
parent=scene,
position=position,
model='sphere',
color=color.white,
scale=0.1
)
self.colony = colony
self.hatching_time = 1000 # Zeit bis zum Schlüpfen
def update(self):
self.hatching_time -= 1
if self.hatching_time <= 0:
self.hatch()
def hatch(self):
# Aus dem Ei schlüpft eine Arbeiterameise
ant = Ant(position=self.position, colony=self.colony, environment=self.colony.environment)
self.colony.add_ant(ant)
destroy(self)
self.colony.eggs.remove(self)
class Colony:
def __init__(self, environment):
self.environment = environment
self.ants = []
self.eggs = []
self.food_storage = 0
nest_x, nest_y = ENV_SIZE[0] // 2, ENV_SIZE[1] // 2
nest_z = environment.get_surface_z(nest_x, nest_y) - 1
self.nest_position = Vec3(nest_x, nest_y, nest_z)
self.queen = QueenAnt(position=self.nest_position, colony=self)
def add_ant(self, ant):
self.ants.append(ant)
def remove_ant(self, ant):
if ant in self.ants:
self.ants.remove(ant)
def add_egg(self, egg):
self.eggs.append(egg)
def update(self):
# Königin aktualisieren
self.queen.update()
# Eier aktualisieren
for egg in self.eggs[:]:
egg.update()
class CameraController(Entity):
def __init__(self):
super().__init__()
self.camera_pivot = Entity()
camera.parent = self.camera_pivot
camera.position = (0, -30, 20)
camera.rotation_x = 30
self.rotation_speed = 100
self.zoom_speed = 20
self.current_zoom = 30
self.focus_position = Vec3(ENV_SIZE[0] // 2, ENV_SIZE[1] // 2, ENV_SIZE[2] // 2)
self.camera_pivot.position = self.focus_position
self.following = False
self.follow_target = None
self.last_click_time = 0
self.double_click_threshold = 0.3
def update(self):
dt = time.dt
# Doppelklick-Erkennung
if mouse.left and mouse.click:
current_time_click = time.time()
if current_time_click - self.last_click_time < self.double_click_threshold:
# Doppelklick erkannt
hit_entity = mouse.hovered_entity
if isinstance(hit_entity, Voxel) or isinstance(hit_entity, Ant):
self.focus_position = hit_entity.position
self.follow_target = hit_entity if isinstance(hit_entity, Ant) else None
self.following = isinstance(hit_entity, Ant)
self.last_click_time = current_time_click
# Kamera drehen
if held_keys['right mouse']:
delta = Vec2(mouse.velocity[0], mouse.velocity[1])
self.camera_pivot.rotation_y += delta.x * dt * self.rotation_speed
self.camera_pivot.rotation_x -= delta.y * dt * self.rotation_speed
self.camera_pivot.rotation_x = max(-90, min(90, self.camera_pivot.rotation_x))
# Zoom
self.current_zoom -= mouse.scroll_y * self.zoom_speed * dt
self.current_zoom = max(5, min(100, self.current_zoom))
camera.position = Vec3(0, -self.current_zoom, self.current_zoom * 0.5)
# Kamerafokus aktualisieren
if self.following and self.follow_target:
self.focus_position = self.follow_target.position
self.camera_pivot.position = lerp(self.camera_pivot.position, self.focus_position, 0.1)
# -----------------------
# Hilfsfunktionen
# -----------------------
def draw_pheromones(environment_grid):
for x in range(ENV_SIZE[0]):
for y in range(ENV_SIZE[1]):
for z in range(ENV_SIZE[2]):
voxel = environment_grid[x][y][z]
for pheromone_type in [FOOD_PHEROMONE, HOME_PHEROMONE, DANGER_PHEROMONE]:
strength = voxel.pheromones[pheromone_type]
if strength > PHEROMONE_THRESHOLD:
Entity(
parent=scene,
position=(x, y, z + 0.5),
model='sphere',
color=PHEROMONE_COLORS[pheromone_type].tint(strength / PHEROMONE_MAX),
scale=strength * 0.1,
double_sided=True,
billboard=True,
render_queue=3000
)
# -----------------------
# Hauptfunktion
# -----------------------
def main():
app = Ursina()
environment = Environment()
colony = Colony(environment)
# Anfangs-Ameisen erstellen
for _ in range(INITIAL_WORKERS):
offset = Vec3(random.uniform(-5, 5), random.uniform(-5, 5), 0)
position = colony.nest_position + offset
# Sicherstellen, dass die Position innerhalb der Umgebung liegt
position.x = max(0, min(position.x, ENV_SIZE[0] - 1))
position.y = max(0, min(position.y, ENV_SIZE[1] - 1))
position.z = environment.get_surface_z(int(position.x), int(position.y))
ant = Ant(position=position, colony=colony, environment=environment)
colony.add_ant(ant)
# Soldatenameisen erstellen
for _ in range(INITIAL_SOLDIERS):
offset = Vec3(random.uniform(-5, 5), random.uniform(-5, 5), 0)
position = colony.nest_position + offset
# Sicherstellen, dass die Position innerhalb der Umgebung liegt
position.x = max(0, min(position.x, ENV_SIZE[0] - 1))
position.y = max(0, min(position.y, ENV_SIZE[1] - 1))
position.z = environment.get_surface_z(int(position.x), int(position.y))
ant = Ant(position=position, colony=colony, environment=environment, caste='SOLDIER')
colony.add_ant(ant)
# Kamera-Controller hinzufügen
camera_controller = CameraController()
# UI-Text für gesammeltes Essen
food_text = Text(text='', position=window.top_left, origin=(0, 0), background=True)
# Update-Funktion definieren
def update():
global current_time_step
current_time_step += 1
# Umwelt aktualisieren
environment.update_pheromones()
environment.spawn_food()
# Ameisen aktualisieren
for ant in colony.ants:
ant.update()
# Kolonie aktualisieren
colony.update()
# Pheromone anzeigen
if held_keys['p']:
draw_pheromones(environment.grid)
# UI aktualisieren
food_text.text = f"Essen gesammelt: {colony.food_storage}"
app.run()
if __name__ == "__main__":
main()
Skript 2:
import random
import numpy as np
from ursina import *
from opensimplex import OpenSimplex
import math
# -----------------------
# Umgebungseinstellungen
# -----------------------
ENV_SIZE = (50, 50, 20) # Breite, Höhe, Tiefe
VOXEL_SIZE = 1
INITIAL_WORKERS = 50
INITIAL_SOLDIERS = 10
FOOD_SPAWN_RATE = 0.005 # Rate, mit der Nahrung erscheint
PHEROMONE_EVAPORATION_RATE = 0.01 # Rate, mit der Pheromone verdunsten
PHEROMONE_DIFFUSION_RATE = 0.1 # Rate, mit der Pheromone diffundieren
PHEROMONE_DEPOSIT_AMOUNT = 10.0 # Menge an Pheromon, die eine Ameise ablegt
PHEROMONE_MAX = 100.0 # Maximaler Pheromonwert
PHEROMONE_THRESHOLD = 1.0 # Mindeststärke für Einfluss
# Voxeltypen
AIR = 0
SOIL = 1
FOOD = 2
NEST = 3
STONE = 4
PLANT = 5
DEBRIS = 6 # Abgetragenes Material
# Pheromonarten
FOOD_PHEROMONE = 0
HOME_PHEROMONE = 1
DANGER_PHEROMONE = 2
# Simulationseinstellungen
DAY_LENGTH = 1000
current_time_step = 0
# Darstellungseinstellungen
ANT_COLORS = {
'WORKER': color.red,
'SOLDIER': color.blue,
'QUEEN': color.yellow
}
PHEROMONE_COLORS = {
FOOD_PHEROMONE: color.green,
HOME_PHEROMONE: color.cyan,
DANGER_PHEROMONE: color.red
}
ANT_SPEED = 0.1
# Ameisenkaste
CASTES = ['WORKER', 'SOLDIER', 'QUEEN']
ANT_LIFESPAN = 5000
# -----------------------
# Klassen
# -----------------------
class Voxel(Entity):
def __init__(self, position=(0, 0, 0), voxel_type=AIR):
super().__init__(
parent=scene,
position=position,
model='cube' if voxel_type != AIR else None,
color=self.get_color(voxel_type),
scale=VOXEL_SIZE
)
self.type = voxel_type
self.pheromones = np.zeros(3) # [FOOD_PHEROMONE, HOME_PHEROMONE, DANGER_PHEROMONE]
def get_color(self, voxel_type):
colors = {
SOIL: color.rgb(139, 69, 19), # Braun
FOOD: color.yellow,
NEST: color.rgb(255, 165, 0), # Orange für Nest
STONE: color.gray,
PLANT: color.green,
DEBRIS: color.rgb(210, 180, 140), # Hellbraun für abgetragenes Material
AIR: color.clear
}
return colors.get(voxel_type, color.clear)
def evaporate_pheromones(self):
self.pheromones *= (1 - PHEROMONE_EVAPORATION_RATE)
self.pheromones = np.clip(self.pheromones, 0, PHEROMONE_MAX)
def diffuse_pheromones(self, neighbors):
for pheromone_type in range(len(self.pheromones)):
total_pheromone = self.pheromones[pheromone_type] * (1 - PHEROMONE_DIFFUSION_RATE)
if neighbors:
diffusion_amount = self.pheromones[pheromone_type] * PHEROMONE_DIFFUSION_RATE / len(neighbors)
for neighbor in neighbors:
neighbor.pheromones[pheromone_type] += diffusion_amount
self.pheromones[pheromone_type] = total_pheromone
class Environment:
def __init__(self):
self.simplex = OpenSimplex(seed=random.randint(0, 10000))
self.grid = np.empty(ENV_SIZE, dtype=object)
self.initialize_environment()
def initialize_environment(self):
for x in range(ENV_SIZE[0]):
for y in range(ENV_SIZE[1]):
# Terrainhöhe basierend auf Simplex-Noise
noise_value = self.simplex.noise2(x / 10, y / 10)
height = int((noise_value + 1) * (ENV_SIZE[2] // 2))
for z in range(ENV_SIZE[2]):
voxel_type = SOIL if z < height else AIR
self.grid[x][y][z] = Voxel(position=(x, y, z), voxel_type=voxel_type)
# Hindernisse und Pflanzen hinzufügen
for _ in range(100):
x = random.randint(0, ENV_SIZE[0] - 1)
y = random.randint(0, ENV_SIZE[1] - 1)
z = self.get_surface_z(x, y)
if z < ENV_SIZE[2]:
voxel_type = STONE if random.random() < 0.5 else PLANT
self.grid[x][y][z] = Voxel(position=(x, y, z), voxel_type=voxel_type)
# Nest positionieren
nest_x, nest_y = ENV_SIZE[0] // 2, ENV_SIZE[1] // 2
nest_z = self.get_surface_z(nest_x, nest_y) - 1
self.grid[nest_x][nest_y][nest_z] = Voxel(position=(nest_x, nest_y, nest_z), voxel_type=NEST)
def get_surface_z(self, x, y):
for z in range(ENV_SIZE[2] - 1, -1, -1):
if self.grid[x][y][z].type != AIR:
return z + 1 if z + 1 < ENV_SIZE[2] else z
return 0
def update_pheromones(self):
# Diffusionsschritt
for x in range(ENV_SIZE[0]):
for y in range(ENV_SIZE[1]):
for z in range(ENV_SIZE[2]):
voxel = self.grid[x][y][z]
if voxel.type == AIR:
neighbors = self.get_neighbors(x, y, z)
if neighbors:
voxel.diffuse_pheromones(neighbors)
# Verdunstungsschritt
for x in range(ENV_SIZE[0]):
for y in range(ENV_SIZE[1]):
for z in range(ENV_SIZE[2]):
self.grid[x][y][z].evaporate_pheromones()
def get_neighbors(self, x, y, z):
neighbors = []
for dx in [-1, 0, 1]:
nx = x + dx
if 0 <= nx < ENV_SIZE[0]:
for dy in [-1, 0, 1]:
ny = y + dy
if 0 <= ny < ENV_SIZE[1]:
for dz in [-1, 0, 1]:
nz = z + dz
if 0 <= nz < ENV_SIZE[2]:
if dx == 0 and dy == 0 and dz == 0:
continue
neighbor = self.grid[nx][ny][nz]
if neighbor.type == AIR:
neighbors.append(neighbor)
return neighbors
def spawn_food(self):
if random.random() < FOOD_SPAWN_RATE:
x = random.randint(0, ENV_SIZE[0] - 1)
y = random.randint(0, ENV_SIZE[1] - 1)
z = self.get_surface_z(x, y)
if z < ENV_SIZE[2] and self.grid[x][y][z].type == AIR:
self.grid[x][y][z] = Voxel(position=(x, y, z), voxel_type=FOOD)
class AntSensorySystem:
def __init__(self):
self.range = 1 # Wahrnehmungsreichweite
def perceive(self, ant):
pheromone_levels = np.zeros(3)
x, y, z = int(ant.position.x), int(ant.position.y), int(ant.position.z)
for dx in range(-self.range, self.range + 1):
nx = x + dx
if 0 <= nx < ENV_SIZE[0]:
for dy in range(-self.range, self.range + 1):
ny = y + dy
if 0 <= ny < ENV_SIZE[1]:
for dz in range(-self.range, self.range + 1):
nz = z + dz
if 0 <= nz < ENV_SIZE[2]:
neighbor = ant.environment.grid[nx][ny][nz]
pheromone_levels += neighbor.pheromones
return pheromone_levels
def detect_food(self, ant):
x, y, z = int(ant.position.x), int(ant.position.y), int(ant.position.z)
for dx in range(-self.range, self.range + 1):
nx = x + dx
if 0 <= nx < ENV_SIZE[0]:
for dy in range(-self.range, self.range + 1):
ny = y + dy
if 0 <= ny < ENV_SIZE[1]:
for dz in range(-self.range, self.range + 1):
nz = z + dz
if 0 <= nz < ENV_SIZE[2]:
voxel = ant.environment.grid[nx][ny][nz]
if voxel.type == FOOD:
return True, np.array([dx, dy, dz])
return False, None
class Leg(Entity):
def __init__(self, ant, index):
super().__init__(
parent=ant,
model='cylinder',
color=color.black,
scale=(0.05, 0.5, 0.05),
origin_y=0.5
)
self.ant = ant
self.index = index
self.update_leg()
def calculate_end_pos(self):
# Berechnung der Endposition des Beins entsprechend der Ameisenbewegung
angle = self.ant.rotation_y + (self.index % 3 - 1) * 30 + (self.index // 3) * 180
rad = math.radians(angle)
offset_x = math.cos(rad) * 0.3
offset_y = math.sin(rad) * 0.3
end_pos = Vec3(self.ant.position.x + offset_x, self.ant.position.y + offset_y, self.ant.position.z - 0.2)
return end_pos
def update_leg(self):
self.start_point = self.ant.position
self.end_point = self.calculate_end_pos()
# Berechnung des Mittelpunkts für die umgedrehte "V"-Form
self.mid_point = (self.start_point + self.end_point) / 2 + Vec3(0, 0, 0.2)
# Erstelle das Mesh für das Bein
self.model = Mesh(
vertices=[self.start_point, self.mid_point, self.end_point],
mode='line',
thickness=2
)
class Ant(Entity):
def __init__(self, position, colony, environment, caste='WORKER'):
super().__init__(
parent=scene,
position=position,
model=None,
color=ANT_COLORS[caste],
scale=0.2
)
self.caste = caste
self.colony = colony
self.environment = environment
self.has_food = False
self.has_soil = False # Ob die Ameise Erde trägt
self.energy = 100
self.age = 0
self.max_age = ANT_LIFESPAN
self.direction = self.random_direction()
self.memory = []
self.state = 'FORAGING' # Mögliche Zustände: FORAGING, DIGGING, DEPOSITING
self.sensory_system = AntSensorySystem()
self.legs = [Leg(self, i) for i in range(6)] # 6 Beine
self.generate_body()
def generate_body(self):
# Kopf, Thorax und Abdomen mit den angegebenen Dimensionen
self.head = Entity(parent=self, model='sphere', color=self.color,
scale=Vec3(0.4, 0.4, 0.4), position=Vec3(0, 0, 0.3))
self.thorax = Entity(parent=self, model='sphere', color=self.color,
scale=Vec3(0.5, 0.5, 0.5), position=Vec3(0, 0, 0))
self.abdomen = Entity(parent=self, model='sphere', color=self.color,
scale=Vec3(0.6, 0.6, 0.6), position=Vec3(0, 0, -0.4))
def random_direction(self):
angle = random.uniform(0, 360)
return Vec3(math.cos(math.radians(angle)), math.sin(math.radians(angle)), 0)
def perceive(self):
return self.sensory_system.perceive(self)
def decide(self):
if self.state == 'FORAGING':
self.decide_foraging()
elif self.state == 'DEPOSITING':
self.decide_depositing()
def decide_foraging(self):
pheromones = self.perceive()
food_pheromone = pheromones[FOOD_PHEROMONE]
home_pheromone = pheromones[HOME_PHEROMONE]
if self.has_food:
# Auf dem Weg zurück zum Nest
if home_pheromone > PHEROMONE_THRESHOLD and random.random() < 0.9:
self.follow_pheromone(HOME_PHEROMONE)
else:
self.direction = self.random_direction()
else:
if food_pheromone > PHEROMONE_THRESHOLD and random.random() < 0.9:
self.follow_pheromone(FOOD_PHEROMONE)
else:
self.direction = self.random_direction()
def decide_depositing(self):
if self.has_food:
# Deposit food logic
x, y, z = int(self.position.x), int(self.position.y), int(self.position.z)
voxel = self.environment.grid[x][y][z]
if voxel.type == NEST:
self.has_food = False
self.colony.food_storage += 1
self.energy += 50 # Energie zurückgewinnen
self.state = 'FORAGING'
def follow_pheromone(self, pheromone_type):
x, y, z = int(self.position.x), int(self.position.y), int(self.position.z)
max_pheromone = -1
best_direction = None
for dx in [-1, 0, 1]:
nx = x + dx
if 0 <= nx < ENV_SIZE[0]:
for dy in [-1, 0, 1]:
ny = y + dy
if 0 <= ny < ENV_SIZE[1]:
for dz in [-1, 0, 1]:
nz = z + dz
if 0 <= nz < ENV_SIZE[2]:
neighbor = self.environment.grid[nx][ny][nz]
pheromone_level = neighbor.pheromones[pheromone_type]
if pheromone_level > max_pheromone:
max_pheromone = pheromone_level
best_direction = Vec3(dx, dy, dz)
if best_direction is not None:
self.direction = best_direction + Vec3(random.uniform(-0.1, 0.1), random.uniform(-0.1, 0.1), 0)
else:
self.direction = self.random_direction()
def move(self):
if np.linalg.norm(self.direction) == 0:
self.direction = self.random_direction()
self.direction = self.direction.normalized() # Normalize direction
new_position = self.position + Vec3(*self.direction) * ANT_SPEED
x, y, z = int(new_position.x), int(new_position.y), int(new_position.z)
if 0 <= x < ENV_SIZE[0] and 0 <= y < ENV_SIZE[1] and 0 <= z < ENV_SIZE[2]:
voxel = self.environment.grid[x][y][z]
if voxel.type in [AIR, FOOD, NEST]:
self.position = new_position
self.energy -= 0.1 # Energieverbrauch
else:
self.direction = self.random_direction()
def act(self):
x, y, z = int(self.position.x), int(self.position.y), int(self.position.z)
voxel = self.environment.grid[x][y][z]
if self.state == 'FORAGING':
if not self.has_food:
if voxel.type == FOOD:
self.has_food = True
voxel.type = AIR
voxel.model = None
voxel.color = color.clear
self.state = 'DEPOSITING'
elif self.state == 'DEPOSITING':
self.decide_depositing()
def deposit_pheromones(self):
# Ablegen von Pheromonen
x, y, z = int(self.position.x), int(self.position.y), int(self.position.z)
voxel = self.environment.grid[x][y][z]
if self.has_food:
voxel.pheromones[HOME_PHEROMONE] += PHEROMONE_DEPOSIT_AMOUNT
else:
voxel.pheromones[FOOD_PHEROMONE] += PHEROMONE_DEPOSIT_AMOUNT
def update(self):
self.age += 1
if self.age > self.max_age or self.energy <= 0:
self.die()
return
self.decide()
self.move()
self.act()
self.deposit_pheromones()
# Aktualisiere die Beine
for leg in self.legs:
leg.update_leg()
def die(self):
self.colony.remove_ant(self)
destroy(self)
class QueenAnt(Entity):
def __init__(self, position, colony):
super().__init__(
parent=scene,
position=position,
model='sphere',
color=ANT_COLORS['QUEEN'],
scale=0.5
)
self.colony = colony
self.laying_timer = 500 # Zeit bis zum nächsten Ei
def update(self):
self.laying_timer -= 1
if self.laying_timer <= 0:
self.lay_egg()
self.laying_timer = 500
def lay_egg(self):
egg = Egg(position=self.position, colony=self.colony)
self.colony.add_egg(egg)
class Egg(Entity):
def __init__(self, position, colony):
super().__init__(
parent=scene,
position=position,
model='sphere',
color=color.white,
scale=0.1
)
self.colony = colony
self.hatching_time = 1000 # Zeit bis zum Schlüpfen
def update(self):
self.hatching_time -= 1
if self.hatching_time <= 0:
self.hatch()
def hatch(self):
# Aus dem Ei schlüpft eine Arbeiterameise
ant = Ant(position=self.position, colony=self.colony, environment=self.colony.environment)
self.colony.add_ant(ant)
destroy(self)
self.colony.eggs.remove(self)
class Colony:
def __init__(self, environment):
self.environment = environment
self.ants = []
self.eggs = []
self.food_storage = 0
nest_x, nest_y = ENV_SIZE[0] // 2, ENV_SIZE[1] // 2
nest_z = environment.get_surface_z(nest_x, nest_y) - 1
self.nest_position = Vec3(nest_x, nest_y, nest_z)
self.queen = QueenAnt(position=self.nest_position, colony=self)
def add_ant(self, ant):
self.ants.append(ant)
def remove_ant(self, ant):
if ant in self.ants:
self.ants.remove(ant)
def add_egg(self, egg):
self.eggs.append(egg)
def update(self):
# Königin aktualisieren
self.queen.update()
# Eier aktualisieren
for egg in self.eggs[:]:
egg.update()
# Neue Eier legen, wenn genügend Nahrung vorhanden ist
if self.food_storage >= 20:
self.food_storage -= 20
self.queen.lay_egg()
class CameraController(Entity):
def __init__(self):
super().__init__()
self.camera_pivot = Entity()
camera.parent = self.camera_pivot
camera.position = (0, -30, 20)
camera.rotation_x = 30
self.rotation_speed = 100
self.zoom_speed = 20
self.current_zoom = 30
self.focus_position = Vec3(ENV_SIZE[0] // 2, ENV_SIZE[1] // 2, ENV_SIZE[2] // 2)
self.camera_pivot.position = self.focus_position
self.following = False
self.follow_target = None
self.last_click_time = 0
self.double_click_threshold = 0.3
def update(self):
dt = time.dt
# Doppelklick-Erkennung
if mouse.left and mouse.click:
current_time_click = time.time()
if current_time_click - self.last_click_time < self.double_click_threshold:
hit_entity = mouse.hovered_entity
if isinstance(hit_entity, Voxel) or isinstance(hit_entity, Ant):
self.focus_position = hit_entity.position
self.follow_target = hit_entity if isinstance(hit_entity, Ant) else None
self.following = isinstance(hit_entity, Ant)
self.last_click_time = current_time_click
# Kamera drehen
if held_keys['right mouse']:
delta = Vec2(mouse.velocity[0], mouse.velocity[1])
self.camera_pivot.rotation_y += delta.x * dt * self.rotation_speed
self.camera_pivot.rotation_x -= delta.y * dt * self.rotation_speed
self.camera_pivot.rotation_x = max(-90, min(90, self.camera_pivot.rotation_x))
# Zoom
self.current_zoom -= mouse.scroll_y * self.zoom_speed * dt
self.current_zoom = max(5, min(100, self.current_zoom))
camera.position = Vec3(0, -self.current_zoom, self.current_zoom * 0.5)
# Kamerafokus aktualisieren
if self.following and self.follow_target:
self.focus_position = self.follow_target.position
self.camera_pivot.position = lerp(self.camera_pivot.position, self.focus_position, 0.1)
# -----------------------
# Hilfsfunktionen
# -----------------------
def draw_pheromones(environment_grid):
for x in range(ENV_SIZE[0]):
for y in range(ENV_SIZE[1]):
for z in range(ENV_SIZE[2]):
voxel = environment_grid[x][y][z]
for pheromone_type in [FOOD_PHEROMONE, HOME_PHEROMONE, DANGER_PHEROMONE]:
strength = voxel.pheromones[pheromone_type]
if strength > PHEROMONE_THRESHOLD:
Entity(
parent=scene,
position=(x, y, z + 0.5),
model='sphere',
color=PHEROMONE_COLORS[pheromone_type].tint(strength / PHEROMONE_MAX),
scale=strength * 0.1,
double_sided=True,
billboard=True,
render_queue=3000
)
# -----------------------
# Hauptfunktion
# -----------------------
def main():
app = Ursina()
environment = Environment()
colony = Colony(environment)
# Anfangs-Ameisen erstellen
for _ in range(INITIAL_WORKERS):
offset = Vec3(random.uniform(-5, 5), random.uniform(-5, 5), 0)
position = colony.nest_position + offset
position.x = max(0, min(position.x, ENV_SIZE[0] - 1))
position.y = max(0, min(position.y, ENV_SIZE[1] - 1))
position.z = max(0, min(position.z, ENV_SIZE[2] - 1))
ant = Ant(position=position, colony=colony, environment=environment)
colony.add_ant(ant)
# Soldatenameisen erstellen
for _ in range(INITIAL_SOLDIERS):
offset = Vec3(random.uniform(-5, 5), random.uniform(-5, 5), 0)
position = colony.nest_position + offset
position.x = max(0, min(position.x, ENV_SIZE[0] - 1))
position.y = max(0, min(position.y, ENV_SIZE[1] - 1))
position.z = max(0, min(position.z, ENV_SIZE[2] - 1))
ant = Ant(position=position, colony=colony, environment=environment, caste='SOLDIER')
colony.add_ant(ant)
# Kamera-Controller hinzufügen
camera_controller = CameraController()
# UI-Text für gesammeltes Essen
food_text = Text(text='', position=window.top_left, origin=(0, 0), background=True)
# Update-Funktion definieren
def update():
global current_time_step
current_time_step += 1
# Umwelt aktualisieren
environment.update_pheromones()
environment.spawn_food()
# Ameisen aktualisieren
for ant in colony.ants:
ant.update()
# Kolonie aktualisieren
colony.update()
# Pheromone anzeigen
if held_keys['p']:
draw_pheromones(environment.grid)
# UI aktualisieren
food_text.text = f"Essen gesammelt: {colony.food_storage}"
app.run()
if __name__ == "__main__":
main()
Skript 3:
import * as ursina from 'https://cdn.jsdelivr.net/npm/ursina-bundle@latest/ursina.min.js';
import * as numpy from 'https://cdn.jsdelivr.net/npm/numpy@1.21.2/dist/numpy.min.js';
import OpenSimplex from 'https://cdn.jsdelivr.net/npm/opensimplex@1.0.1/opensimplex.min.js';
// -----------------------
// Umgebungseinstellungen
// -----------------------
const ENV_SIZE = [50, 50, 20]; // Breite, Höhe, Tiefe
const VOXEL_SIZE = 1;
const INITIAL_WORKERS = 50;
const INITIAL_SOLDIERS = 10;
const FOOD_SPAWN_RATE = 0.005; // Rate, mit der Nahrung erscheint
const PHEROMONE_EVAPORATION_RATE = 0.01; // Rate, mit der Pheromone verdunsten
const PHEROMONE_DIFFUSION_RATE = 0.1; // Rate, mit der Pheromone diffundieren
const PHEROMONE_DEPOSIT_AMOUNT = 10.0; // Menge an Pheromon, die eine Ameise ablegt
const PHEROMONE_MAX = 100.0; // Maximaler Pheromonwert
const PHEROMONE_THRESHOLD = 1.0; // Mindeststärke für Einfluss
// Voxeltypen
const AIR = 0;
const SOIL = 1;
const FOOD = 2;
const NEST = 3;
const STONE = 4;
const PLANT = 5;
const DEBRIS = 6; // Abgetragenes Material
// Pheromonarten
const FOOD_PHEROMONE = 0;
const HOME_PHEROMONE = 1;
const DANGER_PHEROMONE = 2;
// Simulationseinstellungen
const DAY_LENGTH = 1000;
let currentTimeStep = 0;
// Darstellungseinstellungen
const ANT_COLORS = {
WORKER: ursina.color.red,
SOLDIER: ursina.color.blue,
QUEEN: ursina.color.yellow
};
const PHEROMONE_COLORS = {
[FOOD_PHEROMONE]: ursina.color.green,
[HOME_PHEROMONE]: ursina.color.cyan,
[DANGER_PHEROMONE]: ursina.color.red
};
const ANT_SPEED = 0.1;
// Ameisenkaste
const CASTES = ['WORKER', 'SOLDIER', 'QUEEN'];
const ANT_LIFESPAN = 5000;
// -----------------------
// Klassen
// -----------------------
class Voxel extends ursina.Entity {
constructor(position = [0, 0, 0], voxelType = AIR) {
super({
parent: ursina.scene,
position: position,
model: voxelType !== AIR ? 'cube' : null,
color: this.getColor(voxelType),
scale: VOXEL_SIZE
});
this.type = voxelType;
this.pheromones = numpy.zeros(3); // [FOOD_PHEROMONE, HOME_PHEROMONE, DANGER_PHEROMONE]
}
getColor(voxelType) {
const colors = {
[SOIL]: ursina.color.rgb(139, 69, 19), // Braun
[FOOD]: ursina.color.yellow,
[NEST]: ursina.color.rgb(255, 165, 0), // Orange für Nest
[STONE]: ursina.color.gray,
[PLANT]: ursina.color.green,
[DEBRIS]: ursina.color.rgb(210, 180, 140), // Hellbraun für abgetragenes Material
[AIR]: ursina.color.clear
};
return colors[voxelType] || ursina.color.clear;
}
evaporatePheromones() {
this.pheromones = this.pheromones.multiply(1 - PHEROMONE_EVAPORATION_RATE);
this.pheromones = numpy.clip(this.pheromones, 0, PHEROMONE_MAX);
}
diffusePheromones(neighbors) {
for (let pheromoneType = 0; pheromoneType < this.pheromones.length; pheromoneType++) {
const totalPheromone = this.pheromones[pheromoneType] * (1 - PHEROMONE_DIFFUSION_RATE);
if (neighbors.length > 0) {
const diffusionAmount = this.pheromones[pheromoneType] * PHEROMONE_DIFFUSION_RATE / neighbors.length;
neighbors.forEach(neighbor => neighbor.pheromones[pheromoneType] += diffusionAmount);
}
this.pheromones[pheromoneType] = totalPheromone;
}
}
}
class Environment {
constructor() {
this.simplex = new OpenSimplex({ seed: Math.floor(Math.random() * 10000) });
this.grid = numpy.zeros(ENV_SIZE, { dtype: Object });
this.initializeEnvironment();
}
initializeEnvironment() {
for (let x = 0; x < ENV_SIZE[0]; x++) {
for (let y = 0; y < ENV_SIZE[1]; y++) {
// Terrainhöhe basierend auf Simplex-Noise
const noiseValue = this.simplex.noise2(x / 10, y / 10);
const height = Math.floor((noiseValue + 1) * (ENV_SIZE[2] / 2));
for (let z = 0; z < ENV_SIZE[2]; z++) {
let voxelType = z < height ? SOIL : AIR;
const voxel = new Voxel([x, y, z], voxelType);
this.grid.set([x, y, z], voxel);
}
}
}
// Hindernisse und Pflanzen hinzufügen
for (let i = 0; i < 100; i++) {
const x = Math.floor(Math.random() * ENV_SIZE[0]);
const y = Math.floor(Math.random() * ENV_SIZE[1]);
const z = this.getSurfaceZ(x, y);
if (z < ENV_SIZE[2]) {
const voxelType = Math.random() < 0.5 ? STONE : PLANT;
this.grid.set([x, y, z], new Voxel([x, y, z], voxelType));
}
}
// Nest positionieren
const nestX = Math.floor(ENV_SIZE[0] / 2);
const nestY = Math.floor(ENV_SIZE[1] / 2);
const nestZ = this.getSurfaceZ(nestX, nestY) - 1;
this.grid.set([nestX, nestY, nestZ], new Voxel([nestX, nestY, nestZ], NEST));
}
getSurfaceZ(x, y) {
for (let z = ENV_SIZE[2] - 1; z >= 0; z--) {
if (this.grid.get([x, y, z]).type !== AIR) {
return z + 1 < ENV_SIZE[2] ? z + 1 : z;
}
}
return 0;
}
updatePheromones() {
// Erste Diffusionsschritt
for (let x = 0; x < ENV_SIZE[0]; x++) {
for (let y = 0; y < ENV_SIZE[1]; y++) {
for (let z = 0; z < ENV_SIZE[2]; z++) {
const voxel = this.grid.get([x, y, z]);
if (voxel.type === AIR) {
const neighbors = this.getNeighbors(x, y, z);
if (neighbors.length > 0) {
voxel.diffusePheromones(neighbors);
}
}
}
}
}
// Zweite Verdunstungsschritt
for (let x = 0; x < ENV_SIZE[0]; x++) {
for (let y = 0; y < ENV_SIZE[1]; y++) {
for (let z = 0; z < ENV_SIZE[2]; z++) {
this.grid.get([x, y, z]).evaporatePheromones();
}
}
}
}
getNeighbors(x, y, z) {
const neighbors = [];
for (let dx = -1; dx <= 1; dx++) {
const nx = x + dx;
if (nx >= 0 && nx < ENV_SIZE[0]) {
for (let dy = -1; dy <= 1; dy++) {
const ny = y + dy;
if (ny >= 0 && ny < ENV_SIZE[1]) {
for (let dz = -1; dz <= 1; dz++) {
const nz = z + dz;
if (nz >= 0 && nz < ENV_SIZE[2]) {
if (dx === 0 && dy === 0 && dz === 0) continue;
const neighbor = this.grid.get([nx, ny, nz]);
if (neighbor.type === AIR) {
neighbors.push(neighbor);
}
}
}
}
}
}
}
return neighbors;
}
spawnFood() {
if (Math.random() < FOOD_SPAWN_RATE) {
const x = Math.floor(Math.random() * ENV_SIZE[0]);
const y = Math.floor(Math.random() * ENV_SIZE[1]);
const z = this.getSurfaceZ(x, y);
if (z < ENV_SIZE[2] && this.grid.get([x, y, z]).type === AIR) {
this.grid.set([x, y, z], new Voxel([x, y, z], FOOD));
}
}
}
}
class AntSensorySystem {
constructor() {
this.range = 1; // Wahrnehmungsreichweite
}
perceive(ant) {
const [x, y, z] = [Math.floor(ant.position.x), Math.floor(ant.position.y), Math.floor(ant.position.z)];
const pheromoneLevels = numpy.zeros(3);
for (let dx = -this.range; dx <= this.range; dx++) {
const nx = x + dx;
if (nx >= 0 && nx < ENV_SIZE[0]) {
for (let dy = -this.range; dy <= this.range; dy++) {
const ny = y + dy;
if (ny >= 0 && ny < ENV_SIZE[1]) {
for (let dz = -this.range; dz <= this.range; dz++) {
const nz = z + dz;
if (nz >= 0 && nz < ENV_SIZE[2]) {
const neighbor = ant.environment.grid.get([nx, ny, nz]);
pheromoneLevels = pheromoneLevels.add(neighbor.pheromones);
}
}
}
}
}
}
return pheromoneLevels;
}
detectFood(ant) {
const [x, y, z] = [Math.floor(ant.position.x), Math.floor(ant.position.y), Math.floor(ant.position.z)];
for (let dx = -this.range; dx <= this.range; dx++) {
const nx = x + dx;
if (nx >= 0 && nx < ENV_SIZE[0]) {
for (let dy = -this.range; dy <= this.range; dy++) {
const ny = y + dy;
if (ny >= 0 && ny < ENV_SIZE[1]) {
for (let dz = -this.range; dz <= this.range; dz++) {
const nz = z + dz;
if (nz >= 0 && nz < ENV_SIZE[2]) {
const voxel = ant.environment.grid.get([nx, ny, nz]);
if (voxel.type === FOOD) {
return [true, new ursina.Vec3(dx, dy, dz)];
}
}
}
}
}
}
}
return [false, null];
}
}
class Leg extends ursina.Entity {
constructor(ant, index) {
super({
parent: ant,
model: new ursina.Mesh({ mode: 'line' }),
color: ursina.color.black
});
this.ant = ant;
this.index = index;
this.maxLength = 0.5; // Maximale Beinlänge
this.restLength = 0.3; // Ruhelänge des Beins
this.angleOffset = ((this.index % 3 - 1) * 30) + ((this.index // 3) * 180); // Versetzte Winkel für Beine
this.startPoint = new ursina.Vec3(0, 0, 0);
this.midPoint = new ursina.Vec3(0, 0, 0);
this.endPoint = new ursina.Vec3(0, 0, 0);
this.updateLeg();
}
calculateEndPos() {
// Berechnung der Endposition des Beins entsprechend der Ameisenbewegung
const angle = this.ant.rotation_y + this.angleOffset;
const rad = angle * Math.PI / 180;
const offsetX = Math.cos(rad) * this.restLength;
const offsetY = Math.sin(rad) * this.restLength;
let endPoint = this.ant.position.add(new ursina.Vec3(offsetX, offsetY, -0.1));
// Raycast nach unten, um die Oberfläche zu finden
const surfaceHit = ursina.raycast(endPoint.add(new ursina.Vec3(0, 0, 1)), new ursina.Vec3(0, 0, -1), { distance: 2, ignore: [this.ant] });
if (surfaceHit.hit) {
endPoint.z = surfaceHit.world_point.z;
} else {
endPoint.z = this.ant.position.z - 0.3; // Fallback, falls keine Oberfläche gefunden wird
}
return endPoint;
}
updateLeg() {
// Update die Position des Beins
this.startPoint = this.ant.position;
this.endPoint = this.calculateEndPos();
// Berechnung des Mittelpunkts für die umgedrehte "V"-Form
this.midPoint = this.startPoint.add(this.endPoint).divideScalar(2).add(new ursina.Vec3(0, 0, 0.2));
// Erstelle das Mesh für das Bein
this.model = new ursina.Mesh({
vertices: [this.startPoint, this.midPoint, this.endPoint],
mode: 'line',
thickness: 2
});
this.vertices = [this.startPoint, this.midPoint, this.endPoint];
}
}
class Ant extends ursina.Entity {
constructor(position, colony, environment, caste = 'WORKER') {
super({
parent: ursina.scene,
position: position,
model: null,
color: ANT_COLORS[caste],
scale: 0.2
});
this.caste = caste;
this.colony = colony;
this.environment = environment;
this.hasFood = false;
this.hasSoil = false; // Ob die Ameise Erde trägt
this.energy = 100;
this.age = 0;
this.maxAge = ANT_LIFESPAN;
this.direction = this.randomDirection();
this.memory = [];
this.state = 'FORAGING'; // Mögliche Zustände: FORAGING, DIGGING, DEPOSITING
this.sensorySystem = new AntSensorySystem();
this.legs = Array.from({ length: 6 }, (_, i) => new Leg(this, i)); // 6 Beine
this.generateBody();
}
generateBody() {
// Kopf, Thorax und Abdomen mit den angegebenen Dimensionen
this.head = new ursina.Entity({
parent: this,
model: 'sphere',
color: this.color,
scale: new ursina.Vec3(0.4, 0.4, 0.4),
position: new ursina.Vec3(0, 0, 0.3)
});
this.thorax = new ursina.Entity({
parent: this,
model: 'sphere',
color: this.color,
scale: new ursina.Vec3(0.5, 0.5, 0.5),
position: new ursina.Vec3(0, 0, 0)
});
this.abdomen = new ursina.Entity({
parent: this,
model: 'sphere',
color: this.color,
scale: new ursina.Vec3(0.6, 0.6, 0.6),
position: new ursina.Vec3(0, 0, -0.4)
});
}
randomDirection() {
const angle = Math.random() * 2 * Math.PI;
return new ursina.Vec3(Math.cos(angle), Math.sin(angle), 0);
}
perceive() {
return this.sensorySystem.perceive(this);
}
decide() {
if (this.state === 'FORAGING') {
this.decideForaging();
} else if (this.state === 'DIGGING') {
this.decideDigging();
} else if (this.state === 'DEPOSITING') {
this.decideDepositing();
}
}
decideForaging() {
const pheromones = this.perceive();
const foodPheromone = pheromones[FOOD_PHEROMONE];
const homePheromone = pheromones[HOME_PHEROMONE];
if (this.hasFood) {
// Auf dem Weg zurück zum Nest
if (homePheromone > PHEROMONE_THRESHOLD && Math.random() < 0.9) {
this.followPheromone(HOME_PHEROMONE);
} else {
this.direction = this.randomDirection();
}
} else {
if (foodPheromone > PHEROMONE_THRESHOLD && Math.random() < 0.9) {
this.followPheromone(FOOD_PHEROMONE);
} else {
this.direction = this.randomDirection();
}
}
}
decideDigging() {
if (Math.random() < 0.1) { // Wahrscheinlichkeit zu graben
this.state = 'DIGGING';
this.hasSoil = true;
}
}
decideDepositing() {
if (this.hasSoil) {
this.depositSoil();
this.state = 'FORAGING';
}
}
followPheromone(pheromoneType) {
const [x, y, z] = [Math.floor(this.position.x), Math.floor(this.position.y), Math.floor(this.position.z)];
let maxPheromone = -1;
let bestDirection = null;
for (let dx = -1; dx <= 1; dx++) {
const nx = x + dx;
if (nx >= 0 && nx < ENV_SIZE[0]) {
for (let dy = -1; dy <= 1; dy++) {
const ny = y + dy;
if (ny >= 0 && ny < ENV_SIZE[1]) {
for (let dz = -1; dz <= 1; dz++) {
const nz = z + dz;
if (nz >= 0 && nz < ENV_SIZE[2]) {
const neighbor = this.environment.grid.get([nx, ny, nz]);
const pheromoneLevel = neighbor.pheromones[pheromoneType];
if (pheromoneLevel > maxPheromone) {
maxPheromone = pheromoneLevel;
bestDirection = new ursina.Vec3(dx, dy, dz);
}
}
}
}
}
}
}
if (bestDirection) {
this.direction = bestDirection.add(new ursina.Vec3(...Array(3).fill(Math.random() * 0.2 - 0.1)));
} else {
this.direction = this.randomDirection();
}
}
move() {
if (this.direction.length() === 0) {
this.direction = this.randomDirection();
}
const newPosition = this.position.add(this.direction.multiplyScalar(ANT_SPEED));
const [x, y, z] = [Math.floor(newPosition.x), Math.floor(newPosition.y), Math.floor(newPosition.z)];
if (x >= 0 && x < ENV_SIZE[0] && y >= 0 && y < ENV_SIZE[1] && z >= 0 && z < ENV_SIZE[2]) {
const voxel = this.environment.grid.get([x, y, z]);
if ([AIR, FOOD, NEST].includes(voxel.type)) {
this.position = newPosition;
this.energy -= 0.1; // Energieverbrauch
} else if (voxel.type === DEBRIS && this.hasSoil) {
this.depositSoil(voxel);
} else {
this.direction = this.randomDirection();
}
}
}
depositSoil() {
const [x, y, z] = [Math.floor(this.position.x), Math.floor(this.position.y), Math.floor(this.position.z)];
const voxel = this.environment.grid.get([x, y, z]);
voxel.type = SOIL;
voxel.model = 'cube';
voxel.color = ursina.color.rgb(139, 69, 19);
this.hasSoil = false;
}
act() {
const [x, y, z] = [Math.floor(this.position.x), Math.floor(this.position.y), Math.floor(this.position.z)];
const voxel = this.environment.grid.get([x, y, z]);
if (this.state === 'FORAGING') {
if (!this.hasFood) {
if (voxel.type === FOOD) {
this.hasFood = true;
voxel.type = AIR;
voxel.model = null;
voxel.color = ursina.color.clear;
this.state = 'DEPOSITING';
}
}
} else if (this.state === 'DEPOSITING') {
if (this.hasFood && voxel.type === NEST) {
this.hasFood = false;
this.colony.foodStorage += 1;
this.energy += 50; // Energie zurückgewinnen
this.state = 'FORAGING';
}
}
}
depositPheromones() {
// Ablegen von Pheromonen
const [x, y, z] = [Math.floor(this.position.x), Math.floor(this.position.y), Math.floor(this.position.z)];
const voxel = this.environment.grid.get([x, y, z]);
if (this.hasFood) {
voxel.pheromones[HOME_PHEROMONE] += PHEROMONE_DEPOSIT_AMOUNT;
} else {
voxel.pheromones[FOOD_PHEROMONE] += PHEROMONE_DEPOSIT_AMOUNT;
}
}
update() {
this.age += 1;
if (this.age > this.maxAge || this.energy <= 0) {
this.die();
return;
}
this.decide();
this.move();
this.act();
this.depositPheromones();
// Aktualisiere die Beine
this.legs.forEach(leg => leg.updateLeg());
}
die() {
this.colony.removeAnt(this);
ursina.destroy(this);
}
}
class QueenAnt extends ursina.Entity {
constructor(position, colony) {
super({
parent: ursina.scene,
position: position,
model: 'sphere',
color: ANT_COLORS['QUEEN'],
scale: 0.5
});
this.colony = colony;
this.layingTimer = 500; // Zeit bis zum nächsten Ei
}
update() {
this.layingTimer -= 1;
if (this.layingTimer <= 0) {
this.layEgg();
this.layingTimer = 500;
}
}
layEgg() {
const egg = new Egg(this.position, this.colony);
this.colony.addEgg(egg);
}
}
class Egg extends ursina.Entity {
constructor(position, colony) {
super({
parent: ursina.scene,
position: position,
model: 'sphere',
color: ursina.color.white,
scale: 0.1
});
this.colony = colony;
this.hatchingTime = 1000; // Zeit bis zum Schlüpfen
}
update() {
this.hatchingTime -= 1;
if (this.hatchingTime <= 0) {
this.hatch();
}
}
hatch() {
// Aus dem Ei schlüpft eine Arbeiterameise
const ant = new Ant(this.position, this.colony, this.colony.environment);
this.colony.addAnt(ant);
ursina.destroy(this);
this.colony.eggs.splice(this.colony.eggs.indexOf(this), 1);
}
}
class Colony {
constructor(environment) {
this.environment = environment;
this.ants = [];
this.eggs = [];
this.foodStorage = 0;
const nestX = Math.floor(ENV_SIZE[0] / 2);
const nestY = Math.floor(ENV_SIZE[1] / 2);
const nestZ = environment.getSurfaceZ(nestX, nestY) - 1;
this.nestPosition = new ursina.Vec3(nestX, nestY, nestZ);
this.queen = new QueenAnt(this.nestPosition, this);
}
addAnt(ant) {
this.ants.push(ant);
}
removeAnt(ant) {
const index = this.ants.indexOf(ant);
if (index !== -1) {
this.ants.splice(index, 1);
}
}
addEgg(egg) {
this.eggs.push(egg);
}
update() {
// Königin aktualisieren
this.queen.update();
// Eier aktualisieren
this.eggs.forEach(egg => egg.update());
// Neue Eier legen, wenn genügend Nahrung vorhanden ist
if (this.foodStorage >= 20) {
this.foodStorage -= 20;
this.queen.layEgg();
}
}
}
class CameraController extends ursina.Entity {
constructor() {
super();
this.cameraPivot = new ursina.Entity();
ursina.camera.parent = this.cameraPivot;
ursina.camera.position = new ursina.Vec3(0, -30, 20);
ursina.camera.rotation_x = 30;
this.rotationSpeed = 100;
this.zoomSpeed = 20;
this.currentZoom = 30;
this.focusPosition = new ursina.Vec3(ENV_SIZE[0] / 2, ENV_SIZE[1] / 2, ENV_SIZE[2] / 2);
this.cameraPivot.position = this.focusPosition;
this.following = false;
this.followTarget = null;
this.lastClickTime = 0;
this.doubleClickThreshold = 0.3;
}
update() {
const dt = ursina.time.dt;
// Doppelklick-Erkennung
if (ursina.mouse.left && ursina.mouse.click) {
const currentTimeClick = ursina.time.time();
if (currentTimeClick - this.lastClickTime < this.doubleClickThreshold) {
// Doppelklick erkannt
const hitEntity = ursina.mouse.hoveredEntity;
if (hitEntity instanceof Voxel || hitEntity instanceof Ant) {
this.focusPosition = hitEntity.position;
this.followTarget = hitEntity instanceof Ant ? hitEntity : null;
this.following = hitEntity instanceof Ant;
ursina.invoke(() => this.updateCameraFocus(), { delay: 0.01 });
}
}
this.lastClickTime = currentTimeClick;
}
// Kamera drehen
if (ursina.heldKeys['right mouse']) {
const delta = new ursina.Vec2(ursina.mouse.velocity[0], ursina.mouse.velocity[1]);
this.cameraPivot.rotation_y += delta.x * dt * this.rotationSpeed;
this.cameraPivot.rotation_x -= delta.y * dt * this.rotationSpeed;
this.cameraPivot.rotation_x = Math.max(-90, Math.min(90, this.cameraPivot.rotation_x));
}
// Zoom
this.currentZoom -= ursina.mouse.scroll_y * this.zoomSpeed * dt;
this.currentZoom = Math.max(5, Math.min(100, this.currentZoom));
ursina.camera.position = new ursina.Vec3(0, -this.currentZoom, this.currentZoom * 0.5);
// Kamerafokus aktualisieren
if (this.following && this.followTarget) {
this.focusPosition = this.followTarget.position;
}
this.cameraPivot.position = this.cameraPivot.position.lerp(this.focusPosition, 0.1);
}
updateCameraFocus() {
this.cameraPivot.position = this.cameraPivot.position.lerp(this.focusPosition, 0.1);
}
}
// -----------------------
// Hilfsfunktionen
// -----------------------
function drawPheromones(environmentGrid) {
for (let x = 0; x < ENV_SIZE[0]; x++) {
for (let y = 0; y < ENV_SIZE[1]; y++) {
for (let z = 0; z < ENV_SIZE[2]; z++) {
const voxel = environmentGrid.get([x, y, z]);
for (const pheromoneType of [FOOD_PHEROMONE, HOME_PHEROMONE, DANGER_PHEROMONE]) {
const strength = voxel.pheromones[pheromoneType];
if (strength > PHEROMONE_THRESHOLD) {
new ursina.Entity({
parent: ursina.scene,
position: new ursina.Vec3(x, y, z + 0.5),
model: 'sphere',
color: PHEROMONE_COLORS[pheromoneType].tint(strength / PHEROMONE_MAX),
scale: strength * 0.1,
doubleSided: true,
billboard: true,
renderQueue: 3000
});
}
}
}
}
}
}
// -----------------------
// Hauptfunktion
// -----------------------
function main() {
const app = new ursina.Ursina();
const environment = new Environment();
const colony = new Colony(environment);
// Anfangs-Ameisen erstellen
for (let i = 0; i < INITIAL_WORKERS; i++) {
const offset = new ursina.Vec3(Math.random() * 10 - 5, Math.random() * 10 - 5, 0);
let position = colony.nestPosition.add(offset);
// Sicherstellen, dass die Position innerhalb der Umgebung liegt
position.x = Math.max(0, Math.min(position.x, ENV_SIZE[0] - 1));
position.y = Math.max(0, Math.min(position.y, ENV_SIZE[1] - 1));
position.z = Math.max(0, Math.min(position.z, ENV_SIZE[2] - 1));
const ant = new Ant(position, colony, environment);
colony.addAnt(ant);
}
// Soldatenameisen erstellen
for (let i = 0; i < INITIAL_SOLDIERS; i++) {
const offset = new ursina.Vec3(Math.random() * 10 - 5, Math.random() * 10 - 5, 0);
let position = colony.nestPosition.add(offset);
position.x = Math.max(0, Math.min(position.x, ENV_SIZE[0] - 1));
position.y = Math.max(0, Math.min(position.y, ENV_SIZE[1] - 1));
position.z = Math.max(0, Math.min(position.z, ENV_SIZE[2] - 1));
const ant = new Ant(position, colony, environment, 'SOLDIER');
colony.addAnt(ant);
}
// Kamera-Controller hinzufügen
const cameraController = new CameraController();
// UI-Text für gesammeltes Essen
const foodText = new ursina.Text({
text: '',
position: ursina.window.topLeft,
origin: [0, 0],
background: true
});
// Update-Funktion definieren
ursina.update = () => {
currentTimeStep += 1;
// Umwelt aktualisieren
environment.updatePheromones();
environment.spawnFood();
// Ameisen aktualisieren
colony.ants.forEach(ant => ant.update());
// Kolonie aktualisieren
colony.update();
// Pheromone anzeigen
if (ursina.heldKeys['p']) {
drawPheromones(environment.grid);
}
// UI aktualisieren
foodText.text = `Essen gesammelt: ${colony.foodStorage}`;
};
app.run();
}
main();ASSISTANT
Nachdem ich die drei von Ihnen bereitgestellten Skripte sorgfältig analysiert habe, möchte ich Ihnen eine optimierte und umfassende Version der Ameisenkolonie-Simulation vorstellen. Diese neue Version integriert die besten Elemente aus den vorhandenen Skripten und verbessert die Umsetzung der Beine gemäß Ihren Vorgaben.
## Analyse der Skripte
### Gemeinsamkeiten:
- **Voxel-basiertes Umfeld:** Beide Python-Skripte nutzen eine Voxelstruktur zur Darstellung der Umgebung, was eine gute Grundlage für die Simulation bietet.
- **Pheromon-System:** Die Pheromon-Diffusion und -Verdunstung wird in beiden Skripten implementiert, was für das Verhalten der Ameisen essenziell ist.
- **Kastenstruktur:** Die verschiedenen Kasten (Arbeiter, Soldat, Königin) sind in beiden Skripten vorhanden und ermöglichen unterschiedliche Verhaltensweisen der Ameisen.
- **Simulation von Eiern und Schlüpfen:** Beide Skripte modellieren das Eiablage- und Schlüpfverfahren der Königin.
### Unterschiede:
- **Beinarbeit:** Das erste Skript verwendet Zylinder zur Darstellung der Beine, während das zweite Skript versucht, die Beine durch Linienmodelle realistischer zu gestalten.
- **JavaScript-Version:** Das dritte Skript in JavaScript bietet ähnliche Funktionalitäten, ist jedoch weniger ausgereift und schwer direkt mit den Python-Skripten zu kombinieren.
### Verbesserungsmöglichkeiten:
- **2D-Beinbewegung:** Die Beine sollten in einer zweidimensionalen Ebene authentisch dargestellt und unabhängig voneinander bewegt werden.
- **Effizienz:** Die Darstellung und Bewegung der Beine sollten ressourcenschonend und dennoch ästhetisch ansprechend umgesetzt werden.
- **Realismus:** Die Beine sollten realistische Längen und Bewegungsmuster aufweisen, um eine authentische Nachbildung zu gewährleisten.
## Optimiertes Skript
Hier präsentiere ich ein kombiniertes und verbessertes Python-Skript unter Verwendung der Ursina-Engine. Dieses Skript integriert die besten Aspekte der ersten beiden Skripte und optimiert die Beinbewegung gemäß Ihren Anforderungen.
```python
import random
import numpy as np
from ursina import *
from opensimplex import OpenSimplex
import math
import time
# -----------------------
# Umgebungseinstellungen
# -----------------------
ENV_SIZE = (50, 50, 1) # 2D: Breite, Länge, Höhe (flach)
VOXEL_SIZE = 1
INITIAL_WORKERS = 50
INITIAL_SOLDIERS = 10
FOOD_SPAWN_RATE = 0.005 # Rate, mit der Nahrung erscheint
PHEROMONE_EVAPORATION_RATE = 0.01 # Rate, mit der Pheromone verdunsten
PHEROMONE_DIFFUSION_RATE = 0.1 # Rate, mit der Pheromone diffundieren
PHEROMONE_DEPOSIT_AMOUNT = 10.0 # Menge an Pheromon, die eine Ameise ablegt
PHEROMONE_MAX = 100.0 # Maximaler Pheromonwert
PHEROMONE_THRESHOLD = 1.0 # Mindeststärke für Einfluss
# Voxeltypen
AIR = 0
SOIL = 1
FOOD = 2
NEST = 3
STONE = 4
PLANT = 5
DEBRIS = 6 # Abgetragenes Material
# Pheromonarten
FOOD_PHEROMONE = 0
HOME_PHEROMONE = 1
DANGER_PHEROMONE = 2
# Simulationseinstellungen
DAY_LENGTH = 1000
current_time_step = 0
# Darstellungseinstellungen
ANT_COLORS = {
'WORKER': color.red,
'SOLDIER': color.blue,
'QUEEN': color.yellow
}
PHEROMONE_COLORS = {
FOOD_PHEROMONE: color.green,
HOME_PHEROMONE: color.cyan,
DANGER_PHEROMONE: color.red
}
ANT_SPEED = 0.2
# Ameisenkasten
CASTES = ['WORKER', 'SOLDIER', 'QUEEN']
ANT_LIFESPAN = 5000
# -----------------------
# Klassen
# -----------------------
class Voxel(Entity):
def __init__(self, position=(0, 0, 0), voxel_type=AIR):
super().__init__(
parent=scene,
position=position,
model='quad' if voxel_type != AIR else None,
color=self.get_color(voxel_type),
scale=VOXEL_SIZE
)
self.type = voxel_type
self.pheromones = np.zeros(3) # [FOOD_PHEROMONE, HOME_PHEROMONE, DANGER_PHEROMONE]
def get_color(self, voxel_type):
colors = {
SOIL: color.rgb(139, 69, 19), # Braun
FOOD: color.yellow,
NEST: color.rgb(255, 165, 0), # Orange für Nest
STONE: color.gray,
PLANT: color.green,
DEBRIS: color.rgb(210, 180, 140), # Hellbraun für abgetragenes Material
AIR: color.clear
}
return colors.get(voxel_type, color.clear)
def evaporate_pheromones(self):
self.pheromones *= (1 - PHEROMONE_EVAPORATION_RATE)
self.pheromones = np.clip(self.pheromones, 0, PHEROMONE_MAX)
def diffuse_pheromones(self, neighbors):
for pheromone_type in range(len(self.pheromones)):
if neighbors:
diffusion_amount = self.pheromones[pheromone_type] * PHEROMONE_DIFFUSION_RATE / len(neighbors)
for neighbor in neighbors:
neighbor.pheromones[pheromone_type] += diffusion_amount
self.pheromones[pheromone_type] *= (1 - PHEROMONE_DIFFUSION_RATE)
class Environment:
def __init__(self):
self.simplex = OpenSimplex(seed=random.randint(0, 10000))
self.grid = np.empty((ENV_SIZE[0], ENV_SIZE[1], 1), dtype=object)
self.initialize_environment()
def initialize_environment(self):
for x in range(ENV_SIZE[0]):
for y in range(ENV_SIZE[1]):
# Terrainhöhe basierend auf Simplex-Noise (2D)
noise_value = self.simplex.noise2(x / 10, y / 10)
height = int((noise_value + 1) * (ENV_SIZE[2] // 2))
for z in range(ENV_SIZE[2]):
voxel_type = SOIL if z < height else AIR
self.grid[x][y][z] = Voxel(position=(x, y, z), voxel_type=voxel_type)
# Hindernisse und Pflanzen hinzufügen
for _ in range(100):
x = random.randint(0, ENV_SIZE[0] - 1)
y = random.randint(0, ENV_SIZE[1] - 1)
z = self.get_surface_z(x, y)
if z < ENV_SIZE[2]:
voxel_type = STONE if random.random() < 0.5 else PLANT
self.grid[x][y][z] = Voxel(position=(x, y, z), voxel_type=voxel_type)
# Nest positionieren
nest_x, nest_y = ENV_SIZE[0] // 2, ENV_SIZE[1] // 2
nest_z = self.get_surface_z(nest_x, nest_y) - 1
self.grid[nest_x][nest_y][nest_z] = Voxel(position=(nest_x, nest_y, nest_z), voxel_type=NEST)
def get_surface_z(self, x, y):
for z in range(ENV_SIZE[2] - 1, -1, -1):
if self.grid[x][y][z].type != AIR:
return z + 1 if z + 1 < ENV_SIZE[2] else z
return 0
def update_pheromones(self):
# Diffusionsschritt
for x in range(ENV_SIZE[0]):
for y in range(ENV_SIZE[1]):
for z in range(ENV_SIZE[2]):
voxel = self.grid[x][y][z]
if voxel.type == AIR:
neighbors = self.get_neighbors(x, y, z)
if neighbors:
voxel.diffuse_pheromones(neighbors)
# Verdunstungsschritt
for x in range(ENV_SIZE[0]):
for y in range(ENV_SIZE[1]):
for z in range(ENV_SIZE[2]):
self.grid[x][y][z].evaporate_pheromones()
def get_neighbors(self, x, y, z):
neighbors = []
for dx in [-1, 0, 1]:
nx = x + dx
if 0 <= nx < ENV_SIZE[0]:
for dy in [-1, 0, 1]:
ny = y + dy
if 0 <= ny < ENV_SIZE[1]:
for dz in [-1, 0, 1]:
nz = z + dz
if 0 <= nz < ENV_SIZE[2]:
if dx == 0 and dy == 0 and dz == 0:
continue
neighbor = self.grid[nx][ny][nz]
if neighbor.type == AIR:
neighbors.append(neighbor)
return neighbors
def spawn_food(self):
if random.random() < FOOD_SPAWN_RATE:
x = random.randint(0, ENV_SIZE[0] - 1)
y = random.randint(0, ENV_SIZE[1] - 1)
z = self.get_surface_z(x, y)
if z < ENV_SIZE[2] and self.grid[x][y][z].type == AIR:
self.grid[x][y][z] = Voxel(position=(x, y, z), voxel_type=FOOD)
class AntSensorySystem:
def __init__(self):
self.range = 3 # Erhöhte Wahrnehmungsreichweite für realistischere Sinne
def perceive(self, ant):
pheromone_levels = np.zeros(3)
x, y, z = int(ant.position.x), int(ant.position.y), int(ant.position.z)
for dx in range(-self.range, self.range + 1):
nx = x + dx
if 0 <= nx < ENV_SIZE[0]:
for dy in range(-self.range, self.range + 1):
ny = y + dy
if 0 <= ny < ENV_SIZE[1]:
for dz in range(-self.range, self.range + 1):
nz = z + dz
if 0 <= nz < ENV_SIZE[2]:
neighbor = ant.environment.grid[nx][ny][nz]
pheromone_levels += neighbor.pheromones
return pheromone_levels
def detect_food(self, ant):
x, y, z = int(ant.position.x), int(ant.position.y), int(ant.position.z)
for dx in range(-self.range, self.range + 1):
nx = x + dx
if 0 <= nx < ENV_SIZE[0]:
for dy in range(-self.range, self.range + 1):
ny = y + dy
if 0 <= ny < ENV_SIZE[1]:
for dz in range(-self.range, self.range + 1):
nz = z + dz
if 0 <= nz < ENV_SIZE[2]:
voxel = ant.environment.grid[nx][ny][nz]
if voxel.type == FOOD:
return True, Vec3(dx, dy, dz)
return False, None
class Leg(Entity):
def __init__(self, ant, index):
super().__init__(
parent=ant,
model='quad',
color=color.black,
scale=(0.05, 0.1),
origin=(0, 0.5)
)
self.ant = ant
self.index = index
self.angle = 0
self.max_angle = 30 # Maximale Schwingung eines Beins
self.speed = random.uniform(2, 5) # Unterschiedliche Schwingungsgeschwindigkeiten
self.offset = index * 60 # Versetzung der Beinbewegungen
self.calculate_position()
def calculate_position(self):
# BeindiaQuaternion rotation beschränkt auf 2D (x, y)
radians = math.radians(self.angle + self.offset)
x = math.cos(radians) * 0.3
y = math.sin(radians) * 0.3
self.position = Vec3(x, y, 0)
# Strecken begrenzen
distance = self.position.xy.length()
if distance > 0.3:
self.position = self.position.normalized() * 0.3
# Rotation des Beins entsprechend der Position
self.rotation = (0, 0, math.degrees(math.atan2(self.position.y, self.position.x)) - 90)
def update(self):
# Schwingung des Beins
self.angle = self.max_angle * math.sin(time.time() * self.speed)
self.calculate_position()
class Ant(Entity):
def __init__(self, position, colony, environment, caste='WORKER'):
super().__init__(
parent=scene,
position=position,
model=None,
color=ANT_COLORS.get(caste, color.white),
scale=0.2
)
self.caste = caste
self.colony = colony
self.environment = environment
self.has_food = False
self.energy = 100
self.age = 0
self.max_age = ANT_LIFESPAN
self.direction = self.random_direction()
self.state = 'FORAGING' # Mögliche Zustände: FORAGING, DEPOSITING
self.sensory_system = AntSensorySystem()
self.legs = [Leg(self, i) for i in range(6)] # 6 Beine
self.generate_body()
def generate_body(self):
# Kopf, Thorax und Abdomen in 2D
self.head = Entity(parent=self, model='sprite', color=self.color,
scale=(0.2, 0.2), position=(0, 0.1))
self.thorax = Entity(parent=self, model='sprite', color=self.color,
scale=(0.25, 0.25), position=(0, 0))
self.abdomen = Entity(parent=self, model='sprite', color=self.color,
scale=(0.3, 0.3), position=(0, -0.1))
def random_direction(self):
angle = random.uniform(0, 360)
return Vec3(math.cos(math.radians(angle)), math.sin(math.radians(angle)), 0)
def perceive(self):
return self.sensory_system.perceive(self)
def decide(self):
if self.state == 'FORAGING':
self.decide_foraging()
elif self.state == 'DEPOSITING':
self.decide_depositing()
def decide_foraging(self):
pheromones = self.perceive()
food_pheromone = pheromones[FOOD_PHEROMONE]
home_pheromone = pheromones[HOME_PHEROMONE]
if self.has_food:
# Auf dem Weg zurück zum Nest
if home_pheromone > PHEROMONE_THRESHOLD and random.random() < 0.9:
self.follow_pheromone(HOME_PHEROMONE)
else:
self.direction = self.random_direction()
else:
if food_pheromone > PHEROMONE_THRESHOLD and random.random() < 0.9:
self.follow_pheromone(FOOD_PHEROMONE)
else:
self.direction = self.random_direction()
def decide_depositing(self):
if self.has_food:
x, y, z = int(self.position.x), int(self.position.y), int(self.position.z)
voxel = self.environment.grid[x][y][z]
if voxel.type == NEST:
self.has_food = False
self.colony.food_storage += 1
self.energy += 50
self.state = 'FORAGING'
def follow_pheromone(self, pheromone_type):
x, y, z = int(self.position.x), int(self.position.y), int(self.position.z)
max_pheromone = -1
best_direction = None
for dx in [-1, 0, 1]:
nx = x + dx
if 0 <= nx < ENV_SIZE[0]:
for dy in [-1, 0, 1]:
ny = y + dy
if 0 <= ny < ENV_SIZE[1]:
for dz in [-1, 0, 1]:
nz = z + dz
if 0 <= nz < ENV_SIZE[2]:
neighbor = self.environment.grid[nx][ny][nz]
pheromone_level = neighbor.pheromones[pheromone_type]
if pheromone_level > max_pheromone:
max_pheromone = pheromone_level
best_direction = Vec3(dx, dy, dz)
if best_direction:
self.direction = best_direction + Vec3(random.uniform(-0.1, 0.1), random.uniform(-0.1, 0.1), 0)
else:
self.direction = self.random_direction()
def move(self):
if self.direction.length() == 0:
self.direction = self.random_direction()
self.direction = self.direction.normalized()
new_position = self.position + self.direction * ANT_SPEED
x, y, z = int(new_position.x), int(new_position.y), int(new_position.z)
if 0 <= x < ENV_SIZE[0] and 0 <= y < ENV_SIZE[1] and 0 <= z < ENV_SIZE[2]:
voxel = self.environment.grid[x][y][z]
if voxel.type in [AIR, FOOD, NEST]:
self.position = new_position
self.energy -= 0.1
else:
self.direction = self.random_direction()
def act(self):
x, y, z = int(self.position.x), int(self.position.y), int(self.position.z)
voxel = self.environment.grid[x][y][z]
if self.state == 'FORAGING':
if not self.has_food and voxel.type == FOOD:
self.has_food = True
voxel.type = AIR
voxel.model = None
voxel.color = color.clear
self.state = 'DEPOSITING'
elif self.state == 'DEPOSITING':
self.decide_depositing()
def deposit_pheromones(self):
x, y, z = int(self.position.x), int(self.position.y), int(self.position.z)
voxel = self.environment.grid[x][y][z]
if self.has_food:
voxel.pheromones[HOME_PHEROMONE] += PHEROMONE_DEPOSIT_AMOUNT
else:
voxel.pheromones[FOOD_PHEROMONE] += PHEROMONE_DEPOSIT_AMOUNT
def update(self):
self.age += 1
if self.age > self.max_age or self.energy <= 0:
self.die()
return
self.decide()
self.move()
self.act()
self.deposit_pheromones()
# Aktualisiere die Beine
for leg in self.legs:
leg.update()
def die(self):
self.colony.remove_ant(self)
destroy(self)
class QueenAnt(Entity):
def __init__(self, position, colony):
super().__init__(
parent=scene,
position=position,
model='quad',
color=ANT_COLORS['QUEEN'],
scale=0.5
)
self.colony = colony
self.laying_timer = 500 # Zeit bis zum nächsten Ei
def update(self):
self.laying_timer -= 1
if self.laying_timer <= 0:
if self.colony.food_storage >= 20:
self.colony.food_storage -= 20
self.lay_egg()
self.laying_timer = 500
def lay_egg(self):
egg = Egg(position=self.position, colony=self.colony)
self.colony.add_egg(egg)
class Egg(Entity):
def __init__(self, position, colony):
super().__init__(
parent=scene,
position=position,
model='quad',
color=color.white,
scale=0.1
)
self.colony = colony
self.hatching_time = 1000 # Zeit bis zum Schlüpfen
def update(self):
self.hatching_time -= 1
if self.hatching_time <= 0:
self.hatch()
def hatch(self):
# Aus dem Ei schlüpft eine Arbeiterameise
ant = Ant(position=self.position, colony=self.colony, environment=self.colony.environment)
self.colony.add_ant(ant)
destroy(self)
self.colony.eggs.remove(self)
class Colony:
def __init__(self, environment):
self.environment = environment
self.ants = []
self.eggs = []
self.food_storage = 0
nest_x, nest_y = ENV_SIZE[0] // 2, ENV_SIZE[1] // 2
nest_z = environment.get_surface_z(nest_x, nest_y) - 1
self.nest_position = Vec3(nest_x, nest_y, nest_z)
self.queen = QueenAnt(position=self.nest_position, colony=self)
def add_ant(self, ant):
self.ants.append(ant)
def remove_ant(self, ant):
if ant in self.ants:
self.ants.remove(ant)
def add_egg(self, egg):
self.eggs.append(egg)
def update(self):
# Königin aktualisieren
self.queen.update()
# Eier aktualisieren
for egg in self.eggs[:]:
egg.update()
class CameraController(Entity):
def __init__(self):
super().__init__()
self.camera_pivot = Entity()
camera.parent = self.camera_pivot
camera.position = (0, -30, 10)
camera.rotation_x = 30
self.rotation_speed = 100
self.zoom_speed = 20
self.current_zoom = 30
self.focus_position = Vec3(ENV_SIZE[0] // 2, ENV_SIZE[1] // 2, 0)
self.camera_pivot.position = self.focus_position
self.following = False
self.follow_target = None
self.last_click_time = 0
self.double_click_threshold = 0.3
def update(self):
dt = time.dt
# Doppelklick-Erkennung
if mouse.left and mouse.click:
current_time_click = time.time()
if current_time_click - self.last_click_time < self.double_click_threshold:
# Doppelklick erkannt
hit_entity = mouse.hovered_entity
if isinstance(hit_entity, Voxel) or isinstance(hit_entity, Ant):
self.focus_position = hit_entity.position
self.follow_target = hit_entity if isinstance(hit_entity, Ant) else None
self.following = isinstance(hit_entity, Ant)
self.last_click_time = current_time_click
# Kamera drehen
if held_keys['right mouse']:
delta = Vec2(mouse.velocity[0], mouse.velocity[1])
self.camera_pivot.rotation_y += delta.x * dt * self.rotation_speed
self.camera_pivot.rotation_x -= delta.y * dt * self.rotation_speed
self.camera_pivot.rotation_x = max(-90, min(90, self.camera_pivot.rotation_x))
# Zoom
self.current_zoom -= mouse.scroll_y * self.zoom_speed * dt
self.current_zoom = max(5, min(100, self.current_zoom))
camera.position = Vec3(0, -self.current_zoom, self.current_zoom * 0.5)
# Kamerafokus aktualisieren
if self.following and self.follow_target:
self.focus_position = self.follow_target.position
self.camera_pivot.position = lerp(self.camera_pivot.position, self.focus_position, 0.1)
# -----------------------
# Hilfsfunktionen
# -----------------------
def draw_pheromones(environment_grid):
for x in range(ENV_SIZE[0]):
for y in range(ENV_SIZE[1]):
for z in range(ENV_SIZE[2]):
voxel = environment_grid[x][y][z]
for pheromone_type in [FOOD_PHEROMONE, HOME_PHEROMONE, DANGER_PHEROMONE]:
strength = voxel.pheromones[pheromone_type]
if strength > PHEROMONE_THRESHOLD:
Entity(
parent=scene,
position=(x, y, z + 0.1),
model='quad',
color=PHEROMONE_COLORS[pheromone_type].tint(strength / PHEROMONE_MAX),
scale=strength * 0.05,
double_sided=True,
billboard=True,
render_queue=3000
)
# -----------------------
# Hauptfunktion
# -----------------------
def main():
app = Ursina()
environment = Environment()
colony = Colony(environment)
# Anfangs-Ameisen erstellen
for _ in range(INITIAL_WORKERS):
offset = Vec3(random.uniform(-5, 5), random.uniform(-5, 5), 0)
position = colony.nest_position + offset
# Sicherstellen, dass die Position innerhalb der Umgebung liegt
position.x = max(0, min(position.x, ENV_SIZE[0] - 1))
position.y = max(0, min(position.y, ENV_SIZE[1] - 1))
position.z = environment.get_surface_z(int(position.x), int(position.y))
ant = Ant(position=position, colony=colony, environment=environment)
colony.add_ant(ant)
# Soldatenameisen erstellen
for _ in range(INITIAL_SOLDIERS):
offset = Vec3(random.uniform(-5, 5), random.uniform(-5, 5), 0)
position = colony.nest_position + offset
# Sicherstellen, dass die Position innerhalb der Umgebung liegt
position.x = max(0, min(position.x, ENV_SIZE[0] - 1))
position.y = max(0, min(position.y, ENV_SIZE[1] - 1))
position.z = environment.get_surface_z(int(position.x), int(position.y))
ant = Ant(position=position, colony=colony, environment=environment, caste='SOLDIER')
colony.add_ant(ant)
# Kamera-Controller hinzufügen
camera_controller = CameraController()
# UI-Text für gesammeltes Essen
food_text = Text(text='', position=window.top_left, origin=(0, 0), background=True)
# Update-Funktion definieren
def update():
global current_time_step
current_time_step += 1
# Umwelt aktualisieren
environment.update_pheromones()
environment.spawn_food()
# Ameisen aktualisieren
for ant in colony.ants:
ant.update()
# Kolonie aktualisieren
colony.update()
# Pheromone anzeigen
if held_keys['p']:
draw_pheromones(environment.grid)
# UI aktualisieren
food_text.text = f"Essen gesammelt: {colony.food_storage}"
app.run()
if __name__ == "__main__":
main()
```
## Verbesserungen und Anpassungen
1. **2D-Umgebung:** Die Simulation ist nun in einer zweidimensionalen Ebene ausgelegt (Breite, Länge, Höhe=1), was die Berechnung und Darstellung vereinfacht und Ressourcen spart.
2. **Authentische Beinbewegung:**
- **2D-Beine:** Die Beine werden nun als zweidimensionale Quads dargestellt, die in einer Ebene schwingen.
- **Unabhängige Bewegung:** Jedes Bein hat eine eigene Schwingungsgeschwindigkeit und Phasenversetzung, um eine realistische und unabhängige Bewegung zu gewährleisten.
- **Beinbegrenzung:** Die Beine können sich nur bis zu einer maximalen Distanz vom Körper ausbreiten, um ein authentisches Aussehen zu bewahren.
3. **Effizienzsteigerung:**
- **Vereinfachte Modelle:** Durch die Reduktion auf 2D und die Verwendung einfacher Formen (Quads) werden die Rechenressourcen geschont.
- **Optimierte Pheromon-Diffusion:** Der Diffusionsprozess wurde effizienter gestaltet, indem unnötige Berechnungen vermieden werden.
4. **Verbesserte Wahrnehmung:**
- **Erhöhte Wahrnehmungsreichweite:** Die Ameisen können nun auf größere Entfernungen Pheromone erkennen, was zu realistischeren Such- und Rückkehrverhalten führt.
5. **Visualisierung der Pheromone:** Die Pheromone werden als transparente Quads über den Voxeln dargestellt, was eine klare Visualisierung ermöglicht, ohne die Performance zu stark zu belasten.
6. **Benutzerinteraktion:** Durch Doppelklicks kann der Benutzer den Fokus auf bestimmte Ameisen oder Voxeln legen, um deren Verhalten näher zu beobachten.
## Fazit
Dieses optimierte Skript kombiniert die Stärken der bisherigen Implementierungen und integriert die gewünschten Verbesserungen für eine realistischere und effizientere Simulation einer Ameisenkolonie. Die Beine werden nun authentisch in 2D dargestellt und unabhängig voneinander bewegt, was das Erscheinungsbild der Ameisen erheblich verbessert. Gleichzeitig bleibt die Simulation ressourcenschonend und basiert auf realistischen Grundregeln.
Sollten Sie weitere Anpassungen oder Erweiterungen wünschen, stehe ich Ihnen gerne zur Verfügung!