Benutzer-Werkzeuge

Webseiten-Werkzeuge


Seitenleiste

ws1718:visualisierungscode

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. 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 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.

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 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 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: 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.rectbeginnen, 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 blockden 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 stepCountergeringer 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 Listegeht. 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:
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()
ws1718/visualisierungscode.txt · Zuletzt geändert: 2018/04/18 18:53 von lenarost