Code of the Killertomaten

Vielleicht interessiert es ja manche hier, wie man in unter 100 Zeilen mit Processing ein solche kleines Spiel programmiert.  Deshalb folgt unten ohne große Erklärungen der Sourcecode. (Falls sich jemand wundert: Wegen eines Bugs im Syntax-Highlighting von wordpress.com für mehrzeilige Kommentare, s. https://github.com/alexgorbatchev/SyntaxHighlighter/issues/34, habe ich nur einzeilige Kommentare verwendet)

In Processing müsst ihr einen neuen „Sketch“ anlegen und die Bilddatei unter „Sketch -> Add File“ hinzufügen (was aber auch nichts weiter tut, als ein Unterverzeichnis data/ anzulegen, wohin die Datei kopiert wird). Den Code reinkopieren, starten – und wochenlanger Spielspaß ist euch gewiss! (Meine fünfjährige Tochter liebt das Spiel. „Mama, hast du schon gesehen, der Papa, der hat ein suuuper Tomatenspiel! Papa, darf ich nochmal?“)

Wenn ihr jetzt schreit „Ja, aber die Tomaten müssten eigentlich beim Abschießen platzen!“ oder „Find ich aber nicht so gut, dass man mehrere Tomaten auf einmal abschießen kann!“ oder „Aber das sollen doch Killertomaten sein – die sollten selbst auch irgendwie was Gefährliches machen!“ oder „Boah, man kann ja einfach auf der Leertaste bleiben – Dauerfeuer, was? Hihi! – Nee, ehrlich, ich finde das sollte nicht gehen!“ oder „Wäre doch voll witzig, wenn da auch Zucchini, Möhren und Blumenkohl rumfliegen würde – das wäre ja auch ein super Einstieg für Vererbung und Polymorphie, weil die dann ja eine andere Methode istUnterMauszeiger() haben müssten!“ – wenn ihr also irgendwas in der Art schreit, dann sage ich: Stimmt!

Genau solche Reaktionen habe ich von den Schülern auch bekommen (na ja, bis auf die letzte, das ist meine eigene!) Und ist das nicht toll? Die Schüler denken sofort und eigenständig über Erweiterungen des Arbeitsauftrags nach und der Lehrer braucht nur noch ihrem Drängen nachzugeben 😉

Und welche Ideen habt ihr?  (Ja, an IPad/Android-Umsetzung habe ich natürlich auch schon gedacht.)

// Angriff der Killertomaten!
// Version 2 - jetzt mit noch spektakulärerer Grafik

class Tomate {
  float xpos, ypos;  // Position der Tomate
  int durchmesser;   // Groesse
  float dirx, diry;  // Bewegungsrichtung

  // Konstruktor: Initialisiert die Tomate nach der Erzeugung
  Tomate(int x, int y, int d, float dx, float dy) {
    xpos = x;
    ypos = y;
    durchmesser = d;
    dirx = dx;
    diry = dy;
  }

  // prüft, ob Maus auf die Tomate zeigt
  boolean istUnterMauszeiger() {
    return dist(mouseX, mouseY, xpos, ypos) < durchmesser/2;
  }

  // berechnet die nächste Position der Tomate und ändert evtl ihre Richtung
  void move() {
    if ((xpos-durchmesser/2< 0) || (xpos+durchmesser/2 > width)) {
      dirx = dirx * -1;
    }
    if ((ypos-durchmesser/2 < 0) || (ypos-durchmesser/2+durchmesser > height)) {
      diry = diry * -1;
    }
    xpos = xpos + dirx;
    ypos = ypos + diry;
  }

  // zeichnet die Tomate an ihrer momentanen Position
  void display() {
    image(bild, xpos-durchmesser/2, ypos-durchmesser/2, durchmesser, durchmesser);
    if (istUnterMauszeiger()) {
      // hebe diejenige Tomaten hervor, auf die gerade "gezielt" wird
      ellipse(xpos, ypos, durchmesser, durchmesser);
    }
  }
}

// Globale Variablen (s. Blog)
int NUM_TOMATEN = 50;   // Gesamtzahl Tomaten
float SPEED = 1.7;      // Geschwindigekeitsfaktor, der benutzt werden kann, um die Schwierigkeit zu steigern/senken
int score = 0;          // Punktestand
ArrayList tomaten = new ArrayList();   // Liste aller noch nicht abgeschossenen Tomaten
float startTime = -1;   // Zeit wird gemessen, sobald die erste Tomate angeklickt wurde
PImage bild;            // speichert die Tomaten-Grafik

// wird automatisch bei Start aufgerufen; bereitet alles für den weiteren Programmablauf vor
void setup() {
  size(700, 500);
  smooth();
  noFill();
  stroke(0);
  bild = loadImage("tomate.png");
  int maxRadius = 35;
  for (int i=0; i < NUM_TOMATEN; i++) {
    int x = (int) random(maxRadius, width-maxRadius);
    int y = (int) random(maxRadius, height-maxRadius);
    int d = (int) random(maxRadius, maxRadius*2);
    float dx = random(-1, 1) * SPEED;
    float dy = random(-1, 1) * SPEED;
    Tomate t = new Tomate(x, y, d, dx, dy);
    tomaten.add(t);
  }
}

// Dies ist die zentrale Komponente des Programms, der "game loop" (http://en.wikipedia.org/wiki/Game_programming#Game_structure).
// draw() wird von Processing automatisch ca. 60mal/Sekunde aufgerufen.
void draw() {
  background(255);
  for (int i=0; i < tomaten.size(); i++) {
    Tomate t1 = (Tomate) tomaten.get(i);
    t1.move();
    t1.display();
    if ((keyPressed) && (key == ' ') && t1.istUnterMauszeiger()) {
      tomaten.remove(i);
      if (startTime == -1) {
        startTime = millis();   // starte Zeitmessung bei der ersten abgeschossenen Tomate
      }
      score = 10000 * (NUM_TOMATEN-tomaten.size()) - (int) (millis()-startTime);
    println("Punktestand: " + score);
    }
  }
  if (tomaten.size() == 0) {
    println();
    println("Gratulation! Du hast den Angriff der Killertomaten abgewehrt!");
    println("Gesamtpunktzahl: " + score);
    exit();
  }
}

Ganz noch kurz zu den „globalen Variablen“: In Wirklichkeit gibt’s sowas in Java natürlich nicht. Das ganze Processing-Programm wird intern in eine Subklasse der Klasse PApplet umgewandelt. D.h. die „globalen“ Variablen sind in Wirklichkeit Attribute dieser Klasse. Man sieht das schön, wenn man den Schritt vom Processing-Editor zu Eclipse macht (http://processing.org/learning/eclipse/). Ich könnte mir vorstellen, dass das ein sinnvoller Dreischritt zur Einführung von Java ist:

  1. Processing mit dem internen Editor: Spielerisch Java kennenlernen ohne das ganze nervige Java-Drumrum
  2. Processing + Eclipse: Erstmal keine neuen Progammierkonzepte, sondern dient v.a. der Einführung in Eclipse
  3. Eclipse mit anderen Java-Projekten: „…und morgen die ganze Welt!“

Je nachdem, was ich im nächsten Schuljahr an Klassen bekomme, kann ich das ja mal ausprobieren.

Tomate


Das harmlose Äußere trügt!

Return of the Killertomaten

Die zweite Doppelstunde Killertomaten (s. hier) nebst beratendem Unterrichtsbesuch vom Fachleiter liegt nun auch schon wieder längere Zeit hinter mir. Ich hatte noch am selben Nachmittag angefangen darüber zu bloggen, dann kamen mir aber die Familie, die Ferien und (was ja anscheinend nicht untypisch ist) Krankheit dazwischen. Damit ich hier wenigstens noch halbwegs aktuell bleibe, will ich mal das Wichtigste zusammenfassen.

Vorsicht: Dieser Post ist für Nicht-Informatiklehrer wahrscheinlich zu technisch.

Killertomaten machen selbst vor Apple-Produkten nicht Halt!Vormerkung Rant: Ich habe jetzt in verschiedenen Klassen und bei verschiedenen Kollegen gesehen, wie an meiner Schule bisher objektorientiertes Programmieren vermittelt wird.  Benutzt wurde in den Stunden, die ich gesehen habe, immer Java (PHP gibt’s auch, aber ich war noch nie dabei).  Sehr unterschiedlich waren die verwendeten Editoren: von Eclipse über BlueJ bis zu JOE und sogar schlicht Windows Notepad.  Gemeinsam war allen besuchten Stunden, dass die Unterrichtsbeispiele häufig gar nicht besonders objektorientiert waren (Zinseszinsrechnung???) und die SuS* im besten Fall relativ mechanisch Java-Idiome anwenden ohne sie wirklich zu verstehen. (Das wichtigste Kennzeichen von OOP ist anscheinend, dass man get() und set() Methoden schreiben muss!)  Im schlechtesten Fall haben sich der Schüler bzw sein Lehrer damit abgefunden, dass Informatik unverständlich und doof ist bzw. der Schüler unverständig und doof ist. Ich bin ja, wenn auch nicht mehr jung, so doch unerfahren und naiv genug immer noch zu glauben, dass das nicht so sein muss.  Und deswegen habe ich einfach mal zwei Doppelstunden lang etwas ausprobiert. (An dieser Stelle: Danke dem Kollegen, der mir das in seiner Klasse ermöglicht hat!)
Rant Ende.

Zur Motivation: Objektorientierung erschließt sich am besten, so meine Ausgangsthese, wenn die Objekte so konkret wie möglich werden, wenn man sie sehen und, wenn schon nicht anfassen, so doch wenigstens mit Maus und Tastatur abknallen beeinflussen kann. Und wenn es viele sind! Denn dann kann man erkennen, dass es ein eigentlich sehr intuitiver und ordnungsstiftender Ansatz ist, wenn jedes Objekt sich um sich selbst kümmert, d.h. seine Eigenschaften selbst verwaltet und seine Fähigkeiten selbst anwendet.

Zur Klasse: Eine Oberstufenklasse, die seit September (also einem Dreiviertelschuljahr) zwei Stunden pro Woche ein Fach namens „OOP“ hat und dort in Java programmiert.  Beste Voraussetzungen also! Ich kenne die Klasse ganz gut aus Mathe, in Informatik hatte ich sie aber bisher noch nicht unterrichtet.  Sie sind nett, ziemlich faul und – richtig geraten – zu 90 Prozent männlichen Geschlechts.

Zum Projekt: In der ersten Doppelstunde durften ein paar Freiwillige erstmal am Beamer versuchen, sich gegenseitig in meiner ersten Version des Spiels zu überbieten.  Diese Version benutzt noch einfache rote Kreise – was aber dem Spielspaß der Freiwilligen keinen Abbruch getan hat…

Tomaten als Kreise

Dann habe ich Processing eingeführt und die Schüler viel damit experimentieren lassen. Zuerst haben sie randomisierte, aber noch statische Bilder erzeugt (Arbeitsauftrag „Ein modernes Kunstwerk“), dann mit draw() auch Animationen.  Am Schluss der Stunde hatten sie einen einzelnen roten Kreis programmiert, der sich über Bildschirm bewegt und am Rand „abprallt“, d.h. seine Richtung ändert.  Allerdings war alles noch völlig prozedural: Koordinaten, Richtung und Größe des Kreises bzw. unserer ersten Tomatenapproximation waren in globalen Variablen gespeichert.  Kein Problem bei nur einer Tomate – aber kann man so eine Armee mordlüstiger Killertomaten generieren? Wohl kaum.  Ein erstes Problembewusstsein für die Notwendigkeit besserer Datenstrukturen war also geweckt.

Diese Stunde hat den Schülern, glaube ich, großen Spaß gemacht.  Einige haben gebeten, noch in die Pause hineinarbeiten zu können, um den Richtungswechsel am Fensterrand noch hinzubekommen. Da ist der Herr Referendar natürlich glücklich…

In der Folgestunde wurde das Problem der Erweiterbarkeit dann wieder aufgegriffen („Wir wollen doch eine Invasion – wir brauchen mehr Tomaten!!!“).  Aber dazu bräuchten wir noch viel mehr globale Variablen.  Wäre es da nicht besser, jede Tomate wäre, wie in der Realität, ein eigenes Objekt und würde ihren eigenen Zustand speichern?

Anhand der Frage „Was passt nicht in die Reihe: x, y, durchmesser, punktestand, xdir, ydir?“ sollten die Schüler Eigenschaften einer Klasse Tomate diskutieren und entwerfen.  Die Details erspare ich euch und mir, aber es wurde doch schnell offensichtlich, dass die Schüler es nicht gewohnt sind, selbst zu modellieren, sondern nur, Vorgegebenes (z.B. ein UML-Klassendiagramm) zu implementieren.  Die Diskussion war gut, aber trotzdem fiel es den Schülern wahnsinnig schwer, ihre (richtigen) Erkenntnisse in ein eigenes Design umzusetzen – obwohl eigentlich nur ein paar globale Variablen aus der prozeduralen Musterlösung in Attribute umgewandelt werden mussten.

Zwei, die es geschafft hatten, führten dann ihre Lösung vor.  Sie hatten eine einzige Methode move() verwendet.  Von draw() 60mal pro Sekunde aufgerufen, werden in dieser Methode die Koordinaten sowie, bei Bedarf, der Richtungsvektor der Tomate angepasst und die Tomate als roten Kreis an die neue Position gezeichnet. Prima so. Als Alternative zeigte ich auch noch meine eigene Version, in der es eine separate Methode display() gibt, d.h. die Bewegung ist von der Darstellung des Objekts entkoppelt.  Warum das Sinn macht, konnte ich live vorführen, indem ich „under the hood“, nur durch Veränderung eines einzigen Befehls in display(), statt des Kreises plötzlich eine Grafik anzeigen ließ. Die Schüler haben mitgetippt und selbst plötzlich eine „echte“ Tomate auf dem Bildschirm animiert! Ich mag mich ja sehr täuschen, aber sie fanden’s schon cool, wie schnell sie das Aussehen ihres Spiels „gepimpt“ hatten – und das, ohne dass die aufrufende Funktion draw() auch nur mitbekommen hat, dass display() plötzlich etwas ganz anderes macht.

Eine weitere sehr schöne Diskussion hatten wir dann, als es darum ging, wie man denn nun von außen mit der Tomate interagieren kann. „Hier fehlt die Action – das soll ja schließlich ein Shooter werden!“ Also auf zur Überprüfung der Treffsicherheit der Spieler. Wieder haben wir festgestellt, dass die Tomate selbst ihre Koordinaten und ihre Größe am besten kennt und dass also auch sie es ist, die am einfachsten die Frage „Zeigt die Maus auf dich?“ beantworten kann.  Nachdem wir über Bounding Boxes, das verwandte Thema der Mouseover-Effekte in CSS und das Problem teilweise transparenter Grafiken wirklich schön diskutiert hatten (kam alles nicht von mir und war auch so nicht geplant, aber ich habe es laufen lassen, weil es toll war, die Schüler beim Mitdenken zu erleben), haben wir uns schließlich darauf geeinigt, dass einfacher meistens besser ist und wir deshalb eine Methode istUnterMauszeiger() schreiben, die die alte Idee der Tomate als Kreis benutzt und schlicht abfragt, ob die Distanz zwischen Mauszeiger und Kreismittelpunkt kleiner als der Radius ist.  Mit diesem schönen Plan ging die Stunde dann zuende…

Fazit: Mir war vorher klar, dass das Projekt, ein Spiel in zwei Doppelstunden zu schreiben ein utopisches ist.  Umso mehr, weil die Klasse ja weder mich, noch Processing, noch eine solche Vorgehensweise kannte.  Aber ich wollte es partout probieren!  Um zu sehen, ob Processing mit seinem schnellen Feedback-Loop (tippen, starten, verbessern, wieder starten!) den Schüler ein kleines bisschen Programmier-Flow bringt, ob graphische Objekte den OOP-Gedanken für die Schüler anschaulicher machen, und aus der vagen Intuition heraus, dass Gamification auch funktioniert, wenn man ein Spiel nicht spielt, sondern entwickelt.

Und hat es funktioniert? Ja und nein. Ich habe die Schüler überfordert, durchaus bewusst, aber das macht es ja nicht besser.  Insbesondere in der zweiten, der OOP-Stunde, fürchte ich, dass die meisten nicht wirklich viel gelernt haben.  Ich habe sie einfach nicht da abgeholt, wo sie standen, sondern eine Stunde gehalten die mir konzeptuell Spaß gemacht hat (und meinem Fachleiter, glaube ich, auch), aber an der Realität ihres Vorwissens und Könnens, das ich vorher aber auch nur schlecht einschätzen konnte, vorbeiging.  Andererseits: Processing hat sehr gut funktioniert, das hat die erste Doppelstunde gezeigt. Und das grundsätzliche Interesse, die Bereitschaft mitzudenken, war ebenfalls spürbar, bei einigen jedenfalls.  Besser als Zinseszinsrechnung war es wahrscheinlich sogar für alle, behaupte ich jetzt einfach mal…

Mein Experiment habe ich gehabt.  Jetzt, wo ich noch relativ am Anfang des Referendariats stehe, möchte ich mir solche Versuchsballone auch noch ab und zu zugestehen. Auf Dauer? Nicht an den Schülern vorbei!

Insgesamt ist mein Fazit aber doch ein hoffnungsvolles und lautet: Wenn es nur schon nächstes Schuljahr wäre und ich einen eigenen, festen Kurs hätte…
Dann, glaube ich, könnte man durchaus die Konzepte, die ich hier wider besseres Wissen vorausgesetzt hatte, in schülergerechtem Tempo einführen.  Vielleicht sogar so, dass sie nach sechs Monaten so weit verfestigt sind, dass man ein solches „Projektchen“ stemmen kann.  Wir werden sehen.

* Ich benutze das „SuS“ jetzt testweise auch mal, damit ich mich nicht so sexistisch fühle, wenn ich wieder nur „Schüler“ geschrieben habe oder so retro-feministisch, wenn ich „SchülerInnen“ schreibe oder so missverständlich bei „Schülerinnen“ oder so umstandskrämerisch bei „Schülerinnnen und Schülern“. Aber „SuS“? Das ist doch eine Abkürzung und kein Wort! Mein Sprachgefühl sträubt sich und zieht sich in den Schmollwinkel zurück. Auch recht, dann habe ich ja jetzt alle Freiheiten, IMHO. LOL 😉  OMG!