Ampelkreuzung I
Inhaltsverzeichnis
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:
Die wichtigsten Eigenschaften und Methoden
TAmpel
- FPhase - Gibt die aktuelle Ampelphase mithilfe eines Integerwertes an (1: Rot 2: Gelb 3: Grün 4: Rot-Gelb)
- FLichtGelb,usw: Die Ampellichter
- Create: Die TShape Eigenschaften werden verwendet, um die Ampel darzustellen
- Lichterzeichnen: Die Create-Prozeduren der Lichter werden aufgerufen
- Switch: Dient dazu die Ampelphase umzuschalten
- SwitchTo: Umspringen auf eine bestimmte Phase
- IsPassable: Gibt true aus wenn die Ampel auf Grün steht (FPhase = 3)
- Destroy: Gibt zuerst die 3 Lichter frei und verwendet dann inherited Destroy
TLicht
- FOffColor / FOnColor: Die Farben für aus- und angeschaltete Lichter
- 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.
Die wichtigsten Eigenschaften und Methoden
TStr
- FHor: Gibt an ob die Straße horizontal (true) oder vertikal (false) ist
- Draw: Zeichnet die Straße, kann nur von TStr verwendet werden (protected)
- GiveX, GiveY: Gibt den X- und Y-Wert zurück, an dem ein Auto aufgesetzt werden kann
TKreuzung
- FAuto: Ein Array bestehend aus den vier Autos, die auf der Kreuzung erscheinen können
- FAmpeln: Ein Array bestehend aus den vier Ampeln, die sich auf der Kreuzung befinden
- FAutomoving: Diese Variable dient dazu, die Endlosschleife der Autobewegungen abbrechen zu können
- FPanel: Dient als Container für die Ampeln und die Autos
- FStr1 / FStr2: Die zwei Straßen aus denen die Kreuzung besteht
- Create: Belegt die Variablen und erstellt die Ampeln
- AutoMove: Startet eine Endlosschleife in der die Autos bewegt werden
- CreateAuto: Erstellt ein Auto auf der angegebenen Straße in der angegebenen Richtung
- DestroyAuto: Löscht ein Auto, damit ein neues erstellt werden kann
- Pause: Eine kurze Pause, damit sich die Autos nicht zu schnell bewegen, übernommen aus einem Programm aus Klasse 11
- StopMoving: Unterbricht die AutoMoving-Endlosschleife, damit das Programm beendet werden kann
- 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:
- 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 ü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
Die letzten von mir erstellten Klassen sind TAuto und TReifen. Beide sind wieder von TShape abgeleitet.
Die wichtigsten Eigenschaften und Methoden
TAuto
- FReifen: Die vier Autoreifen
- FDir: Die Richtung in die das Auto fährt (true - vorwärts, false - rückwärts)
- FHor: Gibt an ob das Auto horizontal (true) oder vertikal (false) ist
- FLength / FWidth: Die Autolänge und Breite, jeweils unabhängig von FHor, entspricht also nicht den TShape-Eigenschaften Height und Width
- FColor: Die Autofarbe
- Create: Hier werden die Variablen belegt und die Reifen erstellt
- Draw: Ändert die von TShape geerbten Eigenschaften, wodurch das Auto sozusagen neu gezeichnet wird
- Move: Bewegt das Auto um eine Koordinate in die angegebene Richtung
- GiveDir, GiveHor, usw.: Gibt die jeweiligen Eigenschaften von TAuto zurück
- Destroy: Löscht zuerst die Reifen und ruft dann inherited Destroy auf
TReifen
- 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
- Create: Belegt nur die Eigenschaften von TReifen
- 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:
- Die Eigenschaft FAuto wurde in FAuto : Array [ 1..4 ] of TListe; geändert
- In der Create-Prozedur wurden die Create-Prozeduren für die vier Listen angefügt FAuto[1]:=TListe.Create; usw.
- Ü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);
- Anstatt FAuto[i].Create aufzurufen, muss jetzt die Append-Prozedur von TListe aufgerufen werden: FAuto[i].Append(TAuto.Create(FStr1.GiveX(Dir),...))
- 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
- 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.
- 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
- Der gesamte Ordner: Datei:Ampelkreuzung.zip
- Die exe-Datei: Datei:PAmpelkreuzung.zip
- Die neue Version mit Erweiterungen:Datei:Ampelkreuzung extended.zip