eSheep – Evolution in C++
Mit eSheep, einer Simulation der Evolution in C++ endet meine Serie der Beispiele aus der Vorlesung zu künstlicher Intelligenz. In diesem letzten großen Beispiel habe ich eine Lebenssimulation geschrieben, in der sich Schafe fortpflanzen und anhand ihres Genoms neue Schafe mit anderen Eigenschaften entstehen. Wird die eigene Population überleben?
eSheep – Evolution in C++
Im finalen eSheep Programm verwende ich viel Funktionalität die ich bereits in den vorangegangenen Programmen implementiert habe. Das ist die Darstellung einer Karte mit der ClanLib Engine und die Wegfindung mit dem A* Algorithmus.
Theorie
In dieser Simulation kann man Schafe erstellen. Mit einem Klick auf die linke Maustaste in einem der Felder erstellt man ein neues Schaf. Dieses wird mit einem zufälligen Genom generiert und darf sein Leben nun auf der Landkarte verbringen. Ein Genom eines Schafs besteht aus Eigenschaften die jeweils einen Wert bekommen. Dieser Wert bestimmt wie ausgeprägt die einzelne Eigenschaft bei dem Schaf ist. Es gibt folgende Eigenschaften:
- Hunger
je höher, desto öfter muss ein Schaf essen - Soziales
je höher, desto mehr andere Schafe braucht das Schaf - Hygiene
je höher, desto mehr hinterlässt das Schaf - Sexualität
je höher, desto öfter wird das Schaf Nachfahren produzieren - Kampf
je höher, desto eher wird es mit Schafen gleichen Geschlechts kämpfen
Mit diesem Genom ist ein Schaf definiert und agiert danach. Jedes Schaf hat immer eine Intuition, der stärkste Trieb steuert was das Schaf als nächstes macht. Ist der Hunger Wert beispielsweise hoch, dann sucht das Schaf ein Grasfeld und wird dort fressen. Ein Schaf durchlebt in seinem Leben folgende Stufen:
- Kind
Sexualität und Kampf haben keine Wirkung - Erwachsen
Sexualität und Kampf sind die wichtigsten Eigenschaften - Alt
Sexualität und Kampf sind weniger wichtig, das Schaf kann sterben
Mit jeder Spielrunde steigen die Bedürfnisse der einzelnen Eigenschaftswerte basierend auf dessen Wert im Genom und dem Alter des Schafs. Dieser Wert beeinflusst was das Schaf als nächstes für eine Aktion ausführt. Es gibt folgende Aktionen:
- Essen
das Schaf sucht einen Weg zu einem Wiesenfeld, frisst dort und wird gesättigt. Der Hygienebedarfwert steigt, der Hunger sinkt. Ein vollständig gefressenes Wiesenfeld wird zur Wüste. - Hygienebedarf
das Schaf ist voll und muss sich entleeren. Auf dem Feld entsteht Dung aus dem später wieder Wiese wachsen kann. Der Hygienebedarfwert sinkt. - Sozial
das Schaf sucht andere Schafe und geht zu ihnen. Je mehr Schafe in der Nähe sind, desto stärker sinkt dieser Wert. - Sexualität
das Schaf sucht einen Erwachsenen Partner anderen Geschlechts. Es wird ein neues Schaf generiert. Dieses ist nicht zufällig, sondern wird aus Kombination der Genome der Eltern erzeugt (+ eine kleine Mutation). - Kampf
das Schaf sucht einen Erwachsenen Kontrahenten anderen Geschlechts. Es kann sein, dass ein Schaf im Kampf getötet wird.
Die aktuellen Werte aller Eigenschaften der Schafe werden als Zahlen rund um das Schaf angezeigt.
Aus der Zahl und Art der Schafe ergeben sich zahlreiche Einflüsse auf die Umwelt. Je nachdem wie Umwelt wird die Evolution der Schafe beeinflusst. In vielen Generationen sieht man, welche der Eigenschaften beziehungsweise Eigenschaftskombinationen die beste Schafherde ergeben.
Implementierung
Die einig neue Klasse ist Sheep. Den kompletten Source Code findet man wie immer auf meiner GitHub Seite. Über einen Mausklick wird in der generateNewSheep Methode ein neues Schaf erstellt. Mit findNewIntention wird basierend auf den Werten vom Schaf eine neue Intention erstellt. Ein Schaf hat immer genau eine Intention, einen Auftrag, so lange bis diese erfüllt ist. Mit jeder Runde ändern sich die Eigenschaftswerte in updateDesires, basierend auf der jeweiligen Ausprägung im Genom. Erst wenn die Intention erfolgreich war werden die wachsenden Bedürfnisse in lowerDesires befriedigt. In jeder Runde wird noch das Alter vom Schaf geprüft und in nextAge eventuell die Altersstufe angehoben. Gleichzeitig wird auch geprüft, ob das Schaf stirbt checkDeath. Finden sich zwei Schafe unterschiedlichen Geschlechts wird in combineParents ein neues Kind Schaf erzeugt.
Im großen und ganzen war das der komplette Code für die Simulation der Schafe. ein paar Eindrücke:
Man erkennt gut, dass die Zahl der Schafe immer mehr wird und dass diese durch mehr Wüste bereits eine bleibende Änderung in der Umwelt hinterlassen haben.
Simulation
Ein so dynamisches System ist schwer zu debuggen. Damit man trotzdem den Überblick behält wurde in der Log Klasse ein Logging implementiert. Dabei wird jede Aktion eines Schafs in einer Excel Datei festgehalten. Zum Start sieht das in etwa so aus:
Man erkennt sehr gut, dass es zum Start nur ein einziges Schaf gibt. Dessen aktive Intuition ist offenbar Soziales, es sucht nach Kontakt. Mehrer Runden lang findet es keinen, da es noch kein zweites Schaf gibt. Das habe ich dann aber doch noch auf die Karte gesetzt…was daraus geworden ist?
Na noch mehr Schafe! Nach vielen Runden und zahlreichen Schafen mehr sieht das schon etwas komplexer aus:
Fazit
Simulationen haben mich schon immer fasziniert. Als Programmierer kann man sich recht einfach eine Welt basteln, in denen die eigenen Gesetze herrschen. Alles virtuelle Leben ist programmierter Regeln unterworfen und dank Zufall und Evolution wird die erstellte Population basierend auf Umwelteinflüssen weiterentwickelt.
Mein eSheep Lebenssimulation mit recht einfach gestrickten Schafen erlebt man schon recht witzige Momente. Künstliche Intelligenz ist ein faszinierendes Forschungsfeld, dass besonders in Spielen sehr spannend zuzusehen ist. Fast immer macht die künstliche Lebensform nicht das was man als Programmierer erwartet. Zufallswerte, Bugs und Evolution tun ihr bestes, so dass jeder Programmstart zum Abenteuer wird.