Inhalt
Aktueller Ordner:
/genalg.py
import tkinter as tk
from tkinter import ttk, messagebox
import random
import time
import threading
from dataclasses import dataclass
from typing import List, Tuple, Optional
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
@dataclass
class Gen:
"""Gen-Klasse für die genetische Information"""
id: int
code: List[str]
def __init__(self, gen_id: int):
self.id = gen_id
self.code = [random.choice(['0', '1']) for _ in range(6)]
def __str__(self):
return ''.join(self.code)
class Weider:
"""Weider-Klasse (Pflanzenfresser) mit genetischem Algorithmus"""
# Gen-Konstanten
FN, FG, RN, RG, FK, RK = 0, 1, 2, 3, 4, 5
def __init__(self, gen: Gen, weider_id: int):
self.gen = gen
self.id = weider_id
self.fit = 80 # Start-Fitness
self.x = 0
self.y = 0
self.active = False
self.init_traits()
def init_traits(self):
"""Initialisiert die Eigenschaften basierend auf dem Gen-Code"""
self.Fg = self.gen.code[self.FG] == '1'
self.Fn = self.gen.code[self.FN] == '1'
self.Rg = self.gen.code[self.RG] == '1'
self.Rn = self.gen.code[self.RN] == '1'
self.Fk = self.gen.code[self.FK] == '1'
self.Rk = self.gen.code[self.RK] == '1'
self.verteidigen = False
self.gefahr = False
self.futter = False
self.weidererkennen = False
self.kooperieren = False
def get_fitness(self) -> int:
return self.fit
def check_gefahr(self, grid, x: int, y: int, cols: int, rows: int):
"""Prüft auf Gefahr durch Räuber in der Nachbarschaft"""
if not self.Fg:
self.gefahr = False
return
danger_count = 0
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
nx = (x + dx) % cols
ny = (y + dy) % rows
if grid[ny][nx] == 2: # Räuber
danger_count += 1
self.gefahr = danger_count > 0
def check_futter(self, grid, x: int, y: int, cols: int, rows: int):
"""Prüft auf Nahrung in der Nachbarschaft"""
if not self.Fn:
self.futter = False
return
food_count = 0
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
nx = (x + dx) % cols
ny = (y + dy) % rows
if grid[ny][nx] == 1: # Nahrung
food_count += 1
self.futter = food_count > 0
def check_weider_erkennen(self, grid, x: int, y: int, cols: int, rows: int):
"""Prüft auf andere Weider in der Nachbarschaft"""
weider_count = 0
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
nx = (x + dx) % cols
ny = (y + dy) % rows
if grid[ny][nx] == 3: # Weider
weider_count += 1
self.weidererkennen = (weider_count > 0) and self.Fk
def check_kooperieren(self):
"""Aktiviert Kooperation wenn Bedingungen erfüllt"""
self.kooperieren = self.weidererkennen and self.Rk
def fressen(self, grid, x: int, y: int, cols: int, rows: int, weider_list):
"""Fress-Verhalten mit Nahrungsverbrauch"""
if self.futter and self.Rn:
# Suche nach benachbarter Nahrung und konsumiere sie
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
nx = (x + dx) % cols
ny = (y + dy) % rows
if grid[ny][nx] == 1: # Nahrung
# Nahrung wird gefressen und verschwindet
grid[ny][nx] = 0
# Fitness-Belohnung
kooperatoren = self.get_kooperatoren(grid, x, y, cols, rows, weider_list)
fitness_gewinn = 12 # Basis-Fitness
if self.kooperieren and kooperatoren > 0:
fitness_gewinn += kooperatoren * 3
elif not self.kooperieren:
fitness_gewinn += 5
self.fit += fitness_gewinn
return
def get_kooperatoren(self, grid, x: int, y: int, cols: int, rows: int, weider_list) -> int:
"""Zählt kooperierende Nachbarn"""
kooperatoren = 0
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
nx = (x + dx) % cols
ny = (y + dy) % rows
if grid[ny][nx] == 3: # Weider
# Finde den entsprechenden Weider in der Liste
for weider_data in weider_list:
if weider_data.x == nx and weider_data.y == ny and weider_data.kooperieren:
kooperatoren += 1
return kooperatoren
def verteidigung(self):
"""Aktiviert Verteidigung wenn Gefahr erkannt"""
self.verteidigen = self.gefahr and self.Rg
def update_fitness(self, value: int):
"""Aktualisiert die Fitness"""
self.fit += value
def check_überbevölkerung(self, grid, x: int, y: int, cols: int, rows: int) -> bool:
"""Prüft auf Überbevölkerung durch zu viele Weider in der Nachbarschaft"""
weider_count = 0
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
nx = (x + dx) % cols
ny = (y + dy) % rows
if grid[ny][nx] == 3: # Weider
weider_count += 1
return weider_count > 3
class EcologicalSimulation:
"""Hauptklasse für die ökologische Simulation"""
def __init__(self):
# Simulationsparameter
self.COLS = 80
self.ROWS = 24
self.MAX_FITNESS = 80
self.STOFFWECHSEL = -1
# Zelltypen
self.LEER = 0
self.NAHRUNG = 1
self.RÄUBER = 2
self.WEIDER = 3
# Farben für die Darstellung
self.colors = {
self.LEER: '#ecf0f1', # Hellgrau
self.NAHRUNG: '#f1c40f', # Gelb
self.RÄUBER: '#e74c3c', # Rot
self.WEIDER: '#27ae60' # Grün
}
# Simulationszustand
self.simulation_running = False
self.generation = 1
self.evolution_counter = 0
self.speed = 150
# Datenstrukturen
self.grid = []
self.weider_list = []
self.gen_pool = []
self.history = []
# GUI
self.root = None
self.canvas = None
self.gen_tree = None
self.initialize_simulation()
def initialize_simulation(self):
"""Initialisiert die Simulation"""
# Grid initialisieren
self.grid = [[self.LEER for _ in range(self.COLS)] for _ in range(self.ROWS)]
# Gen-Pool erstellen
self.gen_pool = [Gen(i) for i in range(16)]
# Weider initialisieren
self.weider_list = []
for i, gen in enumerate(self.gen_pool):
self.weider_list.append(Weider(gen, i))
# Zufällige Startverteilung
self.randomize_grid()
def randomize_grid(self):
"""Erstellt eine zufällige Startverteilung"""
for y in range(self.ROWS):
for x in range(self.COLS):
rand = random.random() * 100
if rand < 12: # 12% Nahrung
self.grid[y][x] = self.NAHRUNG
elif rand < 15: # 3% Räuber
self.grid[y][x] = self.RÄUBER
elif rand < 25: # 10% Weider
inactive_weider = next((w for w in self.weider_list if not w.active), None)
if inactive_weider:
inactive_weider.x = x
inactive_weider.y = y
inactive_weider.active = True
inactive_weider.fit = self.MAX_FITNESS
inactive_weider.init_traits()
self.grid[y][x] = self.WEIDER
else:
self.grid[y][x] = self.LEER
def count_neighbors(self, x: int, y: int, cell_type: int) -> int:
"""Zählt Nachbarn eines bestimmten Typs"""
count = 0
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
nx = (x + dx) % self.COLS
ny = (y + dy) % self.ROWS
if self.grid[ny][nx] == cell_type:
count += 1
return count
def crossover(self, gen1: Gen, gen2: Gen, mutation_rate: float = 0.1) -> Gen:
"""Führt Crossover und Mutation durch"""
child = Gen(len(self.gen_pool))
crossover_point = random.randint(0, 5)
# Crossover
for i in range(crossover_point + 1):
child.code[i] = gen1.code[i]
for i in range(crossover_point + 1, 6):
child.code[i] = gen2.code[i]
# Mutation
if random.random() < mutation_rate:
mutation_point = random.randint(0, 5)
child.code[mutation_point] = '1' if child.code[mutation_point] == '0' else '0'
return child
def apply_genetic_algorithm(self, selection_pressure: float = 0.25):
"""Wendet den genetischen Algorithmus an"""
active_weider = [w for w in self.weider_list if w.active]
if len(active_weider) < 2:
return
# Sortiere nach Fitness
active_weider.sort(key=lambda w: w.get_fitness(), reverse=True)
# Selektion
selection_count = max(2, int(len(active_weider) * selection_pressure))
best_genes = [w.gen for w in active_weider[:selection_count]]
# Neuen Gen-Pool erstellen
new_gen_pool = best_genes.copy()
while len(new_gen_pool) < 16:
parent1 = random.choice(best_genes)
parent2 = random.choice(best_genes)
child = self.crossover(parent1, parent2)
new_gen_pool.append(child)
self.gen_pool = new_gen_pool
# Weider mit neuen Genen aktualisieren
for i, weider in enumerate(self.weider_list):
if i < len(self.gen_pool):
weider.gen = self.gen_pool[i]
weider.init_traits()
weider.fit = self.MAX_FITNESS
def next_generation(self):
"""Berechnet die nächste Generation"""
new_grid = [[self.LEER for _ in range(self.COLS)] for _ in range(self.ROWS)]
new_weider_list = []
# Nahrung und Räuber verarbeiten
for y in range(self.ROWS):
for x in range(self.COLS):
cell = self.grid[y][x]
if cell == self.NAHRUNG:
if random.random() < 0.15:
new_grid[y][x] = self.NAHRUNG
elif cell == self.RÄUBER:
weider_count = self.count_neighbors(x, y, self.WEIDER)
if weider_count >= 1 and random.random() < 0.8:
new_grid[y][x] = self.RÄUBER
elif random.random() < 0.2:
new_grid[y][x] = self.RÄUBER
# Weider verarbeiten
for weider in self.weider_list:
if not weider.active:
continue
x, y = weider.x, weider.y
# Weider-Logik anwenden
weider.check_gefahr(self.grid, x, y, self.COLS, self.ROWS)
weider.check_futter(self.grid, x, y, self.COLS, self.ROWS)
weider.check_weider_erkennen(self.grid, x, y, self.COLS, self.ROWS)
weider.check_kooperieren()
weider.fressen(self.grid, x, y, self.COLS, self.ROWS, self.weider_list)
weider.verteidigung()
weider.update_fitness(self.STOFFWECHSEL)
# Überlebensprüfung
if (weider.get_fitness() <= 0 or
weider.check_überbevölkerung(self.grid, x, y, self.COLS, self.ROWS)):
continue
if weider.gefahr and not weider.verteidigen:
continue
# Bewegung
new_x = (x + random.randint(-1, 1)) % self.COLS
new_y = (y + random.randint(-1, 1)) % self.ROWS
if new_grid[new_y][new_x] == self.LEER:
new_grid[new_y][new_x] = self.WEIDER
weider.x, weider.y = new_x, new_y
new_weider_list.append(weider)
# Vermehrung
if (weider.get_fitness() > self.MAX_FITNESS * 0.7 and
random.random() < 0.15):
child_x = (new_x + random.randint(-1, 1)) % self.COLS
child_y = (new_y + random.randint(-1, 1)) % self.ROWS
if new_grid[child_y][child_x] == self.LEER:
new_grid[child_y][child_x] = self.WEIDER
parent_gen = random.choice([w.gen for w in self.weider_list if w.active])
child_gen = self.crossover(weider.gen, parent_gen)
child_weider = Weider(child_gen, len(self.weider_list))
child_weider.x, child_weider.y = child_x, child_y
child_weider.active = True
new_weider_list.append(child_weider)
# Zufällige neue Nahrung und Räuber
for y in range(self.ROWS):
for x in range(self.COLS):
if new_grid[y][x] == self.LEER:
rand = random.random() * 100
if rand < 10:
new_grid[y][x] = self.NAHRUNG
elif rand < 12:
new_grid[y][x] = self.RÄUBER
self.grid = new_grid
self.weider_list = [w for w in self.weider_list if w in new_weider_list] + \
[w for w in new_weider_list if w not in self.weider_list]
# Evolution
self.evolution_counter += 1
if self.evolution_counter >= 8:
self.apply_genetic_algorithm()
self.evolution_counter = 0
self.generation += 1
def get_statistics(self) -> dict:
"""Gibt aktuelle Statistiken zurück"""
nahrung = sum(row.count(self.NAHRUNG) for row in self.grid)
raeuber = sum(row.count(self.RÄUBER) for row in self.grid)
weider = sum(row.count(self.WEIDER) for row in self.grid)
active_weider = [w for w in self.weider_list if w.active]
avg_fitness = sum(w.get_fitness() for w in active_weider) / max(1, len(active_weider))
return {
'nahrung': nahrung,
'raeuber': raeuber,
'weider': weider,
'avg_fitness': avg_fitness,
'generation': self.generation
}
class EcologicalSimulationGUI:
"""GUI für die ökologische Simulation"""
def __init__(self):
self.simulation = EcologicalSimulation()
self.setup_gui()
def setup_gui(self):
"""Erstellt die grafische Benutzeroberfläche"""
self.root = tk.Tk()
self.root.title("Ökologische Simulation - Zellularautomat & Genetischer Algorithmus")
self.root.geometry("1400x900")
# Hauptframe
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Grid konfigurieren
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(1, weight=1)
# Steuerungsbereich
control_frame = ttk.LabelFrame(main_frame, text="Simulationssteuerung", padding="10")
control_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
# Buttons
ttk.Button(control_frame, text="▶ Start", command=self.start_simulation).grid(row=0, column=0, padx=5)
ttk.Button(control_frame, text="⏸ Stopp", command=self.stop_simulation).grid(row=0, column=1, padx=5)
ttk.Button(control_frame, text="🔁 Zurücksetzen", command=self.reset_simulation).grid(row=0, column=2, padx=5)
# Geschwindigkeit
ttk.Label(control_frame, text="Geschwindigkeit:").grid(row=0, column=3, padx=(20, 5))
self.speed_var = tk.StringVar(value="150")
speed_combo = ttk.Combobox(control_frame, textvariable=self.speed_var,
values=["500", "300", "150", "50"], state="readonly", width=10)
speed_combo.grid(row=0, column=4, padx=5)
speed_combo.bind('<<ComboboxSelected>>', self.update_speed)
# Statistik-Anzeige
stats_frame = ttk.Frame(control_frame)
stats_frame.grid(row=0, column=5, padx=(20, 0))
self.stats_label = ttk.Label(stats_frame,
text="Generation: 1 | Nahrung: 0 | Räuber: 0 | Weider: 0 | Fitness: 80")
self.stats_label.grid(row=0, column=0)
# Hauptanzeige-Bereich
display_frame = ttk.Frame(main_frame)
display_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
display_frame.columnconfigure(0, weight=3)
display_frame.columnconfigure(1, weight=1)
display_frame.rowconfigure(0, weight=1)
# Linke Seite: Grid und Legende
left_frame = ttk.Frame(display_frame)
left_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 10))
left_frame.columnconfigure(0, weight=1)
left_frame.rowconfigure(0, weight=1)
left_frame.rowconfigure(1, weight=0)
# Grid-Canvas
grid_frame = ttk.LabelFrame(left_frame, text="Simulations-Grid")
grid_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
grid_frame.columnconfigure(0, weight=1)
grid_frame.rowconfigure(0, weight=1)
self.canvas = tk.Canvas(grid_frame, width=640, height=192, bg="white")
self.canvas.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Legende
legend_frame = ttk.Frame(left_frame)
legend_frame.grid(row=1, column=0, sticky=(tk.W, tk.E))
colors = self.simulation.colors
legend_items = [
("Leer", colors[0]),
("Nahrung", colors[1]),
("Räuber", colors[2]),
("Weider", colors[3])
]
for i, (text, color) in enumerate(legend_items):
frame = ttk.Frame(legend_frame)
frame.grid(row=0, column=i, padx=10)
tk.Canvas(frame, width=20, height=20, bg=color).grid(row=0, column=0, padx=(0, 5))
ttk.Label(frame, text=text).grid(row=0, column=1)
# Rechte Seite: Genetische Information
right_frame = ttk.Frame(display_frame)
right_frame.grid(row=0, column=1, sticky=(tk.W, tk.E, tk.N, tk.S))
right_frame.columnconfigure(0, weight=1)
right_frame.rowconfigure(0, weight=1)
right_frame.rowconfigure(1, weight=1)
# Parameter
param_frame = ttk.LabelFrame(right_frame, text="Simulationsparameter", padding="10")
param_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
# Mutationsrate
ttk.Label(param_frame, text="Mutationsrate:").grid(row=0, column=0, sticky=tk.W)
self.mutation_var = tk.IntVar(value=10)
ttk.Scale(param_frame, from_=1, to=20, variable=self.mutation_var,
orient=tk.HORIZONTAL).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=5)
self.mutation_label = ttk.Label(param_frame, text="10%")
self.mutation_label.grid(row=0, column=2)
# Selektionsdruck
ttk.Label(param_frame, text="Selektionsdruck:").grid(row=1, column=0, sticky=tk.W)
self.selection_var = tk.IntVar(value=25)
ttk.Scale(param_frame, from_=10, to=50, variable=self.selection_var,
orient=tk.HORIZONTAL).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=5)
self.selection_label = ttk.Label(param_frame, text="25%")
self.selection_label.grid(row=1, column=2)
param_frame.columnconfigure(1, weight=1)
# Genetische Codes
gen_frame = ttk.LabelFrame(right_frame, text="Genetische Codes", padding="10")
gen_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
gen_frame.columnconfigure(0, weight=1)
gen_frame.rowconfigure(0, weight=1)
# Treeview für Gen-Anzeige
columns = ("ID", "Gen-Code", "Fitness", "Aktiv", "Verteilung")
self.gen_tree = ttk.Treeview(gen_frame, columns=columns, show="headings", height=15)
for col in columns:
self.gen_tree.heading(col, text=col)
self.gen_tree.column(col, width=80)
self.gen_tree.column("Gen-Code", width=120)
self.gen_tree.column("Verteilung", width=100)
scrollbar = ttk.Scrollbar(gen_frame, orient=tk.VERTICAL, command=self.gen_tree.yview)
self.gen_tree.configure(yscrollcommand=scrollbar.set)
self.gen_tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
gen_frame.columnconfigure(0, weight=1)
gen_frame.rowconfigure(0, weight=1)
# Initiale Anzeige aktualisieren
self.update_display()
self.update_gen_display()
def update_speed(self, event=None):
"""Aktualisiert die Simulationsgeschwindigkeit"""
self.simulation.speed = int(self.speed_var.get())
def start_simulation(self):
"""Startet die Simulation"""
if not self.simulation.simulation_running:
self.simulation.simulation_running = True
self.run_simulation()
def stop_simulation(self):
"""Stoppt die Simulation"""
self.simulation.simulation_running = False
def reset_simulation(self):
"""Setzt die Simulation zurück"""
self.stop_simulation()
self.simulation = EcologicalSimulation()
self.update_display()
self.update_gen_display()
def run_simulation(self):
"""Haupt-Simulationsschleife"""
if self.simulation.simulation_running:
self.simulation.next_generation()
self.update_display()
self.update_gen_display()
self.root.after(self.simulation.speed, self.run_simulation)
def update_display(self):
"""Aktualisiert die Grid-Anzeige"""
self.canvas.delete("all")
cell_size = 8
colors = self.simulation.colors
for y in range(self.simulation.ROWS):
for x in range(self.simulation.COLS):
cell_type = self.simulation.grid[y][x]
color = colors[cell_type]
x1 = x * cell_size
y1 = y * cell_size
x2 = x1 + cell_size
y2 = y1 + cell_size
self.canvas.create_rectangle(x1, y1, x2, y2, fill=color, outline="")
# Statistiken aktualisieren
stats = self.simulation.get_statistics()
stats_text = (f"Generation: {stats['generation']} | "
f"Nahrung: {stats['nahrung']} | "
f"Räuber: {stats['raeuber']} | "
f"Weider: {stats['weider']} | "
f"Fitness: {stats['avg_fitness']:.1f}")
self.stats_label.config(text=stats_text)
# Parameter-Labels aktualisieren
self.mutation_label.config(text=f"{self.mutation_var.get()}%")
self.selection_label.config(text=f"{self.selection_var.get()}%")
def update_gen_display(self):
"""Aktualisiert die Gen-Anzeige"""
# Alte Einträge löschen
for item in self.gen_tree.get_children():
self.gen_tree.delete(item)
# Gen-Statistiken sammeln
gen_usage = {}
for weider in self.simulation.weider_list:
if weider.active:
gen_id = weider.gen.id
gen_usage[gen_id] = gen_usage.get(gen_id, 0) + 1
# Neue Einträge hinzufügen
for gen in self.simulation.gen_pool:
is_active = gen.id in gen_usage
usage_count = gen_usage.get(gen.id, 0)
# Finde die Fitness des Weiders mit diesem Gen
fitness = 0
for weider in self.simulation.weider_list:
if weider.gen.id == gen.id and weider.active:
fitness = weider.get_fitness()
break
fitness_percent = min(100, (fitness / self.simulation.MAX_FITNESS) * 100)
# Gen-Beschreibung
traits = []
if gen.code[0] == '1': traits.append("Nahrung")
if gen.code[1] == '1': traits.append("Gefahr")
if gen.code[2] == '1': traits.append("Fressen")
if gen.code[3] == '1': traits.append("Verteidigen")
if gen.code[4] == '1': traits.append("Weider")
if gen.code[5] == '1': traits.append("Kooperieren")
description = ", ".join(traits) if traits else "Passiv"
self.gen_tree.insert("", "end", values=(
gen.id + 1,
f"{''.join(gen.code)}\n{description}",
f"{fitness}",
f"{'✓' if is_active else '✗'} ({usage_count})",
f"{fitness_percent:.1f}%"
))
def run(self):
"""Startet die GUI"""
self.root.mainloop()
# Hauptprogramm
if __name__ == "__main__":
print("Starte Ökologische Simulation...")
app = EcologicalSimulationGUI()
app.run()