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()