Hier werden die Unterschiede zwischen zwei Versionen gezeigt.
Beide Seiten der vorigen Revision Vorhergehende Überarbeitung Nächste Überarbeitung | Vorhergehende Überarbeitung | ||
ss19:code [2019/08/19 23:00] leanderh |
ss19:code [2019/09/06 16:36] (aktuell) leanderh |
||
---|---|---|---|
Zeile 1: | Zeile 1: | ||
- | [[Mutationen_Kreaturen_begrenzteSchritte_Leander]] \\ | + | ====Code==== |
- | [[zufällig_Bewegung_Kreaturen_Futtergenerierung_turtle_Eric_Verena]] \\ | + | Unser aktueller Code steht in 4 Versionen zur Verfügung: \\ |
- | [[Mutationen_Kreaturen_eineEigenschaft_zweiEltern_Leander]]\\ | + | \\ |
- | [[Pygame_Beispielprogramm_mit_Hitbox_Eric]] \\ | + | 1. {{:ss19:neuronal_infinity_map.zip|}} \\ |
- | [[Mapgeneration_für_große_Populationen_Eric]]\\ | + | Das Verhalten der Kreaturen dieser Version geschieht auf Basis neuronaler Netze. Die Karte ist "unendlich" groß, d.h. wenn die Kreaturen die Mapgrenze überqueren, tauchen sie auf der anderen Seite der Map wieder auf. \\ |
- | [[erstes_pygame_simulation_Alex]]\\ | + | \\ |
- | [[listen_verarbeitung_Leander]]\\ | + | 2. {{:ss19:polynom_infinity_map.zip|}} \\ |
- | [[Test]]\\ | + | Das Verhalten der Kreaturen dieser Version geschieht auf Basis von Polynomen. Die Karte ist "unendlich" groß, d.h. wenn die Kreaturen die Mapgrenze überqueren, tauchen sie auf der anderen Seite der Map wieder auf. \\ |
- | [[temporärer code]]\\ | + | \\ |
- | [[sichtfeld_grad_Alex]]\\ | + | 3. {{:ss19:neuronal_map_limits.zip|}} \\ |
- | [[Finaler Code]] \\ | + | Das Verhalten der Kreaturen dieser Version geschieht auf Basis neuronaler Netze. Die Karte ist "limitiert", d.h. wenn die Kreaturen die Mapgrenze überqueren, sterben sie. \\ |
+ | \\ | ||
+ | 4.{{:ss19:polynome_map_limits.zip|}} \\ | ||
+ | Das Verhalten der Kreaturen dieser Version geschieht auf Basis neuronaler Netze. Die Karte ist "limitiert", d.h. wenn die Kreaturen die Mapgrenze überqueren, sterben sie. \\ | ||
+ | \\ | ||
+ | Einige Ältere Versionen des Codes sowie einige alternative Ansätze findet man hier: [[Ältere Versionen des Codes]].\\ | ||
+ | \\ | ||
+ | Alle vier Versionen bestehen aus den Dateien map, player und constants. Bei den Versionen auf Basis neuronaler Netze gibt es zudem noch die Datei neuronal, die eben dieses Netz aufbaut. \\ | ||
+ | Für alle Versionen werden einige Pakete benötigt. Das Paket Pygame sorgt für die graphische Darstellung der Kreaturen und des Futters. Mit matplotlib.pyplot werden die Statistiken erstellt. Die Pakete math und random sind für die zufälligen Komponenten und einige Berechnungen. Mit copy werden die Kreaturen kopiert. Und numpy wird für das neuronale Netz benötigt. \\ | ||
+ | Die Datei Player wird im Verlauf des Textes erklärt, der Anfang dieser Erläuterung ist durch das fettgeschriebene Wort **Player** gekennzeichnet.\\ | ||
+ | In **constants** sind alle wichtigen Konstanten für die Map und die Player enthalten. Alle Konstanten daraus sind GROSSGESCHRIEBEN, daran kann man sie erkennen. Alle GROSSGESCHRIEBENEN Konstanten sind also in constants enthalten und erklärt.\\ | ||
+ | Das Kernstück des Programms ist die **Map**. Wir teilen diese in verschiedene gleichartige Chunks auf, um die Rechenzeit zu minimieren. Jeder Chunk hat dabei seine Position (x und y Koordinaten der linken unteren Ecke), seine Länge und Höhe, welche in der Regel den gleichen Wert haben und die anfängliche Wahrscheinlichkeit, das Futter erscheint, Fooddensity genannt. Zudem gibt es die Möglichkeit, die Chunks zu benennen und sie sich überlappen zu lassen. Desweitern ist in jedem Chunk noch eine Liste mit den im Chunk vorhandenen Futter angelegt. | ||
+ | Vergleiche hier die Initialisierungsfunktion der Chunks: | ||
+ | <code> | ||
+ | def __init__(self,x,y,length,height,overlay,name): | ||
+ | self.x = x | ||
+ | self.y = y | ||
+ | self.length = length | ||
+ | self.height = height | ||
+ | self.overlay = overlay | ||
+ | self.objects = [] #Liste der festen Objekte | ||
+ | self.density = FOODDENSITY #Wahrscheinlichkeit für neue Objekte | ||
+ | self.name = name | ||
+ | </code> | ||
+ | Die Objekte werden durch die Funktion generate() erstellt und zu der Objektliste hinzugefügt. Es gibt über die Variable multi die Möglichkeit, hier noch mal die Menge des Futters in diesem Chunk zu verändern. | ||
+ | <code> | ||
+ | #generiert zufällig verteilte Objekte in dem Chunk | ||
+ | def generate(self,multi=1): | ||
+ | a = Object(0,0,0) | ||
+ | numb = self.length*self.height*self.density/a.size*multi | ||
+ | for i in range(int(numb)): | ||
+ | self.objects.append(Object(random.randint(self.x+int(self.height*0.1),self.getendx()-int(self.height*0.1)-a.size),random.randint(self.y+int(self.height*0.1),self.getendy()-int(self.height*0.1)-a.size),i)) | ||
+ | </code> | ||
+ | Die Objekte werden durch die Klasse Object erstellt. Alle Objekte haben ihre Koordinaten (x und y), welche sich auch nicht verändern, es gibt also keine sich bewegenden Futterstücke. Zudem haben die Objekte einen Status, also ob sie schon gefressen wurden, einen Stamina Wert der sich aus ihrer Größe und dem Grundfutterstaminawert ergibt und der Kreaturen gutgeschrieben wird, die das Futterstück fressen und ihre rect() Funktion, welche für Pygame benötigt wird. Vergleiche hierfür die Initialisierungsfunktion: | ||
+ | <code> | ||
+ | #Initialisiert das Objekt | ||
+ | def __init__(self,x,y,name): | ||
+ | self.x=x #x-Pos | ||
+ | self.y=y #y-Pos | ||
+ | self.status = True #Aktiv oder nicht | ||
+ | self.name = name #ID | ||
+ | self.size = random.randint(3,5) #Größe | ||
+ | self.rect = pygame.Rect(self.x, self.y, self.size,self.size) #Rechteck in Pygame | ||
+ | self.stamina=FOOD_STAMINA*self.size | ||
+ | </code> | ||
+ | Zudem haben die Objekte noch die Möglichkeit zu mutieren. Dies ist für den Fall gedacht, dass nicht jede Generation gleich viele Objekte generiert werden sollen, sondern die Objekte aus den nicht gefressenen Objekten der vorherigen Generation erzeugt werden. Bei der Mutation wird einerseits die Größe der Objekte verändert und andererseits ein zufälliger neuer Punkt für das neue Objekt innerhalb eines Quadrates der Seitenlänge xh um das alte Objekt gewählt. Zudem wird wieder für Pygame die rect() Funktion eingefügt. | ||
+ | <code> | ||
+ | #Mutiert die Objekte | ||
+ | def mutate(self,xmin,xh): | ||
+ | self.size=self.size+random.choice(range(-9,10))*0.01 #Mutiert Größe | ||
+ | self.x=self.x+random.choice(range(xmin,xmin+xh)) #legt x und | ||
+ | if self.x<0: | ||
+ | self.x=self.x*(-1) | ||
+ | self.y=self.y+random.choice(range(xmin,xmin+xh)) #y in der Nähe der Werte des Vorbilds fest | ||
+ | if self.y<0: | ||
+ | self.y=self.y*(-1) | ||
+ | self.rect = pygame.Rect(self.x, self.y, self.size,self.size) | ||
+ | </code>\\ | ||
+ | Die Karte selber, zu welcher sich die Chunks ja zusammensetzen, hat ebenfalls einige Eigenschaften. Da wäre zunächst ihre als Mapsize bezeichnete Seitenlänge (Da die Karte ja quadratisch ist, gibt es nur eine Seitenlänge). Dann die Größe der Chunks und wie sehr sich diese Überlappen können (overlap). Desweiteren ist in der Karte gespeichert, welche Spieler sich gerade auf ihr bewegen. Für jeden dieser Spieler ist die Welt die Karte. | ||
+ | <code> | ||
+ | #Initilisiert die Map | ||
+ | #def__init__(Liste der Objekte,Liste der Spieler,Gesamtgröße der Map,Größe der Chunks,Liste der alten Objekte) | ||
+ | def __init__(self,playerlist,size,sizechunk,old_objects): | ||
+ | self.worldsize = size #Worldsize | ||
+ | self.chunksize = sizechunk #Chunksize wird später static | ||
+ | self.overlay = sizechunk*0.2 #Overlay der Chunks | ||
+ | self.player = playerlist #Spielerliste | ||
+ | for player in self.player: | ||
+ | player.world = self | ||
+ | self.maplist = [] #2-Dim Liste der Chunks | ||
+ | self.initiate(old_objects) | ||
+ | self.playerupdate() | ||
+ | </code> | ||
+ | Wie in dem Code bereits zu sehen gibt es noch zwei für die Initialisierung der Karte wichtige Funktionen der Karte. Da wäre zunächst die initiate() Funktion. In sie wird eine Liste der nicht gefressenen Objecte der vorherigen Generation eingegeben, bzw. wenn es keine vorherige Generation gab, eine 0. In initiate() wird zunächst die Anzahl der Chunks auf der Karte pro Reihe errechnet, der factor. Dann werden die Chunks der Reihe nach durchlaufen, von unten nach oben und von links nach rechts. Es werden dann nach der oben beschriebenen Funktion die Chunks generiert. Wenn entsprechend eingestellt, wird Futter in der Umgebung des überlebenden Futters aus der letzten Runde generiert, d.h. solange old_objects ungleich 0 ist, werden die einzelnen Objekte durchgegangen und alle, die in dem gerade betrachteten Chunk waren, werden kopiert, mutiert und der neuen Objektliste des Chunks hinzugefügt. Des weiteren wird noch eine kleine Menge Futter zufällig neu generiert. Sollte deaktiviert sein, dass das Futter aus dem alten Futter generiert wird, wird das Futter jede Generation neu generiert. | ||
+ | <code> | ||
+ | #Initilisiert die Grundmap, sowie weist jedem Chunk die Objekte hinzu | ||
+ | def initiate(self,old_objects): | ||
+ | factor = int(self.worldsize/self.chunksize) | ||
+ | #print(factor) | ||
+ | R = 0 | ||
+ | a=0 | ||
+ | u=0 | ||
+ | #Durchläuft die y-Richtung der Chunks | ||
+ | for i in range(factor): | ||
+ | line = [] | ||
+ | a+=1 | ||
+ | qqq = 110351524512345 | ||
+ | qqqq = 12345 | ||
+ | #Durchläuft die x-Richtung der Chunks | ||
+ | for e in range(factor): | ||
+ | u+=1 | ||
+ | line.append(Chunk(self.chunksize*e,self.chunksize*i,self.chunksize,self.chunksize,self.overlay,e+i*factor)) | ||
+ | #Wenn entsprechend eingestellt, wird Futter in der Umgebung des überlebenden Futters aus der letzten Runde generiert | ||
+ | if FOOD_GENERATION_PER_OLD_FOOD: | ||
+ | if old_objects==0: #Für die erste Runde wird in jedem Chunk entsprechend Futter generiert | ||
+ | line[e].generate() | ||
+ | else: | ||
+ | objects=[] #die Liste der zu kopierenden Objekte in dem Chunk | ||
+ | for o in old_objects: | ||
+ | if o.x<self.chunksize*(e+1) and o.y<self.chunksize*(i+1): | ||
+ | if o.x>=self.chunksize*e and o.y>=self.chunksize*i: | ||
+ | objects.append(o) | ||
+ | #kopiere die Objekte so oft, wie es der geamten Anzahl der Objekte angemessen ist | ||
+ | for o in objects: | ||
+ | if (MAPSIZE/CHUNKANZAHL)<len(objects): | ||
+ | N=random.randint(0,2) | ||
+ | elif (MAPSIZE/CHUNKANZAHL)/2<len(objects): | ||
+ | N=random.randint(0,4) | ||
+ | else: | ||
+ | N=random.randint(0,6) | ||
+ | n=0 | ||
+ | |||
+ | while n<N: | ||
+ | ob=copy.copy(o) | ||
+ | ob.mutate(-20,40) | ||
+ | line[e].objects.append(ob) | ||
+ | |||
+ | n+=1 | ||
+ | #Füge in jedem Chunk noch mal eine kleine Anzahl Objekte hinzu | ||
+ | line[e].generate(FOOD_PER_GENERATION) | ||
+ | |||
+ | else: | ||
+ | if random.random()<0.7: | ||
+ | line[e].generate() | ||
+ | |||
+ | self.maplist.append(line) | ||
+ | del line | ||
+ | |||
+ | </code> | ||
+ | Die andere bereits in der Initialisierung verwendete Funktion ist die playerupdate() Funktion. Diese unterscheidet sich, je nachdem ob man eine limitierte map vorliegen hat oder eine unbegrenzte. Ist die Map limitiert, wird die Liste der Spieler durchgegangen und geprüft, ob sich diese noch innerhalb der Mapgrenzen befinden. ist dies nicht der Fall, so stirbt die Kreatur. | ||
+ | <code> | ||
+ | #überprüft die Chunkposition des Spielers | ||
+ | def playerupdate(self): | ||
+ | |||
+ | #Durchläuft die Liste der Player | ||
+ | q=0 | ||
+ | while q < len(self.player): | ||
+ | |||
+ | #Chunkposition in der Liste | ||
+ | i = self.player[q].chunkpos[0] | ||
+ | e = self.player[q].chunkpos[1] | ||
+ | |||
+ | x = self.player[q].x #x-Pos des Spielers | ||
+ | y = self.player[q].y #y-Pos des Spielers | ||
+ | x0 = 0 #kleinste x-Pos der Chunks | ||
+ | y0 = 0 #kleinste y-Pos der Chunks | ||
+ | xn = self.worldsize #größte x-Pos der Chunks | ||
+ | yn = self.worldsize #größte x-Pos der Chunks | ||
+ | |||
+ | |||
+ | if x <= x0 or x >= xn or y <= y0 or y >= yn: | ||
+ | self.player[q].alive=False | ||
+ | else: | ||
+ | #Da sich die Map auch in den negativen Bereich erweitert, erhalten alle Chunks einen Shift. | ||
+ | self.player[q].chunkpos[1] = int(x/self.chunksize) | ||
+ | self.player[q].chunkpos[0] = int(y/self.chunksize) | ||
+ | |||
+ | q+=1 | ||
+ | </code> | ||
+ | Ist die Map hingegen unbegrenzt, so wird, sollte die Kreatur außerhalb der Mapgrenzen sein, diese in den gegenüberliegenden Chunk gesetzt. | ||
+ | <code> | ||
+ | #überprüft die Chunkposition des Spielers | ||
+ | def playerupdate(self): | ||
+ | |||
+ | #Durchläuft die Liste der Player | ||
+ | q=0 | ||
+ | while q < len(self.player): | ||
+ | |||
+ | #Chunkposition in der Liste | ||
+ | i = self.player[q].chunkpos[0] | ||
+ | e = self.player[q].chunkpos[1] | ||
+ | |||
+ | x = self.player[q].x #x-Pos des Spielers | ||
+ | y = self.player[q].y #y-Pos des Spielers | ||
+ | x0 = 0 #kleinste x-Pos der Chunks | ||
+ | y0 = 0 #kleinste y-Pos der Chunks | ||
+ | xn = self.worldsize #größte x-Pos der Chunks | ||
+ | yn = self.worldsize #größte x-Pos der Chunks | ||
+ | |||
+ | |||
+ | if x <= x0 or x >= xn or y <= y0 or y >= yn: | ||
+ | self.player[q].alive=False | ||
+ | else: | ||
+ | #Da sich die Map auch in den negativen Bereich erweitert, erhalten alle Chunks einen Shift. | ||
+ | self.player[q].chunkpos[1] = int(x/self.chunksize) | ||
+ | self.player[q].chunkpos[0] = int(y/self.chunksize) | ||
+ | |||
+ | q+=1 | ||
+ | </code> | ||
+ | Erzeugt wird die Map in der Funktion simulate_one_round(). Diese hat als Eingabe die Liste der mutierten Kreaturen und die Liste der Objekte aus der vorherigen Generation. Nach dem erstellen der Map laufen die Kreaturen solange auf dieser herum, bis alle Tod sind. Dabei tut jede Kreatur bei jedem Durchlauf der while-Schleife einen Schritt. Dazu wird zunächst die ganze Karte mit der Hintergrundfarbe "übermalt". Danach wird die bereits beschriebene Funktion playerupdate() ausgeführt, danach wird mit der Funktion hitbox() geprüft, ob die Kreaturen entsprechend stehen, dass sie Futter einsammeln und dann werden die Objekte mit drawobj() gezeichnet. Es wird nun bei jeder Kreatur die Stamina geprüft, und sollte diese zu niedrig sein, stirbt die Kreatur. Sollte die Kreatur dann noch leben, wird sie in einer zu ihrem Staminawert passendem Blauton gezeichnet (Dunkel: Hohe Stamina, Hell: Niedrige Stamina). Dann werden die Kreaturen über die in den Kreaturen vorhandenen move() Funktionen bewegt und zu guter Letzt wird noch einmal geprüft, ob noch wenigstens eine Kreatur lebt. Ist dies nicht der Fall, so wird die Schleife abgebrochen, sonst Fängt sie wieder von vorne an. | ||
+ | <code> | ||
+ | def simulate_one_round(playerl,objects): | ||
+ | global screen | ||
+ | |||
+ | clock = pygame.time.Clock() | ||
+ | |||
+ | map01 = Map(playerl,MAPSIZE,MAPSIZE//CHUNKANZAHL,objects)#erstellt die Map | ||
+ | |||
+ | moveframes = 5 #Anzahl der Frames | ||
+ | runde = 0 #Zählt die Schleifendurchläufe | ||
+ | done = False #Falls True beendet das Programm | ||
+ | winner = [] | ||
+ | while not done: | ||
+ | #clock.tick(50) #Für größere Pausen zwischen den Runden | ||
+ | |||
+ | #Beendet das Programm falls ... | ||
+ | for event in pygame.event.get(): | ||
+ | if event.type == pygame.QUIT: | ||
+ | done =True | ||
+ | |||
+ | screen.fill((0,255,0)) #Setzt die Hintergrundfarbe | ||
+ | map01.playerupdate() #Überprüft alle Spieler auf ihren Chunk | ||
+ | map01.hitbox() #Überprüft die Hitbox zu allen Objekten in diesem Chunk | ||
+ | map01.drawobj() #Zeichnet alle Objekte | ||
+ | for player in playerl: | ||
+ | if player.stamina<0: | ||
+ | player.alive=False | ||
+ | |||
+ | #Zeichnet den Spieler mit einer Farbe, abhängig von dem Ausdauer des spielers | ||
+ | for player in playerl: | ||
+ | if player.alive: | ||
+ | if player.stamina<player.max_stamina: | ||
+ | b=255-255/(player.max_stamina)*player.stamina | ||
+ | |||
+ | else: | ||
+ | b=0 | ||
+ | |||
+ | pygame.draw.rect(screen, (0,b,b),player.rect) | ||
+ | |||
+ | #Bewege die Spieler | ||
+ | for player in map01.player: | ||
+ | if player.alive: | ||
+ | player.move() | ||
+ | |||
+ | #Bei keine Spieler wird das Programm beendet | ||
+ | if map01.get_number_of_living_players()==0: | ||
+ | |||
+ | break | ||
+ | |||
+ | pygame.display.flip() | ||
+ | #time.sleep(20) | ||
+ | runde += 1 | ||
+ | |||
+ | print("Eine Simulationsrunde ist beendet")#Programm ist beendet | ||
+ | return map01 | ||
+ | </code> | ||
+ | Ein paar Worte zu der Funktion hitbox(): Diese unterscheidet sich abhängig davon, ob die map unendlich oder endlich ist. In beiden Versionen wird für jede Kreatur eine Liste mit den Objekten aus dem Chunk in dem sich diese Kreatur befindet und den an diesen Chunk angrenzenden Chunks erstellt. Dann wird geprüft, ob die Kreatur mit einem oder mehreren der Futterstücke "kollidiert", also ob sie diese frisst. Der Unterschied liegt in dem Erstellen dieser Listen mit Objekten. In der begrenzten Version wird für jeden angrenzenden Chunk geprüft, ob er sich von den Koordinaten des Chunks her auf der Karte befindet. | ||
+ | <code> | ||
+ | def hitbox(self): | ||
+ | #Durchläuft jeden Spieler | ||
+ | for p in self.player: #erstellt eine liste, in der alle Objekte des Chunks des Spielers und der Umgebenden Chunks gespeichert sind | ||
+ | if p.alive: | ||
+ | obj_list=[] | ||
+ | q = p.chunkpos[0] | ||
+ | w = p.chunkpos[1] | ||
+ | #Fügt der Liste Objekte aus Chunks zu, welche an den Chunk des spielers angrenzen | ||
+ | #und nicht außerhalb der Map liegen | ||
+ | for ob in self.maplist[q][w].objects: | ||
+ | obj_list.append(ob) | ||
+ | |||
+ | if q-1>=0: | ||
+ | for ob in self.maplist[q-1][w].objects: | ||
+ | obj_list.append(ob) | ||
+ | if w-1>=0: | ||
+ | for ob in self.maplist[q-1][w-1].objects: | ||
+ | obj_list.append(ob) | ||
+ | |||
+ | if w-1>=0: | ||
+ | for ob in self.maplist[q][w-1].objects: | ||
+ | obj_list.append(ob) | ||
+ | if q+1<int(self.worldsize/self.chunksize): | ||
+ | for ob in self.maplist[q+1][w-1].objects: | ||
+ | obj_list.append(ob) | ||
+ | |||
+ | if q+1<int(self.worldsize/self.chunksize): | ||
+ | for ob in self.maplist[q+1][w].objects: | ||
+ | obj_list.append(ob) | ||
+ | if w+1<int(self.worldsize/self.chunksize): | ||
+ | for ob in self.maplist[q+1][w+1].objects: | ||
+ | obj_list.append(ob) | ||
+ | |||
+ | if w+1<int(self.worldsize/self.chunksize): | ||
+ | for ob in self.maplist[q][w+1].objects: | ||
+ | obj_list.append(ob) | ||
+ | if q-1<=0: | ||
+ | for ob in self.maplist[q-1][w+1].objects: | ||
+ | obj_list.append(ob) | ||
+ | </code> | ||
+ | Bei der unendlichen Version wird hingegen, sollte der Chunk außerhalb der Kartengrenzen sein, der auf der gegenüberliegenden Seite liegende Chunk auf Futterstücke geprüft. | ||
+ | <code> | ||
+ | #Hitboxberechung der Objekte und den Spielern | ||
+ | def hitbox(self): | ||
+ | #Durchläuft jeden Spieler | ||
+ | |||
+ | |||
+ | for p in self.player: #erstellt eine liste, in der alle Objekte des Chunks des Spielers und der Umgebenden Chunks gespeichert sind | ||
+ | if p.alive: | ||
+ | obj_list=[] | ||
+ | q = p.chunkpos[0] | ||
+ | w = p.chunkpos[1] | ||
+ | #Wenn der entsprechende Neben dem Chunk des Spielers liegende Chunk außerhalb der Karte liegen sollte, | ||
+ | #wird stattdessen der an Chunk genommen, in den die Kreatur bei einer Grenzübertretung eintreten würde | ||
+ | if q-1<0: | ||
+ | q0 = int(self.worldsize/self.chunksize)-1 | ||
+ | else: | ||
+ | q0=q-1 | ||
+ | |||
+ | if w-1<0: | ||
+ | w0 = int(self.worldsize/self.chunksize)-1 | ||
+ | else: | ||
+ | w0=w-1 | ||
+ | |||
+ | if q+1>=int(self.worldsize/self.chunksize): | ||
+ | q2 = 0 | ||
+ | else: | ||
+ | q2=q+1 | ||
+ | |||
+ | if w+1>=int(self.worldsize/self.chunksize): | ||
+ | w2 = 0 | ||
+ | else: | ||
+ | w2=w+1 | ||
+ | |||
+ | for ob in self.maplist[q][w].objects: | ||
+ | obj_list.append(ob) | ||
+ | |||
+ | for ob in self.maplist[q][w0].objects: | ||
+ | obj_list.append(ob) | ||
+ | |||
+ | for ob in self.maplist[q][w2].objects: | ||
+ | obj_list.append(ob) | ||
+ | |||
+ | for ob in self.maplist[q0][w].objects: | ||
+ | obj_list.append(ob) | ||
+ | |||
+ | for ob in self.maplist[q0][w0].objects: | ||
+ | obj_list.append(ob) | ||
+ | |||
+ | for ob in self.maplist[q0][w2].objects: | ||
+ | obj_list.append(ob) | ||
+ | |||
+ | for ob in self.maplist[q2][w].objects: | ||
+ | obj_list.append(ob) | ||
+ | |||
+ | for ob in self.maplist[q2][w0].objects: | ||
+ | obj_list.append(ob) | ||
+ | |||
+ | for ob in self.maplist[q2][w2].objects: | ||
+ | obj_list.append(ob) | ||
+ | </code> | ||
+ | Der Rest läuft bei beiden Versionen gleich ab, die noch nicht gefressenen Objekte die sich in Reichweite der Kreatur befinden werden darauf geprüft, ob sie mit der Kreatur "kollidieren". Ist dies der Fall, frisst die Kreatur sie. | ||
+ | <code> | ||
+ | #überprüft die Objekte der Liste auf ihre erreichbarkeit in einem Schritt und ob sie noch nicht gefressen wurden | ||
+ | for ob in obj_list: | ||
+ | if (p.x-ob.x)**2+(p.y-ob.y)**2<(p.speed*10)**2 and ob.status: | ||
+ | #Prüft dann, ob der Spieler mit dem Objekt kollidiert | ||
+ | if p.rect.colliderect(ob.rect): | ||
+ | #Frißt dann das Objekt | ||
+ | ob.status = False | ||
+ | |||
+ | p.stamina+=ob.stamina | ||
+ | p.collected_food+=1 | ||
+ | </code> | ||
+ | Die Funktion simulate_one_round() gibt schlussendlich die map zurück, und zwar an den Hauptcode. Dieser besteht im Grunde aus zwei while Schleifen. Die erste durchläuft die angegebene Anzahl an Durchläufen des Codes, die zweite durchläuft die angegebene Anzahl an Generationen. Vor jedem Durchlauf werden erst noch einige Zähler und die Statistiken vorbereitet. Außerdem wird durch die Funktion make_first_generation() die erste Generation Kreaturen erschaffen. Dafür werden so viele Kreaturen erschaffen wie vorgegeben und in einer Liste (playerl) gespeichert. | ||
+ | <code> | ||
+ | a=0 | ||
+ | while a<5: #Anzahl der Durchläufe des Programms | ||
+ | if __name__=="__main__": | ||
+ | n=0 #Generationen -Zähler | ||
+ | best=0 #Zähler für die am längsten überlebende Kreatur | ||
+ | list_generation_number=[] #Liste für die Generationennummer | ||
+ | |||
+ | pygame.init() | ||
+ | screen = pygame.display.set_mode((MAPSIZE,MAPSIZE)) #erstellt den Hauptbildschirm | ||
+ | |||
+ | playerl = make_first_generation() | ||
+ | |||
+ | survived_objects=0 #für die erste Generation Kennzeichen, dass es noch keine vorherige Objekte gibt | ||
+ | #für spätere Generationen eine Liste mit den überlebenden Objekten | ||
+ | |||
+ | list_menge=[] #Liste der anzahl der Kreaturen in jeder Generation | ||
+ | list_object_menge=[] #Liste der anzahl der Objekte in jeder Generation | ||
+ | |||
+ | #erstellen von Objekten für Statistiken. Der angegebene "Name" gibt jeweils die betrachtete Eigenschaft an. Achtung: Richtig schreiben. | ||
+ | speed_statistic=statistic("speed") | ||
+ | size_statistic=statistic("size") | ||
+ | sight_statistic=statistic("sight") | ||
+ | fat_statistic=statistic("fat") | ||
+ | survived_steps_statistic=statistic("survived_steps") | ||
+ | </code> | ||
+ | Darauf folgt die Generationen Schleife. Diese läuft so lange, bis die Spezies ausstirbt, maximal aber über so viele Generationen wie vorgegeben (hier: 1000 Generationen). In jeder Generation wird die Funktion simulate_one_round() einmal ausgeführt, um die entsprechende Generation zu simulieren. Danach werden die Listen für die Statistiken mit den entsprechenden Werten gespeist und die beste Kreatur ermittelt, das heißt es wird geprüft, ob es in dieser Generation eine Kreatur gab, die länger überlebte als alle vorherigen Kreaturen. Dann wird aus der Generation eine neue Generation Kreaturen mit der Funktion make_selected_and_mutated_generation() erzeugt. Außerdem wird geprüft, ob in dieser neuen Generation überhaupt noch Kreaturen vorhanden sind. Wenn nicht wird die Schleife abgebrochen. Zum Schluss werden noch die nicht gefressenen Futterstücke herausgefiltert und für die nächste Generation in eine Liste gepackt. | ||
+ | <code> | ||
+ | while n<1000: #Anzahl der mximal betrachteten Generationen | ||
+ | #while True: | ||
+ | |||
+ | list_generation_number.append(n) #Fügt die Nummer der Generation zur Liste hinzu | ||
+ | |||
+ | map01 = simulate_one_round(playerl,survived_objects) | ||
+ | |||
+ | #Menge der Objekte auf der Map | ||
+ | ob_menge=0 | ||
+ | for l in map01.maplist: | ||
+ | for c in l: | ||
+ | for o in c.objects: | ||
+ | ob_menge+=1 | ||
+ | |||
+ | list_object_menge.append(ob_menge) | ||
+ | list_menge.append(len(map01.player)) | ||
+ | |||
+ | #Einfügen der Werte in die Statistiken | ||
+ | speed_statistic.eingabe(map01.player) | ||
+ | size_statistic.eingabe(map01.player) | ||
+ | sight_statistic.eingabe(map01.player) | ||
+ | fat_statistic.eingabe(map01.player) | ||
+ | survived_steps_statistic.eingabe(map01.player) | ||
+ | |||
+ | |||
+ | n+=1 | ||
+ | #sucht den besten Spieler über alle Generationen | ||
+ | for pl in map01.player: | ||
+ | pl.generation=n | ||
+ | if pl.survived_steps>best: | ||
+ | best_player=pl | ||
+ | best_player_generation=n | ||
+ | best_player_round=a | ||
+ | best=pl.survived_steps | ||
+ | |||
+ | playerl = make_selected_and_mutated_generation(playerl) | ||
+ | |||
+ | if playerl == []: | ||
+ | print("Das Leben hat keinen Weg gefunden!") | ||
+ | break | ||
+ | |||
+ | survived_objects=[] | ||
+ | |||
+ | #fügt den survived objects die überlebenden Objekte zu | ||
+ | for l in map01.maplist: | ||
+ | for c in l: | ||
+ | for o in c.objects: | ||
+ | if o.status: | ||
+ | survived_objects.append(o) | ||
+ | survived_objects.sort(key = lambda o: -o.x) | ||
+ | </code> | ||
+ | Dieses make_selected_and_mutated_generation() funktioniert je nach Einstellung unterschiedlich. Ist eingestellt, dass die Kreaturen in Abhängigkeit der erreichten Werte der Vorgängerversion (CREATURE_GENERATION_PER_OLD_CREATURE = True) erzeugt werden, so wird zunächst eine Liste erstellt, in welcher alle Kreaturen gespeichert sind, welche bestimmte Anforderungen an die Anzahl der gemachten Schritte und des gesammelten Futters erfüllen. Diese erzeugen dann jeweils so viele Nachkommen, wie es der Anzahl der gemachten Schritte und des gesammelten Futters angemessen ist, das heißt für je COLLECTED_FOOD_FOR_CHILD gesammelte Futter und für je SURVIVED_STEPS_FOR_CHILD gemachte Schritte wird die Kreatur einmal kopiert und mutiert. | ||
+ | <code> | ||
+ | def make_selected_and_mutated_generation(playerl,reduce_generation = 10): | ||
+ | if CREATURE_GENERATION_PER_OLD_CREATURE: | ||
+ | possible_player=[] | ||
+ | new_playerl = [] | ||
+ | for player in playerl: #erstellt liste mit Kreaturen, welche die Fortpflanzungskriterien erfüllen | ||
+ | if player.collected_food>=MIN_COLLECTED_FOOD and player.survived_steps>MIN_SURVIVED_STEPS: | ||
+ | possible_player.append(player) | ||
+ | for player in possible_player: #erstellt aus diesen Kreaturen eine Anzahl mutierte Kopien der Kreaturen, | ||
+ | s=0 #die der Menge gesammelten Futters und der Anzahl der überlebten Schritte entspricht. | ||
+ | f=0 | ||
+ | while f<player.collected_food: | ||
+ | newplayer = copy.copy(player) | ||
+ | newplayer.model = copy.deepcopy(player.model) | ||
+ | newplayer.chunkpos = [0,0] | ||
+ | newplayer.color = (27,27,27) | ||
+ | newplayer.alive = True | ||
+ | newplayer.dist = [] | ||
+ | newplayer.fit = 0 | ||
+ | newplayer.survived_steps = 0 | ||
+ | newplayer.total_stamina = 0 | ||
+ | newplayer.model.mutate_params(0.03) | ||
+ | newplayer.set_start_position(random.randint(MAPSIZE*0.1,MAPSIZE*0.9),random.randint(MAPSIZE*0.1,MAPSIZE*0.9)) | ||
+ | newplayer.past = [[],[],[]] | ||
+ | newplayer.mutate() | ||
+ | new_playerl.append(newplayer) | ||
+ | f+=COLLECTED_FOOD_FOR_CHILD | ||
+ | |||
+ | while s<player.survived_steps: | ||
+ | newplayer = copy.copy(player) | ||
+ | newplayer.model = copy.deepcopy(player.model) | ||
+ | newplayer.chunkpos = [0,0] | ||
+ | newplayer.color = (27,27,27) | ||
+ | newplayer.alive = True | ||
+ | newplayer.dist = [] | ||
+ | newplayer.fit = 0 | ||
+ | newplayer.survived_steps = 0 | ||
+ | newplayer.total_stamina = 0 | ||
+ | newplayer.model.mutate_params(0.03) | ||
+ | newplayer.set_start_position(random.randint(MAPSIZE*0.1,MAPSIZE*0.9),random.randint(MAPSIZE*0.1,MAPSIZE*0.9)) | ||
+ | newplayer.past = [[],[],[]] | ||
+ | newplayer.mutate() | ||
+ | new_playerl.append(newplayer) | ||
+ | s+=SURVIVED_STEPS_FOR_CHILD | ||
+ | </code> | ||
+ | Ist hingegen CREATURE_GENERATION_PER_OLD_CREATURE = False, so wird jede Runde aus den am längsten überlebenden Kreaturen der Elterngeneration eine feste Anzahl an neuen Kreaturen kopiert und mutiert. | ||
+ | <code> | ||
+ | else: #erstellt eine feste Anzahl an Kreaturen, sollte es entsprenchen in constatns festgelegt sein | ||
+ | playerl.sort(key = lambda player: -player.survived_steps) | ||
+ | possible_player=[] | ||
+ | new_playerl = [] | ||
+ | print("mut",len(playerl)) | ||
+ | for player in playerl: | ||
+ | if player.collected_food!=0: | ||
+ | possible_player.append(player) | ||
+ | for player in possible_player[:int(PLAYERCOUNT/reduce_generation)]: | ||
+ | for i in range(reduce_generation): | ||
+ | newplayer = copy.copy(player) | ||
+ | newplayer.alive = True | ||
+ | newplayer.survived_steps = 0 | ||
+ | newplayer.stamina = START_STAMINA | ||
+ | newplayer.collected_food = 0 | ||
+ | newplayer.mutate() | ||
+ | newplayer.movement_parameters() | ||
+ | |||
+ | newplayer.set_start_position(random.randint(MAPSIZE*0.1,MAPSIZE*0.9),random.randint(MAPSIZE*0.1,MAPSIZE*0.9)) | ||
+ | new_playerl.append(newplayer) | ||
+ | print(len(new_playerl)) | ||
+ | return new_playerl | ||
+ | </code> | ||
+ | Ganz zum Schluss jedes Durchlaufs werden die Graphen für die Statistiken erstellt: | ||
+ | <code> | ||
+ | #Plotten der Graphen | ||
+ | b=a*2 | ||
+ | |||
+ | plt.figure(b) | ||
+ | plt.subplot(221) | ||
+ | speed_statistic.plot(list_generation_number,b) | ||
+ | plt.subplot(222) | ||
+ | size_statistic.plot(list_generation_number,b) | ||
+ | plt.subplot(223) | ||
+ | sight_statistic.plot(list_generation_number,b) | ||
+ | plt.subplot(224) | ||
+ | fat_statistic.plot(list_generation_number,b) | ||
+ | |||
+ | b+=1 | ||
+ | plt.figure(b) | ||
+ | plt.subplot(221) | ||
+ | survived_steps_statistic.plot(list_generation_number,b) | ||
+ | plt.subplot(222) | ||
+ | plt.plot(list_generation_number,list_menge) | ||
+ | plt.subplot(223) | ||
+ | plt.plot(list_generation_number,list_object_menge) | ||
+ | |||
+ | a+=1 | ||
+ | </code>\\ | ||
+ | **player**\\ | ||
+ | Die Kreaturen, auch **Player** genannt, haben eine Vielzahl von Eigenschaften, die auch von der Version des Codes abhängt. Die wichtigsten sind sicher der Status (am Leben oder nicht), die Position (x und y), die Größe (angegeben als eine halbe Seitenlänge), die Sichtweite (sight), die Geschwindigkeit (Speed), die Ausdauer (Stamina) und der Winkel (angle). Darüber hinaus gibt es aber noch weitere Eigenschaften. Betrachte dafür die Initialisierungsfunktion für Spieler mit neuronalem Netz: | ||
+ | <code> | ||
+ | #Initialisiert den Spieler | ||
+ | def __init__(self,x,y,speed,name): | ||
+ | self.x=x #x-Pos | ||
+ | self.y=y #y-Pos | ||
+ | self.size = 1 #Größe | ||
+ | self.sight = 10 #Sichtweite | ||
+ | self.fat = 0 #Die Dicke der Fettschicht der Kreaturen | ||
+ | self.speed = 1 #Geschwindigkeit | ||
+ | self.inner_temprature = 0 #Die Innere Temperatur der Kreaturen | ||
+ | self.skin_temprature = 0 #Die Hauttemperatur der Kreaturen | ||
+ | self.status = True #Aktiv oder nicht | ||
+ | self.chunkpos = [0,0] #Speichert die Position des Chunks | ||
+ | self.stamina = START_STAMINA #Ausdauer | ||
+ | self.angle = 0 #Laufwinkel | ||
+ | self.anglechange = 45 | ||
+ | self.rect=pygame.Rect(int(self.x)-int(self.size),int(self.y)-int(self.size),self.size*2,self.size*2)#Rechteck in Pygame | ||
+ | self.name=name #ID | ||
+ | self.vx = 0#?? | ||
+ | self.vy = 0 | ||
+ | self.status = True | ||
+ | self.alive = True #ist die Kreatur noch am Leben? | ||
+ | self.viewpoint = 0 | ||
+ | self.survived_steps = 0 #Anzahl der überlebten Schritte | ||
+ | self.sichtfeld = 0 #die Abdeckung des Sichtfeldes | ||
+ | self.viewangle = 60 #Der Winkel des Sichtfeldes | ||
+ | self.v = False | ||
+ | self.world = None | ||
+ | self.past = [[],[],[]] #Die "Erinnerung" der Kreatur an die letzten Schritte | ||
+ | self.model = make_model() | ||
+ | self.dist = [] | ||
+ | self.minangle = 0 | ||
+ | self.minspeed = 0 | ||
+ | self.maxangle = 0 | ||
+ | self.maxspeed = 0 | ||
+ | self.fit = 0 | ||
+ | self.color = (27,27,27) | ||
+ | self.collected_food = 0 #Menge des eingesammelten Futters | ||
+ | self.total_stamina = 0 | ||
+ | </code> | ||
+ | Alle Kreaturen haben die Mutation() Funktion. In dieser werden einige Eigenschaften festgelegt, bzw. verändert. Wenn die Kreaturen zur ersten Generation gehören, werden die Eigenschaften neu festgelegt: | ||
+ | <code> | ||
+ | if initial: | ||
+ | self.speed = random.randint(1,2) | ||
+ | self.size = random.randint(3,6) | ||
+ | self.sight = random.randint(10,30)*self.size | ||
+ | self.angle = random.randint(0,360) | ||
+ | self.fat = random.randint(0,max(int(self.size/2),1)) | ||
+ | self.memory_length = random.randint(0,5) | ||
+ | </code> | ||
+ | Wenn die Kreaturen nicht zur ersten Generation gehören, sind sie ja Kopien von Kreaturen der vorherigen Generation. Es sind also auch diese Eigenschaften übernommen worden. Diese werden also nur leicht verändert: | ||
+ | <code> | ||
+ | else: | ||
+ | self.speed = self.speed+random.choice(range(-9,10))*0.01 | ||
+ | self.sight = self.sight+random.choice(range(-9,10))*0.01*self.sight | ||
+ | self.size = self.size+random.choice(range(-9,10))*0.02 | ||
+ | self.fat = self.fat+random.choice(range(-9,10))*0.01 | ||
+ | self.memory_length = self.memory_length+random.choice([-1,0,1]) | ||
+ | if self.fat <0: | ||
+ | self.fat==0 | ||
+ | elif self.fat >self.size/2: #Fettschicht kann nicht dicker sein als ein viertel der Größe | ||
+ | self.fat=self.fat/2 | ||
+ | </code> | ||
+ | Zum Schluss werden noch einige Eigenschaften festgelegt, die in jeder Generation neu gesetzt werden: | ||
+ | <code> | ||
+ | self.death=random.randint(1,MAX_DEATH) | ||
+ | self.stamina = START_STAMINA+self.fat*self.size | ||
+ | self.max_stamina = self.stamina*STAMINA_KOEFFIZIENT | ||
+ | </code> | ||
+ | Nur die Kreaturen, deren Bewegung auf Basis von Polynomen abläuft, haben die Funktion movement_parameters(). Über diese werden die Polynome, auf denen ja die ganze Reaktion beruht, erstellt bzw. verändert, je nachdem ob die Kreatur aus der ersten Generation ist oder nicht. Dies geschieht über die Klasse der Polynome, auf die ich gleich noch eingehe. Außerdem wird noch die aktuelle Länge des jeweiligen Polynoms an die Kreatur zurückgegeben. | ||
+ | <code> | ||
+ | def movement_parameters(self,initial=False): | ||
+ | |||
+ | if initial: #wählt eine Länge l des Polynoms und erzeugt dieses | ||
+ | l=random.randint(2,10) | ||
+ | #Die eingaben für die erstellung des Polynoms sind Länge, Minimale und Maximale Potenz, Minimale und Maximale Koeffizienten, | ||
+ | #Minimale und Maximale Ausgabe des Polynoms sowie die Liste der Möglichen Eingaben in das Polynom und die aktuellen Länge des | ||
+ | #Gedächtnisses für evtl. tiefere Erinnerungspolynome | ||
+ | self.poly_move=polynom(l,0,10,0,10,0,10,["m","s"],self.memory_length) | ||
+ | l=random.randint(2,10) | ||
+ | self.poly_angle=polynom(l,0,10,0,10,0,10,["m","s"],self.memory_length) | ||
+ | l=random.randint(2,10) | ||
+ | self.poly_input=polynom(l,0,10,0,10,0,10,["f","u","k"],0,True) | ||
+ | else: #verändert das Polynom in Länge und dem Wert der Koeffizienten | ||
+ | self.poly_move.change_poly | ||
+ | self.poly_angle.change_poly | ||
+ | self.poly_input.change_poly | ||
+ | |||
+ | self.complexity_movement = self.poly_move.length | ||
+ | self.complexity_angle = self.poly_angle.length | ||
+ | </code> | ||
+ | Hingegen alle Kreaturen haben die Funktionen looking_forward() und sightcheck(). Diese sind für das "nach vorne schauen" der Kreaturen. Diese geben also die Abdeckung des Sichtfeldes zurück. Aufgerufen werden diese Funktionen über die move() Funktion. Diese bewegt die Kreatur. Hier wird zunächst die Stamina der Kreatur geprüft. Liegt diese über dem maximalen Staminawert, den die Kreatur annehmen kann und der bei der Mutation festgelegt wird, so wird er auf diesen zurückgesetzt. Dann wird unter info eine Liste von allen Objekten gespeichert, die möglicherweise in Reichweite sind. Diese Liste wird dann in Looking_forward() eingegeben und unter sichtfeld wird dann zurückgegeben, wie sehr das Sichtfeld der Kreatur mit Futter bedeckt ist. | ||
+ | <code> | ||
+ | #bewegt den Spieler | ||
+ | def move(self): | ||
+ | #Überprüfe, ob die Maximale Stamina überschritten wird und verringere die Stamina, sollte dem so sein | ||
+ | if self.stamina>self.max_stamina: | ||
+ | self.stamina=self.max_stamina | ||
+ | |||
+ | angle_old = self.angle | ||
+ | info = self.world.get_info(self) | ||
+ | |||
+ | if len(info)!=0: | ||
+ | self.looking_forward(info) | ||
+ | a = self.sichtfeld | ||
+ | else: | ||
+ | a=0 | ||
+ | |||
+ | if self.survived_steps>self.death: | ||
+ | self.alive=False | ||
+ | </code> | ||
+ | Nun gibt es drei Möglichkeiten:\\ | ||
+ | 1. Hat die Kreatur schon mehr Schritte überlebt als die Variable death, welche unter mutate() festgelegt wurde groß ist, so stirbt die Kreatur. Dies begrenzt die maximal mögliche Schrittzahl der Kreatur. | ||
+ | <code> | ||
+ | if self.survived_steps>self.death: | ||
+ | self.alive=False | ||
+ | </code> | ||
+ | 2. Ist dies nicht der Fall und hat die Kreatur genügend Stamina für den nächsten Schritt, so wird dieser durchgeführt. Dieser Teil ist wieder von der Version abhängig. Im Falle neuronaler Netze wird eine Liste von Werten (inputlist), bestehend aus dem sichtfeld und der Erinnerung der Kreatur erstellt und durch das neuronale Netz geprüft. Dieses gibt dann die entsprechenden Reaktionen für die Veränderung des Winkels und der Schrittweite zurück. | ||
+ | <code> | ||
+ | elif self.stamina >= self.speed+self.size**2+self.sight*0.25: | ||
+ | |||
+ | inputlist = [] | ||
+ | inputlist.append(a) | ||
+ | if len(self.past[0])>4: | ||
+ | for i in self.past[0][-5:]: | ||
+ | inputlist.append(1/(i+1)) | ||
+ | for i in self.past[1][-5:]: | ||
+ | inputlist.append(1/(i+1)) | ||
+ | for i in self.past[2][-5:]: | ||
+ | inputlist.append(1/(i+1)) | ||
+ | else: | ||
+ | for i in range(15): | ||
+ | inputlist.append(0) | ||
+ | #print(len(inputlist)) | ||
+ | #print(inputlist) | ||
+ | #print(len(inputlist)) | ||
+ | #print(inputlist) | ||
+ | new_angle , new_speed = self.model.calc(inputlist) | ||
+ | </code> | ||
+ | Dann wird diese neue Veränderung zum Gedächtnis hinzugefügt und die Veränderung des x und y Wertes der Kreatur errechnet. | ||
+ | <code> | ||
+ | self.angle += new_angle | ||
+ | self.angle = self.angle%360 | ||
+ | self.speed += new_speed | ||
+ | self.speed = min(max(0, self.speed),3) | ||
+ | self.past[0].append(self.angle/360) | ||
+ | self.past[1].append(self.speed/10) | ||
+ | self.past[2].append(a/300) | ||
+ | |||
+ | |||
+ | self.x+=self.speed*math.cos(self.angle/180*math.pi) | ||
+ | self.y+=self.speed*math.sin(self.angle/180*math.pi) | ||
+ | self.rect=pygame.Rect(int(self.x)-int(self.size),int(self.y)-int(self.size),self.size*2,self.size*2) | ||
+ | </code> | ||
+ | Zum Schluss wird der durch den Schritt erlittene Energieverlust errechnet. Dieser besteht aus einem Grundverlust, den jede Kreatur jede Runde erleidet (MIN_STAMINA_LOSS_PER_STEP), dem Verlust durch die Temperatur der Umgebung, welche sich aus dem gesamten Umfang der Kreatur multipliziert mit dem Temperaturunterschied zwischen der Hauttemperatur der Kreatur und der Umgebungstemperatur errechnet und dem Verlust an Energie durch die Eigenschaften der Kreatur. | ||
+ | <code> | ||
+ | ## Es ist wichtig, eine Modellierungs zu machen, die nicht | ||
+ | ## schon ein eindeutiges Evolutionsergebnis nahelegt. | ||
+ | ## Schneller zu gehen, sollte mehr Energie kosten. | ||
+ | ## Ein größeres Areal abzufressen sollte eine Energie | ||
+ | ## kosten, die proportional zum Areal ist. | ||
+ | self.skin_temprature=self.inner_temprature-self.fat | ||
+ | |||
+ | #Der Energieverlust durch Temperatur ergibt sich aus dem Umfang, also dem freiligenden Teil des Körpers | ||
+ | energy_loss_through_temprature=(self.skin_temprature-TEMPRATURE)*8*self.size | ||
+ | |||
+ | if energy_loss_through_temprature<0: | ||
+ | energy_loss_through_temprature=0 | ||
+ | |||
+ | #Der Stamina-verlust an sich: | ||
+ | |||
+ | self.stamina -= MIN_STAMINA_LOSS_PER_STEP+0.5*(abs(self.speed)**2+self.size) | ||
+ | self.survived_steps += 1 | ||
+ | #self.stamina -= 1*(5+energy_loss_through_temprature+0.1*(self.speed+0.5*self.size**2+self.sight*0.2) ) | ||
+ | self.total_stamina += self.stamina | ||
+ | #self.survived_steps += 1 | ||
+ | </code> | ||
+ | 3. Hat die Kreatur nicht genügend Energie, so stirbt sie. | ||
+ | <code> | ||
+ | else: | ||
+ | self.fit = self.total_stamina #self.survived_steps | ||
+ | self.alive = False | ||
+ | </code> | ||
+ | Kreaturen mit Polynomen haben nun noch die Klasse Polynom. Jedes Polynom hat einige Eigenschaften. | ||
+ | <code> | ||
+ | length=0 #Startlänge des Polynoms | ||
+ | poly=[] #Das Polynom an sich | ||
+ | min_potenz=0 #minimale Startpotenz | ||
+ | max_potenz=0 #maximale Startpotenz | ||
+ | min_koeffizient=0 #min Startkoeffizient | ||
+ | max_koeffizient=0 #max Startkoeffizient | ||
+ | min_output=0 #min Ausgabe | ||
+ | max_output=0 #max Ausgabe | ||
+ | </code> | ||
+ | Darüber hinaus hat jedes Polynom noch eine Liste (poly), in welchem das Polynom an sich gespeichert ist. Wird ein Polynom Initialisiert, so wird es auch direkt erstellt. Dafür werden der Länge des Polynoms entsprechend viele Monome bzw. Listen erstellt. In diesen ist dann jeweils der Koeffizient (a), die Potenz(b) und die Art des Inputs in das Monom (c) gespeichert. Letzteres könnte eine Zahl oder eine Liste sein. Liegt eine Liste vor, so nennen wir das ein Erinnerungspolynom, weil hier die Erinnerung der Kreatur eingesetzt wird. In diesem Fall wird für dieses Erinnerungspolynom als eigenes Polynom in dem Polynom erstellt. Dieses bezeichnen wir auch als Deep Poly und markieren das entsprechend durch die Variable deep_poly. Dadurch wird gekennzeichnet, dass in diesem Polynom keine weiteren Polynome stecken. | ||
+ | <code> | ||
+ | def __init__(self,l,minp,maxp,mink,maxk,mino,maxo,dif_monos,memory_length,deep_poly=False): | ||
+ | self.length=l | ||
+ | self.min_potenz=minp | ||
+ | self.max_potenz=maxp | ||
+ | self.min_koeffizient=mink | ||
+ | self.max_koeffizient=maxk | ||
+ | self.min_output=mino | ||
+ | self.max_output=maxo | ||
+ | self.poly=[] | ||
+ | self.deep_poly=deep_poly #ist das Polynom ein tiefes Polynom, also ein Polynom zur einbindung der Erinnerung | ||
+ | |||
+ | n=0 | ||
+ | while n<self.length: #erstellen des Polynoms | ||
+ | a=random.choice(range(self.min_koeffizient,self.max_koeffizient+1)) | ||
+ | b=random.choice(range(self.min_potenz,self.max_potenz+1)) | ||
+ | c=random.choice(dif_monos) #c legt die Art des Inputs fest mit hier mr=Erinnerung der Schrittweite, ma=Erinnerung der Winkeländerung und mf=Erinnerung der Futterabdeckung | ||
+ | if self.deep_poly==False and c=="m": #Für Erinnerungspolynome | ||
+ | new_poly=polynom(memory_length,0,10,0,10,0,1,["mf","mr","ma"],0,True) #Erinnerungspolynom wird erstellt | ||
+ | self.poly.append([a,b,c,new_poly]) | ||
+ | else: | ||
+ | self.poly.append([a,b,c,[]]) | ||
+ | n+=1 | ||
+ | </code> | ||
+ | Jedes Polynom hat die Möglichkeit, sich zu verändern, dies geschieht durch die Funktion change_poly(). Diese wird immer dann genutzt, wenn eine neue Generation gebildet wird. Hierbei wird zunächst evtl. die Länge des Polynoms geändert. Ist dies der Fall wird, wenn nötig, eine entsprechende Anzahl an Monomen hinzugefügt und das Polynom neu gemischt. | ||
+ | <code> | ||
+ | def change_poly(self,memory_length): #Änderung, also Mutation des Polynoms | ||
+ | n=0 | ||
+ | self.A=[] | ||
+ | if self.length<2: | ||
+ | self.length=random.choice(range(0,2))+self.length | ||
+ | else: | ||
+ | self.length=random.choice(range(-1,2))+self.length | ||
+ | while self.length>len(self.poly): | ||
+ | a=random.choice(range(self.min_koeffizient,self.max_koeffizient+1)) | ||
+ | b=random.choice(range(self.min_potenz,self.max_potenz+1)) | ||
+ | c=random.choice(dif_monos_list) #c legt die Art des Inputs fest mit hier mr=Erinnerung der Schrittweite, ma=Erinnerung der Winkeländerung und mf=Erinnerung der Futterabdeckung | ||
+ | if self.deep_poly==False and c==1: #Für Erinnerungspolynome | ||
+ | new_poly=polynom(memory_length,0,10,0,10,0,1,3,0,True) #Erinnerungspolynom wird erstellt | ||
+ | self.poly.append([a,b,c,new_poly]) | ||
+ | else: | ||
+ | self.poly.append([a,b,c,[]]) | ||
+ | random.shuffle(self.poly) | ||
+ | </code> | ||
+ | Danach wird so lange ein Monom aus der Liste genommen und vom Koeffizienten und der Potenz her leicht verändert und dem neuen Polynom hinzugefügt, bis die angestrebte Länge des Polynoms erreicht ist. dieses neue Polynom wird dann zu Stammpolynom. | ||
+ | <code> | ||
+ | while n<self.length: | ||
+ | mutation=random.choice(range(0,MUTATIONSWAHRSCHEINLICHKEIT_VERHALTEN)) | ||
+ | if mutation==0: | ||
+ | a=random.choice(range(-5,6)) | ||
+ | b=random.choice(range(-9,10)) | ||
+ | aneu=(self.poly[n][0])+0.01*a | ||
+ | bneu=(self.poly[n][1])+0.01*b | ||
+ | self.A.append([aneu,bneu,self.poly[n][2],self.poly[n][3]]) | ||
+ | else: | ||
+ | self.A.append(self.poly[n]) | ||
+ | |||
+ | n+=1 | ||
+ | self.poly=self.A | ||
+ | </code> | ||
+ | Zum Schluss gibt es noch zwei Reaktionsfunktionen, eine für Polynome mit möglichen Unterpolynomen (reaction()) und eine für Polynome definitiv ohne Unterpolynome (Deep_reactio()). \\ | ||
+ | Für reaction() werden die Eingaben Sichtfeld(s) und die Liste der Erinnerungen (Memory) benötigt. Es wird die Liste der Monome durchgegangen. Je nach Wert von c wird entweder s eingesetzt oder es wird eine Deep_reaction() mit der Liste der Erinnerungen ausgelöst. | ||
+ | <code> | ||
+ | def reaction(self,s,memory): #Reaktion der Kreatur auf einen bestimmten Umstand für das normale Polynom | ||
+ | c=0 | ||
+ | |||
+ | for el in self.poly: | ||
+ | |||
+ | if el[0]<0: | ||
+ | a=random.randint(0,10) | ||
+ | else: | ||
+ | a=el[0] | ||
+ | |||
+ | if el[1]<0: | ||
+ | b=random.randint(0,10) | ||
+ | else: | ||
+ | b=el[1] | ||
+ | |||
+ | if el[2]=="s": | ||
+ | x=s | ||
+ | elif el[2]=="m": | ||
+ | x=el[3].deep_reaction(memory) | ||
+ | </code> | ||
+ | Es wird dann der Wert von c an den bei der Initialisierung angegebenen Maximalwert des Outputs angepasst und c ausgegeben. | ||
+ | <code> | ||
+ | c=c+a*x**b | ||
+ | #c=c/len(poly) | ||
+ | while c>self.max_output: | ||
+ | c=self.max_output-(c-self.max_output) | ||
+ | while c<self.min_output: | ||
+ | c=c+(self.min_output-c) | ||
+ | |||
+ | return c | ||
+ | </code> | ||
+ | Die Deep_reaction() läuft ähnlich ab, es werden aber nicht mehr Monome betrachtet als die Erinnerung lang ist. Von dem Wert c hängt es ab, welcher Teil der Erinnerung betrachtet wird: Die Veränderung des Winkels, die Schrittweite oder die Eingabe. | ||
+ | <code> | ||
+ | def deep_reaction(self,l): #Reaktion für erinnerungspolynome | ||
+ | if self.deep_poly: | ||
+ | c=0 | ||
+ | N=min(len(self.poly),len(l)) | ||
+ | n=0 | ||
+ | |||
+ | while n<N: | ||
+ | |||
+ | if self.poly[n][0]<0: | ||
+ | a=random.randint(0,10) | ||
+ | else: | ||
+ | a=self.poly[n][0] | ||
+ | |||
+ | if self.poly[n][1]<0: | ||
+ | b=random.randint(0,10) | ||
+ | else: | ||
+ | b=self.poly[n][1] | ||
+ | |||
+ | if self.poly[n][2]=="mr": | ||
+ | x=l[n][0] | ||
+ | |||
+ | elif self.poly[n][2]=="ma": | ||
+ | x=l[n][1] | ||
+ | |||
+ | elif self.poly[n][2]=="mf": | ||
+ | x=l[n][2] | ||
+ | |||
+ | c=c+a*x**b | ||
+ | n+=1 | ||
+ | #c=c/len(poly) | ||
+ | while c>self.max_output: | ||
+ | c=self.max_output-(c-self.max_output) | ||
+ | while c<self.min_output: | ||
+ | c=c+(self.min_output-c) | ||
+ | |||
+ | return c | ||
+ | |||
+ | else: | ||
+ | return 0 | ||
+ | </code> | ||
+ | Zum Schluss gibt es noch die Funktion show_poly() welche dazu da ist, zum Schluss des map codes, wenn das Polynom der am längsten überlebenden Kreatur betrachtet werden soll, das ganze Polynom mit all seien unterpolynomen zu zeigen. | ||
+ | <code> | ||
+ | def show(self): #zeigt das Polynom als ganzes, mit allen tieferen Polynomen | ||
+ | self.A = [] | ||
+ | for p in self.poly: | ||
+ | if p[2]=="s": | ||
+ | self.A.append(p) | ||
+ | if p[2]=="m": | ||
+ | deep=p[3].poly | ||
+ | self.A.append([p[0],p[1],p[2],deep]) | ||
+ | </code> | ||
+ | |||
+ | |||
+ | \\ | ||
+ | |||
+ | |||
+ | |||
+ |