Programmierübungen
Im Folgenden finden sich abwechselnd ein kurzer Einleitungstext gefolgt von
einer oder mehreren dazugehörigen Aufgaben. Fällt Dir die erste
Aufgabe zu einem Thema sehr leicht, so kannst Du die weiteren ruhig
überspringen. Fällt Dir die Aufgabe hingegen schwer, so empfiehlt
es sich, auch die weiteren noch zu bearbeiten.
Diese Seite werde ich einige Zeit im Netz stehen lassen, Du kannst also immer
nachsehen, bzw. Aufgaben üben. Möglicherweise werde ich noch einige
Musterlösungen anbieten, dies ist allerdings nicht sicher.
Viel Spaß damit!
- Tippen, Speichern und Ausführen
- Die Variablentyperweiterung ,Array'
- Ein Wort zu Methoden
- Übergabe von Werten beim Programmstart
- For-Schleifen
- While-Schleifen
- If-Selektion
- Spaß mit Objekten
- Noch mehr Spaß mit Objekten
- Die Herausforderung
Tippen, Speichern und Ausführen
So geht's:
- Starte den Emacs, den XEmacs oder einen anderen Editor
- Öffne eine Datei, die [Klassenname].java heißt. Achtung: Auf die Groß- Kleinschreibung achten!
- Tippe Dein Programm.
- Speichere es ab.
- Öffne ein XTerm-Fenster
- Gib den Befehl setup java ein, so er noch nicht in Deiner .login-Datei steht.
- Gib den Befehl javac [Dateiname] ein - das kann etwas dauern.
- Führe das Programm mit dem Befehl java [Dateiname] ([Argumente]) aus. Die Argumente können auch entfallen.
Probier's:
-
Tippe das folgende Programm ab, kompiliere es (javac) und führe es aus.
public class HelloWorld {
public static void main(String args[]) {System.out.println("Hello World!"); }
}
-
Erstelle ein Programm, das die Mitteilung ,Hello World!' in einen Kasten aus
Sternen setzt:
****************
* Hello World! *
****************
Arrays
Was ist das?
Ein Array ist eine Erweiterung eines Variablentyps, und zwar werden mehrere Variablen
des gleichen Typs unter einem Namen zusammengefaßt. Um die einzelnen
Variablen unterscheiden zu können, werden sie mit einem Index versehen, der
von 0 bis (Größe des Arrays)-1 geht. Der Index steht in eckigen Klammern.
OK, ein Beispiel: String args[] ist ein Array von Strings, das ,args' heißt. Will
man auf das 3. Element von args[] zugreifen, so schreibt man: args[2]. Diese Verbindung
von Variablenname und Arrayindex kann wie ein neuer Variablenname benutzt werden.
Der Nutzen liegt darin, daß man nun über eine Variable auf eine Variable
zugreifen kann. Nehmen wir an, in der int-Variable nr sei abgespeichert, auf welchen Datensatz man zugreifen
möchte. Dann kann dieser Datensatz direkt z.B. vermittels System.out.println(name[nr]); ausgegeben werden. Natürlich
kann man name[nr] auch in anderen Befehlen benutzen, wie
einen ,normalen' Variablennamen auch.
So geht's:
Um ein Array einzurichten, benutzt man folgenden Programmcode:
| Variablentyp | Variablenname | Instantiierung | |
| String | Namen[] | = | new String[100]; |
| int | Matrnr[] | = | new int[100]; |
| int | fib[] | = | {0, 1, 1, 2, 3, 5, 8}; |
| Boolean | flags[]; |
In Zeile 1 und 2 werden Arrays fester Größe eingerichtet. Auf die einzelnen
Elemente kann in der Folge mit Namen[nr] bzw. mit Matrnr[nr] zugreifen, wobei der Wert von nr von 0 bis 99 reichen darf.
In Zeile 3 werden gleichzeitig die einzelnen Elemente des Arrays instantiiert: fib[4] hat z.B. den Wert 3. Die Größe dieses Arrays ist
auch festgelegt, sie reicht von 0 bis 6.
In der letzten Zeile wird ein Array definiert, aber noch nicht instantiiert. Bevor man
das Array benutzt, muß man dieses nachholen. Diese Möglichkeit benutzt man,
falls man die Größe des Arrays erst später festlegen möchte.
Probier's:
Sorry, aber die sinnvolle Benutzung von Arrays setzt die Kenntnis von Schleifen voraus. Übungen folgen also erst später...
Ein Wort zu Methoden
Was ist das?
Methoden sind Unterprogramme: Häufig benutzte Befehlsfolgen werden
zusammengefaßt und mit einem Namen versehen. Wenn nun - wie ein neuer Befehl -
dieser Name benutzt wird, wird die Methode aufgerufen, also die dort stehende
Befehlsfolge abgearbeitet. Am Ende der Methode oder aber nach Ausführung des
return-Befehls kehrt das Programm direkt an die Stelle
nach dem Methodenaufruf zurück.
Eine Methode hast Du schon kennengelernt: Die main-Methode.
Diese wird nicht von Dir aufgerufen, sondern ist die Methode, die beim Starten des
Programms aufgerufen wird.
Um Methoden flexibel zu halten, kann man ihr Parameter übergeben. Das bedeutet,
daß der Methode Variablen zur Verfügung stehen, deren Werte beim Aufruf
der Methode angegeben werden.
So geht's:
Sieh Dir noch einmal das Beispiel der main-Methode an:
public static void main(String args[]) {
}
Was bedeutet public? Das bedeutet, daß diese Methode überall sichtbar ist,
d.h. sie kann von jeder anderen Klasse oder Programm aufgerufen werden. Weiterhin gibt
es noch ,private' (nur von der eigenen Klasse aus aufrufbar) und ,protected' (nur von
der eigenen und abgeleiteten Klassen aus aufrufbar).
Was bedeutet static? Das heißt, daß die Variablen dieser Methode einmal
beim ersten Aufruf angelegt werden, jeder weitere Aufruf greift dann auf die gleichen
Variablenwerte zu. Das Gegenstück ist ,dynamic'; dies ist dann die
Standarteinstellung, die nicht explizit angegeben werden muß.
Was bedeutet void? Das bedeutet, daß diese Methode keinen Wert zurückliefert.
Eine Methode kann auch wie eine (mathematische) Funktion benutzt werden, die dann
einen Wert zurückliefert. Im Programm kann eine solche Methode dann wie eine
Variable vom Typ des Rücklieferungswerts benutzt werden (abgesehen von einer
Wertzuweisung, die an eine Funktion natürlich nicht möglich ist). Will man
eine solche Funktion erstellen, so gibt man anstatt von ,void' den Typ der Methode an;
In der Methode folgt dann irgendwann der Befehl return. Der
Wert/die Variable, die hinter return steht, wird zurückgeliefert.
public int foo() ist z.B. eine öffentliche,
nicht-statische Funktion namens ,foo', die einen Wert vom
Typ ,int' zurückliefert.
Nun folgt der Name der Methode (der üblicherweise klein geschrieben wird). Dann
folgt entweder ein leeres Klammerpaar (s. foo) oder eine
Klammer, in der die Argumente aufgelistet sind (s. main).
Die einzelnen Variablen werden mit Typ angegeben und durch Kommata getrennt. main wird z.B. ein Array von Strings übergeben, das ,args' heißt. Es werden dabei nicht die Variablen
übergeben, sondern neue Variablen angelegt, und selbige dann mit dem
Übergabewert instantiiert.
Schließlich folgen die Befehle der Methode, eingefaß von geschweiften
Klammern.
Probier's:
Für diese Übungen ist die Kenntnis des nächsten Abschnitts wichtig, sie folgen nach den Übungen des folgenden Kapitels.
Übernehmen von Argumenten aus der Befehlszeile
Was ist das?
Argumente sind zusätzliche Informationen, die der Ausführende eines
Programmes angeben kann. Sie folgen nach dem Programmaufruf aus dem XTerm,
getrennt durch Leerzeichen:
java [Dateiname] [1. Argument] [2. Argument]...
So geht's:
main ist auch eine Methode, d.h. man kann ihr auch
Argumente übergeben. Diese sind ein Array von Strings. Üblicherweise
nennt man dieses Array ,args', aber auch jeder andere Name ist natürlich
zulässig. Die Übergabe der Argumente findet sich in der runden Klammer
nach dem Methodennamen ,main' (siehe auch das
Programmgerüst zum Abtippen)
Dabei steht das erste übergebene Argument in args[0],
das zweite in args[1], usw.
Will man andere Typen als einen String als Argument erhalten, so muß man
zunächst die Argumente als String einlesen, und anschließend in den
gewünschten Typ konvertierten. Beispiel:
int i=Integer.parseInt(args[0]);
initialisiert eine int-Variable i und weist ihr den int-Wert des 1. Arguments zu.
Probier's:
- Implementiere ein Programm ,Tier.java', das auf den Aufruf ,java Tier Kamel' ,Kamel ist ein Tier' ausgibt.
- Implementiere ein Programm ,Add.java', das auf den Aufruf ,java Add [x1] [x2]' die Summe aus x1 und x2 ausgibt. D.h., Du mußt also zunächst beide Werte in ints konvertieren, dann addieren und letztendlich ausgeben.
- Schreibe das ,Add'-Programm so um, daß es eine Methode namens ,plus' benutzt, die die Addition ausführt. plus soll public sein, und einen Wert vom Typ int zurückliefern. Als Argumente werden ebenfalls zwei ints übergeben.
- Schreibe ein Programm namens ,Fib', dem als Parameter eine int übergeben wird, und das die [Parameter]ste Fibonaccizahl
berechnet.
Benutze dabei die rekursive Definition ( F(0)=0; F(1)=1; F(n)=F(n-1)+F(n-2) ), benutze rekursive Aufrufe, d.h., daß sich Deine Methode selbst aufrufen soll.
Merke: Hier kommt es nicht auf eine effiziente Implementierung an!
Jetzt folgen die Aufgaben zu den Methoden!
For-Schleifen
Was ist das?
Schleifen sind Programmkonstrukte, die Wiederholungen (Iteration) möglich machen. Eines der einfachsten Beispiele ist die For-Schleife. Sie benutzt eine Zählvariable, eine Inkrementfolge und eine Abbruchbedingung. Normalerweise nutzt man eine int, die bei jedem Schleifendurchlauf um 1 erhöht wird und als bricht bei Erreichen eines bestimmten Wertes ab: Damit kann man eine n-fache Ausführung einer Befehlfolge erreichen.
So geht's:
for (int i=0; i<n; i=i+1) { [Befehlsfolge] }
for ist der Befehl, in der Klammer wird eine neue int
Variable erzeugt und mit 0 instantiiert. Damit ist die Zählvariable festgelegt.
Mit i=i+1 oder i++ wird die
Inkrementsfolge festgelegt: Bei jedem Schleifendurchlauf wird i um eins erhöht.
Schließlich folgt noch eine Abbruchbedingung: Sobald i=n (das ist eine andere
intVariable, die vorher festgelegt worden sein muß,
man kann natürlich auch eine feste Zahl angeben), wird die Schleife nicht mehr
ausgeführt.
Hinweis: Obwohl man meist ints als Zählvariablen benutzt, gibt es auch
durchaus Fälle, in denen ganz andere Konstrukte benutzt werden, wie z.B.
enumerations.
Probier's:
- Implementiere ein Programm, das die Zahlen von 0 bis n auf dem Bildschirm ausgibt, wobei n dem ersten Parameter entspricht.
- Erweitere dieses Programm derart, daß es vom ersten zum zweiten Parameter zählt.
- Implementiere ein Programm namens ,Kasten', das ein Quadrat aus Sternen (s. Tippen, Aufgabe 2) malt. Die Größe des Quadrats soll als Parameter übergeben werden können.
- Implementiere eine effiziente Version von ,Fib', die von 0 bis n zählt und dabei den Wert von F(i), F(i-1) und F(i-2) jeweils neu berechnet. n soll dabei als Parameter dem Programm übergeben werden.
While-Schleife
Was ist das?
Die While-Schleife verfügt im Gegensatz zur For-Schleife nur über eine Abbruchbedingung. Ob und wann diese erzeugt wird, hängt von den Befehlen der Schleife ab.
So geht's:
while ( [Bedingung] )
{ [Befehlsfolge] }
while ist der Befehl. Die Bedingung ist ein Ausdruck, dem ein Boolscher
Wert zugeordnet werden kann, also z.B. eine Variable vom Typ Boolean, oder ein
Vergleich (n==o, 7<m, p!=q), oder ähnliches.
In dieser Bedingung können auch logische Verknüpfungen benutzt werden:
| Operator | Bedeutung |
| ! | Negation |
| && | UND-Verknüpfung |
| || | ODER-Verknüpfung |
Die Schleife wird so lange ausgeführt, bis die Bedingung nicht mehr gilt (den
Wert false annimmt).
Gilt die Bedingung vor Ausführung der While-Schleife noch nicht, so wird diese
vollständig übersprungen. Mit dem Befehl do {
[Befehlsfolge] } while ( [Bedingung] ); kann man ein einmaliges Durchlaufen der Schleife erzwingen.
Probier's:
- Schreibe das Programm, das vom ersten Eingabeargument zum zweiten zählt, mit Hilfe einer Whileschleife
- Noch einmal Fibonacci: Das Programm soll die nächstkleinere Fibonaccizahl zum ersten Parameter ausgeben, ist der Parameter also z.B. im Bereich von 5 bis 7, so soll 5 ausgegeben werden, im Bereich von 8 bis 12 8, usw.
- Implementiere ein Programm, das die Zahl n ausgibt, so daß gilt, das die Summe der Zahlen von 1 bis n gerade noch kleiner als der Übergabeparameter ist.
Die If-Selektion
Was ist das?
Die If-Selektion entspricht so ungefähr der Selektion bei
Registermaschinen-Programmen. Es gibt eine Bedingung, und falls diese wahr (true) ist,
so wird das eine Programm ausgeführt, falls sie unwahr (false) ist, wird ein
anderes Programm ausgeführt. Nach der Selektion kann das Programm linear
fortgeführt werden.
Allerdings ist die Bedingung nicht nur, ob eine Variable den Wert 0 hat, sondern eine
ebenso allgemeine Bedingung wie bei der While-Schleife.
So geht's:
if ( [Bedingung] ) { [Befehlsfolge 1] } else { [Befehlsfolge 2] }
if und else sind die Befehle,
die Bedingung ist ein Konstrukt, dem sich ein boolscher Werte (true/false) zuordnen
läßt (s. While-Schleife).
Die Befehlsfolge 1 wird ausgeführt, wenn die Bedingung wahr ist, die Befehlsfolge
2 in jedem anderen Fall. Die else-Klausel ist nicht
notwendig.
Probier's:
- Implementiere ein Programm, das ausgibt, ob das 1. Argument gerade oder ungerade ist.
- Implementiere ein Programm, das nur Dich begrüßt, d.h. es soll "Hallo [Name]" ausgeben, falls das 1. Argument Deinem Namen entspricht, in jedem anderen Fall soll nichts ausgegeben werden.
- Erweitere das Programm aus Aufgabe 1, so daß der Reihe nach für jedes Argument ausgegeben wird, ob es gerade oder ungerade ist. Benutzte eine Schleife und die Funktion args.length(), die angibt, wie groß ein Array ist.
Spaß mit Objekten
Was ist das?
Ich gebe zu: Die Überschrift ist irreführend. Zumindest anfangs machen Objekte
alles andere als Spaß...
Du hast schon oft Variablen benutzt. Diese hatten dann einen bestimmten Typ z.B. int,
und Du konntest bestimmte Operationen mit ihnen ausführen, z.B. die Addition.
Objekte sind nun - im weitesten Sinne - selbst definierte Variablen. Das ist nicht so
schlimm wie es klingt: Im Prinzip stellst Du nur die bereits bekannten Variablentypen
zusammen. Z.B. läßt sich der Variablentyp "Schachbrett" als 64 Integer-Werte
darstellen, wobei eine 0 freies Feld bedeutet, eine 1 weißer Bauer, eine -1
schwarzer Bauer, usw.
Zudem stellst Du bestimmte Methoden zur Verfügung, die für Instanzen dieses
Objektes genutzt werden könen. Auf Schachbrett wäre z.B. eine ,ziehe
Figur'-Methode sinnvoll.
So geht's:
Die Klasse
Jedes Objekt ist eine eigene Klasse. Willst Du also ein neues Objekt entwerfen, so
fängst Du mit
class [Klassenname] {
an. Beachte: Diese Klasse ist nicht public, nur
eigenständige Programme sind public. Klassennamen werden üblicherweise
groß geschrieben (z.B. Schachbrett, Tupel,...) Methoden hingegen klein (z.B.
addiere, zaehleHinzu,...). Dies ist eine Konvention, aber nicht notwendig. Trotzdem
empfehle ich, daß man sich an diese Regeln hält.
Die Variablen
Nach dem Titel der Klasse werden die Klassenvariablen definiert. Dies sind die Variablen, die in der ganzen Klasse verfügbar sein sollen. Diese Variablen können public, private oder protected sein (siehe dazu Methoden).
Der Konstruktor
Der Konstruktor wird aufgerufen, wenn eine Instanz der Klasse gebildet werden soll.
Er trägt immer den gleichen Namen wie die Klasse (auch
Groß-/Kleinschreibung) und ist public.
Wenn man ein Objekt benutzen will, so wird dieser Konstruktor
aufgerufen, und es werden ihm in der Regel auch die Werte übergeben, mit denen
die Klassenvariablen instantiiert werden.
Methoden einer Klasse
Ein Objekt bzw. Klasse verfügt natürlich auch über Methoden. Mit diesen
können Aktionen ausgeführt werden oder die Variablenwerte verändert.
Die Programmierung solcher Methoden ist identisch zur Implementierung von Methoden in einem Programm bzw. einer öffentlichen Klasse.
Beispiel für ein Objekt
class Tupel { // Klassenname
private int second; // 2. Klassenvariable
public Tupel() { // 1. Konstruktor
second=0;
public Tupel(int f, int s) { // 2. Konstruktor
second=s;
public int getFirst() { // Eine Methode
public void setSecond(int s) { // Noch eine Methode
public void makeZero() {Weil Methoden so schön sind...
second=0;
Probier's:
- Tippe die Beispielklasse ab und kompiliere sie. Du erhälst eine Datei namens Tupel.class, die nicht ausführbar ist.
- Erweitere die Klasse um einen Konstruktor, der nur einen int-Wert übergeben bekommt und first und second mit diesem Wert instantiiert.
- Implementiere die Methoden getSecond() und setFirst(int f).
- Implementiere eine toString() Methode, die folgenden
String zurückliefert: "([first]/[second])"
Anmerkung: Diese Methode wird von dem Befehl System.out.print() automatisch aufgerufen, falls im Argument eine Variable vom Typ Tupel vorkommt. - Implementiere eine equal(Tupel t)-Methode, die true zurückgibt, falls das Tupel t mit dem aktuellen Tupel übereinstimmt, d.h. wenn sowohl first- als auch second-Werte identisch sind, und false sonst.
Wie benutzt man Objekte?
So geht's:
Tupel t = new Tupel(3,5);
Diese Zeile erstellt eine Variable namens t vom Typ Tupel,
und instantiiert selbige mit den Werten 3 und 5. Dieses Instantiieren entspricht dem
Aufruf des Konstruktors.
Um eine Methode für ein Objekt aufzurufen, tippt man zunächst den Namen des
Objekts (oder der Klasse, falls auf kein spezielles Objekt zugegriffen werden soll),
dann direkt einen Punkt, dann den Namen der Methode und schließlich die Klammer
mit den Argumenten, bzw. eine leere Klammer.
Beispiel:
int x=t.getSecond();
t.setZero();
Beispiel:
t.second=7;
final static int LEER=0;
final static int WHITE_PAWN=1;
final static int BLACK_PAWN=-1;
usw. Auf diese Werte wird dann mit dem Befehl Schachbrett.WHITE_PAWN zugegriffen. Noch einmal ein komplexer Beispielaufruf:
my_board.setFigure(new Place("h", 8),Schachbrett.BLACK_TOWER);
my_board ist eine Variable vom Typ Schachbrett. Für diese wird die Methode setFigure aufgerufen. Diese Methode erwartet zwei Argumente: Eins vom Typ Place (ähnlich dem Tupel) und eins vom Typ int. Das erste wird gleich ganz neu entworfen, und direkt nach dem Aufruf wieder verworfen. Das zweite entspricht dem int-Wert, der in der Klasse Schachbrett der final static Variablen BLACK_TOWER zugewiesen wurde.
Klingt kompliziert, aber ist einfach, wenn man es ein paar mal gemacht hat...
Probier's:
- Entwirf eine Klasse TestTupel, in der eine Variable vom Typ Tupel entworfen wird (ohne Wertübergabe) und anschließend auf dem Bildschirm ausgegeben.
- Es sollen die ersten beiden Argumente, die beim Aufruf von TestTupel übergeben werden, an die Variable vom Typ Tupel übergeben werden.
- Es soll ausgegeben werden, ob die first und second Werte des so instantiierten Tupels gleich sind.
- Es soll verglichen werden, ob das Tupel aus den ersten zwei Argumenten gleich dem Tupel aus Argument 3 und 4 ist.
Die Herausforderung: Das Springer Problem
Probier's:
Das Springer Problem
Auf einem Schachbrett soll ein Springer von einem der Eckfelder aus jedes andere Feld genau einmal besuchen. Der Springer zieht ein Feld in eine Richtung, dann dreht er sich um 90 Grad und zieht zwei Felder weit, oder aber er zieht zwei Felder in eine Richtung und dann ein weiteres in 90 Grad Richtung. Das ziehen erfolgt dabei immer gerade, nie diagonal.
Wie funktioniert's?
Zur Lösung dieses Problems kann man Backtracking benutzen. Das bedeutet,
daß man der Reihe nach jedes noch nicht besuchte, vom Springer erreichbare
Feld aufsucht, und von dort aus fortsetzt. Findet man kein freies erreichbares Feld
mehr, so nimmt man den letzten Zug zurück und versucht von dieser letzten Position
aus die nächste Möglichkeit.
Probiere, ob Du es mit diesen Informationen schon schaffst, falls nicht, folgen hier
noch weiter Informationen:
Zunächst die Klassenübersicht. Hier siehst Du, welche Klasseneinteilung sich
anbietet.
Danach folgen eine genauere Erläuterung, was diese Klassen zur Verfügung
stellen sollen.
Schließlich folgt noch eine Liste von Tipps, wie der Algorithmus implementiert
werden kann. Es gibt natürlich auch andere
Lösungswege.
Für diejenigen, die völlig verzweifelt sind, folgt noch eine
Musterlösung.
Die Klassenübersicht
Das Schachbrett
Diese Klasse entspricht hauptsächlich einem zweidimensionalen Array vom Typ
Boolean. Zweidimensional bedeutet, daß es zwei Indizes gibt, statt einer
eckigen Klammer gibt es zwei. Man kann durch Angabe von zwei Werten (waagrecht
und senkrecht) auf dieses Array zugreifen.
Ist der Boolean-Wert false, so wurde das Feld bereits besucht (ist verboten),
ist er true, so ist das Feld noch unbesucht, kann also noch betreten werden.
Die Plätze
Die Platz-Klasse stellt eine waagrechte und eine senkrechte Koordinate zusammen. In dieser Klasse sollte u.a. auch eine toString-Methode zur Verfügung gestellt werden.
Die Lösungsklasse
Die Lösungsklasse verfügt über ein Schachbrett und ein Array vom Typ Platz. In diesem Array sollen der Reihe nach die besuchten Felder abgespeichert werden. Zudem ist die Lösungsklasse public und besitzt eine Main-Methode. Das erste Übergabeargument dieser Klasse soll die Größe des Brettes festlegen.
Ein paar Tipps:
Diese Tipps helfen, falls der Lösungsansatz oben nicht reicht, aber versuch es
erst einmal ohne!
- Don't panic!
- Benutze eine counter-Variable, die angibt, welcher Zug gerade bearbeitet wird.
- Benutze eine Methode, die den Counter als Argument hat. Diese Methode ruft sich selbst rekursiv auf.
- Der Springer hat acht mögliche Zielfelder. Speichere diese Zielfelder in einem Array ab (in welche Klasse paßt die Methode, die die Zielfelder berechnet, am besten?) und teste die Zielfelder mit einer For-Schleife.
- Die rekursiven Aufrufe erfolgen aus der For-Schleife heraus. Ist die For-Schleife beendet, dann wurde wohl keine Lösung von diesem Feld aus gefunden. Der Zug wird durch das Ende der Methode automatisch zurückgenommen. Du mußt nur noch das Feld dieses Zuges wieder befreien, da der Springer es nach Rücknahme des Zuges noch nicht benutzt hat.
Musterlösung, nur für Warmduscher
Die Musterlösung zählt auch die Anzahl der gefundenen Lösungen und die Anzahl der Hamiltonzyklen (der Springer könnte nach dem Besuch aller Felder direkt auf das Startfeld springen).


