Entwickler-Ecke

Sonstiges (Delphi) - 4 Gewinnt Gewinnabfrage Erklärung


ChickenHotSauce - Do 11.06.20 23:36
Titel: 4 Gewinnt Gewinnabfrage Erklärung
Hallo Zusammen,
ich sitze momentan an einem Tutorial zu 4 Gewinnt von Delphi-Treff.de.Hier der Link: https://www.delphi-treff.de/tutorials/grafik-und-spiele/vier-gewinnt/. Ich arbeite das ganze durch und bin bis jetzt noch nirgends hängen geblieben, aber die function zur Gewinnabfrage kam und kommt einfach nicht in meinen Kopf rein. Könnte mir das jemand bitte erklären?
Hier der Code:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
function TForm1.GewinnerFeststellen(x: Integer; y: Integer; spieler: TFeldBesetzung): Boolean;
var
   richtung: array[0..7,0..1of Integer;
   a,b,i,j,k: Integer;
begin
   //Überprüfung, ob der Spieler gewonnen hat
   Result  := false;
   a  := 0;
   i  := x-1;
   j  := y-1;
   if i < 0 then Inc(i);
   if j < 0 then Inc(j);

   //Überprüfung ob der Spieler auf den umliegenden Feldern schon gesetzt hat
   while (j <= y+1and (j <= FSpielfeldHoehe-1do
   begin
      if (FSpielfeld[i,j] = spieler) and not((i=x) and (j=y)) then
      begin
         richtung[a,0]  := i-x;
         richtung[a,1]  := j-y;
         Inc(a);
      end;
      Inc(i);

      if (i > x+1or (i > FSpielfeldBreite-1then
      begin
         i  := x-1;
         if i < 0 then Inc(i);
         Inc(j);
      end;
   end;

   //Weiterverfolgung in den jeweiligen Richtungen, ob mindestens 4 Steine in einer
   //Reihe sind

   for k := 0 to a - 1 do
   begin
      i  := x; j  := y; b  := 0;
      while (i >= 0and (j >= 0and (i <= FSpielfeldBreite-1and (j <= FSpielfeldHoehe-1)
            and (FSpielfeld[i,j] = spieler) do
      begin
         Inc(b);
         i  := i+richtung[k,0];
         j  := j+richtung[k,1];
      end;
      i  := x-richtung[k,0]; j  := y-richtung[k,1];
      while (i >= 0and (j >= 0and (i < FSpielfeldBreite-1and (j < FSpielfeldHoehe-1)
            and (FSpielfeld[i,j] = spieler) do
      begin
         Inc(b);
         i  := i-richtung[k,0];
         j  := j-richtung[k,1];
      end;
      if b > 3 then Result  := true;
   end;
end;

Danke schonmal im Voraus!

Moderiert von user profile iconTh69: Delphi-Tags hinzugefügt


Th69 - Fr 12.06.20 09:43

Hallo und :welcome:

das ist doch in dem Tutorial Seite 10 [https://www.delphi-treff.de/tutorials/grafik-und-spiele/vier-gewinnt/10/] erklärt.
Es werden alle 8 Nachbarfelder überprüft, ob dort ebenfalls ein Spielerstein ist und wenn ja, dann wird in die Richtung weiterüberprüft, wie viele gleichfarbige Steine dort sind und dasselbe dann für die Gegenrichtung (d.h. - (Zeilen 47 - 53) statt + (Zeilen 39 - 45)) und die Anzahl (b) zusammengezählt.

Der Code ist aber zum einen sehr unleserlich als auch inperformant:
- Es ist überflüssig, erst die Richtungen zu bestimmen und danach dann diese weiter zu überprüfen, dies kann in einem Durchgang erledigt werden.
- Sobald eine 4er-Reihe gefunden wurde, kann die Funktion mit true verlassen werden.
- gleiche Abfragen sollten in eigene Funktionen ausgelagert werden (z.B. ob eine Position gültig, d.h. innerhalb des Spielfeldes, ist - dies wird sicherlich auch noch in anderen Programmteilen benötigt).
- statt alle 8 (Nachbar-)Richtungen zu überprüfen, reicht es nur 4 zu nehmen (da ja sowieso die Gegenrichtung jeweils mitüberprüft wird).

Auch die Benennung der Funktion könnte besser sein, z.B. HatSpielerGewonnen (denn es wird ja konkret für einen Spieler überprüft, ob sein Stein an Position (x, y) gewonnen hat).

Und generell noch ein Hinweis (bzgl. des Tutorials):
Es ist keine gute Programmierung alles in eine Form-Klasse zu packen. UI und (Spiel-)Logik sollten in getrennten Klassen (Units) implementiert werden!

PS: Warum neigen (Delphi-)Programmierer dazu, häufig nur einbuchstabige Variablen zu verwenden?


ChickenHotSauce - Fr 12.06.20 20:47

Danke für die Antwort Th69!
Das Prinzip, das auf Seite 10 hab ich ja verstanden, aber die Variabeln sprechen einfach nicht zu mir. X, Y und B ist mir klar, aber der Rest...
Hast du vielleicht besser Bezeichnungen, die das vielleicht verständlicher machen?
Und wie meinst du das mit UI-Unit und Logik-Unit?
Bin noch ziemlicher Anfänger :)


Th69 - Fr 12.06.20 21:32

Wie schon geschrieben, finde ich den Code auch nicht elegant, aber ich versuche mich mal:
- a wird in der ersten while-Schleife gesetzt und gibt die maximale Anzahl von erkannten Richtungen an (welche dann in der zweiten Schleife als Endwert genommen wird).
- i und j sind nur Hilfsvariablen, welche innerhalb der Schleifen die jeweils aktuelle (Nachbar-)Position bezeichnen (ausgehend von x und y).
- Und richtung ist das zweidimensionale Array, welches die relative Position (-1, 0, 1) angibt, an der ein gleicher Nachbarstein platziert ist. Die erste Dimension gibt den Index des Nachbarn an (von 0 bis max. 7 - der berechnete Maximalwert ist ja dann in a gespeichert) und die zweite Dimension entspricht dem X- und Y-Wert.

Bei der Aufteilung des Codes in UI und Logik gilt grob folgendes:
- In die Logik-Klasse (bzw. Unit) kommt der Code, welcher nur interne Berechnungen durchführt (wie z.B. die reine Spiellogik, d.h. Aufbau des internen Spielbrettes, Zugmöglichkeiten, Gewinnlogik, etc.). Diesen Code kann man dann für verschiedene Projekte wiederverwenden (z.B. reine Konsolenprojekte, Webprojekte, Unit-Tests, ...), da sie unabhängig von der UI sind.
- In die UI-Klasse (d.h. hier jetzt in die Form-Klasse) kommen dagegen alle Ein- und Ausgaben, d.h. Darstellung und Benutzerinteraktionen (Maus, Tastatur, ...), da diese speziell für diesen Projekttyp sind.

Dann als 3. Schicht (der sog. 3-Schichten-Architektur) gibt es dann noch die Datenzugriffsschicht (engl. "data access layer), in der alle externen Datenzugriffe, wie z.B. auf Dateien, Datenbanken, u.ä. implementiert werden.
Im [Artikel] Drei-Schichten-Architektur [https://mycsharp.de/wbb2/thread.php?threadid=111860] wird dies gut (kurz und knapp) erklärt - zwar besonders für C#, aber dies ist Programmiersprachen-unabhängig.

Der Aufbau einer Unit in Delphi wurde in dem Tutorial ja auch schon am Anfang gezeigt, und unter Programmaufbau: Units [https://www.delphi-treff.de/object-pascal/programmaufbau/#m4] (sowie dem Folgeabschnitt "Units verwenden") wird dies noch mal explizit erklärt.
Du würdest also z.B. eine weitere Unit "VierGewinnt" zu deinem Projekt hinzufügen (und den entsprechenden Code dann aus der Form1-Klasse, die man übrigens dann auch besser in VierGewinntForm umbenennen sollte, in diese neue Unit verschieben).

Selbst wenn du diesen Aufwand jetzt noch nicht bei diesem Tutorial-Projekt machen möchtest, so solltest du bei eigenen Projekten möglichst von Anfang an dran denken (dies fördert klarere Strukturen und unabhängigere Programmierung): Nichts ist schlimmer, als nachher Dateien mit mehreren Tausend Zeilen Quellcode zu haben.


ChickenHotSauce - Fr 12.06.20 21:36

Danke für die Erklärung!
Perfekt!