/* * MUSTERLOESUNG FUER DAS GEHEIMNIS DER IRREN BUSFAHRER * ---------------------------------------------------- * * Version 2.2, 08.03.2002 */ import java.awt.*; import java.applet.*; public class BusplanLoesung extends Applet implements Runnable /* das müsst Ihr nicht verstehen */ { /* Die Grösse des Applet-Fensters. */ final int FENSTERMAX_I = 800; final int FENSTERMAX_J = 800; /* Das sind die Eckpunkte unseres Stadtplans. */ final double XMIN = -2.2, XMAX = 2.2, YMIN = -2.2, YMAX = 2.2; /* Definition der Konstanten a,b. Am Punkt a,b beginnt die irre Busfahrt. */ final double A = 0.3; final double B = 0.5; /* Eigentlich müsste man bis in alle Ewigkeit im Bus sitzen bleiben, um zu testen, ob er wirklich für immer und ewig in der Stadt bleibt. Mit etwas Vertrauen reicht es aber aus, die ersten paar Stationen mitzufahren und zu sehen, ob er nicht heimlich abhaut. MAXITER gibt an, wieviele Stationen wir mitfahren. */ final int MAXITER = 20; /* hauptProgramm ************************************************************ Hier wird zuerst die Stadtgrenze gezeichnet (Kreis). Anschliessend wird die Fahrstrecke für den Startpunkt (A,B) gezeichnet. */ public void hauptProgramm(Graphics bildschirm) { /* Aufruf der Methode, welche die Stadtgrenze zeichnet. */ zeichneStadtgrenze (bildschirm); /* Aufruf der Methode, die die Fahrstrecke berechnet. */ int i = berechneFahrstrecke(A, B, bildschirm); } /* *********************** Eigene Methoden ******************* */ /* berechneFahrstrecke **************************************************** Hier wird die Fahrstrecke berechnet. Solange der Bus die Stadt nicht verlässt, wird die Position der nächsten Station auf der irren Fahrt berechnet. Der Rückgabewert ist die Anzahl bedienter Busstationen: Er kann gleich der Konstanten MAXITER sein. In diesem Fall ist der Bus innerhalb der Stadtgrenze geblieben. Oder aber der Rückgabewert ist eine Zahl kleiner als MAXITER. In diesem Fall hat der Bus nach entsprechend vielen Halten die Stadt verlassen. */ int berechneFahrstrecke (double a, double b, Graphics bildschirm) { double x = a; double y = b; int i = 0; /* Anzahl Bushaltestellen, die man besucht hat */ double xnew; double ynew; /* Neue Haltestellen berechnen, solange der Bus innerhalb der Stadtgrenze (Kreis: mit Radius 2, also x*x + y*y <= 4) ist. Nach Anzahl MAXITER hat man Vertrauen in den Busfahrer und bricht die Reise ebenfalls ab. */ while ( ((x*x + y*y) <= 4) && (i < MAXITER) ) { /* Position der nächsten Bushaltestelle berechnen. Die Formel steht in der Anleitung. */ xnew = x*x - y*y + a; ynew = 2*x*y + b; /* Aufruf der Methode, die die Strecke von der letzten zur neuen Haltestelle zeichnen soll. */ zeichneFahrstrecke (x, y, xnew, ynew, bildschirm); schlafe(200); /* Der Bus fährt weiter. Die neue Haltestelle wird zur aktuellen. */ x = xnew; y = ynew; i++; } /* Die Anzahl Haltestellen, die man mitgefahren ist (i), wird an die aufrufende Methode zurückgegeben. Diesen Wert braucht man für ein weiteres Rätsel der irren Busfahrer. */ return i; } /* Ende von berechneFahrstrecke */ /* ******** Methoden für die Bildschirmdarstellung ************ */ /* zeichneFahrstrecke ******************************************** Die Busstrecke wird im Stadtplan eingezeichnet. Die Position der aktuellen und der letzten Haltestelle bekommt man als Parameter. Die Koordinaten der Haltestellen werden nun in Bildschirmkoordinaten umgerechnet. */ void zeichneFahrstrecke (double x, double y, double xnew, double ynew, Graphics bildschirm) { int bildschirmI, bildschirmJ; int bildschirmInew, bildschirmJnew; /* Bildschirmkoordinaten für die beiden Bushaltestellen berechnen. */ bildschirmI = berechneBildschirmI (x); bildschirmInew = berechneBildschirmI (xnew); bildschirmJ = berechneBildschirmJ (y); bildschirmJnew = berechneBildschirmJ (ynew); /* Linie der Fahrstrecke zeichnen: Von der letzten Haltestelle zur neuen Haltestelle. */ bildschirm.drawLine (bildschirmI, bildschirmJ, bildschirmInew, bildschirmJnew); } /* Ende von zeichneFahrstrecke */ /* zeichneStadtgrenze *********************************************** Diese Methode berechnet die Koordinaten für die Stadtgrenze und zeichnet sie. Die Position und Grösse des Kreises werden definiert, indem ein Quadrat um den Kreis herum festgelegt wird. Dazu werden der obere linke Eckpunkt, die Breite und Höhe angegeben. */ void zeichneStadtgrenze (Graphics bildschirm) { int linksObenI, linksObenJ; int rechtsUntenI, rechtsUntenJ; int breite, hoehe; linksObenI = (int)berechneBildschirmI (-2.0); linksObenJ = (int)berechneBildschirmJ (2.0); rechtsUntenI = (int)berechneBildschirmI (2.0); rechtsUntenJ = (int)berechneBildschirmJ (-2.0); breite = rechtsUntenI - linksObenI; hoehe = rechtsUntenJ - linksObenJ; /* Der Kreis wird im Graphikfenster gezeichnet. */ bildschirm.drawOval (linksObenI, linksObenJ, breite, hoehe); } /* Ende von zeichneStadtgrenze */ /* berechneBildschirmI / berechneBildschirmJ ***************************************************** Die Koordinaten des Stadtplans (von XMIN bis XMAX und von YMIN bis YMAX) müssen in Bildschirmkoordinaten (I,J) umgerechnet werden. Zuerst werden die Koordinaten vom linken Stadtplanrand gemessen (statt vom Nullpunkt aus: x-XMIN). Die Division durch die Stadtplanbreite (XMAX-XMIN) ergibt den "prozentualen" Abstand vom linken Stadtplanrand. Dieser Wert wird mit der Fensterbreite multipliziert, um den Abstand vom linken Fensterrand zu berechnen. Die Umrechnung in J-Richtung ist analog. Achtung: Bei den Bildschirmkoordinaten in j-Richtung ist der Nullpunkt oben! Zunehmende j-Werte wachsen nach unten, also gerade umgekehrt als wie man es sich gewohnt ist.*/ int berechneBildschirmI (double x) { double bildschirmI; bildschirmI = ((x-XMIN) / (XMAX-XMIN)) * FENSTERMAX_I ; /* Es können nur Punkte (und Linien) mit Integer-Koordinaten gezeichnet weden. Alle double Zahlen werden deshalb auf Integer abgebildet. */ return (int) bildschirmI; } /* Ende von berechneBildschirmI */ int berechneBildschirmJ (double y) { double bildschirmJ; bildschirmJ = FENSTERMAX_J - (((y-YMIN) / (YMAX-YMIN)) * FENSTERMAX_J ); /* Es können nur Punkte (und Linien) mit Integer-Koordinaten gezeichnet weden. Alle double Zahlen werden deshalb auf Integer abgebildet. */ return (int) bildschirmJ; } /* Ende von berechneBildschirmJ */ /* berechneStadtplanX / berechneStadtplanY ***************************************************** Die Koordinaten des Bildschirmes (X,Y) müssen in Stadtplan-Koordinaten (I,J) umgerechnet werden. Wenn wir die Zahl i durch die Konstante FENSTERMAX_I teilen, haben wir den "prozentualen" Anteil. Multipliziert mit (XMAX-XMIN) ergibt das den Abstand vom linken Stadtrand. Wenn wir also XMIN addieren, haben wir die Stadtplan- koordinate. Die Umrechnung in Y-Richtung ist analog. Achtung: Bei den Bildschirmkoordinaten in j-Richtung ist der Nullpunkt oben! Zunehmende j-Werte wachsen nach unten, also gerade umgekehrt als wie man es sich gewohnt ist.*/ double berechneStadtplanX (int i) { double stadtplanX; /* Da die Stadtplanwerte nicht nur ganze Zahlen sind, muss dafür gesorgt werden, dass Java mit double Zahlen rechnet.*/ stadtplanX = ( ((double)i / FENSTERMAX_I) * (XMAX-XMIN) ) + XMIN; return stadtplanX; } /* Ende von berechneBildschirmI */ double berechneStadtplanY (int j) { double stadtplanY; /* Da die Stadtplanwerte nicht nur ganze Zahlen sind, muss dafür gesorgt werden, dass Java mit double Zahlen rechnet.*/ stadtplanY = ( ((double)(FENSTERMAX_J-j) / FENSTERMAX_J) * (YMAX-YMIN) ) + YMIN; return stadtplanY; } /* Ende von berechneBildschirmI */ /* ******************************************************* * Hier beginnt der kryptische Teil vom Java-Programm. Den Teil brauchst du nicht * zu verstehen. Sogar Profis haben Mühe, immer genau den Überblick zu behalten. */ public Graphics zeichnungsBrett; public Image zeichnungsBrettBild; private Thread thread; private boolean isRunning = false; public void init() { /* Wird zu Beginn (vom Appletviewer) aufgerufen, wenn das Applet gestartet wird. */ thread = null; zeichnungsBrettBild = createImage(FENSTERMAX_I, FENSTERMAX_J); zeichnungsBrett = zeichnungsBrettBild.getGraphics(); } public void start() { /* Kreiert einen neuen Thread, der nur für unser hauptProgramm zuständig ist. * Der "alte" Thread bleibt auch aktiv, er sorgt dafür, dass das Gezeichnete * wenn nötig neu dargestllt wird. */ if (thread == null) { thread = new Thread(this); } thread.start(); } public void run() { /* Diese Methode wird durch thread.start(); ausgelöst. Sie ruft unsere * Methode hauptProgramm() auf und beendet den Thread anschliessend. */ isRunning = true; hauptProgramm(zeichnungsBrett); isRunning = false; } public void stop() { /* Wenn der Appletviewer beendet wird, so soll auch der Thread, der für das * Neuzeichnen zuständig ist, beendet werden. */ if ( (thread != null) && thread.isAlive() ) { thread.interrupt(); thread = null; isRunning = false; } } public void zeichneZeichnungsBrettAufDenBildschirm(Graphics bildschirm) { bildschirm.drawImage(zeichnungsBrettBild, 0, 0, null); } public void update(Graphics bildschirm) { /* Wird vom der Methode repaint aufgerufen. * Wird keine update Methode deklariert, so wird der Bildschirm weiss gezeichnet * und anschliessend die Methode paint aufgerufen */ zeichneZeichnungsBrettAufDenBildschirm(bildschirm); } public void paint(Graphics bildschirm) { /* zeichnet den Bildschirm neu, wenn ein anderes Fenster vorher einen Teil * verdeckt hat und nun dieses Fenster sich verändert hat. */ zeichneZeichnungsBrettAufDenBildschirm(bildschirm); } public void schlafe(int schlafZeit) { /* legt das Programm eine gewisse Zeit schlafen. Die Zahl schlafZeit gibt die * Dauer des Schlafens in Millisekunden an. */ try { if (isRunning) { repaint(); Thread.sleep(schlafZeit); } } catch (InterruptedException e) { // Exception abfangen } } } /* Ende von Busplan */