====== Visualisierungscode ====== Um eine Evolution erfolgreich darstellen zu können, brauchen wir, neben dem Hauptcode, auch einen idealen Visualisierungscode. ===== Turtle ===== Für **analytische Zwecke** nutzten wir durchgehend Turtle. Das Programm zeigte uns präzise das Verhalten unserer virtuell erschafften Kreaturen an. Besonders hilfreich wurde das Paket bei der Programmierung der Vererbung und bei ersten Tests aller Ideen. Sehr gut war bei Turtle erkennbar, ob eine Tochtergeneration tatsächlich auf die Werte der Mutter zugreift oder nicht. {{ :ws1718:turtle.png |}} Als Hauptvisualisierungsprogramm fiel Turtle jedoch raus. Zum einen bietet Turtle __keinen grafischen Spielraum__ und zum anderen ist es nicht möglich sich mehrere Kreaturen gleichzeitig zeichnen zu lassen. Somit ist es sichtlich **ungeignet für eine detailierte Darstellung** unserer Ideen. ==== Aufbau vom Code ==== Nach den Importen und den ''#settings'', wo die Fenstergröße eingestellt wird, muss die Turtle für uns ein Interface zeichnen. Die Funktion ''preparing'' macht genau dies und zeichnet uns in Bruchteilen einer Sekunde den Start, das Ziel, Zwischenmarkierungen und Kreise um das Ziel, für das Veranschaulichen einer Näherung zum Ziel. \\ Damit sich eine Kreatur bewegen kann gibt es die ''gogo'' Funktion. Sie funktioniert genauso wie im Hauptcode nur mit dem zusätzlichen Abgreifen der Koordinaten. Alle weiteren Funktionen sind Zusatz und existieren für **analytische Visualisierung...:** * ''clear()'' löscht alles gezeichnete und zeichnet wieder ein leeres Interface\\ ''reset()'' setzt die Turtle wieder zum Koordinaten Ursprung (bezogen auf den Simulations-Koordinatensystem)\\ ''new()'' erstellt eine neue Kreatur und lässt sie loslaufen\\ ''compare()'' erstellt eine neue Kreatur und lässt sie neben der alten loslaufen\\ ''erbung(n,s)'' lässt n viele Generationen der Spezies s loslaufen, die alle voneinander erben **...oder zum testen von Ideen:** * ''settings()'' errechnet die benötigte Fenstergröße um Start und Ziel anzeigen zu können\\ ''changeGoal()'' ändert die Simulations-Ziel Koordinaten und lässt das Turtlefenster neuberechnen ==== Turtle als Test-Tool ==== Wie schon angesprochen können mit Turtle viele Ideen **umgesetzt** werden bevor sie in komplizierteren Visualisierungen, wie Pygame, eingesetzt werden. Wenn wir also unsere Pygame-Visualisierung **flexibler** machen wollen, versuchen wir zuerst Turtle so flexibel wie möglich zu gestalten. So kann Turtle **schon jetzt** mit allen Situationen einer einzelnen Kreatur umgehen oder passt jede Position vom Ziel an das Fenster an etc. Ebenfalls nutzt Turtle bereits den **Koordinatenumrechner** vom Hauptcode, welcher ansonsten noch keinen Einsatz zeigen konnte, obwohl er eine wichtige Rolle in Flexibilität spielt. Der Koordinatenumrechner ist eine Klasse, in die sämtliche Umrechnungsfunktionen hinein programmiert werden können. Die für **Turtle wichtige Funktion** ist dabei ''sim2turt''! Es werden dabei alle Simulationskoordinaten für die Turtle-Visualisierung umgerechnet, damit, egal wie groß das Turtlefenster ist, immmer alle Zeichnungen wie Interface und Kreaturen auf das Fenster **abgestimmt** sind. Dazu berechnet der Umrechner Variablen mit ein, wie die Fenstergrößen oder einem ''zoom''-Faktor. Um die Flexibilität zu erhöhen, kann der Umrechner sowohl mit Tupeln als auch mit zwei Variablen als Input umgehen und beherrscht **mehrere Funktionen**. Neben dem typischen Koordinatenumrechnen kann er, wenn z.B ''function=distance'' übergeben wird, auch andere Dinge berechnen, wie die im Beispiel die umgerechneten Distanzen für Turtle. ---- ===== Pygame ===== PyGame bot uns im Gegensatz zu Turtle wesentlich umfangreichere Visualisierungsmöglichkeiten für unser Projekt. Einer der wichtigsten Vorteile dieses Pakets war dabei, dass wir damit in der Lage dazu waren, mehrere Kreaturen gleichzeitig auf dem Bildschirm erscheinen zu lassen. Mit Turtle konnte man maximal eine Kreatur beobachten und so zwar den Hauptcode verbessern, aber nicht dessen volles Potential graphisch darstellen. Wie der Name schon sagt ist PyGame an sich für Videospiele mit Python entwickelt worden und ist leider nicht besonders gut dokumentiert. Die beste Dokumentation, die wir gefunden hatten waren die Tutorials von [[https://www.youtube.com/playlist?list=PLsk-HSGFjnaH5yghzu7PcOzm9NhsW0Urw|KidsCanCode]] auf YouTube.\\ Das grundsätzliche Konzept eines jeden Videospiels, das man mit PyGame programmiert, ist die sogenannte **Game Loop**. Diese sorgt dafür, dass das Spiel angezeigt wird und sich je nach bestimmten Eingaben etwas auf dem Bildschirm verändert. Auf dem Folgenden Bild erkennt man die Bestandteile einer Game Loops. In diese unterteilt sich auch der **Skeleton** eines PyGame-Projekts.\\ {{ ws1718:game_loop.png }} //''Quelle: https://youtu.be/VO8rTszcW4s''// Der **Process Input** ist alles, was extern vom Code selbst eingegeben wird. Dazu gehören z.B. Interaktionen mit der Maus oder den Tasten auf dem Keyboard. Auch so etwas wie ein „x“ am oberen Fensterrand zum Schließen des Programms muss in diesen Bereich des Codes eingebaut werden.\\ In der **Update** Section werden in jedem Frame die neuen Daten verarbeitet, die sich seit dem letzten Frame verändert haben, in unserem Fall also jegliche Fortbewegungen der Kreaturen.\\ Die **Render** Section kann man auch als **Drawing** Section bezeichnen. Sie lässt die Daten, die sich während des Updates verändert haben, auf dem Bildschirm ausgeben. \\ Die **Uhr** am Ende der Zeichnung soll symbolisieren, dass die Game Loop sich in jedem Frame wiederholt, wobei dies innerhalb von einer Sekunde z.B. 24 Mal (24 fps = Anzahl der Bilder für ein flüssiges Bild) oder auch 60 Mal (Bildrate in modernen Videospielen) geschehen kann. Je nach dem wie gut der Prozessor des verwendeten Endgerätes ist, kann das Programm dann auch bei einer zu hohen Framerate ruckeln oder hängen. \\ Nach dieser etwas längeren Einführung nun zum eigentlichen Code. \\ ==== PyGame Skeleton ==== Das Grundgerüst unseres PyGame-Codes haben wir aus dem [[https://github.com/kidscancode/pygame_tutorials/blob/master/pygame%20template.py|Skeleton Code]] von KidsCanCode übernommen. Da dieser für jedes PyGame Projekt so gut wie gleich ist, werden wir ihn an dieser Stelle nicht näher erklären, sondern auf das zugehörige [[https://youtu.be/VO8rTszcW4s|Tutorial]] verweisen. \\ Auf dieser Basis konnten wir dann die nötigen Klassen, Funktionen und Counter einführen, um die Hindernisse, Kreaturen und Statistiken illustrieren zu können. ==== Klassen ==== ===Hindernisse=== Für alle Hindernisse, die auf unserer Karte zu sehen sind (namentlich die Regentropfen, die Wolke und die Plattformen) haben wir Klassen angelegt. Von der Struktur her sind die Klassen sehr ähnlich, weswegen hier nur eine exemplarisch erklärt wird. Das ist die Klasse für unsere **Regentropfen**: class DROP(pygame.sprite.Sprite): """Regentropfen""" def __init__(self, x, y): pygame.sprite.Sprite.__init__(self) self.x = x self.y = y self.image = pygame.image.load(os.path.join(img_folder, "drop.png")).convert() self.image.set_colorkey(BLACK) self.image = pygame.transform.scale(self.image, (30,50)) self.mask = pygame.mask.from_surface(self.image) self.rect = self.image.get_rect() self.rect.center = (self.x, self.y) Der Klasse wird ein **PyGame Sprite** übergeben. Da alle Objekte in PyGame als Sprites bezeichnet werden, ist dies an dieser Stelle obligatorisch.\\ Genauso muss in jeder Klasse eine ''__init__''-Funktion enthalten sein, der relevante Variablen (in diesem Fall z.B. die x- und y-Koordniaten) übergeben werden, die für die Vorgänge in der Klasse benötigt werden. \\ Als erste Zeile in dieser Funktion muss zudem die ''__init__''-Funktion selbst initialisiert werden. Darauf folgt das Einbauen und Positionieren der Regentropfen-Bilder.\\ Dazu müssen zunächst die übergebenen x- und y-Koordinaten mit ihrer eigenen Selbstreferenz gleichgesetzt werden.\\ Mit den drei Zeilen, die mit "''self.image''" beginnen wird zuerst erreicht, dass das Bild namens "drop.png" aus einem zusätzlichen Ordner namens "img" geladen wird. Der ''.convert()''-Befehl muss an das Ende des Ausdrucks gesetzt werden, sonst entsteht eventuell eine falsche Darstellung des eigentlichen Bildes. \\ Der ''set_colorkey''-Befehl entfernt jegliche Stellen im Bild mit der vermerkten Farbe. Alle selbstgezeichneten Bilder wurden freigestellt und mit einem schwarzen Hintergrund ausgestattet, sodass durch diesen Befehl das Bild ohne schwarzen Hintergrund auf dem Bildschirm zu sehen ist. \\ Der ''transform.scale()''-Befehl, der in der darauffolgenden Zeile verwenden wird, gibt uns die Möglichkeit ohne zusätzliche Bildbearbeitung, direkt in PyGame die Größe des Bildes bequem anzupassen. Dabei steht die erste Zahl in den Klammern für die Länge und die zweite für die Breite. \\ Bevor die nächste Zeile verstanden werden kann, muss der ''self.rect''-Befehl erklärt werden. Hier wird nämlich erstmal ein Rechteck (rect) um das Regentropfen-Bild mit dem Schwarzen Hintergrund erstellt. Im Weiteren wird dieses Rechteck mit dem ''.center''-Befehl in die Mitte des Bildes gelegt. Das ist bei jedem PyGame-Sprite erforderlich, damit Pygame das Bild auch als Objekt wahrnehmen und nachverfolgen kann. Zwar können Bilder freigestellt werden, jedoch gelten sie im PyGame-Algorithmus nur als Rechtecke mit speziellen Eigenschaften. An dieser Stelle kommt die ausgelassene Zeile und der ''self.mask''-Befehl ins Spiel. \\ Wir möchten bei unseren Hindernissen und auch später bei unseren Kreaturen nicht, dass (selbst wenn man es wegen den freigestellten Bildern so nicht sieht) nur die Rechtecke um die Objekte herum mit einander kollidieren, sondern die Umrisse der Bilder, wie sie durch die freigestellten Bilder sichtbar sind. Dafür wird eine **Maske** (mask) erstellt. Diese wird bei der **Kollision** weiterverwendet werden. Diese Bilderreihe soll besser veranschaulichen, was diese Stelle im Code bewirkt: {{ ws1718:kollision.gif }} //''Quelle: https://youtu.be/Dspz3kaTKUg''// ===Kreaturen=== class CREA(pygame.sprite.Sprite): """erstellt die kreaturen""" def __init__(self,spec): pygame.sprite.Sprite.__init__(self) self.c1 = cr.Creature(species=spec) self.c1.appendWay() self.image = all_images[spec] self.spec = spec self.rect = self.image.get_rect() self.rect.center = (0,0) self.block_index = 0 def update(self): if self.block_index < len(self.c1.briefing): block = self.c1.briefing[self.block_index] self.c1.nextStep(block) self.rect.center = (self.c1.stats["pos"][0]+100 , self.c1.stats["pos"][1]+360) self.block_index += 1 if self.c1.stats["step_counter"] < moveCap and not self.c1.stats["dead"]: self.c1.appendWay() else: self.c1.stats["dead"] = True if (self.c1.stats["pos"][0]+100) < 0 or (self.c1.stats["pos"][0]+100) > 1000: self.c1.stats["dead"] = True if (self.c1.stats["pos"][1]+360) < 0 or (self.c1.stats["pos"][1]+360) > 720: self.c1.stats["dead"] = True Wie bei jeder anderen Klasse auch in pygame, wird der **CREA Klasse** zunächst ein Pygame Sprite übergeben. In der ''__init__''-Funktion wird diese obligatorisch selbst initialisiert in der ersten Zeile. \\ Zudem wird der ''__init__''-Funktion ein Variable spec zugewiesen. Diese Variable beinhaltet die Information, welche **Spezie** realisiert werden soll. Dazu greifen wir auf die Klasse Creature des Hauptcodes zu und erhalten alle **nötigen Eigenschaften der Kreatur.** \\ Es folgt die Zeile ''self.c1.appendWay()''. Diese Funktion der Creature Klasse des Hauptcodes ist dafür zuständig **neue Schritte für die Kreatur zu generieren**. Beim Erstellen der Kreatur werden hier direkt eine gewisse Anzahl Schritte erstellt. Die eigentliche Bewegung findet erst in der update-Funktion statt.\\ Anschließend ordnen wir jeder Kreatur ein **Spezies abhängiges Bild zu **( "''self.image = all_images[spec]''"). Somit erreichen wir zusätzlich einen visuellen Unterschied der Kreaturen gemäß ihrer Art. \\ Die Zeilen, die mit ''self.rect''beginnen, umgeben das **Bild mit einem rectangle** und positionieren dieses. Das Prinzip ist das selbe, wie in den Klassen der Hindernisse. \\ Für die nächste Funktion der Klasse ist die letzte Zeile der ''__init__''-Funktion wichtig. Sie liefert uns eine Variable, die später einen Index- Counter bildet. Um diese genauer nach zuvollziehen werfen wir einen Blick auf die update-Funktion der CREA-Klasse. \\ Die **update-Funktion** der CREA-Klasse ist vor allem wichtig für die **Bewegung unserer erzeugten Kreaturen**. Hier kommt der ''self.block_index'' -Counter zum Einsatz. Zunächst stellen wir hier eine Bedingung auf. Sie bewirkt, dass die Bewegung der Kreatur nur solange von statten geht, wie Schritte (gespeichert im briefing) vorhanden sind. Ansonsten stirbt die Kreatur.\\ Auf Grund der Strukturierung des ** briefings **arbeiten wir hier mit zwei Indizier. Das briefing besteht aus einer **Liste von Blocks**. Ein Block beschreibt einen Schritt. Die Blocks enthalten Informationen über die Richtung und Schrittlänge. \\ Zunächst speichern wir in der Variable ''block''den **aktuellen Schritt** ab, der jetzt ausgeführt werden soll. Damit beim nächsten Durchlauf der nächste Schritt ausgeführt wird, erweitern wir den ''self.block_index''-Counter um 1. \\ Dann greifen wir auf eine weitere Funktion der Creature-Klasse des Hauptcodes zu. "''nextStep(block)''" ist dafür zuständig die Kreatur **einen Schritt ausführen** zu lassen. Alles was sie dazu braucht sind die Informationen über den Schritt. Diese übergeben wir anhand der ''block'' – Variable, die eben diese Informationen enthält. \\ Um die Kreatur auch **visuell zu bewegen**, müssen die Koordinaten des rects bewegt werden. Hier setzen wir dieses einfach auf die **neuen x- und y-Koordinaten der Kreatur**. Diese Informationen erhalten wir ebenfalls aus dem Hauptcode. (''self.rect.center = self.c1.stats[“pos“][0]+100, self.c1.stats[“pos“][1]+360'')\\ Zusätzlich kommt hier der ''stepCounter'' zur Sprache. Die Liste der Schritte(briefing) wird immer **schrittweise verlängert**. Wenn der ''stepCounter''**geringer als die movecap wird und die Kreatur nicht als tot gemeldet** wurde (zum Beispiel durch Kollision), wird erneut die Funktion ''appendWay()''' der Creature-Klasse des Hauptcodes verwendet. Diese **fügt immer neue Schritte zum briefing hinzu**, solange die Bedingungen erfüllt sind.\\ Nach dieser Bedingung finden wir noch zwei weitere Bedingungen in der update-Funktion. Diese **überprüfen mit jedem Durchlauf die Koordinaten der Kreaturen**. Wenn diese über den **Bildschirmrand hinaus** sind, werden die Kreaturen auf dead gesetzt und sterben. ====weiter im Code...==== Nachdem wir unsere Klassen definiert haben, wird es wichtig unsere Sprites zu gruppieren und auf zurufen. Alle Objekte, die wir erstellen, ob Hindernisse oder Kreaturen, ordnen wir zunächst zu der Gruppe "''all_sprites = pygame.sprite.Group()''" zu. Diese ist wichtig für jeden Update- Durchlauf. Somit können wir direkt alle Objekte gleichzeitig aktualisieren. \\ Zusätzlich ordnen wir jedes Objekt entweder in die Hinderniss-Gruppe ("''obstacles = pygame.sprite.Group()''") oder in die Kreaturen-Gruppe ("'' colliC = pygame.sprite.Group()''") ein. Diese Unterteilung wird später wichtig für die Kollision sein. \\ Nachdem wir diese Gruppen defimierten, erstellen wir eine Liste alles Kreaturen. In dieser Liste werden alle Informationen der einzelnen Kreaturen gespeichert. Anhand des Ratings und der "''.sort()''"-Funktion hilft diese uns eine neue Generation zu erstellen. Später dazu mehr. \\ Es folgen zwei in sich greifende Schleifen zur **Erstellung der Kreaturen**. Die erste Schleife mit dem Parameter i, wird solange ausgeführt, wie Kreaturen erstellt werden sollen. Die zweite Schleife mit dem Parameter n, geht rückwerts durch die Anzahl an Spezien. Mit diesen beiden Schleifen wird erzielt, dass soviel Kreaturen erstellt werden, wie es angegeben ist. Dabei wird zusätzlich auf die gerechte Verteilung der Spezienanzahl geachtet. Nach Erstellung der Kreatur, wird diese sowohl in die allgemeine Sprite- Gruppe, als auch in die Kreaturen- Gruppe, als auch in die Liste "''creature''" geordnet. \\ Als nächstes werden einige Funktionen wichtig... ====Funktionen...==== ===...zum Erstellen der restlichen Objekte === Wolke, Plattformen und Tropfen werden alle gleich erstellt. Jedes Objekt wird durch **Aufrufen der Klasse** und **Angabe der Koordinaten** erstellt. Daraufhin wird es zunächst der allgemeinen **Sprite_Gruppe** und der **Hinderniss-Gruppe** zugeordnet. ===...für die Kreaturen=== ==bubbly(): Die ''bubbly()''-Funktion ist zuständig für die Kollision. Das Prinzip ist eine simple** hit-box-Abfrage**. Mit Hilfe des ''spritecollide()''- Befehls wird für jedes Objekt in der Kreaturen-Klasse eine Kollisionsabfrage mit jedem Objekt der Hinderniss-Klasse gemacht. Falls die Abfrage positiv sein sollte, wird die Kreatur auf **dead** gesetzt. ==newGen(n) == Nun kommen wir zu einer äußerst wichtigen Funktion der Simulation. Als erstes fügen wir an dieser Stelle **zwei Counter** ein, die in der Visualisierung mit abgebildet werden. \\ Der erste Counter (''gencnt'') zählt die **Generationen** mit und wird dementsprechend bei jedem Abruf der Funktion um 1 erweitert. \\ Der zweite Counter (''ratingcnt'') wird bei jedem Abruf der Funktion genullt. Erst nachdem alle Kreaturen erstellt werden, wird dieser mit dem **Rating aller Kreaturen** erweitert. \\ Beide Counter werden im laufendem Programm am rechten Bildrand angezeigt. Somit kann man mit den fortschreitenden Generationen, die **Entwicklung unserer Evolution** erkennen. \\ Um nun eine neue Generation zuerstellen, nutzen wir zunächst die angekündigte ''.sort()''-Funktion. Durch sortieren der **creatures-Liste**, wird uns eine absteigende Reihenfolge der **Ratings** der Kreaturen ausgegeben. \\ Für die neu erstellte Liste nutzen wir eine erste Schleife mit dem ''enumerate''-Befehl. Mit ihm gelingt es uns sowohl durch den **Index i**, als auch durch das **Element c** von ''creatures'' geteilt durch n zugehen. **n** beschreibt hier den Selektionsdruck und legt somit fest, wie viele Kreaturen vererben dürfen.\\ In der **ersten Schleife** speichern wir unter der Variable ''spec'' die jeweilige Spezie jedes Elementes der Liste. \\ Es folgt eine **zweite Schleife**, die durch eine von **n-anhängige Liste**geht. Dabei wird für jedes n-tel eine neue Kreatur **abhängig der vererbten Eigenschaften** erstellt ( ''cr.procreation(c.c1)''). Anschließend wird mit der ''appendWay()''- Funktion für die neue Kreatur ein neuer Weg erstellt (abnhängig von den vererbten Eigenschaften). Zu guter Letzt wird jeder neu erstellten Kratur ihre **alte Spezie** durch die Variable ''spec'' zugeordnet und ein **dazu passendes Bild**. \\ Somit haben wir eine neue Generation, die auf die Eigenschaften der Mutter zugreift. def newGen(n): """neue Generation""" global gencnt global ratingcnt gencnt+=1 ratingcnt = 0 for c in creatures: c.block_index = 0 ratingcnt += c.c1.career["score"] creatures.sort(key=lambda w: w.c1.rating(), reverse=True) for i,c in enumerate(creatures[0:len(creatures)/n]): spec = c.spec for j in range(n)[::-1]: creatures[(len(creatures)/n)*j+i].c1 = cr.procreation(c.c1) #hier creatures[(len(creatures)/n)*j+i].c1.appendWay() creatures[(len(creatures)/n)*j+i].spec = spec creatures[(len(creatures)/n)*j+i].image = all_images[spec] ==stats()== Zu dieser Funktion gibt es nicht viel zu sagen. Die Funktion ist dafür zuständig die **Kreaturen jeder Spezie zu zählen**. Die Anzahl wird mit jedem Durchlauf genullt. Mit einer Doppelschleife gehen wir durch die creatures-Liste und einer erzeugten Liste abhängig der Spezienzahl. Wenn die allgemeine Spezienzahl mit der Spezienzahl der Kreatur über einstimmt, wird der jeweilige Counter erhöht. Auf die Werte dieser Funktion greifen wir später beim Erstellen der Balken zurück. def stats(): """ Anzahl der Spezien """ global speccnt speccnt = [0 for i in range(amountSp)] for c in creatures: for n in range(amountSp): if c.spec == n: speccnt[n]+=1 ===...für die Ausgabe auf dem Bildschirm=== ==draw_text== Die ''draw_text()''-Funktion ist dazu da, Text und sich verändernde Werte auf dem Bildschirm auszugeben. def draw_text(surf,text,size,x,y): """ ermöglicht das Schreiben in den screen """ font= pygame.font.Font(font_name, size) text_surface = font.render(text, True, BLACK) text_rect = text_surface.get_rect() text_rect.topleft=(x,y) surf.blit(text_surface,text_rect) Bei "''font=''" wird festgelegt, welche Schriftart in welcher Größe für die Ausgabe verwendet werden wird. Dazu wird der Funktion "''font_name''" und "''size''" übergeben. "''font_name''" wurde vorher oben im Visualisierungscode unter ''#initialisieren'' so definiert: font_name= pygame.font.match_font('brush script') Mit dem ''font.match_font''-Befehl wird eine auf dem verwendeten Endgerät gespeicherte Schriftart von PyGame ausgewählt und imitiert. Da die Schriftart, für die wir uns entscheiden haben nicht auf jedem Rechner vorhanden ist, kann es sein, dass das Schriftbild an anderen Computern anders aussieht. \\ Im nächsten Schritt unter "''text_surface=''"kann man der Verwendung von Kantenglättung zustimmen und eine Textfarbe auswählen. Am besten erkennt man an dem Bild rechts, was mit Kantenglättung eigentlich gemeint ist: {{ ws1718:antialias.gif?193}} \\ Mit "True" haben wir uns für "Anti-aliased" entschieden.\\ Im Weiteren wird um den Text ein Rechteck erstellt und mit dem ''.topleft''-Befehl in der oberen linken Ecke ausgerichtet. Mit dem ''surf.blit''-Befehl wird der Text vor alle anderen Objekte gezeichnet.\\ //''Quelle: http://www.snowbound.com/sites/snowbound.com/files/images/antialias.gif''// == draw_bar == Die ''draw_bar()''-Funktion haben wir für die bunten Überlebensbalken am rechten Bildrand eingebaut. def draw_bar(surf, x, y, pct, clr): """ zeicnet Balken für die Skala """ if pct < 0: pct = 0 BAR_LENGTH = 100 BAR_HEIGHT = 20 fill = (pct/100.*BAR_LENGTH) outline_rect = pygame.Rect(x,y,BAR_LENGTH, BAR_HEIGHT) fill_rect = pygame.Rect(x,y,fill,BAR_HEIGHT) pygame.draw.rect(surf,clr,fill_rect) pygame.draw.rect(surf, BLACK, outline_rect,2) Die if-Bedingung in der Funktion bewirkt, dass der Balken niemals negative Werte darstellen kann, indem solche Werte abgefangen und auf 0 gesetzt werden. \\ Durch "''BAR_LENGTH''" und "''BAR_HEIGHT''" deklarieren und initialisieren wir Variablen für die Länge und Breite des Balkens.\\ In der "''fill=''"-Zeile wird eine Variable festgelegt, die die Länge des bunten Balkens proportional zu dem jeweiligen ''pct''-Wert verändert. Dass die Veränderung auch dynamisch auf dem Bildschirm mitzuverfolgen ist, wird mit Hilfe von ''fill_rect'' erreicht. Durch ''outline_rect='' legen wir um den beweglichen bunten Balken einen statischen Umriss. Die ''pygame.draw.rect()''-Funktionen fungieren als Zusammenfassung aller vorher festgelegten Eigenschaften eines Balkens, was die Ausführung dieser in der draw()-Funktion erleichtert. Da die zwei gleichnamigen Funktionen unterschiedlich viele Übergabevariablen benötigen, dürfen sie denselben Namen tragen. \\ == draw == Die "''draw()''"-Funktion wurde dazu erstellt, die zuvor beschriebenen Funktionen an einer Stelle gesammelt ausführen zu können, sodass Änderungen leicht eingebunden werden können. def draw(): #->screen screen.fill(BLACK) screen.blit(background, (0,0)) pygame.draw.rect(screen, ROSE, [1000, 0, 280, 720]) pygame.draw.rect(screen, BLACK, [1000, 0, 280, 720], 2) pygame.draw.rect(screen, BLACK, [0, 0, 1000, 720], 2) #->sprites all_sprites.draw(screen) #->Text draw_text(screen,("Start"), 25, 65,320) draw_text(screen,("Ziel"), 25, 870,320) draw_text(screen,("Generation: " + str(gencnt)), 30, 1050,10) draw_text(screen,("Sieger: " + str(finpergencnt)), 30, 1050,50) draw_text(screen,("Rating: " + str(int(ratingcnt/amountEnt))), 30, 1050, 90) draw_text(screen,("Einhorn:"), 18, 1050,150) draw_text(screen,("Pinguin:" ), 18, 1050,200) draw_text(screen,("Schmetterling:"), 18, 1050,250) #->Balken draw_bar(screen, 1050,170, speccnt[0], WHITE) draw_bar(screen, 1050,220, speccnt[1], BLACK) draw_bar(screen, 1050,270, speccnt[2], YELLOW) Unter ''#->screen'' haben wir die ''pygame.draw.rect()''-Funktion verwendet, um am rechten Rand des Fensters ein violettes Rechteck zu erstellen, welches die Fläche bildet, auf der die Werte und die Balken dargestellt werden. Des Weiteren wird hier auch ein schwarzer Umriss um ebendieses Rechteck und den gesamten Hintergrund gezogen.\\ Die Funktion ''all_sprites.draw(screen)'' ermöglicht die Darstellung aller Sprites auf dem Bildschirm. \\ Unter ''#->Balken'' und ''#->Text'' werden die die ''draw_bar''- und ''draw_text''-Funktionen aufgeführt. In der hier verzeichneten Version unseres Codes werden nur 3/10 Spezies abgebildet. Die Syntax ist jedoch auch für die weiteren Spezies analog. Zu diesen Abschnitten ist noch zu sagen, dass Ausdrücke der Form ''str(gencnt)'' den Zweck erfüllen, die Zahl, die sich hinter der eingeklammerten Variable verbirgt, in einen String umzuwandeln, da die ''draw_text''-Funktion nur Text, also Strings, ausgeben kann. Die verschiedenen Counter-Variablen werden an einer anderen Stelle näher erläutert.\\ ====Game loop==== Damit wir am Ende auch alles darstellen können, kommen wir jetzt zum eigentlichen ''Game loop''. \\ Hier greifen wir auf das bekannte Skeleton von Pygame zu, welches wir schon anfangs verlinkten. \\ Wir setzen running auf "''True''", rufen alle **Sprites** (außer die Kreaturen) mit der ''defSprites()'' auf und gehen dann in die obligatorsiche **while-Schleife**. \\ """ Game loop """ running = True defSprites() while running: clock.tick(FPS) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: pygame.quit() Wir rufen zunächst die Kollisionsfunktion ''bubbly()'' auf. Anschließend überprüfen wir mit einer for-Schleife durch die creatures-list, ob alle Kreaturen tot sind. Sollte dies der Fall sein, erstellen wir eine neue Generation mit der ''newGen()''-Funktion. Zusätzlich rufen wir an dieser Stelle ''stats()'' ab, um die Spezienanzahl der neuen Generation zuzählen. \\ bubbly() for c in creatures: if not c.c1.stats["dead"]: allDead = False break allDead = True if allDead: newGen(selection) stats() Nach diesem Schritt kommen wir zu unserer Update-section. Hier werden zunächst alle Sprites mit dem Befehl ''all_sprites.update()'' aktualisiert. Der ''finpergencnt''-Counter zählt alle Kreaturen, die das Ziel erreicht haben. Dieser wird nach jeder Generation wieder genullt. \\ #Update all_sprites.update() finpergencnt = 0 for c in creatures: if c.c1.stats["success"]: finpergencnt+=1 Abschließend wird in der draw-section unsere ''draw()''-Funktion aufgerufen, in der wir alle zu zeichnenden Objekte finden. Um den screen regelmäßig zu aktualisieren, nutzen wir die ''pygame.display.flip()''-Funktion. Somit sind wir am Ende unseres Programmes angekommen. #zeichnen draw() pygame.display.flip()