01 Wiedereinstieg

Wiedereinstieg: Würfeln bis 100 #

Das Spiel kennenlernen #

Sieh’ dir die folgenden Klassen an. Versuche, den Sinn jeder Methode zu verstehen, bevor du die Kommentare liest.

class Würfel {
   int seiten;

   Würfel(int seiten) {
      this.seiten = seiten;
   }
   
   int würfle() {
      return Random.randint(1, this.seiten);
   }

}
  1. Offensichtlich möchte hier jemand Würfel-Objekte erzeugen. class Würfel eröffnet die Beschreibung dieser Objekte. (Erzeugt werden sie dann, mit dieser Klasse als Bauplan, woanders.)
  2. int seiten deklariert ein Attribut (vermutlich die Anzahl der Seiten eines nach dieser Klasse erzeugten Würfel-Objekts). Ein anderer Name für Attribut ist Instanzvariable.
  3. Die Methode Würfel ist die Konstruktor-Methode. Mit Würfel(int seiten) nimmt sie schon bei der Erzeugung eines neuen Würfel-Objekts die Anzahl der Seiten entgegen, und speichert diese mit this.seiten = seiten in der Instanzvariable seiten ab. Der Konstruktor hat keine return-Anweisung, gibt aber ein Objekt der Klasse Würfel zurück.
  4. Die Klasse Würfel legt eine einzige Methode (neben dem Konstruktor) für Würfel-Objekte fest, nämlich mit würfle das Würfeln. Die Methode ist sehr kurz, sie gibt (mit der return-Anweisung) eine Zufallszahl zwischen 1 und der Anzahl der Seiten des Würfels zurück.
class Spielfigur {
   String farbe;
   int punkte;
   Würfel würfel;

   Spielfigur(String farbe) {
      this.farbe = farbe;
      this.punkte = 0;
   }

   void zeigePunkte() {
      println("Ich habe " + this.punkte + " Punkte.", farbe);
   }

   int gibPunkte() {
      return this.punkte;
   }

   void erhalteWürfel(Würfel w) {
      this.würfel = w;
   }

   Würfel gibWürfel() {
      Würfel w = this.würfel;
      this.würfel = null;
      return w;
   }

   void würfle() {
      this.punkte = this.punkte + this.würfel.würfle();
   }

   boolean hatGewonnen() {
      boolean gewonnen = this.punkte > 100;
      if(gewonnen) {
         println("Ich habe gewonnen!", this.farbe);
      }
      return gewonnen;
   }

}
  1. Du solltest jetzt in der Lage sein, die Attribute, die in dieser Klasse Spielfigur festgelegt werden, zu finden: hier sind das farbe (vom Typ String), punkte (vom Typ int) und würfel (vom Typ Würfel – diese Variable speichert eine sogenannte Referenz auf ein Würfel-Objekt. Was das genau ist, wirst du in diesem Kurs noch lernen. Für den Augenblick kannst du dir eine Referenz als eine Art Fernbedienung vorstellen, mit der man ein Würfel-Objekt steuern kann.)
  2. Manche der Methoden haben Rückgabewerte – int, boolean oder Würfel (beim letzten wird die Fernbedienung für ein Würfel-Objekt zurückgegeben.) Andere sind mit void gekennzeichnet und geben also nichts zurück. Sieh’ dir diese Methoden genauer an: manche geben etwas am Bildschirm aus (zeigePunkte()), andere verändern etwas am Objektzustand (erhalteWürfel und würfle). (Diese Veränderungen nennt man übrigens Seiteneffekte.)
/*
 * b und r spielen ein Würfelspiel.
 * Beide würfeln abwechselnd und addieren ihre Würfe.
 * Wer zuerst mehr als 100 Punkte hat, hat gewonnen.
 */
class Spiel {
   
   Spielfigur b;
   Spielfigur r;
   Würfel w;

   public static void main(String[] args) {
      Spiel spiel = new Spiel();
      spiel.los();
   }

   Spiel() {
      b = new Spielfigur("blue");
      r = new Spielfigur("red");
      w = new Würfel(6);
      b.erhalteWürfel(w);
   }
   
   void los() {
      while (!(b.hatGewonnen()) && !(r.hatGewonnen())) {
         b.würfle();
         b.zeigePunkte();
         r.erhalteWürfel(b.gibWürfel());
         r.würfle();
         r.zeigePunkte();
         b.erhalteWürfel(r.gibWürfel());
      }
   }

}
  1. Die erste Zeile beginnt hier mit den Zeichen /* – damit beginnt ein mehrzeiliger Kommentar. Dieser wird beendet in der Zeile mit */. Kommentare liefern menschlichen Leserinnen und Lesern (hoffentlich) nützliche Hinweise und werden bei der Ausführung vom Computer ignoriert.
  2. In der Online-IDE können wir eigentlich einfach drauflosschreiben – dann wird unser Code Zeile für Zeile ausgeführt. Java-Programme haben hingegen üblicherweise einen Einstiegspunkt, von dem aus das Programm gestartet wird. Das ist die main-Methode, die mit der Zeile public static void main(String[] args) losgeht. Am Ende dieses Kurses sollst du alle hier enthaltenen Wörter verstehen, jetzt genügt zu wissen, dass bei main (engl. für so etwas wie »Hauptsache«) unser Programm startet. Hier wird ein Spiel-Objekt erzeugt (ja, die Klasse erzeugt hier ein Objekt von sich selbst!), und an diesem Objekt die Methode los() aufgerufen.
  3. Der Konstruktor Spiel kümmert sich darum, dass alle für das Spiel benötigten Objekte erzeugt werden. In b und r werden Spielfigur-Referenzen abgespeichert, in w eine Würfel-Referenz. (Hier hätte man auch this.b, this.r und this.w schreiben können. Weil es aber keine lokalen Variablen gibt, die genauso heißen, können wir uns das this hier sparen.) Am Ende der Konstruktormethode bekommt Spieler b das Würfelobjekt.
  4. In der Methode los() steckt nun das eigentliche Spiel. Die Zeile
    while (!(b.hatGewonnen()) && !(r.hatGewonnen())) {
    
    bedeutet, wenn man sie übersetzt:
    Solange   b nicht gewonnen hat   UND   r nicht gewonnen hat …
    
    Die öffnende geschweifte Klammer zum Schluss leitet dann den Block ein, den diese while-Wiederholung von vorne beginnt, bis eines der Spielfigur-Objekte gewonnen hat: b würfelt, zeigt die Punkte, übergibt den Würfel an r (r.erhalteWürfel(b.gibWürfel()) ist eine tolle Zeile oder?), r würfelt, zeigt die Punkte, und gibt den Würfel zurück.

Die im Folgenden gezeigten Abschnitt können alle in einer Datei stehen.

from random import randint
from time import sleep
  1. Hier werden aus zwei Modulen der Standardbibliothek Funktionen importiert.
  2. randint ist eine Funktion, die aus einem gegebenen Intervall eine Zufallszahl zurückgibt.
  3. sleep pausiert die Ausführung eines Programmes für eine gewünschte Zeit (in Sekunden).
class Würfel:
    
    def __init__(self, seiten: int):
        self.seiten = seiten
    
    def würfle(self):
        return randint(1, self.seiten)
  1. Offensichtlich möchte hier jemand Würfel-Objekte erzeugen. class Würfel eröffnet die Beschreibung dieser Objekte. (Erzeugt werden sie dann, mit dieser Klasse als Bauplan, woanders.)
  2. Die Methode __init__ ist die Konstruktor-Methode. Mit (self, seiten: int) nimmt sie schon bei der Erzeugung eines neuen Würfel-Objekts die Anzahl der Seiten entgegen, und speichert diese mit self.seiten = seiten in der hier initialisierten Instanzvariable seiten ab. Der Konstruktor hat keine return-Anweisung, gibt aber ein Objekt der Klasse Würfel zurück. Ein anderes Wort für Instanzvariable ist Attribut.
  3. Die Klasse Würfel legt eine einzige Methode (neben dem Konstruktor) für Würfel-Objekte fest, nämlich mit würfle das Würfeln. Die Methode ist sehr kurz, sie gibt (mit der return-Anweisung) eine Zufallszahl zwischen 1 und der Anzahl der Seiten des Würfels zurück.
class Spielfigur:
    
    def __init__(self, nummer):
        self.nummer = nummer
        self.punkte = 0
    
    def zeige_punkte(self):
        print('Spieler ' + str(self.nummer) + ': Ich habe ' + str(self.punkte) + ' Punkte.')
    
    def gib_punkte(self):
        return self.punkte

    def erhalte_würfel(self, würfel):
        self.würfel = würfel

    def gib_würfel(self):
        w = self.würfel
        self.würfel = None
        return w

    def würfle(self):
        self.punkte += self.würfel.würfle()

    def hat_gewonnen(self):
        gewonnen = self.punkte > 100
        if gewonnen:
            print('Spieler ' + str(self.nummer) + ': Ich habe gewonnen!')
        return gewonnen
  1. Du solltest jetzt in der Lage sein, die Attribute, die in dieser Klasse Spielfigur festgelegt werden, zu finden: hier sind das nummer , punkte und würfel (vom Typ Würfel – diese Variable speichert eine sogenannte Referenz auf ein Würfel-Objekt. Was das genau ist, wirst du in diesem Kurs noch lernen. Für den Augenblick kannst du dir eine Referenz als eine Art Fernbedienung vorstellen, mit der man ein Würfel-Objekt steuern kann.)
  2. Manche der Methoden haben Rückgabe-Anweisungen – sie geben int, boolean oder Würfel zurück (beim letzten wird die Fernbedienung für ein Würfel-Objekt zurückgegeben.) Andere geben nichts zurück. Sieh’ dir diese Methoden genauer an: manche geben etwas am Bildschirm aus (zeige_punkte()), andere verändern etwas am Objektzustand (erhalte_würfel und würfle). (Diese Veränderungen nennt man übrigens Seiteneffekte.)
def main():
    eins = Spielfigur(1)
    zwei = Spielfigur(2)
    w = Würfel(6);
    eins.erhalte_würfel(w)
   
    while not eins.hat_gewonnen() and not zwei.hat_gewonnen():
        eins.würfle()
        eins.zeige_punkte()
        zwei.erhalte_würfel(eins.gib_würfel())
        zwei.würfle()
        zwei.zeige_punkte()
        eins.erhalte_würfel(zwei.gib_würfel())
        sleep(.3)

if __name__ == '__main__':
    main()
  1. Mit Python können wir eigentlich einfach drauflosschreiben – dann wird unser Code Zeile für Zeile ausgeführt. Komplexere Programme haben hingegen üblicherweise einen Einstiegspunkt, von dem aus das Programm gestartet wird. Das ist hier die main-Funktion, die mit der Zeile def main(): losgeht. Sie wird in der letzten Zeile aufgerufen, nachdem abgeprüft wurde, ob das Programm selbst ausgeführt wurde. (Der Vergleich schlägt fehl, wenn das Programm nur als Modul irgendwoanders eingebunden wird.)
  2. In der main-Funktion werden alle für das Spiel benötigten Objekte erzeugt . In eins und zwei werden Spielfigur-Referenzen abgespeichert, in w eine Würfel-Referenz. Am Ende bekommt Spieler eins das Würfelobjekt.
  3. In der while-Wiederholung steckt nun das eigentliche Spiel. Die Zeile
    while not eins.hat_gewonnen() and not zwei.hat_gewonnen():
    
    bedeutet, wenn man sie übersetzt:
    Solange   eins nicht gewonnen hat   UND   zwei nicht gewonnen hat …
    
    Der Doppelpunkt zum Schluss leitet dann den Block ein, den diese while-Wiederholung von vorne beginnt, bis eines der Spielfigur-Objekte gewonnen hat: eins würfelt, zeigt die Punkte, übergibt den Würfel an zwei (zwei.erhalte_würfel(eins.gib_würfel()) ist eine tolle Zeile oder?), zwei würfelt, zeigt die Punkte, und gibt den Würfel zurück.

Aufgabe: das Spiel erweitern #

Hier spielen nun zwei Objekte gegeneinander – ist das schon ein richtiges Spiel?

Erweitere das Spiel! Bekommst du es hin, dass das Spiel Eingaben vom menschlichen Spieler erwartet, z.B. so?

Ich habe 4 Punkte.

Du bist dran. Würfle, und gib dann deine Zahl ein: 
3
Der menschliche Spieler hat 3.
Ich habe 8 Punkte.

Du bist dran. Würfle, und gib dann deine Zahl ein: 
4
Der menschliche Spieler hat 7.
Ich habe 14 Punkte.
…

Tipps für Online-IDE Java #

Tipp 1

Schreibe eine Klasse MenschlicherSpieler, die von der Klasse Spielfigur erbt. Dann kannst du die Methoden zeigePunkte und würfle überschreiben, um das Verhalten anzupassen.

Mit einer solchen Klasse musst du am Spiel fast nichts ändern. Statt eines zweiten Spielfigur-Objekts genügt es nun, mit r = new MenschlicherSpieler("red") in r eine Referenz auf ein MenschlicherSpieler-Objekt zu haben. (Nicht einmal der Typ der Variable muss geändert werden!)

Tipp 2

Wie ging das gleich mit dem Erben nochmal? extends in der ersten Zeile macht klar, von welcher Klasse geerbt wird:

class MenschlicherSpieler extends Spielfigur {
Tipp 3

Um eine Methode zu überschreiben genügt es, in der neuen Klasse eine gleich benannte Methode zu schreiben, die daneben den gleichen Rückgabetypen hat und die gleichen Parameter entgegennimmt.

void zeigePunkte() {
  println("Der menschliche Spieler hat " + this.punkte + ".", farbe);
}
Tipp 4

Das Einlesen von Zahlen lässt sich in der Online-IDE mit der statischen Input-Klasse erledigen. Diese beiden Zeilen (in einer neuen würfle-Methode) lassen die menschliche Spielerin oder den Spieler eine Zahl eingeben und addieren diese Zahl zu den vorhandenen Punkten hinzu.

int gewürfelteZahl = Input.readInt("Du bist dran. Würfle, und gib dann deine Zahl ein: ");
this.punkte = this.punkte + gewürfelteZahl;
Lösungsvorschlag

Die Klasse MenschlicherSpieler

class MenschlicherSpieler extends Spielfigur {

   void zeigePunkte() {
      println("Der menschliche Spieler hat " + this.punkte + ".", farbe);
   }
 
   void würfle() {
      int gewürfelteZahl = Input.readInt("Du bist dran. Würfle, und gib dann deine Zahl ein: ");
      this.punkte = this.punkte + gewürfelteZahl;
   }

}

Nur eine Änderung in der Klasse Spiel

class Spiel {
   
   Spielfigur b;
   Spielfigur r;
   Würfel w;

   public static void main(String[] args) {
      Spiel spiel = new Spiel();
      spiel.los();
   }

   Spiel() {
      b = new Spielfigur("blue");
      r = new MenschlicherSpieler("red");
      w = new Würfel(6);
      b.erhalteWürfel(w);
   }
   
   void los() {
      while (!(b.hatGewonnen()) && !(r.hatGewonnen())) {
         b.würfle();
         b.zeigePunkte();
         r.erhalteWürfel(b.gibWürfel());
         r.würfle();
         r.zeigePunkte();
         b.erhalteWürfel(r.gibWürfel());
      }
   }

}

Tipps für Python #

Tipp 1

Schreibe eine Klasse MenschlicherSpieler, die von der Klasse Spielfigur erbt. Dann kannst du die Methoden zeige_punkte und würfle überschreiben, um das Verhalten anzupassen.

Mit einer solchen Klasse musst du am Spiel fast nichts ändern. Statt eines zweiten Spielfigur-Objekts genügt es nun, mit zwei = MenschlicherSpieler(2) in zwei eine Referenz auf ein MenschlicherSpieler-Objekt zu haben.

Tipp 2

Wie ging das gleich mit dem Erben nochmal? In den Klammern hinter der Klassenbezeichnung wird auf die Klasse verwiesen, von der geerbt wird.

class MenschlicherSpieler(Spielfigur):
Tipp 3

Um eine Methode zu überschreiben genügt es, in der neuen Klasse eine gleich benannte Methode zu schreiben, die die gleichen Parameter entgegennimmt.

def zeige_punkte(self):
    print('Spieler ' + str(self.nummer) + '(menschlich): Ich habe ' + str(self.punkte) + ' Punkte.');
Tipp 4

Das Einlesen von Zahlen lässt sich in Python mit der eingebauten input-Funktion erledigen. Diese beiden Zeilen (in einer neuen würfle-Methode) lassen die menschliche Spielerin oder den Spieler eine Zahl eingeben und addieren diese Zahl zu den vorhandenen Punkten hinzu.

erwürfelt = int(input('Du bist dran. Würfle, und gib dann deine Zahl ein: '))
self.punkte += erwürfelt
Lösungsvorschlag

Die Klasse MenschlicherSpieler

class MenschlicherSpieler(Spielfigur):
    
    def zeige_punkte(self):
        print('Spieler ' + str(self.nummer) + '(menschlich): Ich habe ' + str(self.punkte) + ' Punkte.');

    def würfle(self):
        erwürfelt = int(input('Du bist dran. Würfle, und gib dann deine Zahl ein: '))
        self.punkte += erwürfelt

Nur eine Änderung in der main-Funktion:

def main():
    eins = Spielfigur(1)
    zwei = MenschlicherSpieler(2)
    w = Würfel(6);
    eins.erhalte_würfel(w)
   
    while not eins.hat_gewonnen() and not zwei.hat_gewonnen():
        eins.würfle()
        eins.zeige_punkte()
        zwei.erhalte_würfel(eins.gib_würfel())
        zwei.würfle()
        zwei.zeige_punkte()
        eins.erhalte_würfel(zwei.gib_würfel())
        sleep(.3)