Für
Janne, Julia, Katrin und Daniel

Anhang B

Kleine Checkliste

Die Suche nach Fehlern hat so manchen Programmentwickler schon an den Rand des Wahnsinns getrieben. Zumal die schlimmsten meist so gut verborgen sind, dass man zuerst daran zweifelt, sie jemals zu finden. Etwas falsch zu machen, ist eben fast immer ärgerlich. Vielleicht hilft die folgende kleine Checkliste weiter, die man sich hin und wieder einmal anschauen sollte:

Anhang A

Python installieren

​​Python lässt sich recht einfach installieren. Du musst nur ein paar Schaltflächen anklicken, um die Installation zu steuern. Im Zweifelsfall kannst du dir aber auch von jemandem helfen lassen.

Angeboten werden zwei Versionen von Python, die sich nicht nur in einigen Punkten unterscheiden. Wir arbeiten in diesem Buch mit der aktuell neuesten Version Python 3.

Einen Moment musst du warten, bis das Installations-Programm loslegt.

Hier kannst du dich entscheiden, in welchen Ordner die Installation erfolgen soll. Sonst kommen deine Dateien in einen AppData-Ordner im Verzeichnis C:\Users.

Und die Installation beginnt. Nun heißt es ein bisschen warten und geduldig sein – besonders dann, wenn dein Computer nicht der flotteste ist. Wie schnell es vorangeht, kannst du an einem Fortschrittsbalken mitverfolgen.

Schließlich kommt die Meldung, dass der ganze Prozess erfolgreich abgeschlossen wurde.

Auf der Seite https://www.python.org/ findest du immer die jeweils aktuelle Python-Version, darüber hinaus auch Dokumentationen und weiteres Material.

Pygame installieren 1

​Weil Pygame nicht zum Python-Paket gehört, ist eine Extra-Installation nötig. Beginnen wir auch hier mit dem Herunterladen.

Wahrscheinlich gibt es mittlerweile neuere Versionen. Weil auch Python sich immer weiterentwickelt, musst du eventuell mehrere Dateien herunterladen und ausprobieren, was mit deiner Python-Version zusammen funktioniert.

Oder:

Du kannst das Setup-Programm auch in den Python-Ordner verschieben und von dort aus starten, dann ersparst du dir bei der Installation später eine Einstellung.

Ist die Datei keine MSI-Datei, sondern eine mit der Kennung WHL, dann musst du die Anweisungen in »Pygame installieren 2« benutzen.

Pygame installieren 2

​Die erste Installation klappt eigentlich fast immer, leider kann es dennoch passieren, dass sich das pygame-Modul nicht in ein Python-Programm importieren lässt, weil dort bestimmte Dateien nicht gefunden werden. In einem solchen Fall gibt es hier eine Alternativ-Installation.

Vorher aber sollte das bereits installierte Pygame-Paket wieder entfernt werden. Das geht einfach, indem du das Setup-Programm erneut aufrufst und dort Remove Python 3 pygame wählst und dann auf Finish klickst.

Danach kann Pygame sauber neu installiert werden.

Dann gib dort diese Befehlszeile ein:

py -m pip install pygame

Nun wird die zu deiner Python-Version passende Pygame-Datei aus dem Internet geholt und in die Python-Unterordner lib und include installiert.

Nun kannst du das Fenster der Eingabeaufforderung schließen, Pygame ist installiert.

Achte darauf, dass in deinem Python-Ordner lib ein Ordner mit dem Namen pygame liegt. Möglicherweise musst du ihn erst aus »site-packages« dorthin kopieren.

Einsatz der Buch-Dateien

Wenn du die Beispiel-Projekte für Python wie auch die Lösungen nutzen willst, lassen sie sich von dieser Seite herunterladen:

http://www.mitp.de/0239

Du kannst sie dann alle in einem Ordner auf deiner Festplatte unterbringen. Ich benutze im Buch einen Ordner namens D:\Python\Projekte. (Im Ordner D:\Python habe ich bei mir auch schon das Python-Paket samt Pygame untergebracht.)

Kapitel 16
Play the Game

In den letzten Kapiteln hast du zwei Spiele programmiert, die man so, wie sie sind, spielen kann. Oder auch nicht, denn es wäre doch deutlich komfortabler, wenn man in beiden für das Treffen einer Wanze oder das Ausweichen einer Figur vor einem Ball Punkte sammeln könnte. Darum sollten wir uns hier noch kümmern.

Außerdem ist das Spielen mit einer Wanze (oder einem Käfer) nicht so herausfordernd wie mit einer ganzen Insektenschar. Schauen wir mal in diesem Kapitel, was sich da noch machen lässt.

In diesem Kapitel lernst du

Punkte sammeln

Um in Pygame einen Text auf der Fensterfläche anzuzeigen, braucht man eine »Unterlage«, in die der Text quasi eingebettet wird. In Pygame heißt die Klasse dafür Surface​. Und so erzeugen wir ein passendes Objekt:

Text = pg.Surface((300,50))
Text.fill(Yellow)

Was dabei herauskommt, ist erst mal nichts weiter als eine rechteckige Fläche, die eigentlich schwarz ist, aber durch die fill-Anweisung machen wir sie gelb, damit sie auf dem Spielfeld (noch) nicht sichtbar ist.

Als Nächstes definieren wir eine Funktion für die Anzeige von Text (→ dodger5.py):

def showMessage(text, Farbe) :
  global Text
  Font = pg.font.SysFont("arial", 48)
  Text = Font.render(text, True, Farbe)

​​Darin müssen wir zuerst eine Schrift haben, wir bedienen uns bei den System-Schriftarten, ich habe Arial mit der Größe 48 ausgesucht. (Probiere ruhig andere Schriftarten und -größen aus.)

​Zuletzt wird der aktuelle Text gerendert, sodass man davon auch etwas sehen kann. Dabei kannst du die Anzeigefarbe bestimmen.

Rendern​, was war das noch mal? Die Darstellung eines grafischen Objekts wird Rendern genannt. Das kann unter anderem ein Bild oder ein Text sein. Für viele Objekte genügt ein Aufruf von blit(), um sie sichtbar zu machen, in unserem Fall muss vorher der anzuzeigende Text in das Surface-Objekt »eingenäht« werden.

Mit diesem Mitteln könnten wir jetzt die Punkte anzeigen, die der Player sammelt, wenn er dem Ball erfolgreich ausgewichen ist. Dazu brauchen wir eine weitere Funktion (→ dodger5.py):

def setScore(num) :
  global Punkte
  Punkte += num
  showMessage("Punkte: " + str(Punkte), Blue)

Die globale Variable Punkte, die hier vorkommt, wird am Programmanfang auf 0 gesetzt:

Punkte = 0

Als Parameter wird die Anzahl der Punkte übernommen, um die der Punktestand steigen soll. Die Funktion showMessage() wird dann dazu benutzt, um die Punktzahl in Blau anzuzeigen. Natürlich muss auch diese Farbe vorher definiert sein:

Blue = (0,0,255)

Nun schauen wir mal, wo wir die neuen Funktionen ins Spiel bringen können. Zum einen soll dort, wo der Player ausgewichen ist, das Punktekonto erhöht werden:

if not Figur.isHit :
  Ball.move(-1, 0)
  if Ball.controlRestart(xMax-50, yMax/2) :
    setScore(1)

Wenn die Figur noch nicht getroffen wurde, kann sich der Ball weiterbewegen, dazu startet er neu, falls er sich ganz links, also im Playerbereich befindet. Du erinnerst dich, dass die Methode controlRestart() einen Rückgabewert hat. Denn können wir hier gut gebrauchen.

Bei erfolgreichem Neustart des Balls wurde der Player ja nicht getroffen, also muss es hier die Punkte geben, die auch sogleich angezeigt werden. Wenn du willst, kannst du großzügiger sein, und den Punktestand auch z.B. in Zehnerschritten erhöhen. Ich war geizig, bei mir kriegt der Player pro Ausweichmanöver nur einen Punkt.

​Damit man die Punktanzeige auch wirklich sehen kann, brauchen wir eine weitere blit-Anweisung:

Fenster.blit(Text, (xMax/2, 10))

Die würde ich vor alle anderen blit-Zeilen setzen, damit Figur und Ball in den vorderen Ebenen bleiben.

Wenn du dir die Abbildung genau anschaust, dann siehst du, dass ich da noch was hinzugeschmuggelt habe: einen Text fürs Spielende. Diese Zeile steht direkt unter der, in der isHit sein True bekommt:

if not Figur.dodge(Ball.y, yMax/2) :
  Figur.isHit = True
  showMessage("Game over", Red)

Ist der Player getroffen, wird statt der Punkte »Game over« angezeigt.

Eine Game-Klasse

Nun hat mittlerweile der Quelltext unseres Dodger-Programms wieder einen beachtlichen Umfang angenommen, obwohl wir doch den Player und den Ball in Klassen-Dateien ausgelagert haben. Schauen wir mal, ob da nicht mehr drin ist, um den Umfang des Hauptprogramms zu verringern.

Ich denke da vor allem an die ganzen Funktionen, die wir hier vereinbart haben. Was, wenn wir eine neue Klasse definieren und diese Funktionen zu Methoden dieser Klasse machen? Probieren wir es aus.

​Ich habe die neue Klasse etwas kühn Game genannt, weil dort spielrelevante Methoden untergebracht werden sollen. Du kannst den Bestand nach und nach erweitern, wenn dir ein paar weitere (gute) Methoden einfallen.

Bevor wir uns an die Bearbeitung der Methoden machen, sollte die allererste Zeile in Quelltext von game.py diese sein:

import pygame as pg

Dann folgt der Kopf der Klasse:

class Game :

Kommen wir zu den Methoden. Hier sind sie in einem Rutsch (→ game.py):

# Startwerte und Textfeld erzeugen
def __init__(self, Farbe) :
  self.Start = 0
  self.Punkte = 0
  self.Text = pg.Surface((300,50))
  self.Text.fill(Farbe)
 
# Info anzeigen
def showMessage(self, text, Farbe) :
  self.Font = pg.font.SysFont("arial", 48)
  self.Text = self.Font.render(text, True, Farbe)
 
# Punkte zählen und anzeigen
def setScore(self, num, Farbe) :
  self.Punkte += num
  self.showMessage("Punkte: " + \ 
  str(self.Punkte), Farbe)
 
# Timer
def getTime(self, Reset) :
  if Reset :
    self.Start = pg.time.get_ticks()
  self.Diff = pg.time.get_ticks() - self.Start
  return self.Diff

Wie du siehst, sind die Unterschiede nicht sonderlich groß. Neu ist eine init-Methode, in der ein Textfeld erzeugt wird, das dann zur Anzeige aller Art von Informationen und Meldungen dienen kann. Die setScore-Methode braucht einen weiteren Parameter, damit die Anzeigefarbe frei gewählt werden kann.

Bauen wir unser neues Klassen-Modul gleich ins Hauptprogramm ein, das jetzt so aussieht (→ dodger6.py):

import pygame as pg
import random
from dplayer import *
from dthing import *
from game import *
 
# Startwerte festlegen
Red = (255,0,0)
Blue = (0,0,255)
Yellow = (255,255,0)
xMax, yMax = 800, 400
 
# Pygame starten, Spiel-Elemente erzeugen
pg.init()
pg.key.set_repeat(20,20)
pg.display.set_caption("My Game")
Fenster = pg.display.set_mode((xMax, yMax))
Figur = Player(20,30)
Ball = Thing("Bilder/ball1.png")
Ball.setPosition(xMax-50, yMax/2, True)
Spiel = Game(Yellow)
 
# Ereignis-Schleife
running = True
while running :
  for event in pg.event.get() :
    if event.type == pg.QUIT :
      running = False
 
    # Tasten abfragen
    if event.type == pg.KEYDOWN :
      if event.key == pg.K_UP :
        Figur.setState(2)
        Spiel.getTime(True)
      if event.key == pg.K_DOWN :
        Figur.setState(1)
        Spiel.getTime(True)

  # Zeit testen, ggf. Figur zurück in Stand
  Zeit = Spiel.getTime(False)
  if Zeit > 200 :
    Figur.setState(0)

  # Ball bewegen, ggf. zurücksetzen
  if not Figur.isHit :
    Ball.move(-1, 0)
    if Ball.controlRestart(xMax-50, yMax/2) :
      Spiel.setScore(1, Blue)
  # Kontrolle, ob Ball im Kontaktbereich
  if (Ball.x < Figur.x+150) :
    # Wenn Player nicht ausweicht, Spiel-Ende
    if not Figur.dodge(Ball.y, yMax/2) :
      Figur.isHit = True
      Spiel.showMessage("Game over", Red)
 
  # Sprite in Fenster positionieren
  Fenster.fill(Yellow)
  Fenster.blit(Spiel.Text, (xMax/2, 10))
  Fenster.blit(Figur.Bild, (Figur.x, Figur.y))
  Fenster.blit(Ball.Bild, (Ball.x, Ball.y))
  pg.display.update()
 
# Pygame verlassen
pg.quit()

Das neue Objekt habe ich Spiel genannt und so erzeugt:

Spiel = Game(Yellow)

Im Spiel selber werden dann (zum Teil an mehreren Stellen) die Methoden Spiel.getTime(), Spiel.setScore() und Spiel.showMessage() aufgerufen. Beachte, dass auch bei einer blit-Anweisung der Parameter Text jetzt zu Spiel gehört.

Wanzen-Sammlung

Machen wir jetzt einen Sprung zurück zum vorigen Spiel mit der Wanze. Wie wäre es, wenn wir auch da ein Punktesystem einbauen würden? Eigentlich Blödsinn, wirst du jetzt sagen: Wenn die Wanze tot ist, ist das Spiel vorbei. Stimmt, aber wie wäre es, wenn wir nicht nur eine einzelne, sondern viele Wanzen über das Spielfeld jagen? Und du hast eine bestimmte Zeit zur Verfügung, um sie alle zu jagen? Dann macht eine Punktevergabe durchaus Sinn.

Kramen wir also unser letztes Buggy-Projekt wieder hervor und schauen wir, wie wir aus einer gleich eine Handvoll Wanzen machen. Dazu vereinbaren wir gleich am Anfang eine Variable, deren Wert du nach Belieben ändern kannst, falls dir die Anzahl nicht reicht:

bugMax = 5

Bei der Erzeugung der Figuren brauchen wir zuerst eine (leere) Liste:

Figur = []

Die füllen wir dann über eine for-Schleife mit Insekten (→ buggy9.py):

for Nr in range(0,bugMax) :
  xPos = random.randint(100,xMax-100)
  yPos = random.randint(50,yMax-100)
  Figur.append(Player(xPos, yPos))

Wie du siehst, habe ich hier zwei Zufallswerte für die Position der kleinen Krabbler benutzt, damit sie bei jedem Spiel woanders auftauchen. In meinem Fall sollte es jetzt fünf Wanzen geben. Auch für die Laufrichtung brauchen wir jetzt Listen, nur so ist es möglich, dass nicht jede Wanze in dieselbe Richtung losläuft. Dazu erzeugen wir zuerst zwei weitere Listen:

xStep = []
yStep = []

Dann packen wir auch die Bestimmung der zufälligen Startrichtung in eine for-Schleife (→ buggy9.py):

for Nr in range(0,bugMax) :
  xStep.append(random.randint(0,2))
  if xStep[Nr] == 0 :
    xStep[Nr] = -1
  yStep.append(random.randint(0,2))
  if yStep[Nr] == 0 :
    yStep[Nr] = -1

In der Ereignis-Schleife muss dann nach einem Mausklick für jede Wanze einzeln kontrolliert werden, ob sie getroffen wurde:

if event.type == pg.MOUSEBUTTONDOWN :
  (xPos, yPos) = pg.mouse.get_pos()
  for Nr in range(0,bugMax) :
    if (xPos > Figur[Nr].x) \ 
    and (xPos < Figur[Nr].x + 100) \
    and (yPos > Figur[Nr].y) \ 
    and (yPos < Figur[Nr].y + 100) :
      Figur[Nr].destroy()

Gegebenenfalls wird die getroffene Wanze »plattgelegt« (und isKilled für diese Wanze auf True gesetzt).

Auch die Grenzkontrolle muss nun für jede Wanze erfolgen (→ buggy9.py):

for Nr in range(0,bugMax) :
  if (Figur[Nr].x < 0) or (Figur[Nr].x > xMax-190) :
    xStep[Nr] = -xStep[Nr]
  if (Figur[Nr].y < 0) or (Figur[Nr].y > yMax-100) :
    yStep[Nr] = -yStep[Nr]

Das Gleiche gilt für die Ausrichtung der Wanze in Laufrichtung:

for Nr in range(0,bugMax) :
  Winkel = atan2(-yStep[Nr], xStep[Nr])
  Winkel = degrees(Winkel) - 90
  Figur[Nr].rotate(Winkel)

Das ist dann aber immer noch nicht alles, auch die move-Anweisung gibt es für jedes Tierchen extra:

for Nr in range(0,bugMax) :
  if not Figur[Nr].isKilled :
    Figur[Nr].step(xStep[Nr]*2, yStep[Nr]*2)
pg.time.delay(5)

Wie du siehst, habe ich die Zeitverzögerung hinter die for-Schleife gesetzt, sonst würde das Spiel immer schneller, je mehr Wanzen erlegt worden sind. (Wenn du das wünschst, nimm die delay-Anweisung mit in die if-Struktur und setze den Zeitwert herunter.)

Nun müssen wir zu guter Letzt noch dafür sorgen, dass jede Wanze auch zu sehen ist. Das wird in dieser letzten Schleife erledigt (→ buggy9.py):

for Nr in range(0,bugMax) :
  Fenster.blit(Figur[Nr].Bild, (Figur[Nr].x, Figur[Nr].y))

Killer-Punkte

Kommen wir nun zur Punktevergabe. Denn jetzt lohnt es sich ja, mein Vorschlag wäre, dass es für jede zerquetschte Wanze 50 oder gar 100 Punkte gibt. Und wenn du später das Spielfeld deutlich größer machst und deutlich mehr Wanzen darauf herumlaufen lässt, dann gibt es auch eine Menge Punkte zu sammeln.

Wie gut, dass wir im letzten Dodger-Projekt eine Game-Klasse definiert und eingesetzt haben. Die können wir nämlich hier unbesehen weiterverwenden. Zuerst wird dazu das Game-Modul eingebunden:

from game import *

Als Nächstes vereinbaren wir ein Spiel-Objekt (diesmal mit grünem Hintergrund):

Spiel = Game(Green)

Sobald eine Wanze getroffen wurde, tritt die setScore-Methode in Aktion:

Spiel.setScore(50, Blue)
Figur[Nr].destroy()

Dabei sollte natürlich Blue vorher als Farbe definiert worden sein:

Blue = (0,0,255)

Damit dann die Punkte auch angezeigt werden, darf ganz unten diese Zeile nicht fehlen (→ buggy10.py):

Fenster.blit(Spiel.Text, (xMax/3, 10))

Du kriegst mehr, als du erwartet hast? Klar, denn wenn du auf eine tote Wanze klickst, gibt es weiter Punkte. So war das aber nicht gedacht, dem müssen wir gleich einen Riegel vorschieben.

Das geht einfacher als gedacht: Wir überprüfen, ob die angeklickte Wanze eben noch gelebt hat, wenn ja, gibt es Punkte (→ buggy10.py):

if not Figur[Nr].isKilled :
  Spiel.setScore(50, Blue)
Figur[Nr].destroy()

Anschließend wird die Wanze geplättet.

Nun wird man immer irgendwann die Höchstpunktzahl erreichen können, der eigentliche Reiz dieses Spiels sollte aber sein, dass man sich dafür schon etwas mehr Mühe geben muss. Und wozu haben wir den Timer der Game-Klasse? Ändern wir das Programm jetzt noch so um, dass dem Spieler nur eine bestimmte Zeit zur Verfügung steht, um alle oder möglichst viele Wanzen zu plätten. Dazu müssen wir zuerst den Timer starten (→ buggy11.py):

Spiel.getTime(True)

Das muss natürlich außerhalb der while-Schleife geschehen.

Dann brauchen wir eine Variable, die innerhalb dieser Schleife ständig die aktuelle Zeit erfasst, damit wir kontrollieren können, wann die Spielzeit verstrichen ist:

Zeit = Spiel.getTime(False)

Hilfreich wäre es für den Spieler, wenn diese Zeit auch angezeigt würde. Da wir aber dazu keine passende Methode haben, müssen wir die Game-Klasse um eine erweitern (→ game.py):

def showAll(self, num, Farbe) :
  self.Punkte += num
  ptext = "  |  Punkte: " + str(self.Punkte)
  ztext = "Zeit: " + str(int(self.Diff/1000))
  self.showMessage(ztext+ptext, Farbe)

Die Methode showAll() fasst die Anzeige von Zeit und Punkten zusammen:

Spiel.showAll(0, Blue)

Hier bedeutet es, dass bei dieser Anzeige keine Punkte hinzuaddiert werden, es geht ja nur um die Anzeige der sich ändernden Zeit.

Auch weiter unten sollte der Aufruf von setScore() so ersetzt werden (→ buggy11.py):

if not Figur[Nr].isKilled :
  Spiel.showAll(50, Blue)

Nachdem die Spielzeit vorbei ist, wird das zum einen durch eine entsprechende Meldung angezeigt:

if Zeit > bugMax*1500 :
  Spiel.showMessage("Game Over", Red)
  running = False

Dabei habe ich die Spielzeit von der Anzahl der vorhandenen Wanzen abhängig gemacht. Zuerst erscheint die Meldung »Game over«, dann wird running auf False gesetzt

Damit nach dem Verlassen der while-Schleife das Spiel nicht zu abrupt endet, müssen wir ganz unten noch eine kleine Pause einbauen:

pg.time.delay(1500)
pg.quit()

Dass du auch alles bequem herunterladen statt eintippen kannst oder hättest können, habe ich ja eigentlich oft genug erwähnt (www.mitp.de/0239).

Zusammenfassung und Schluss

Nun sind wir leider am Ende aller Kapitel angelangt. Es gibt bestimmt eine Menge, was in den vergangenen Kapiteln in Sachen Python und Pygame noch nicht behandelt wurde. Aber du verfügst über mehr als solide Grundlagen und bist eigentlich inzwischen in der Lage, die letzten Programme zu verbessern oder selbst neue zu programmieren.

Viel Neues gab es hier nicht, aber immerhin sind noch ein paar Wörter zu deinem Wortschatz hinzugekommen:

Surface

Klasse für Oberflächen zur Anzeige von Text und Grafik

Font

Klasse für Schriften

render

Text/Schrift rendern

Es lohnt auf jeden Fall, sich das anzuschauen, was Pygame noch zu bieten hat, z.B. unter dieser Adresse im Internet:

http://www.pygame.org/docs/

Hier findest du den kompletten Wortschatz dokumentiert. Für Python gibt es die Dokumentation auf dieser Seite:

https://docs.python.org/3/

Noch mehr zum Nachschlagen und Blättern findest du hier:

https://wiki.python.org/moin/FrontPage

Einen (kleinen?) Wermutstropfen gibt es: Alle Seiten sind in englischer Sprache.