5 TURTLE-Grafik:

Zum vorigen Kapitel Zum Inhaltsverzeichnis Zum nächsten Kapitel


Eine komfortable Möglichkeit, Grafiken herzustellen, bietet die Turtle-Grafik. Sie stellt eine "Schildkröte" zur Verfügung, die man über eine Zeichenfläche bewegen kann, wobei sie dort eine "Spur" hinterlassen kann. Das beste an der Turtle-Grafik ist, dass man sich nicht mit Koordinaten herumschlagen muss! Man sagt der Turtle einfach: "Gehe um soundsoviele Pixel vorwärts." Und dann tut sie das, und hinterlässt dabei einen entsprechenden Strich auf der Zeichenfläche. Und dann sagt man (z.B.): "Drehe dich um 45 Grad nach rechts!", und auch das wird sie schön brav tun. Es ist wirklich kinderleicht, und das war auch genau die Intention des Erfinders der Turtle-Grafik, Seymour Papert: er wollte den Computer für Kindergarten-Kinder handhabbar machen! Dafür veröffentlichte er 1968(!) die Programmiersprache LOGO, deren erfolgreichster Teil in der Implementierung einer Turtle-Grafik bestand. Wenngleich LOGO inzwischen ziemlich in Vergessenheit geraten ist, hat die Turtle-Grafik seit dieser ersten Implementierung einen wahren Siegeszug angetreten.

Es gibt viele verschiedene Realisierungen für die Turtle-Grafik. Die meisten packen die Turtle in eine eigene Komponente, und die muss dann erst mal in der Programmier-Umgebung installiert werden. Das ist nicht immer einfach; speziell in Schulnetzen ergeben sich dabei häufig Probleme.

Für Delphi hat Herr Frieder Sprandel einst eine ziemlich luxuriöse Turtle geschrieben und sie in eine eigene Komponente gepackt. Leider erlauben manche der neueren Versionen von Delphi (wie z.B. der "Turbo Delphi Explorer") keine eigenen Komponenten-Installationen mehr. Deshalb müssen wir einen anderen Weg gehen: wir trennen die eigentliche "Zeichenmaschine Turtle" von der Zeichenfläche, für die wir eine Standard-Komponente von Delphi benutzen, nämlich eine PaintBox. Da hinein setzen wir die eigentliche Turtle, die dann alle ihre Ausgaben schön auf die darunterliegende Paintbox zeichnet. Die Implementierung der zugehörigen Klasse "TTurtle" verpacken wir in eine eigene Unit namens "mTurtle2", wobei die nachgestellte "2" eine Versions-Nummer andeuten soll.

Der Nachteil dieser Lösung ist, dass wir die Turtle beim Starten unseres Programms erst erzeugen müssen. Das ist aber einfach und zuverlässig möglich, wenn wir es in der "OnCreate"-Methode des Formulars erledigen:
   procedure TForm1.FormCreate(Sender: TObject);
     begin
     Turtle1 := TTurtle.Create(PaintBox1);
     end;
Beachten Sie, dass Sie diese OnCreate-Methode im Objekt-Inspektor auf der Registerkarte "Ereignisse" Ihres Formulars (normalerweise "Form1") erzeugen müssen - nur dann wird die Methode auch intern korrekt "verdrahtet". Zwischen "begin" und "end" dieser Methode tragen Sie dann die obige Zeile ein, mit der die eigentliche Turtle erzeugt wird. Damit Delphi das aber auch so machen kann, müssen wir vor dem nächsten Kompiler-Lauf noch im "private"-Bereich von TForm1 eine Variable "Turtle1" vom Typ "TTurtle" deklarieren, in der dann unser Turtle-Objekt abgespeichert wird. Ein Probelauf des Compilers offenbart allerdings ein weiteres Problem: "Undefinierter Bezeichner TTurtle". Wir müssen noch die Unit "mTurtle2" in die "uses"-Liste der "Unit1" aufnehmen. Diese Liste steht ganz oben im Quelltext von "Unit1" und listet alle Units auf, die von "Unit1" benutzt werden. Zur besseren Übersicht hier nochmals all die einzelnen Schritte, die wir für ein erfolgreiches Initialisieren der Turtle ausführen müssen, gleich in einer etwas sinnvolleren Reihenfolge:


  1. Kopiere die Unit "mTurtle2.pas" ins Projektverzeichnis.
  2. Nimm diese Unit in die "uses"-Liste der "Unit1.pas" auf.
  3. Deklariere im "private"-Abschnitt von TForm1 eine Variable "Turtle1" vom Typ "TTurtle".
  4. Füge dem Formular eine PaintBox hinzu; standardmäßig heißt diese "PaintBox1".
  5. Erzeuge im Objekt-Inspektor eine "OnCreate"-Methode für unser Formular "TForm1".
  6. Rufe in dieser Methode den Konstruktor der Klasse "TTurle" auf, übergib ihm die PaintBox1 und speichere die zurückgelieferte TTurtle-Instanz in der Variablen "Turtle1" ab.


Okay, das klingt nun schon etwas kompliziert, und manches wird Ihnen wohl im Augenblick noch ein wenig mysteriös erscheinen, aber wenn Sie das fünfte Turtle-Grafik-Programm geschrieben hat, dann werden Sie mit diesem Prozedere schon klarkommen, denn: Übung macht den Meister! ;-)

Wenn wir unser Programm in diesem Zustand mal versuchsweise starten, dann erscheint die PaintBox, und in ihrer Mitte ein kleines gleichschenkliges Dreieck, das nach oben schaut. Dies ist die (grafische Repräsentation der) Turtle. Um die Turtle nun etwas zeichnen zu lassen, erzeugen wir auf dem Panel einen "Zeichnen"-Knopf, in dessen Klick-Prozedur wir die entsprechenden Turtle-Graphikbefehle eintragen. Das Fenster könnte dann so aussehen:

Turtle in Aktion

Wenn wir die Turtle nun um 100 Pixel vorwärts gehen lassen wollen, dann schreiben wir in die Klick-Prozedur des "Zeichnen"-Knopfes hinein:
   procedure TForm1.BtnZeichnenClick(Sender: TObject);
     begin
     Turtle1.FD(100);
     end;

Hier ist eine kurze Liste der wichtigsten Turtle-Grafikbefehle:
  • FD (s: Real) {"ForwarD"} geht um die Strecke s nach vorne
  • BK (s: Real) {"BacK"} geht um die Strecke s rückwärts
  • LT (w: Real) {"LeftTurn"} dreht sich um den Winkel w nach links
  • RT (w: Real) {"RightTurn"} dreht sich um den Winkel w nach rechts
  • PD {"PenDown"} senkt den Zeichenstift aufs Papier
  • PU {"PenUp"} hebt den Zeichenstift vom Papier ab
  • HOME setzt die Turtle an die Standard-Startposition
  • CS {"ClearScreen"} löscht die Zeichnung
  • ST {"ShowTurtle"} zeigt die Turtle an
  • HT {"HideTurtle"} verbirgt die Turtle
Eine vollständigere Liste finden Sie in der HTML-Hilfe-Datei zur Turtle. Und wenn Sie auch noch hinter die letzten Geheimnisse der Turtle kommen wollen, können Sie schließlich den Quelltext der Unit studieren: es gibt da noch einige ganz erstaunliche undokumentierte Details...
Für Java gibt es zahlreiche Implementierungen der Turtle-Graphik, wie z.B. im "Virtuellen Campus Projekt" der PH Bern, wo eine ganze Online-Lernumgebung mit einer sehr leistungsfähigen Turtle-Graphik zur Verfügung gestellt wird. Wer eine weniger opulente Lösung bevorzugt, kann sich auch mit der spartanischen Turtle zufrieden geben, welche mit dem "JavaEditor" von Herrn Röhner geliefert wird. Diese verzichtet z.B. auf eine graphische Repräsentation der eigentlichen Turtle, also des "Zeichenkopfes", was die Implementierung natürlich deutlich schlanker hält. Trotzdem hat man den Komfort, die Turtle im graphischen Formular-Designer des JavaEditors mit ein paar Mausklicks einfügen zu können. Und alles, was für uns in dieser Unterrichtseinheit über Turtle-Graphik wichtig ist, ist mit dieser schmalen Turtle auch schon abgedeckt. Deshalb entscheiden wir uns hier für die im JavaEditor zur Verfügung gestellte Turtle-Komponente, -- auch wenn man sie trotz allem zuerst noch installieren muss.

Es empfiehlt sich, über dem vorgesehenen Zeichenbereich der Turtle zunächst ein JPanel zu platzieren, in welchem man einen oder mehrere Knöpfe zum Auslösen von Zeichenfunktionen der Turtle unterbringen kann. In den meisten Fällen genügen zwei Knöpfe, einer zum "Zeichnen" der gewünschten Figur und einer zum "Löschen" der Zeichnung. Der restliche Bereich des Programmfensters sollte von der Turtle-Komponente ausgefüllt werden. Die im visuellen Design festgelegten Gebietsverteilungen werden zur Laufzeit einfach übernommen: der JavaEditor arbeitet mit "absoluter Positionierung" der Komponenten. Daher ist es klug, auf die Skalierung des Fensters zur Laufzeit zunächst zu verzichten und das JFrame-Attribut "Resizeable" auf "false" stehen zu lassen. Im Formular-Designer könnte das Programmfenster dann so aussehen:

Turtle in Aktion

Am Anfang steht die (unsichtbare) Turtle in der Mitte des Turtle-Fensters und schaut nach rechts. Um die Turtle etwas zeichnen zu lassen, tragen wir in die Klick-Methode des "Zeichnen"-Knopfs die entsprechenden Turtle-Graphikbefehle ein. Wenn die Turtle z.B. um 100 Pixel vorwärts gehen und dabei einen Strich zeichnen soll, dann schreiben wir:
   public void button1_ActionPerformed(ActionEvent evt) {
     turtle1.draw(100);
   }
Hier ist eine kurze Liste der wichtigsten Turtle-Grafikbefehle:
  • draw(double ds) : geht zeichnend um ds Pixel in die aktuelle Richtung
  • drawto(double x, double y) : geht zeichnend von der aktuellen Position (turtleX, turtleY) geradlinig zur Position (x, y)
  • move(double ds) : geht ohne zu zeichnen um ds Pixel in die aktuelle Richtung
  • moveto(double x, double y) : geht ohne zu zeichnen von der aktuellen Position (turtleX, turtleY) zur Position (x, y)
  • turn(double angle) : dreht die Richtung der Turtle relativ um den Winkel angle (im Gradmaß)
  • turnto(double angle) : setzt die Richtung der Turtle absolut auf den Winkel angle (im Gradmaß)
  • clear() : löscht die Zeichnung und setzt die Turtle auf die Startposition, d.h. in die Mitte des Turtle-Fensters
Daneben gibt es noch einige wichtige Eigenschaften der Turtle, welche in Variablen gespeichert sind, auf die Sie lesend und schreibend zugreifen können:
  • turtleX, turtleY : sind zwei double-Variablen, die die aktuellen Position der Turtle enthalten.
  • foreground : ist eine Variable vom Typ Color, die die aktuelle Zeichenfarbe der Turtle enthält.
Eine vollständigere Liste finden Sie in der HTML-Hilfe-Datei zur Turtle.


Auf den ersten Blick sieht es so aus, als könnte diese Turtle nur vorwärts gehen. Das ist aber nicht der Fall: wenn Sie den Methoden "draw()" bzw "move()" einen negativen Wert für ds übergeben, dann bewegt sich die Turtle um |ds| entgegengesetzt zu ihrer aktuellen Richtung, kurz: rückwärts! Analog hat ein positiver Wert von angle eine Linksdrehung zur Folge, wenn er an "turn()" übergeben wird, ein negativer hingegen eine Rechtsdrehung.

Obwohl die Turtle-Graphik oben als weitgehend koordinatenfrei vorgestellt wurde, verfügt diese Implementierung zusätzlich zu den relativen Zeichenbefehlen ("draw()", "move()" und "turn()") noch über einen vollständigen Satz entsprechender Befehle, die als Argumente absolute Koordinatenwerte erwarten. Das dabei zugrunde gelegte Koordinaten-System ist das Pixelkoorinatensystem des Turtle-Fensters, mit dem Nullpunkt in der linken oberen Ecke!
    Bei vielen anderen Turtle-Implementierungen liegt der Koordinatenursprung in der Mitte des Turtle-Fensters, die y-Achse zeigt nach oben statt (wie hier) nach unten, und ein "clear()" löscht nicht nur die Zeichnung, sondern setzt auch die Position der Turtle auf den Startpunkt (0, 0) zurück. Das ist bei unserer Spar-Turtle leider alles nicht der Fall.
Turtle in Aktion Die absoluten Befehle "drawto()", "moveto()" und "turnto()" stehen eigentlich im Widerspruch zur Grundidee der Turtle-Graphik: nämlich eine einfache Zeichenmaschine zur Verfügung zu stellen, die schon von kleinen Kindern benutzt werden kann! In der Regel kann man auf diese "un-LOGO-ischen" Befehle weitgehend verzichten, und Sie sollten das nach Möglichkeit auch so handhaben. Die nebenstehende Zeichnung wurde z.B. völlig ohne diese absoluten Befehle erstellt:


Bearbeiten Sie nun die folgende Aufgabe:
  1. Das Haus vom Nikolaus:

    Schreiben Sie ein Programm, das eine Turtle-Komponente und einen Knopf enthält. Auf Knopfdruck soll "das Haus vom Nikolaus" gezeichnet werden. Falls Sie das für den Zeichen-Algorithmus brauchen können: man kann in einem Programm auch die Wurzel aus 2 errechnen, nämlich mit "sqrt(2)". - Schaffen Sie die Zeichnung ohne abzusetzen, d.h. in einem Zug?

    In einer Luxusversion ("Villa vom Nikolaus") könnten Sie noch für ein rotes Dach sorgen....
    Lösungsvorschlag: [Delphi] [Java]



Wenn Sie nun als fortgeschrittener Delphi-Schüler Ihr Nikolaus-Programm gleich schön luxusmäßig gestaltet haben, indem Sie zuerst eine TPanel-Komponente (mit "align = alRight") zur Aufnahme der Steuer-Knöpfe erzeugt haben, und dann die PaintBox1 (mit "align = alClient") auf den ganzen restlichen Platz des Fensters ausgedehnt haben - dann wird Ihnen schmerzlich auffallen, dass sich bei Größenänderungen des Formulars leider der Zeichenbereich der Turtle nicht entsprechend mitverändert: er bleibt genau so groß (oder klein), wie er zu Programmbeginn eingestellt wurde. Aber auch dafür gibt es Abhilfe: erzeugen Sie in Ihrem Formular eine "OnResize"-Methode, und rufen Sie darin "Turtle1.Resize;" auf!

Dann wird bei Größenänderungen des Formulars die Größe des Zeichenbereichs der Turtle an die aktuelle Größe der Paintbox angepasst. Allerdings geht dabei die zuvor angezeigte Zeichnung verloren! Das ist bei unseren einfachen Programmen aber leicht zu verschmerzen: ein Klick auf den entsprechenden Knopf behebt das Problem. (Eine allgemeingültige Lösung ist in komplizierteren Programmen übrigens nicht ganz einfach zu implementieren.)
Wenn Sie nun als fortgeschrittener Java-Schüler Ihr Nikolaus-Programm gleich schön luxusmäßig gestaltet haben, indem Sie das JFrame-Fenster zur Laufzeit skalierbar gemacht haben, dann wird Ihnen aufgefallen sein, dass weder die JPanel-Komponente noch die Turtle-Komponente bei einer Größenänderung adequat reagieren. Um das zu beheben, müssen Sie sowohl für das JPanel als auch für die Turtle-Komponente eine Behandlungsmethode für das (reichlich versteckte) Ereignis "ancestorResized" erzeugen. Erzeugen Sie dann die Ereignismethoden und ergänzen Sie sie folgendermaßen:
    public void panel1_AncestorResized(HierarchyEvent evt) {
      panel1.setBounds(0, 0, getWidth(), 50);
    }
    public void turtle1_AncestorResized(HierarchyEvent evt) {
      turtle1.setBounds(0, 50, getWidth(), getHeight() - 50);
    }
Dann wird bei Größenänderungen des Formulars die Größe der beiden Komponenten an die aktuelle Fenstergröße angepasst.



Und hier sind weitere Aufgaben für kreative Computer-Zeichner:

  1. Das Quadrat-Problem:

    Ein Programm soll in einer Turtle-Komponente auf Knopfdruck ein Quadrat zeichnen. Schreiben Sie ein solches Programm.

    Wenn Sie sich die Liste der dazu nötigen Zeichenbefehle anschauen, fällt auf, dass da 4 mal genau dasselbe gemacht wird. Eine solche Situation verlangt nach einer Schleife. Formulieren Sie die Zeichenprozedur mit Hilfe einer WHILE-Schleife um.
    Lösungsvorschlag: [Delphi] [Java]




  2. Das N-Eck-Problem:

    Schreiben Sie ein Programm, das ein regelmäßiges N-Eck zeichnet. Die Eckenzahl soll dabei vom Benutzer eingegeben werden. Sorgen Sie dafür, dass keine kleinere Eckenzahl als 3 eingegeben werden kann.

    Auf Knopfdruck soll eine eventuell vorhandene Zeichnung gelöscht und das neue N-Eck gezeichnet werden.

    Verwenden Sie eine WHILE-Schleife, um das N-Eck zu zeichnen. Wie groß ist der Winkel, um den sich die Turtle an jeder Ecke drehen muss? (Falls Ihnen das nicht klar ist, dann überlegen Sie zunächst, um welchen Winkel sich die Turtle insgesamt drehen muss, bis das N-Eck vollständig gezeichnet ist.)


    Erweiterung: Wenn Sie hinreichend viel Geometrie können und in der 10. Klasse gut aufgepasst haben, sollte es Ihnen gelingen, das N-Eck im Fenster zu zentrieren (wenigstens für große Eckenzahlen) und die Seitenlänge automatisch so zu wählen, dass das N-Eck in den verfügbaren Zeichenbereich passt.
    Lösungsvorschlag: [Delphi] [Java]



Zum vorigen Kapitel Zum Inhaltsverzeichnis Zum nächsten Kapitel