Home » Tutorials » Grafik und Spiele » Vier gewinnt

Vier gewinnt

Die Kapselung der Darstellung

Die naheliegendste Möglichkeit wäre natürlich jetzt, an allen stellen wo das nötig ist, unsere Zeichenfunktion für das Gitter aufzurufen, also beim Programmstart, wenn die Größe des Formulars geändert wird und wenn ein Spieler einen Zug macht. Solange wir nur das Gitter zeichnen geht das auch in Ordnung. Später werden aber noch weitere Zeichenbefehle dazu kommen, zum Beispiel für die Spielsteine. Sobald wir also etwas neues Zeichnen wollen, müssen wir an allen drei Stellen im Programm den Aufruf der neuen Funktion ergänzen. Das Ganze ist natürlich fehleranfällig und wenn bei einer komplexeren Grafik dann wesentlich mehr Funktionen aufgerufen werden und zum Debuggen vielleicht auch einzelne entfernt oder verändert werden, dann wird die Angelegenheit furchtbar unübersichtlich und bereitet eine Menge Arbeit.

Wesentlich praktischer wäre es da natürlich, wir müssten die Zeichenfunktionen nur an einer einzigen Stelle aufrufen und könnten diese dann einfach wiederholen wo sie benötigt wird. Dazu packen wir alle Zeichenfunktionen in eine Prozedur. Das heißt wir kapseln sie. Wir haben also eine Prozedur, die absolut nichts anderes macht, als einige andere aufzurufen.
Das hat den Vorteil, dass wir an unseren drei Stellen nur eine Prozedur aufrufen müssen. Diese übernimmt dann alles weitere. Selbst wenn wir später weitere Darstellungen einbauen, müssen wir nur noch die eine Prozedur verändern.

Dazu erstellen wir eine neue private Prozedur von Form1, die auf den Namen „Darstellen“ hört. In dieser Prozedur erledigen wir fürs erste drei Dinge. Zum einen muss logischerweise das Gitter gezeichnet werden, zum anderen muss vor jedem Zeichenvorgang die Bitmap geleert werden. Sonst würde der alte Inhalt erhalten bleiben, was spätestens bei bewegten Objekten zum Problem auswächst.

Das Leeren der Bitmap geht ganz einfach, wir zeichnen einfach nur ein weißes Rechteck über die gesamte Fläche. Natürlich sind auch andere Farben möglich, wenn wir eine bestimme Hintergrundfarbe erreichen wollen. Dazu würde es sich dann anbieten, eine Konstante im Programm festzulegen, um einheitlich an einer Position die Farbe einstellen zu können.
Für ein Rechteck besitzt Canvas die Funktion Rectangle, welche als Parameter die x- und y-Koordinaten der linken oberen und rechten unteren Ecke erwartet. Der Rahmen wird in der aktuellen Stiftfarbe (Pen.Color) und der Inhalt in der Pinselfarbe (Brush.Color) gezeichnet. Diese müssen demnach zunächst auf Weiß gesetzt werden.

FGrafik.Canvas.Pen.Color := clWhite;
FGrafik.Canvas.Brush.Color := clWhite;
FGrafik.Canvas.Rectangle(0,0,FGrafik.Width, FGrafik.Height);

Am Ende der Prozedur müssen wir natürlich unser Bild auf den Bildschirm bringen, da dieses bisher ja nur im Speicher ist.
Dies geht mit einem einfachen Befehl:

Anzeigeflaeche.Canvas.Draw(0, 0, FGrafik);

Der Befehl kopiert die Bitmap an die Stelle in der Paintbox, die mit den ersten beiden Parametern angegeben wird.

Als nächstes zeichnen wir dann unser Gitter in die Bitmap, indem wir einfach die Prozedur GitterZeichnen aufrufen. Natürlich muss dieser Aufruf vor dem Kopieren in die Paintbox erfolgen.
Ganz so einfach dann allerdings doch nicht, denn die Prozedur erwartet drei Parameter, wenn sie die nicht bekommt, wird spätestens beim Compilieren eine Beschwerde ausgegeben.
Um diesem Problem bei zu kommen, definieren wir drei neue Variablen im private-Bereich der Klasse TForm1:

FSpielfeldBreite: Integer;
FSpielfeldHoehe: Integer;
FFelderBreite: Integer;

Diese werden dann in genau der Reihenfolge als Parameter übergeben (GitterZeichnen(FSpielfeldBreite, FSpielfeldHoehe,
Ffelderbreite);).
Doof ist an der Sache nur, dass die Variablen bisher noch keinen Wert haben. Das könnte zu Problemen führen, auf jeden Fall aber zu unerwünschten Ergebnissen.

Deshalb bekommen sie jetzt noch in der Create-Prozedur des Formulars einen Wert:

FSpielfeldBreite := 7;
FSpielfeldHoehe := 6;
FFelderBreite := 25;

Wenn wir gerade dabei sind, Variablen zu befüllen, sollten wir das direkt auch mit FLinks und FUnten tun, diese sind nämlich auch noch frisch und vor allem leer und da „leer“ bei der Programmierung keinesfalls gleichbedeutend mit „0“ ist, sondern eher ein absolut beliebiger Wert, der gerade an der Stelle im Speicher stand, sollte jede Variable initialisiert werden, das heißt vor der ersten Verwendung einen Wert bekommen. Da sich natürlich die Anfangspositionen für das Zeichnen ändern, wenn sich das Formular in der Größe verändert (zumindest wenn das Spielfeld immer zentral sein soll), ist es hier nicht zweckmäßig, die Werte im FormCreate festzulegen.

Betrachten wir die Ereignisse des Formulars, dann stellen wir fest, dass es da ein OnFormResize gibt. Wer jetzt etwas von Englisch versteht, der ahnt schon: das Dingen wird ausgelöst, wenn sich die Größe verändert. Damit ist es genau das, was wir suchen.
Als Kompensation für den vielen Text des Kapitels:

Aufgabe:
Überlege dir eine Formel, die dafür sorgt, dass das Gitter immer in der Mitte der Zeichenfläche ist und setze diese im „FormResize“ um.

Anschließend können wir das Ganze Speichern und dann F9 drücken und freudig im Kreis hüpfen, weil wir unser Gitter nun auch auf dem Bildschirm sehen.
Aussehen kann das Ganze dann so:

procedure TForm1.GroessenEinstellungenAnpassen;
begin
  FGrafik.Width := Anzeigeflaeche.Width;
  FGrafik.Height := Anzeigeflaeche.Height;
  FLinks := FGrafik.Width div 2 - FSpielfeldBreite div 2 * FFelderbreite;
  FUnten := FGrafik.Height div 2 + FSpielfeldHoehe div 2 * Ffelderbreite;
end;

Zunächst wird unsere Bitmap auf die selbe Größe gebracht wie die Paintbox. Anschließend ermitteln wir die linke und die untere Grenze. Dazu wird von der Mitte der Zeichenfläche die Hälfte der Spielfeldgröße abgezogen.

Auch hier ist das Ganze gekapselt und wir rufen im FormResize nur noch GroessenEinstellungenAnpassen auf.

Allerdings sehen wir immer noch nichts, wenn wir das Programm starten. Das Einzige, was wir bisher noch nicht aufgerufen haben, ist unsere „Darstellen“ Procedure.

Ein möglicher Gedanke wäre, diese im FormResize mit zu verwenden. Dummerweise wird die Paintbox erst nach dem Formular angepasst. Das heißt, wenn wir beim Verändern des Formulars etwas in die Paintbox zeichnen, ist es sofort danach wieder weg.

In solchen Fällen bietet es sich an, wenn wir uns einfach die möglichen Ereignisse anschauen. Dabei finden wir im Normalfall ein geeignetes Ereignis. Beim Durchgehen der Ereignisse der Paintbox finden wir OnPaint. Es wird immer genau dann aufgerufen, wenn die Paintbox neu gezeichnet wird, ist also für uns optimal bzw. sogar genau für solche Zwecke gedacht.
Dort schreiben wir unser „Darstellen“ rein. Das bringt noch einen ganz angenehmen Vorteil mit sich: Wenn das Fenster im Hintergrund war oder teilweise überdeckt wurde, wird jetzt direkt das Spielfeld neu gezeichnet. Ohne diese Funktion wären die Teile die überdeckt waren danach weiß und erst nach dem nächsten Neuzeichnen wieder sichtbar. Jetzt ist auf jeden Fall unser Gitter sichtbar und wir können uns dem nächsten Kapitel widmen.