Zur Themenübersicht    

6. Die Oberfläche



1. UML-Daigramm




2. Grundkonzept

Zur grafischen Darstellung der Auswirkung der Befehle besitzt das Hauptformular ein Objekt der Klasse TFeld und den Roboter (TBender).
Angesichts der sehr umfangreichen Klassen der Oberfläche werden wir uns im folgenden darauf beschränken, nur das Grundkonzept und die wichtigsten Methoden und Attribute der Klasse TFeld und TBender zu erläutern. Die Aufgabe und Arbeitsweise der Klasse TObjekt bzw. ihrer Nachfolgeklassen TItem und THinderniss ergeben sich direkt aus den Methodennamen. Die Klassen dienen lediglich dazu, Items bzw. Hindernisse an genau vorgegebenen Pixel-Koordinaten mit genau vorgegeben Abmessungen zu zeichnen außerdem bieten sie den Vorteil, dass sich Objekte sehr gut in einem Array verwalten lassen.
Das Feld speichert die Art und die Position der Objekte in einem zwei-dimensionalen Array, auf den der Roboter, um Items aufzunehmen etc, über die "Kennt-Beziehung" und die entsprechenden Methoden zugreifen kann. Der Roboter wird nicht in diesem Array gespeichert. Das Array dient lediglich zum Verwalten der Hindernisse und Items.
Das Feld speichert alle Positionen als Feld-Koordinaten, die die Position im HatFeld-Array repräsentieren. Erst beim Zeichnen werden diese in Pixel-Koordinaten umgewandelt.
Der Roboter kümmert sich um seine Pixelposition und Abmessungen selber, das Feld speichert lediglich die Position des Roboters auf dem Feld in zwei Attributen. Damit reicht es den Roboter anzusprechen, wenn er seine Position oder ähnliches ändern soll. Der Roboter teilt diese Positionsänderungen dann dem Feld über die "Kennt-Beziehung" mit und veranlasst einen repaint. Desweiteren kann er über einen Methodenzeiger Informationen über seinen Ort und Status bei jeder Änderung dem Formular direkt mitteilen.
Klicks auf das Image des Feldes leitet das Hauptformular an dieses weiter, damit es dort neue Items bzw. Hindernisse erstellen kann.
Wenn das Feld gezeichnet werden soll, errechnet es die Position und Abmessungen der Objekte und veranlasst diese, sich anhand der ermittelten Daten zu zeichnen.

3. Die Klasse TFeld

3.1. Typendefinitionen

Um die Items an einer Feld-Koordinate zu verwalten wird folgender Typ benötigt:
type
  TFeldPunkt = array of TObjekt;

3.2. Die wichtigsten Attribute und Methoden der Klasse TFeld

Mit dem Konstruktor "init" und dem Desktruktor "gibFrei" kann wie üblich eine Instanz der Klasse erzeugt bzw. vernichtet werden.
Im Attribut "hatFeld : array of array of TFeldPunkt" wird jeder Koordinate des Feldes ein Speicherplatz für die dort vorhandenen Objekte zugeordent.
In den Attributen  "FeldPunktBreite" und "FeldPunktTiefe" wird die Größe einees Koordinatenpunktes in Pixeln gespeichert. Dies ist besonders wichtig, da die gesammte Verwaltung auf Koordinaten im hatFeld-Array basiert, zum zeichnen jedoch Pixel-Koordinaten benötigt werden.

Mit den Methoden "setze..." und "gib..." wird auf die dazugehörenden Attribute bzw. Feldkoordinaten zugegriffen.

procedure TFeld.SpeichereFeld (pFileName: String); virtual;
Mit dieser Methode werden alle Informationen (Position des Roboters, der Objekte, etc) des Feldes in ein INI-File mit dem Namen pFileName geschrieben.

procedure TFeld.LadeFeld (pFileName: String);
Mit dieser Methode werden alle Informationen (Position des Roboters, der Objekte, etc) des Feldes aus dem INI-File mit dem Namen pFileName gelesen und das Feld danach entsprechend gezeichnet.

procedure TFeld.erstelleHindernisAnPosition (pXKoordinate: Integer; pYKoordinate: Integer);
Mit dieser Methode kann vom Hauptformular aus ein Hindernis an der als Parameter übergebenen Pixel-Koodinate erstellt werden.
Dazu wird zunächst die Pixel-Koordinate in eine Koordinate im hatFeld-Array umgewandelt und der entsprechende FeldPunkt dann verändert.

procedure TFeld.erstelleItemAnPosition (pXKoordinate: Integer; pYKoordinate: Integer);
Mit dieser Methode kann vom Hauptformular aus ein Item an der als Parameter übergebenen Pixel-Koodinate hinzugefügt werden.
Dazu wird zunächst die Pixel-Koordinate in eine Koordinate im hatFeld-Array umgewandelt und der entsprechende FeldPunkt dann verändert.

procedure TFeld.entferneObjekteAnPosition (pXKoordinate: Integer; pYKoordinate: Integer);
Mit dieser Methode kann vom Hauptformular aus ein Objekt an der als Parameter übergebenen Pixel-Koodinate entfernt werden.
Dazu wird zunächst die Pixel-Koordinate in eine Koordinate im hatFeld-Array umgewandelt und der entsprechende FeldPunkt dann verändert.

procedure TFeld.erstelleFeld (pBreite: Integer; pTiefe: Integer; pRoboterStartXPosition: Integer; pRoboterStartYPosition: Integer; pItemAnzahl: Integer; pHindernisAnzahl:Integer);
Diese Methode erzeugt ein Feld mit den als Parameter übergebenen Daten. Dazu werden die Länge und Breite des HatFeld-Array entsprechend der übergebenen Parameter gesetzt, und die gewünschte Anzahl an Items und Hindernissen erzeugt und positioniert. Zusätzlich werden die Attribute "FeldPunktTiefe" und "FeldPunktBreite" neu berechnet.

procedure TFeld.zeichneObjekteAnPosition (pXPosition: Integer; pYPosition: Integer);
Mit dieser Methode wird das Zeichnen des als Parameter übergebenen Feldpunktes veranlasst. Dazu werden Position und Abmessungen der dort vorhandenen Items bzw. Hindernisse berechnete und diese dann anhand der ermittelten Daten zum Zeichnen veranlasst.

procedure TFeld.zeichneDich;
Mit dieser Methode wird das genze Feld neugezeichnet. Dazu wird als erstes alles mit einem weißen Viereck überzeichnet. Danach werden zuerst die Gitterlinien und danach jeder Feldpunkt (mit der
Methode "zeichneObjekteAnPosition") gezeichnet. Der Roboter wird bei dieser Methode nicht neugezeichet, da lediglich die durch den HatFeld-Array verwalteten Objekte neugezeichnet werden.

3.3. Entscheidende und interessante Codestellen







Die als Parameter übergebenen Daten
werden in den entsprechenden Attributen
gespeichert.







Die Länge und Breite des HatFeld-Arrays
wird entsprechend der Parameter gesetzt.



Solange wie noch nicht alle gewünschte
Items erstellt wurden...
...wird zufällig eine Koordinate ermittelt



...ein Item an dieser Koordinate hinzugefügt
...die Anzahl der noch zu erstellenden Items verringert




Solange wie noch nicht alle gewünschte
Hindernisse erstellt wurden...
...wird zufällig eine Koordinate ermittelt

Wenn an dieser Koordinate noch kein Item
vorhanden ist...

...wird an dieser Koordinate ein Hindernis erstellt
...die Anzahl der noch zu erstellenden Hindernisse
   verringert


procedure TFeld.erstelleFeld (pBreite: Integer; pTiefe: Integer; 
                              pRoboterStartXPosition: Integer; pRoboterStartYPosition: Integer; 
                              pItemAnzahl: Integer; pHindernisAnzahl: Integer);
var
  loopX, loopY : Integer;
  tmpXPosition, tmpYPosition : Integer;
begin
  FeldTiefe := pTiefe;
  FeldBreite := pBreite;
  FeldPunktTiefe := kenntImage.Height / FeldTiefe;
  FeldPunktBreite := kenntImage.Width / FeldBreite;

  RoboterXPosition := pRoboterStartXPosition;
  RoboterYPosition := pRoboterStartYPosition;
  RoboterBlickRichtung := 0;


  SetLength(hatFeld,FeldBreite);
  for loopX := 0 to FeldBreite - 1 do
  begin
    SetLength(hatFeld[loopX],FeldTiefe);
    for loopY := 0 to FeldTiefe - 1
      do SetLength(hatFeld[loopX][loopY],0);
  end;

  //--Items erstellen--
  while pItemAnzahl <> 0 do
  begin
    tmpXPosition := Random(feldBreite);
    tmpYPosition := Random(feldTiefe);
    if ((tmpXPosition <> pRoboterStartXPosition) or (tmpYPosition <> pRoboterStartYPosition)) then
    begin
      SetLength(hatFeld[tmpXPosition][tmpYPosition], length(hatFeld[tmpXPosition][tmpYPosition]) + 1);
      hatFeld[tmpXPosition][tmpYPosition]
             [length(hatFeld[tmpXPosition][tmpYPosition]) - 1] := TItem.init(kenntImage.canvas);
      pItemAnzahl := pItemAnzahl - 1;
    end;
  end;
  //--Items erstellen--

  //--Hindernisse erstellen--
  while pHindernisAnzahl <> 0 do
  begin
    tmpXPosition := Random(feldBreite);
    tmpYPosition := Random(feldTiefe);
    if (tmpYPosition <> pRoboterStartYPosition)) and ((tmpXPosition <> pRoboterStartXPosition) or 
       (length(hatFeld[tmpXPosition][tmpYPosition]) = 0) then
    begin
      SetLength(hatFeld[tmpXPosition][tmpYPosition], length(hatFeld[tmpXPosition][tmpYPosition]) + 1);
      hatFeld[tmpXPosition][tmpYPosition]
             [length(hatFeld[tmpXPosition][tmpYPosition]) - 1] := THindernis.init(kenntImage.canvas);
      pHindernisAnzahl := pHindernisAnzahl - 1;
    end;
  end;
  //--Hindernisse  erstellen--

  zeichneDich;
end;



4. Die Klasse TBender

Der Roboter verwaltet fast alle ihn betreffenden Daten selber, lediglich seine Position auf dem Feld speichert das Feld.

4.1. Die wichtigsten Attribute und Methoden der Klasse TBender

Mit dem Konstruktor "init" und dem Desktruktor "gibFrei" kann wie üblich eine Instanz der Klasse erzeugt bzw. vernichtet werden.
Im Attribut "hatRucksack : Array of TObjekt" speichert Bender welche Items er eingesammelt hat.
Im Attribut "kenntFeld : TFeld" wird das Feld für die "Kennt-Beziehung" gespeichert.
Mit dem Methodenzeiger "StatusGeaendert" kann Bender dem Hauptformular Änderungen an seiner Position, etc  mitteilen.

function TBender.vorneFrei : Boolean;
Hier wird in Abhängigkeit der Blickrichtung des Roboters überprüft, ob das nächste Feld vor ihm begehbar ist. Dazu wird zunächst überprüft, ob er vor einer Wand steht. Danach wird geprüft, ob auf dem nächsten Feld ein Hinderniss vorhanden ist. Wenn beides nicht der Fall ist, dann wird true zurückgegeben.

procedure TBender.setzeZurueck;
Mit dieser Methoder wird Bender Rucksack geleert.

Die Funktion und Arbeitsweise der anderen Methoden ergeben sich direkt aus ihrem Namen.

4.2. Entscheidende und interessante Codestellen




Bender bewegt sich nur wenn vorne auch frei ist.

Die aktuellen Koordinaten werden vom Feld geholt.


Je nach Blickrichtung werden die Koordinaten
verändert.



Die neuen Koordinaten werdem dem Feld mitgeteilt.


Feld und Roboter werden neugezeichnet.
procedure TBender.vor;
var
  XPosition, YPosition : Integer;
begin
  if vorneFrei then
  begin
    XPosition := kenntFeld.gibRoboterXPosition;
    YPosition := kenntFeld.gibRoboterYPosition;

    case kenntFeld.GibRoboterBlickRichtung of
      0: XPosition := XPosition + 1;
      180: XPosition := XPosition - 1;
      90: YPosition := YPosition - 1;
      270: YPosition := YPosition + 1
    end;

    kenntFeld.SetzeRoboterXPosition(XPosition);
    kenntFeld.SetzeRoboterYPosition(YPosition);

    kenntFeld.zeichneDich;
    zeichneDich;
  end;
end;






Nur wenn sich auf Bender Position ein Item befindet
kann diese aufgenommen werden


Der FeldPunkt an Benders Position wird vom Feld
geholt

Das letzte Item auf dem Feldpunkt wird in Benders
Rucksack gespeichert und aus dem Feldpunkt entfernt.
Der geänderte Feldpunkt wird an Bender Position im HatFeld-Array gespeichert.

Feld und Roboter werden neugezeichnet.
procedure TBender.nimm;
var
  tmpFeldPunkt : TFeldPunkt;
begin
  if itemDa then
  begin


    tmpFeldPunkt := kenntFeld.gibFeldPunktAnRoboterPosition;


    setLength(hatRucksack, length(hatRucksack) + 1);
    hatRucksack[length(hatRucksack) - 1] := tmpFeldPunkt[length(tmpFeldPunkt) - 1];
    tmpFeldPunkt[length(tmpFeldPunkt) - 1] := NIL;    
    SetLength(tmpFeldPunkt, length(tmpFeldPunkt) - 1);

    kenntFeld.setzeFeldPunktAnRoboterPosition(tmpFeldPunkt);


    kenntFeld.zeichneDich;
    zeichneDich;
  end;
         
end;