Ampelkreuzung I

Aus Informatik
Wechseln zu: Navigation, Suche

Aufgabenstellung

Als Aufgabe sollte die Darstellung einer Ampelkreuzung, mit timergesteuert-umschaltenden Ampeln und fahrenden Autos erarbeitet werden.


Erste Überlegungen

Ich begann das Projekt indem ich mir zuerst Gedanken über die graphische Darstellung machte (ein Weg, den ein Informatiker meiner Meinung nach auf keine Fall einschlagen sollte), was bei diesem Projekt allerdings denkbar war, da die graphische Darstellung wohl einen Großteil des Projektes in Anspruch nehmen würde. Zuerst, um einen leicht zu bewältigenden Einstieg in das Projekt zu haben begann ich, eine Ampel zu programmieren:

Die Klasse TAmpel

Die Klasse TAmpel ist von der Klasse TShape abgeleitet. Die Ampel hat die Form eines abgerundeten Rechtecks. Zusätlich zur Ampel habe ich noch eine Klasse erstellt: TLicht. Diese Klasse basiert ebenfalls auf TShape und stellt ein Ampellicht dar. Im Klassendiagramm sieht das ganze so aus:

UML Ampel.JPG

Die wichtigsten Eigenschaften und Methoden

TAmpel

  1. FPhase - Gibt die aktuelle Ampelphase mithilfe eines Integerwertes an (1: Rot 2: Gelb 3: Grün 4: Rot-Gelb)
  2. FLichtGelb,usw: Die Ampellichter
  3. Create: Die TShape Eigenschaften werden verwendet, um die Ampel darzustellen
  4. Lichterzeichnen: Die Create-Prozeduren der Lichter werden aufgerufen
  5. Switch: Dient dazu die Ampelphase umzuschalten
  6. SwitchTo: Umspringen auf eine bestimmte Phase
  7. IsPassable: Gibt true aus wenn die Ampel auf Grün steht (FPhase = 3)
  8. Destroy: Gibt zuerst die 3 Lichter frei und verwendet dann inherited Destroy

TLicht

  1. FOffColor / FOnColor: Die Farben für aus- und angeschaltete Lichter
  2. Off / Onn: An- und Ausschalten des Lichtes (Onn heißt nicht "On" da Letzteres ein vereits belegter Ausdruck ist)

Die Klassen TStr und TKreuzung

Die Klasse TKreuzung ist das Herzstück der Ampelkreuzung, sie erstellt die Autos, platziert die Ampeln und regelt den Verkehr; die Klasse TStr ist eine Straße, die mithilfe der Rectangle-Prozedur der Canvas-Eigenschaft einer TImage-Komponente dargestellt wird. Die Klasse TStr bestimmt außerdem die Punkte an denen die Autos "gespawnt" werden.

UML Kreuzung.JPG

Die wichtigsten Eigenschaften und Methoden

TStr

  1. FHor: Gibt an ob die Straße horizontal (true) oder vertikal (false) ist
  2. Draw: Zeichnet die Straße, kann nur von TStr verwendet werden (protected)
  3. GiveX, GiveY: Gibt den X- und Y-Wert zurück, an dem ein Auto aufgesetzt werden kann

TKreuzung

  1. FAuto: Ein Array bestehend aus den vier Autos, die auf der Kreuzung erscheinen können
  2. FAmpeln: Ein Array bestehend aus den vier Ampeln, die sich auf der Kreuzung befinden
  3. FAutomoving: Diese Variable dient dazu, die Endlosschleife der Autobewegungen abbrechen zu können
  4. FPanel: Dient als Container für die Ampeln und die Autos
  5. FStr1 / FStr2: Die zwei Straßen aus denen die Kreuzung besteht
  6. Create: Belegt die Variablen und erstellt die Ampeln
  7. AutoMove: Startet eine Endlosschleife in der die Autos bewegt werden
  8. CreateAuto: Erstellt ein Auto auf der angegebenen Straße in der angegebenen Richtung
  9. DestroyAuto: Löscht ein Auto, damit ein neues erstellt werden kann
  10. Pause: Eine kurze Pause, damit sich die Autos nicht zu schnell bewegen, übernommen aus einem Programm aus Klasse 11
  11. StopMoving: Unterbricht die AutoMoving-Endlosschleife, damit das Programm beendet werden kann
  12. Switch: Schaltet die angegebene Ampel um
CanAutoMove

Prüft ob sich das angegebene Auto bewegen kann, gibt false zurück, wenn sich dieses an einer Ampel befindet, die auf Rot oder Gelb steht (anfangs durften die Autos auch bei Gelb fahren, was allerdings zu einigen Crashs führte)

 function TKreuzung.CanAutoMove (i: integer) : Boolean;
 // Prüfen ob sich das Auto bewegen kann abhängig davon ob sich das Auto an einem
 // entsprechenden Ampelpunkt befindet und ob die TAmpel-Funktion IsPassable den
 // Rückgabewert True liefert
 begin
   if Assigned(FAuto[i]) then
     if ((FAuto[i].GiveDir) and (FAuto[i].GiveHor) and (FAuto[i].GiveX=(FX1-FAuto[i].GiveLength)) and (not FAmpeln[1].IsPassable)) or
      ((not FAuto[i].GiveDir) and (FAuto[i].GiveHor) and (FAuto[i].GiveX=FX2) and (not FAmpeln[3].IsPassable)) or
      ((not FAuto[i].GiveDir) and (not FAuto[i].GiveHor) and (FAuto[i].GiveY=(FY1-FAuto[i].GiveLength)) and (not FAmpeln[4].IsPassable)) or
      ((FAuto[i].GiveDir) and (not FAuto[i].GiveHor) and (FAuto[i].GiveY=FY2) and (not FAmpeln[2].IsPassable))
     then Result:=false
     else Result:=true
   else Result:=false;
 end;
Erklärung
  • CanAutoMove enthält eine if Abfrage, die False zurückgibt, wenn eine von 4 Bedingungen erfüllt ist. Um dies näher zu erklären möchte ich mich auf eine der 4 Bedingungen beschränken:
 if ((FAuto[i].GiveDir) and (FAuto[i].GiveHor) and (FAuto[i].GiveX=(FX1-FAuto[i].GiveLength)) and (not FAmpeln[1].IsPassable)) or [..]
 then Result:=false
  • Mit "(FAuto[i].GiveDir) and (FAuto[i].GiveHor)" wird überprüft auf welcher Straße sich das Auto befindet und in welche Richtung es fährt
  • Mit "and (FAuto[i].GiveX=(FX1-FAuto[i].GiveLength)" wird überprüft ob sich das Auto vor einer zu der jeweiligen Straße gehörigen Ampel befindet (FX1 ist die X-Koordinate des rechten oberen Punktes der Kreuzung)
  • Mit "and (not FAmpeln[2].IsPassable)" wird überprüft ob die Ampel gerade auf Rot oder Gelb steht
  • Sind alle diese Bedingungen erfüllt, steht das Auto gerade vor einer roten oder gelben Ampel, darf sich also nicht bewegen, folglich wird false zurückgegeben
  • Die Vier Bedingungen beziehen sich auf die vier Straßenstücke und die vier zugehörigen Ampeln

Probleme

Das größte Problem meiner Ampelkreuzung ist, dass maximal vier Autos zugelassen sind, zwei auf jeder Straße, wobei eines in die eine, das andere in die andere Richtung fährt.

Die Klasse TDirAmpel

  • Beim Einbinden der Ampeln in die Kreuzung fiel mir auf, dass diese nur so ausgerichtet sein können:

TAmpel.JPG

  • Für die Kreuzung würden die Ampeln allerdings in alle vier Himmelsrichtungen ausgerichtet sein müssen; deshalb beschloss ich eine Tochterklasse von TAmpel zu erstellen, nämlich TDirAmpel. Im Klassendiagramm sieht das folgendermaßen aus:

TDirAmpel.JPG

  • TDirAmpel übernimmt die meisten Prozeduren von TAmpel, es werden nur Create und Lichterzeichnen überschrieben. Um die Position der einzelnen Lichter zu bestimmen, sind einige neue Variablen nötig, die allerdings alle intern berechnet werden. Als einziger zusätzlicher Parameter in der Create-Prozedur ist Direction notwendig. Damit wird die Ausrichtung der Ampel festgelegt.
  • Hat Direction den Wert:
    • 1: Die Ampel zeigt nach oben (so wie TAmpel)
    • 2: Die Ampel zeigt nach rechts
    • 3: Die Ampel zeigt nach unten
    • 4: Die Ampel zeigt nach links
  • In der Create-Prozedur werden nun abhängig von Direction die Variablen LX1,LX2,usw. festgelegt, wobei LX1 und LY1 für die Position des ersten Lichtes stehen, usw.
  • In der Prozedur Lichterzeichnen werden nun mit diesen Werten die Lichter erstellt
  • Die Klasse TAmpel wird in meiner Ampelkreuzung gar nicht verwendet, hier wird nur noch TDirAmpel benutzt

Die Klassen TAuto und TReifen

TAuto.JPG Die letzten von mir erstellten Klassen sind TAuto und TReifen. Beide sind wieder von TShape abgeleitet.

Die wichtigsten Eigenschaften und Methoden

TAuto

  1. FReifen: Die vier Autoreifen
  2. FDir: Die Richtung in die das Auto fährt (true - vorwärts, false - rückwärts)
  3. FHor: Gibt an ob das Auto horizontal (true) oder vertikal (false) ist
  4. FLength / FWidth: Die Autolänge und Breite, jeweils unabhängig von FHor, entspricht also nicht den TShape-Eigenschaften Height und Width
  5. FColor: Die Autofarbe
  6. Create: Hier werden die Variablen belegt und die Reifen erstellt
  7. Draw: Ändert die von TShape geerbten Eigenschaften, wodurch das Auto sozusagen neu gezeichnet wird
  8. Move: Bewegt das Auto um eine Koordinate in die angegebene Richtung
  9. GiveDir, GiveHor, usw.: Gibt die jeweiligen Eigenschaften von TAuto zurück
  10. Destroy: Löscht zuerst die Reifen und ruft dann inherited Destroy auf

TReifen

  1. FNr: Die Nummer des Reifens, TReifen geht im Voraus von 4 Reifen aus
    • Reifennr. 1: Hinten Rechts
    • Reifennr. 2: Vorne Rechts
    • Reifennr. 3: Hinten Links
    • Reifennr. 4: Vorne Links
  2. Create: Belegt nur die Eigenschaften von TReifen
  3. Move: Belegt die von TShape geerbten Eigenschaften, abhängig von der Reifennummer und der Lage des Autos (Horizontal/Vertikal)

Das Hauptprogramm

Das Hauptprogramm enthält ein Panel und eine Image-Komponente, die direkt übereinandergelegt sind, das Panel wird von den von TShape abgeleiteten Klassen benötigt, die Image-Komponente von TStr, da diese Klasse die TCanvas-Eigenschaft benötigt.

FormCreate

In der FormCreate-Prozedur werden zwei Straßen und eine Kreuzung erzeugt:

 procedure TForm1.FormCreate(Sender: TObject);
 begin
   Str1:=TStr.Create(0,200,150,Image1.Width,True,Image1);
   Str2:=TStr.Create(250,0,150,Image1.Height,False,Image1);
   Kreuzung1:=TKreuzung.Create(Str1,Str2,Image1,Panel1);
 end;

Die Autobuttons

Vier Buttons dienen dazu, jeweils an einem Straßenstück ein Auto aufzusetzen, hier beispielhaft der Quelltext von BtnAuto1Click:

 procedure TForm1.BtnAuto1Click(Sender: TObject);
 begin
   Kreuzung1.DestroyAuto(1);  // Zuerst wird das evtl. bereits existierende Auto entfenrt
   Kreuzung1.CreateAuto(1,1,true,clBlue); // Dann wird ein neues Auto anhand Autonummer,
                                          // Straßennummer, Richtung und Farbe erstellt
   if not Automoving then      // Wenn TKreuzung.Automove noch nicht gestartet wurde,
   begin                       // wird dies hier erledigt. Dies ist nur einmal im
     Kreuzung1.AutoMove;       // gesamten Programmverlauf nötig, kann aber nicht in
     Automoving:=true;         // der FormCreate-Prozedur erledigt werden, da die Form
   end;                        // sonst aufgrund der Enlosschleife nicht erstellt werden kann

end;

Der Timer zur Steuerung der Ampelphasen

  • Ein Timer sorgt dafür, dass die Ampeln der Kreuzung umgeschaltet werden. Dieser Timer arbeitet in zwei Phasen. Der Grünphase, in der die Ampeln entweder Grün oder Rot sind oder der Gelbphase in der die Ampeln entweder Gelb oder Rot-Gelb sind. Dazu werden zwei Variablen benötigt, die Zählvariable i (integer) und die Bool'sche Variable Grünphase, ist diese true, so befinden wir uns in einer Grünphase, ist sie false, in einer Gelbphase.
 procedure TForm1.Timer1Timer(Sender: TObject);
 begin
   if (Gruenphase) and (i<5) then inc(i) //Zuerst wird die Zählervariable i von 1 bis 5 gezählt
   else if Gruenphase then               //Wenn i = 5 ist werden die Ampeln umgeschaltet,
   begin                                 //i wird auf 1 zurückgesetzt
     i:=1;                               //und Grünphase wird auf false gesetzt, wodurch die
     Kreuzung1.Switch(1);                //Gelbphase eingeleitet wird
     Kreuzung1.Switch(2);
     Kreuzung1.Switch(3);
     Kreuzung1.Switch(4);
     Gruenphase:=false
   end
   else
   begin
     if (i < 3) then inc(i)
     else
       begin
         i:=1;
         Kreuzung1.Switch(1);
         Kreuzung1.Switch(2);
         Kreuzung1.Switch(3);
         Kreuzung1.Switch(4);
         Gruenphase:=true;
       end;
   end;
   end;

Erweiterungen

Die Folgenden Erweiterungen wurden noch in das Programm eingebaut:

Bitmap als Hintergrund

  • Ein Problem der Ampelkreuzung war der relativ unansprechende Hintergrund. Als Lösung für dieses Problem habe ich mich entschlossen, die Klasse TStr komplett auf Visualisierung verzichten zu lassen und stattdessen eine Bitmap-Grafik als Hintergrund zu verwenden. Zu diesem Zweck habe ich die TStr-Prozedur Draw komplett entfernt und die bereits vorhandene Image-Komponente im Hauptprogramm zur Anzeige der Grafik verwendet.

Farb-Zufallsgenerator für die Autos

Die Autos erschienen bisher immer nur in vorgegebenen Farben. Um das zu umgehen schrieb ich in der Klasse TKreuzung eine Function RandomColor, die eine Zufallsfarbe ausgibt, wobei ich mich der Hilfe der Entwicklungsumgebung bediente:

 function TKreuzung.RandomColor : TColor;
 var
   a,b,c: byte;
 begin
   Randomize;             
   a:=Random(256);
   b:=Random(256);
   c:=Random(256);
   Result:=RGB(a,b,c);
 end;

Diese Funktion wird nun beim Autoerstellen aufgerufen, sodass jedes neue Auto eine zufällige Farbe hat.

Die Verbindung des ADT-Liste mit TKreuzung

Das größte Problem meines Programmes war bisher, dass pro Straße immer nur ein Auto fahren konnte. Also kam ich auf die Idee, statt eines einzelnen Autos pro Straßenseite jeweils eine Liste von Autos zu verwenden. Dabei griff ich auf den im Unterricht bereits erstellten abstrakten Datentyp TListe zurück. Dafür waren einige Änderrungen in TKreuzung notwendig, die wichtigsten werden im Folgenden erläutert:

  1. Die Eigenschaft FAuto wurde in FAuto : Array [ 1..4 ] of TListe; geändert
  2. In der Create-Prozedur wurden die Create-Prozeduren für die vier Listen angefügt FAuto[1]:=TListe.Create; usw.
  3. Überall wo eine Prozedur von TAuto aufgerufen wurde, muss das jetzt über die GetContent-Funktion von TListe geschehen. Z.B.: FAuto[i].GetContent.Move(FAuto[i].GetContent.GiveDir);
  4. Anstatt FAuto[i].Create aufzurufen, muss jetzt die Append-Prozedur von TListe aufgerufen werden: FAuto[i].Append(TAuto.Create(FStr1.GiveX(Dir),...))
  5. Die AutoMove-Endlosschleife wurde komplett überarbeitet und bewegt nun nicht mehr nur ein Auto, sondern alle in der Liste enthaltenen Autos:
 procedure TKreuzung.AutoMove;
 var i,j: integer;
 begin
 FAutomoving:=true;
   while FAutomoving do // sollte das Programm unterbrochen werden, muss FAutomoving
   begin                // nur auf false gesetzt werden
     for i:=1 to 4 do
     if not FAuto[i].IsEmpty then
       begin
         FAuto[i].First;
         for j := 1 to FAuto[i].GetCount do
         begin
           if (CanAutoMove(i)) and (CanAutoMove2(i)) then
             FAuto[i].GetContent.Move(FAuto[i].GetContent.GiveDir);
           FAuto[i].Next;
         end;        
         DestroyAuto(i);
       end;
     Pause(1);
   end
  1. Wenn ein Auto das Bild verlässt, sollte der Speicher freigegeben werden, deshalb habe ich die DestroyAuto-Prozedur komplett überarbeitet, sie wird jetzt immer mit in der AutoMove-Endlosschleife aufgerufen, prüft erst ob ein Auto das Bild verlassen hat und, wenn ja, entfernt dieses mithilfe der TListe-Prozedur Delete.
  2. Eine neue Funktion CanAutoMove2 überprüft ob ein Auto sich in einem anderen befindet (wenn zwei Autos in kurzem Abstand nacheinander auf der selben Straße erstellt wurden); in diesem Fall bleibt das zweite Auto stehen und bewegt sich erst wieder, wenn ein Abstand von 10 Pixeln zwischen beiden Autos besteht. Ein Ausschnitt aus der Funktion:
   if FAuto[i].GetContent.GiveHor then
   begin
     temp:= FAuto[i].GetContent.GiveX;
     FAuto[i].Next;
     if Abs(temp - FAuto[i].GetContent.GiveX) > (FAuto[i].GetContent.GiveLength + 10)
     then Result := true
     else Result := false
   end

Der Auto-Zufallsgenerator

Als letzte kleine Änderung, ist jetzt nur noch ein Button auf dem Formular zu sehen, dieser erstellt ein Auto, das zufällig auf einer der vier Straßenseiten aufgesetzt wird.

Das Programm