Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Tutorials und Kurse (https://www.delphipraxis.net/36-tutorials-und-kurse/)
-   -   Delphi Snake (das Spiel) (https://www.delphipraxis.net/22871-snake-das-spiel.html)

Pr0g 25. Mai 2004 13:40


Snake (das Spiel)
 
In diesem Tutorial werde ich erklären, wie man einen Snakeklon in Delphi programmiert. Am Ende dieses Tutorials kann ein Beispielcode heruntergeladen werden.


Die Grundlagen

Zuerst muss geklärt werden, wie das ganze funktionieren wird. Das Spielfeld besteht aus einzelnen Feldern. Jedes Feld kann leer sein, oder einen Teil der Schlange, bzw. ein Stück Futter enthalten. Zum Speichern der einzelnen Felder wird ein zwei dimensionales Array genommen. Jedes Feld hat einen Index und kann folgende Werte beinhalten:
Code:
-1 -> Futter
 0 -> ein leeres Feld
>0 -> einen Teil der Schlange
Alle Werte, die größer als 0 sind stellen einen Teil der Schlange da, wobei das Feld mit der höchsten Zahl (immer die Länge der Schlange) den Kopf darstellt. Dazu gleich mehr. Die folgende Abbildung zeigt die einzelnen Indizes der Felder, so wie sie später auch im Programm genutzt werden:




Die Schlange

Nun müssen wir uns überlegen, wie die Position der einzelnen Schlangenteile am besten gespeichert wird. Ich will euch nun ein System erläutern, welches wir in diesem Tutorial nutzen werden und welches ich sehr gut finde. Gehen wir mal davon aus, dass die Schlange beim Start eine Länge von 5 hat, im Feld mit dem Index (0/0) startet und die aktuelle Laufrichtung (auch dazu gleich mehr) nach unten führt. Also wird zuerst in das Feld (0/0) die Länge der Schlange, also der Wert 5 gespeichert. Nun folgt eine Routine, die bei jeder Bewegung ausgeführt wird. Diese geht alle Felder durch und verringert jeden Wert, der höher als 0 ist um 1. In Feld (0/0) ist nun also der Wert 4 gespeichert. Da unsere Schlange nach unten geht müssen wir noch die neue Position für den Kopf setzen. Dies wäre das Feld (0/1), welches nun den Wert 5 erhält. Wenn wir nun die Routine von eben wieder durchlaufen lassen, dann wäre im Feld (0/0) der Wert 3, im Feld (0/1) der Wert 4 gespeichert und der neue Kopf würde (falls die Laufrichtung nicht geändert wird) im Feld (0/2) wieder mit dem Wert 5 gespeichert werden. Nach einiger Zeit wäre der Wert im Feld (0/0) bei 0 angekommen, also die Schlänge wäre nicht mehr auf diesem Feld. Bei einer Länge von 5 hätte wir dann 5 Felder mit Werten höher als 0. Das Feld mit dem Kopf hat den Wert 5, die dahinterliegenden Felder die Werte 4 bis 1. Da das Feld mit dem Wert 1 nach der Routine immer wegfällt und der Kopf neu gesetzt wird kommt eine Bewegung zustande.


Die Laufrichtung

Die Laufrichtung, sowohl auf der X als auch auf der Y Achse werden wir in eine Variable vom Typ "TPoint" speichern. Die Variable heißt "richtung" und kann folgende Wertkombinationen enthalten. Der erste Wert steht in "richtung.X", der zweite in "richtung.Y":
Code:
( 0 / -1) -> nach oben
( 1 /  0) -> nach rechts
( 0 /  1) -> nach unten
(-1 /  0) -> nach links
Die Bewegung

Mit Hilfe der Laufrichtung können wir die neue Position des Kopfs berechnen. Dazu benötigen wir aber auch noch die alte, bzw. aktuelle Position des Kopfs. Diese könnte man natürlich jedes Mal aus dem Array heraussuchen, jedoch ist es leichter und schneller, wenn man diese auch in einer Variable vom Typ "TPoint" speichern würde. Nennen wir die Variable mal "kopf". Ist der Kopf der Schlange auf Feld (1/2), so hat "kopf.X" den Wert 1 und "kopf.Y" den Wert 2. Nun werden die Werte aus "richtung" zu den Werten aus Kopf addiert. Dazu ein Beispiele für einen Schritt nach oben, die anderen Richtung laufen gleich ab, ausgehend von dem Feld (1/2):
Delphi-Quellcode:
// "richtung" -> (0/-1)

kopf.X = kopf.X + richtung.X
kopf.Y = kopf.Y + richtung.Y

kopf.X = 1 + 0
kopf.Y = 2 + (-1)

kopf.X = 1
kopf.Y = 2 - 1

kopf.X = 1
kopf.Y = 1
Der Kopf hat nun die Position (1/1), also ein Feld höher als vorher.


Die Ausgabe

Um das Spiel auch sehen zu können, brauchen wir eine Fläche, wo wir drauf zeichnen können. Dazu nehmen wir eine "TPaintBox". Für jedes Feld zeichnen wir ein Kästchen. Würden wir für die leeren Felder die Farbe Schwarz (auf dem Bild Grau), für die Schlange Grün und für das Futter Rot nehmen, so könnte die Ausgabe im Spiel folgendermaßen aussehen (die Zaheln werden im Spiel natürlich nicht zu sehen sein, sollen jedoch nun auf der Abbildung die Werte verdeutlichen):




Der Code

Nun aber genug Theorie, kommen wir zum Code. Startet Delphi oder erstellt ein neues Projekt. Plaziert nun eine "TPaintBox" (Komponentenregister "System") mit einer Breite und Höhe von jeweils 250 auf der Form. Setzt nun einen "TTimer" auf die Form und setzt bei "Interval" den Wert 100 und bei "Enabled" den Wert "False". Nun brauchen wir noch 5 "TButtons". Der erste wird zum Starten des Spiels genutzt, die anderen sind für den Wechsel der Laufrichtung zuständig. Legt nun am Anfang des Codes folgende Konstanten an:
Delphi-Quellcode:
...
uses...

const
  farben: Array [0..2] of TColor = (clBlack, clLime, clRed);
  raster = 10;
  breite = 24; //0 bis 24 -> 25
  hoehe = 24;

type
  TForm1 = class(TForm)
...
In der Konstante "farben" werden die Farben zum späteren Darstellen der leeren Felder, der Schlange und des Futters gespeichert. In "raster" steht, wie viele Pixel ein Feld breit und hoch ist. In "breite" und "hoehe" wird angegeben, wie viele Felder es gibt. Wir nehmen ein Fläche von 25x25 Feldern. Nun zu den Variablen. Diese werden kommen in den "private" Abschnitt:
Delphi-Quellcode:
...
  private
    { Private-Deklarationen }
    map: Array [0..breite] of Array [0..hoehe] of Integer;
    kopf,
    richtung,
    futter: TPoint;
    laenge,
    punkte: Integer;
    ende: Boolean;
    bmp: TBitmap;
  public
    { Public-Deklarationen }
  end;
...
"map" ist unser Spielfeld, wie vorhin besprochen. Darin werden die verschiedenen Werte der einzelnen Felder gespeichert. In "kopf" und "futter" wird die aktuelle Position des Kopfes der Schlange und des Futters gespeichert. In "richtung" die Laufrichtung, wie oben besprochen. In "laenge" wird die die aktuelle Länge der Schlange gespeichert und in "ende", ob das Spiel noch läuft oder nicht. Nun definieren wir mehrere Prozeduren, die gleich jeweils erläutert werden, auch in den "private" Abschnitt unter die Variablen:
Delphi-Quellcode:
...
    procedure enable_buttons(status: Boolean);
    procedure spiel_ende;
    procedure neues_futter;
    procedure paint_map;
    procedure calc_snake;
  public
    { Public-Deklarationen }
  end;
...
Die Prozedur "enable_buttons" sorgt dafür, dass die Richtungswechsel-Buttons bspw. nur aktiv sind, wenn das Spiel auch läuft, als Parameter wird angegeben, ob die Buttons aktiv ("True") oder nicht aktiv ("False") sein sollen. Die Prozeduren werden unter dem Implementationsteil geschrieben:
Delphi-Quellcode:
...
implementation

{$R *.dfm}

procedure TForm1.enable_buttons(status: Boolean);
begin
  Button2.Enabled := status;
  Button3.Enabled := status;
  Button4.Enabled := status;
  Button5.Enabled := status;
end;
...
Unter "enable_buttons" kommt die Prozedur "spiel_ende". Diese wird beim Ende des Spiels aufgerufen. Die einzelnenen Schritte in der Prozedur sollten (später jedenfalls) klar sein:
Delphi-Quellcode:
...
procedure TForm1.spiel_ende;
begin
  ende := True;
  Timer1.Enabled := False;
  enable_buttons(False);
  Button1.Enabled := True;
end;
...
Nun kommt die Prozedur "neues_futter". Diese berechnet so lange eine neue zufällige Futterposition, bis ein freies Feld gefunden wurde. Danach enthält dieses Feld den Wert "-1":
Delphi-Quellcode:
...
procedure TForm1.neues_futter;
begin
  futter := Point(Random(breite), Random(hoehe));
  while map[futter.X, futter.Y]<>0 do
    futter := Point(Random(breite), Random(hoehe));
  map[futter.X,futter.Y] := -1;
end;
...
Die nächste Prozedur, "paint_map", ist für die Ausgabe zuständig. Mit zwei Schleifen wird die gesammte "map" durchgegangen und für jedes Feld die passende Farbe aus dem Array "farben" herausgesucht. "-1" war das Futter, "0" ein leeres Feld und alles andere ein Teil der Schlange. Dann wird an der entsprechenden Position ein Kästchen mit der zuvor ermittelten und in "col" gespeicherten Farbe geszeichnet:
Delphi-Quellcode:
...
procedure TForm1.paint_map;
var
  i, j: Integer;
  col: TColor;
begin
  for i := 0 to breite do
    for j := 0 to hoehe do
    begin
      case map[i, j] of
        -1: col := farben[1];
         0: col := farben[0];
      else
        col := farben[2];
      end;
      PaintBox1.Canvas.Brush.Color:=col;
      PaintBox1.Canvas.FillRect(Rect(i*raster, j*raster, (i+1)*raster, (j+1)*raster));
    end;
end;
...
Die letzte (selbsgeschriebene) Prozedur, "calc_snake", ist für das Berechnen der neuen Kopfposition und das verringern aller Werte in "map" um eins zuständig. Eine genauere Erklärung kommt nach dem Code:
Delphi-Quellcode:
...
procedure TForm1.calc_snake;
var
  i, j: Integer;
begin
  for i := 0 to breite do
    for j := 0 to hoehe do
      if map[i, j]>0 then
        Dec(map[i, j]);
  Inc(kopf.X, richtung.X);
  Inc(kopf.Y, richtung.Y);
  if (kopf.X<0) or (kopf.X>breite) or (kopf.Y<0) or (kopf.Y>hoehe) then
  begin
    spiel_ende;
    ShowMessage('Sie haben den Rand berührt!');
  end;
  if (map[kopf.X, kopf.Y]>0) and (not ende) then
  begin
    spiel_ende;
    ShowMessage('Sie haben sich selbst gebissen!');
  end;
  if (kopf.X=futter.X) and (kopf.Y=futter.Y) then
  begin
    Inc(laenge);
    neues_futter;
  end;
  map[kopf.X, kopf.Y] := laenge;
end;
...
Zuerst wird mit zwei Schleifen jedes Feld, welches einen Wert höher als 0 enthält um eins verringert. "Dec(a)" ist eine kürzere Schreibweise für "a := a-1". Diese Routine wurde vorhin schon im Abschnitt "Die Schlange" erläutert. Nun werden, wie in "Die Laufrichtung" beschrieben, die Werte aus "richtung" zu den aktuellen Werten aus "kopf" addiert. Die Funktion "inc(a, b)" ist eine kürzere Schreibweise für "a := a+b;". Danach wird geprüft, ob der Kopf den Rand oder einen Teil der Schlange berührt hat und gegebenenfalls das Spiel beendet und eine entsprechende Meldung ausgegeben. Dann wird geprüft, ob der Kopf auf dem Feld mit dem Futter steht. Wenn ja, dann wird die Länge der Schlange um eins erhöht und eine neue Futterposition errechnet. Zum Schluss wird der Kopf an die neue Position gesetzt. Gebt nun dem "Button1" die Caption "Start" und klickt doppelt drauf um in das "OnClick"-Ereignis zu gelangen. Dort schreibt ihr folgenden Code rein:
Delphi-Quellcode:
...
procedure TForm1.Button1Click(Sender: TObject);
var
  i, j: Integer;
begin
  Button1.Enabled := False;
  for i:=0 to breite do
    for j:=0 to hoehe do
      map[i, j] := 0;
  kopf := Point(1, 1);
  laenge := 5;
  richtung := Point(0, 1);
  map[kopf.X, kopf.Y] := laenge;
  neues_futter;
  paint_map;
  enable_buttons(True);
  ende := False;
  Timer1.Enabled := True;
end;
...
Nun eine Erklärung des Codes. Der Startbutton wird deaktiviert, dann wird mit einer Schleife die gesamte "map" auf den Wert "0" gesetzt. Als nächstes wird die Startposition des Kopfes angegeben. "kopf := Point(x, y);" ist dabei eine kürzere Schreibweise für "kopf.X := x; kopf.Y := y". Die Länge der Schlange wird auf 5 und die Laufrichtung "nach unten" gesetzt. Nun wird die erste Position des Kopfs in "map" eingetragen, dann eine neue Futterposition berechnet und das Spiel wird ausgegeben. Zum Schluss werden die Richtungswechsel-Buttons aktiviert und der Timer für das Spiel gestartet. Nun kommt der Code für den Timer. Klickt doppelt auf "Timner1" und fügt folgenden Code in dessen "OnTimer"-Ereignis ein:
Delphi-Quellcode:
...
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  calc_snake;
  if not ende then
    paint_map;
end;
...
Bei jedem Timeraufruf wird die Prozedur "calc_snake" aufgerufen. Ist das Spiel noch nicht zu Ende, so wird die Ausgabe gezeichnet. Diese Abfrage ist trotz deaktivieren des Timers bei Spielende notwendig, da sonst nach Ende noch ein Schritt gezeicht wird. Nun kommen die Richtungswechsel-Buttons. Gebt "Button2" die Caption "oben" und fügt folgenden Code in dessen "OnClick"-Ereignis ein:
Delphi-Quellcode:
...
procedure TForm1.Button2Click(Sender: TObject);
begin
  if richtung.Y=0 then
    richtung := Point(0, -1);
end;
...
Die "If"-Abfrage vor dem Setzen der neuen Richtung ist dazu da um einen direkten Richtungswechsel zu verhindern. So ist es bspw. nicht Möglich während man nach unten läuft die Richtung nach oben zu ändern. "Button3" bekommt die Caption "links" und folgenden Code:
Delphi-Quellcode:
...
procedure TForm1.Button3Click(Sender: TObject);
begin
  if richtung.X=0 then
    richtung := Point(-1, 0);
end;
...
"Button4" die Caption "rechts" und folgenden Code:
Delphi-Quellcode:
...
procedure TForm1.Button4Click(Sender: TObject);
begin
  if richtung.X=0 then
    richtung := Point(1, 0);
end;
...
Und "Button5" die Caption "unten" und folgenden Code:
Delphi-Quellcode:
...
procedure TForm1.Button5Click(Sender: TObject);
begin
  if richtung.Y=0 then
    richtung := Point(0, 1);
end;
...
Zu guter Letzt klickt ihr noch doppelt auf die "Form1" und schreibt in das "OnCreate"-Ereignis folgenden Code:
Delphi-Quellcode:
...
procedure TForm1.FormCreate(Sender: TObject);
begin
  Randomize;
end;
...
Der Befehl "Randomize" sorgt dafür, dass die Zufallszahlen (bei der Futterposition) auch wirklich Zufällig sind und sich nicht bei jedem Start wiederholen.


Ende

Wenn ihr alles richtig gemacht habt, sollte sich das Spiel nun kompilieren lassen. Falls ihr noch einen Punktezähler einbauen wollt, so definiert am Anfang eine Integervariable, die er dann beim Start "Button2" auf 0 setzt. Immer wenn nun in "calc_snake" sie Länge der Schlange um eins erhöht wird, könnt ihr auch euren Punktezähler um einen beliebigen Wert erhöhen und diesen dann bspw. in einem Label ausgeben. Zu Beginn habe ich Konstanten gewählt um die Rater und Spielfeldgröße anzugeben. Dadurch ist es Problemlos möglich die Spielfeldgröße zu ändern. Tragt dazu einfach neue Werte in "breite", "hoehe" und "raster" ein und passt die Größe der Paintbox an. Die könnt ihr folgendermaßen errechnen: "(breite+1)*raster" und die Höhe entsprechend auch.


Download des Beispielcodes: snake_src.zip (1.99Kb)

MfG Pr0g

Ultimator 25. Mai 2004 13:42

Re: Snake (das Spiel)
 
Cool! :thuimb:
Ist echt gut gemacht. Aber eine Kritik hätt ich noch: Die Mehrzahl von "Index" ist Indizes :mrgreen:

Pr0g 25. Mai 2004 13:53

Re: Snake (das Spiel)
 
Zitat:

Zitat von Ultimator
[..]Die Mehrzahl von "Index" ist Indizes :mrgreen:

Danke, das wusste ich nicht :wink:

Ultimator 25. Mai 2004 13:54

Re: Snake (das Spiel)
 
Bitte, Bitte. :thuimb:

Jens Schumann 25. Mai 2004 14:08

Re: Snake (das Spiel)
 
Hallo,
ich halte das für ein Tutorial ziemlich ungeeignet.
Der Code verstößt in allen Punkten gegen die Styleguides in Luckie's Artikel
CodeDesign
Hier meine Kritikpunkte:
Delphi-Quellcode:
  public
    { Public-Deklarationen }
    procedure enable_buttons(status: Boolean);
    procedure spiel_ende;
    procedure neues_futter;
    procedure paint_map;
    procedure calc_snake;
  end;
Die proceduren werden von keinem anderen Objekt aufgerufen, noch werden sie außerhalb des Forms benötigt. Deshalb gehöre´n sie in den private Abschnitt.

Delphi-Quellcode:
var
  map: Array [0..breite] of Array [0..hoehe] of Integer;
  kopf,
  richtung,
  futter: TPoint;
  laenge: Integer;
  ende: Boolean;
Globale Variablen sind extrem schlechter Stil und sind unbedingt zu vermeiden.

Delphi-Quellcode:
procedure TForm1.calc_snake;
var
  i, j: Integer;
begin
  for i := 0 to breite do
    for j := 0 to hoehe do
      if map[i, j]>0 then
        Dec(map[i, j]);
  Inc(kopf.X, richtung.X);
  Inc(kopf.Y, richtung.Y);
  if (kopf.X<0) or (kopf.X>breite) or (kopf.Y<0) or (kopf.Y>hoehe) then
  begin
    spiel_ende;
    ShowMessage('Sie haben den Rand berührt!');
  end;
  if map[kopf.X, kopf.Y]>0 then
  begin
    spiel_ende;
    ShowMessage('Sie haben sich selbst gebissen!');
  end;
  if (kopf.X=futter.X) and (kopf.Y=futter.Y) then
  begin
    Inc(laenge);
    neues_futter;
  end;
  map[kopf.X, kopf.Y] := laenge;
end;
Berechnung ist mit der Benutzeroberfläche verbunden. Evt wäre eine function, die ein dem
Ereignis entsprechendes Ergebnis zurück liefert besser.

Delphi-Quellcode:
  for i := 0 to breite do
    for j := 0 to hoehe do
      if map[i, j]>0 then
        Dec(map[i, j]);
Wenn man lange genug spielt könnte map[i,j] evt. den gültigen Bereich eines Integers verlassen !!! Das kann zu schwer auffindbaren Fehlern führen.


Eigentlich ist es Beispiel um zu zeigen wie man es nicht machen sollte.

Pr0g 25. Mai 2004 14:23

Re: Snake (das Spiel)
 
Zitat:

Zitat von Jens Schumann
Der Code verstößt in allen Punkten gegen die Styleguides in Luckie's Artikel
CodeDesign

Die Styleguides habe ich noch nicht gelesen, werde ich nachholen.

Zitat:

Zitat von Jens Schumann
Globale Variablen sind extrem schlechter Stil und sind unbedingt zu vermeiden.

Was ist daran so schlecht? Oder steht das auch in den Styleguides?

Zitat:

Zitat von Jens Schumann
Berechnung ist mit der Benutzeroberfläche verbunden. Evt wäre eine function, die ein dem Ereignis entsprechendes Ergebnis zurück liefert besser.

Könntest du das vielleicht noch ein bisschen erläutern und sagen, was an meiner Variante nicht so gut ist?

Zitat:

Zitat von Jens Schumann
Delphi-Quellcode:
  for i := 0 to breite do
    for j := 0 to hoehe do
      if map[i, j]>0 then
        Dec(map[i, j]);
Wenn man lange genug spielt könnte map[i,j] evt. den gültigen Bereich eines Integers verlassen !!! Das kann zu schwer auffindbaren Fehlern führen.

Das verstehe ich nun nicht. Wann sollte map[i, j] den gültigen Integerbereich verlassen? Der Wert kann nicht kleiner null werden. Und in der höhe ist er auch begrenzt, da das Spielfeld nach einiger Zeit zu voll ist, um weitere Punkte zu bekommen, also die Länge zu erhöhen.

MfG Pr0g

Jens Schumann 25. Mai 2004 14:32

Re: Snake (das Spiel)
 
Hallo,
zu 1) Die Styleguides lesen kann ich empfehlen.
zu 2) Das Thema "Globale Variablen" haben wir hier im Forum schon oft diskutiert. Steht aber auch in den Styleguides.
zu 3) In calc_snake wird ShowMessage aufgerufen (Verknüpfung mit der Oberfläche). Das ist der Kanckpunkt. Evt. ist das ja für Dich kein Problem. Aber Du hast ein Tutorial geschrieben !!! Da sollte so etwas berücksichtigt werden. Schließlich nehmen sich andere Leute den Source als Vorbild.
zu 4)Du hast recht. Hier war ich etwas zu oberflächlich - tut mit Leid

Pr0g 25. Mai 2004 14:36

Re: Snake (das Spiel)
 
Zu 2: Was könnte ich mit den globalen Variablen denn machen? Schließlich werden sie ja gebraucht.

70UR157 25. Mai 2004 14:38

Re: Snake (das Spiel)
 
...zum Thema "globale Variablen vermeiden": gibts denn in Object Pascal lokale Variablen mit globaler Lebensdauer? (C/C++ Stichwort "static")

mfg

mirage228 25. Mai 2004 14:38

Re: Snake (das Spiel)
 
Zitat:

Zitat von Pr0g
Zu 2: Was könnte ich mit den globalen Variablen denn machen? Schließlich werden sie ja gebraucht.

Ab in den public / private Abschnitt der Form damit!

mfG
mirage228

Jens Schumann 25. Mai 2004 14:39

Re: Snake (das Spiel)
 
Zitat:

Zitat von Pr0g
Zu 2: Was könnte ich mit den globalen Variablen denn machen? Schließlich werden sie ja gebraucht.

Die werden gebraucht das stimmt. Du solltest Sie unter private als Formular Member deklarieren. Ist OOP mäßiger.

mirage228 25. Mai 2004 14:41

Re: Snake (das Spiel)
 
Ahja, was mir noch auffiel: Du benennst deine Prozeduren teils in Englisch und teils in Deutsch. Also entweder ganz oder gar nicht :shock:

mfG
mirage228

neolithos 25. Mai 2004 14:44

Re: Snake (das Spiel)
 
Zitat:

Zitat von 70UR157
...zum Thema "globale Variablen vermeiden": gibts denn in Object Pascal lokale Variablen mit globaler Lebensdauer? (C/C++ Stichwort "static")

mfg

Neue Frage neuer Thread!

NEIN! Da benötigt man eine Globale Variable! Den Zugriff beschränkt man mit der Position.


Delphi-Quellcode:
interface

...

implementation

... viel code ... << hier ist die Variable dwStatic NICHT sichtbar

var dwStatic : Cardinal = 10;

procedure Func;
begin
  Inc(dwStatic);
  Writeln(dwStatic);
end;

... viel code ... << hier ist die Variable dwStatic aber immernoch sichtbar

end.

Jens Schumann 25. Mai 2004 14:49

Re: Snake (das Spiel)
 
Wenn man gegen den rechten Rand läuft kommt die Meldung rechter Rand berührt und anschließend die Meldnung Sie haben sich selbst gebissen :shock:

Pr0g 25. Mai 2004 14:50

Re: Snake (das Spiel)
 
Zu 3: Meinst du mit den Messageboxen, dass ich bspw. in "calc_snake" nur zwei Variablen setze und dann nach dem Aufruf der Prozedur im Timer je nach Variablenwert die Messagebox anzeigen lasse?

nailor 25. Mai 2004 15:12

Re: Snake (das Spiel)
 
um pr0g mal ein wenig in schutz zu nehmen:

ich finde das tutorial ziemlich gut, weil es sehr ausführlich und sehr verständlich ist.

zu den einzelnen punkten mal ein paar sachen:

Indizes --> nicht wirklich schlimm und sehr einfach zu korrigieren
procedure (private) --> in einem anfängertutorial absolut unerheblich. und wer weiß, ob man nicht vielleicht irgendann mal davon ableiten will? alles panisch in privates zu bauen ist kappes, weil so sachen wie private zum schutz des programmieres vor selbstgemachten bugs dienen, nicht als um styleguides zu rechtfertigen
Globale Variablen --> nicht schön. aber das ist a) ein anfängertutorial und b) ist das in 10 sek per copy&past im private der form
Berechnung ist mit der Benutzeroberfläche --> a) anfängertutorial. b) man kann wie gesagt wegen jedem scheiss rumheulen, oder es auch lassen
Wenn man lange genug spielt könnte map[i,j] evt. den gültigen Bereich eines Integers verlassen !!! --> schon klar. zur seite? oder in welche richtung?
Englisch und teils in Deutsch --> ja uuuund???
Rand berührt und anschließend die Meldnung Sie haben sich selbst gebissen --> wow. ein fehler. aber das dürfte zu fixen sein

hugh, gereizter nailor hat gesprochen

naja, hab mich ein wenig aifgeregt. aber man muss ja nicht alles schlechtmachen

Pr0g 25. Mai 2004 15:21

Re: Snake (das Spiel)
 
Ich habe den Code nun angepasst und auch den Fehler mit den zwei Messageboxen behoben, wenn man den rechten Rand berührt.

phlux 25. Mai 2004 15:23

Re: Snake (das Spiel)
 
Hmm, mal abgesehen von den Fehlern die andere hier bemängeln (mich hätten sie nicht gestört, ich hätt dein Tutorial wenn eh nur als Denkanstoss genommen) würd ich sagen das ich deine Anleitung recht gut find und mir endlich mal ein Blick "Hinter die Kulissen" gewährt hat, wie Snake eigentlich funktioniert :thumb: :thumb:

neolithos 25. Mai 2004 15:29

Re: Snake (das Spiel)
 
Mal zur Systemmathik. Denkanstoß:

Ich habe das mal mit einem Ring-Buffer gelöst:

Delphi-Quellcode:
type
  TSnake = record
    aBody : array [0..ciMaxBuffer] of TPoint;
    iHead,         // Index des Kopfelementes
    iGrow,         // Um wieviel elemente soll die Schlange noch wachsen
    iLen : Integer; // Länge der Schlange
  end;
Ich hoffe das reicht.

EDIT: iGrow hinzugefügt

Pr0g 25. Mai 2004 15:46

Re: Snake (das Spiel)
 
Vielleicht ist es einigen nicht ganz ersichtlich. Es ging mehr um das Grundprinzip von Snake. Die Codes sind für Anfänger beigefügt, die das Erklärte nicht umsetzen können. Wenn nun bspw. noch ein Record im Code genutzt wird, dann müsste dies auch wieder erklärt werden und das war eigentlich nicht meine Absicht. :wink:

neolithos 25. Mai 2004 15:53

Re: Snake (das Spiel)
 
Mir ging es eigentlich um den Record sondern, die Art und weise wie ich die Schlange im Speicher halte.

Bei die ist Spielfeld und Schlange eins. Bei mir ist dies klar getrennt.

Übrigens das weiterrücken der Schlange ist bei mir einfach zu lösen.

Delphi-Quellcode:
with Snake do
  begin
   Inc(iHead);
   if iHead > ciMaxBuffer then
      iHead := 0;
   aBody[iHead] := Point();
  end;
Achtung: Die Schlange sollte nie länger werden als ciMaxBuffer.

Pr0g 25. Mai 2004 16:05

Re: Snake (das Spiel)
 
Wie würdest du bei deinem Code das Entfernen des letzten Stücks der Schlange realisieren? Alle Elemente des Arrays eins nach vorne verschieben?

Wofür ist die Variable "iGrow" gedacht?

neolithos 25. Mai 2004 16:23

Re: Snake (das Spiel)
 
Das interessante bei der Sache ist das die Schlange im Speicher nicht verschoben wird, sondern ich setze den Anfang nur neu.

Bsp.:
Speicher:

Code:
  iHead = 2
  iLen = 3
  ((1, 2), (1, 3), (2, 3), (?, ?), (?, ?), ...)
Nach der Bewegung:
Code:
  iHead = 3
  iLen = 3
  ((1, 2), (1, 3), (2, 3), (3, 3), (?, ?), ...)
Noch eine:
Code:
  iHead = 4
  iLen = 3
  ((1, 2), (1, 3), (2, 3), (3, 3), (4, 3), (?, ?), ...)
(Index bei 0 beginnend)


Das Grow (Wachstum) kümmert sich um das längerwerden der Schlange.

Ungefähr so:

Delphi-Quellcode:
if iGrow > 0 then
   begin
     Inc(iLen);
     Dec(iGrow);
   end;
Bsp:
Code:
  iGrow = 2
  iHead = 2
  iLen = 3
  ((1, 2), (1, 3), (2, 3), (?, ?), (?, ?), ...)
Bewegung:
Code:
  iGrow = 1
  iHead = 3
  iLen = 4
  ((1, 2), (1, 3), (2, 3), (3, 3), (?, ?), ...)
Noch eine:
Code:
  iGrow = 0
  iHead = 4
  iLen = 5
  ((1, 2), (1, 3), (2, 3), (3, 3), (4, 3), (?, ?), ...)
Eine ohne Wachstum da iGrow = 0
Code:
  iGrow = 0
  iHead = 5
  iLen = 5
  ((1, 2), (1, 3), (2, 3), (3, 3), (4, 3), (5, 3), (?, ?), ...)
Wie man sieht kommt man bei meinen Algo ohne irgentwelche aufwendigen Schleifen aus. Was bei Handy, GTR oder alten Rechnern wichtig ist. Da die ja nicht so Schnell sind.

Pr0g 25. Mai 2004 16:58

Re: Snake (das Spiel)
 
Zitat:

Zitat von neolithos
Nach der Bewegung:
Code:
  iHead = 3
  iLen = 3
  ((1, 2), (1, 3), (2, 3), (3, 3), (?, ?), ...)

Das Element mit dem Index 3 (iHead) stellt den Kopf der Schlange da, richtig? Nun hat die Schlange eine Länge von 3 (iLen), also werden die Elemente 3 bis 1 genutzt um die Schlange darzustellen, etc. Das würde bedeuten, dass das Element 0 ungenutzt ist, oder? Wenn ja, dann würde beim nächsten Schritt zusätzlich noch das Element 1 ungenutzt sein, sofern sich die Länge nicht ändert, da der Kopf ja nun den Index 4 hat. Doch dann wäre nach einiger Zeit kein Element mehr vorhanden, um den Kopf zu setzen. Oder?

[Edit]Schon gut, habe die Zeile if iHead > ciMaxBuffer then iHead := 0; in deinem Code übersehen :wink:[/edit]

neolithos 25. Mai 2004 17:07

Re: Snake (das Spiel)
 
Ist diese Erkenntnis eine neues Tutorial Wert? :)

Snake für Fortgeschrittene! :mrgreen:

Hinweis:
Bei einem einfachen Snake würde ich nur das erste Element zeichnen und das letzte überzeichnen mit dem Levelfeld.

Pr0g 25. Mai 2004 17:15

Re: Snake (das Spiel)
 
Zitat:

Zitat von neolithos
Ist diese Erkenntnis eine neues Tutorial Wert? :)

Vielleicht irgendwann mal, sofern du keins über das von dir genutze System schreibst.

Zitat:

Zitat von neolithos
Bei einem einfachen Snake würde ich nur das erste Element zeichnen und das letzte überzeichnen mit dem Levelfeld.

Hast recht, würde auch gehen. Ich hatte eigentlich geplant einen zweiten Teil zu schreiben, in dem die Zeichenfunktion so ergänzt wird, dass die Schlange aus einem Grafikset, also mit Kurven, Kopf usw. gezeichnet wird, kam bis jetzt aber nicht dazu, bzw. hatte keine Lust.

Vielleicht mache ich irgendwann mal eine Neuauflage oder Ergänzung zu diesem Turorials :-D

neolithos 25. Mai 2004 17:25

Re: Snake (das Spiel)
 
Zitat:

Zitat von Pr0g
Vielleicht irgendwann mal, sofern du keins über das von dir genutze System schreibst.

Keine Sorge von mir wird es keine Tutorial geben,

1. Ich bin schlecht im Erklären,
2. Die liebe Reschschrebung,
3. Denke ich das ich dazu schon viel zuviel weis (das soll kein Eigenlob sein).

-> max. ein Tutorial für fortgeschrittene Sachen.

Jens Schumann 25. Mai 2004 17:27

Re: Snake (das Spiel)
 
Zitat:

Zitat von nailor
naja, hab mich ein wenig aifgeregt. aber man muss ja nicht alles schlechtmachen

Ich wollte das Tutorial bestimmt nicht schlecht machen. Eher war es mein ansinnen konstruktive Kritik zu äußern.
Wenn ich mich im Ton vergriffen habe bitte um Entschuldigung.



P.S. Die größten Kritiker der Elche waren früher selber welche. :mrgreen:

MasterC 17. Sep 2004 16:44

Re: Snake (das Spiel)
 
Hi, echt geiles Tutorial! :thumb:

Leider geht der Downloadlink vom Beispiel nicht mehr.

Kannst du das bitte nochmal hochladen?

MFG Chris.

Nicodius 26. Sep 2004 09:05

Re: Snake (das Spiel)
 
jo bitte wwäre nett ;)

gutes TUT!

Yadon 26. Sep 2004 11:02

Re: Snake (das Spiel)
 
hi,

zur Mittagszeit ging der Download auch nicht, schade, kannst das noch mal ändern?

Gruß Yadon

Wuaegner 26. Sep 2004 11:48

Re: Snake (das Spiel)
 
Auch noch mein Senf dazu : Das in einem Tutorial guter Programmierstil vorkommen sollte mag ja schon stimmen, aber wenn man dieses Tutorial als Denkanstoß für sein eigenes Snake spiel nehmen möchte ist es gut! Es geht ja in erster Linie darum, zu sehen, wie man das Snakespil umsetzen kann, und mit dieser Idee geht man dann an sein eigenes Spiel ran und schreibt seinen eigenen Code , wenn möglich dann auch in gutem Stil. Aber als Bsp. für eine Realisierung des Snakespiels finde ich dieses Turoial echt gut! :thumb:

Airborn 13. Nov 2004 11:48

Re: Snake (das Spiel)
 
Liste der Anhänge anzeigen (Anzahl: 1)
Mal eine ganz dumme Frage eines Anfängers *g*

Ich hab mal versucht das Programm nachzubasteln, compilieren tut er das auch noch, nur laufen will es nicht ordnungsgemäß. Hat irgeneiner von euch Profis einen Tipp für mich? Leider ist ja das Bsp. Programm nicht mehr zum downloadverfügbar, damit ich mal selbst nach dem Fehler suchen könnte.

Aus irgendeinem Grund bewegt sich die Schlange nicht :(

Hier auch nochmal der Soure:
Delphi-Quellcode:
unit snake;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

const
    farben: Array [0..2] of TColor = (clBlack, clLime, clRed);
    raster: integer = 10;
    breite: integer = 24;
    hoehe: integer = 24;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Timer1: TTimer;
    PaintBox1: TPaintBox;
    procedure Button1Click(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    map: Array [0..24] of Array [0..24] of Integer;
    kopf,
    richtung,
    futter: TPoint;
    laenge,
    punkte: Integer;
    ende: Boolean;
    bmp: TBitmap;
    procedure enable_buttons(status: Boolean);
    procedure spiel_ende;
    procedure neues_futter;
    procedure paint_map;
    procedure calc_snake;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.enable_buttons(status: Boolean);
begin
        Button2.Enabled := status;
        Button3.Enabled := status;
        Button4.Enabled := status;
        Button5.Enabled := status;
end;

procedure TForm1.spiel_ende;
begin
        ende:= True;
        Timer1.Enabled := False;
        enable_buttons(False);
        Button1.Enabled := True;
end;

procedure TForm1.neues_futter;
begin
        futter := Point(Random(breite), Random(hoehe));
        while map[futter.X, futter.Y]<>0 do
                futter:= Point(Random(breite), Random(hoehe));
        map[futter.X,futter.Y] := -1;
end;

procedure TForm1.paint_map;
var i,j: integer; col:TColor;
begin
        for i := 0 to breite do
                for j := 0 to hoehe do
                begin
                        case map[i, j] of
                        -1: col := farben[1];
                         0: col := farben[0];
                        else
                         col := farben[2];
                end;
                PaintBox1.Canvas.Brush.Color:=col;
                PaintBox1.Canvas.FillRect(Rect(i*raster, j*raster, (i+1)*raster, (j+1)*raster));
        end;
end;

procedure TForm1.calc_snake;
var i,j: Integer;
begin
for i := 0 to breite do
for j := 0 to hoehe do
if map[i,j]>0 then
Dec(map[i,j]);
Inc(kopf.X, richtung.X);
Inc(kopf.Y, richtung.Y);
if (kopf.X<0) or (kopf.X>breite) or (kopf.Y<0) or (kopf.Y>hoehe) then
begin
spiel_ende;
ShowMessage('Sie haben den Rand berührt!');
end;
if (map[kopf.X, kopf.Y]>0) and (not ende) then
begin
spiel_ende;
ShowMessage('Sie haben sich selbst gebissen!');
end;
if (kopf.X=futter.X) and (kopf.Y=futter.Y) then
begin
Inc(laenge);
neues_futter;
end;
map[kopf.X, kopf.Y] := laenge;
end;
procedure TForm1.Button1Click(Sender: TObject);
var i,j: Integer;
begin
Button1.Enabled := False;
for i:=0 to breite do
for j:=0 to hoehe do
map[i,j] := 0;
kopf:= Point(1,1);
laenge:= 5;
richtung:= Point(0,1);
map[kopf.X,kopf.Y] := laenge;
neues_futter;
paint_map;
enable_buttons(True);
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
calc_snake;
if not ende then
paint_map;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
if richtung.Y=0 then
richtung :=Point(0,-1);
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
if richtung.X=0 then
richtung:=point(-1,0);
end;

procedure TForm1.Button4Click(Sender: TObject);
begin
if richtung.X=0 then
richtung:=Point(1,0);
end;

procedure TForm1.Button5Click(Sender: TObject);
begin
if richtung.Y=0 then
richtung:= Point(0,1);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
randomize;
end;

end.

Pr0g 13. Nov 2004 13:37

Re: Snake (das Spiel)
 
Die Subdomain hat sich verändert, daher ist der Beispielcode nun hier zu finden: http://pr0g.net/knowhow/files/snake_src.zip


Ersetz mal den Code von "Button1Click" durch folgenden:

Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
var
  i, j: Integer;
begin
  Button1.Enabled := False;
  for i:=0 to breite do
    for j:=0 to hoehe do
      map[i, j] := 0;
  kopf := Point(1, 1);
  laenge := 5;
  richtung := Point(0, 1);
  map[kopf.X, kopf.Y] := laenge;
  neues_futter;
  paint_map;
  enable_buttons(True);
  ende := False;
  Timer1.Enabled := True;
end;

Airborn 13. Nov 2004 17:39

Re: Snake (das Spiel)
 
thx, jetzt geht es :) :)

profi 27. Dez 2004 21:56

Re: Snake (das Spiel)
 
also wenn ich den quellcode eingebe dann geht alles, d.h. die schlange flitzt über die paintbox nur kann ich die richtungsänderung nicht eingeben.

Die "If"-Abfrage vor dem Setzen der neuen Richtung ist dazu da um einen direkten Richtungswechsel zu verhindern. So ist es bspw. nicht Möglich während man nach unten läuft die Richtung nach oben zu ändern. "Button3" bekommt die Caption "links" und folgenden Code:
Quellcode: markieren
Delphi-Quellcode:
...
procedure TForm1.Button3Click(Sender: TObject);
begin
  if richtung.X=0 then
    richtung := Point(-1, 0);
end;
...
Wenn ich da auf den Button3 klicke um den Quelltext einzutragen dann kommt ne meldung:
"cannot find implementation of method3click

Da weiß ich nimmer weiter.
Nur zur Info falls einer ne Lösung weiß
ich bin n totaler Noob und hab nur alles genau abgeschrieben wie es im tutorial stand
bitte schreibts einfach *g*
danke

[edit=Christian Seehase]Delphi-Tags gesetzt. Bitte künftig selber machen. Danke. Mfg, Christian Seehase[/edit]

Pr0g 28. Dez 2004 12:17

Re: Snake (das Spiel)
 
Am besten wäre es, wenn du nur den Button3 auf deine Form machst, dann Doppelklickt drauf, damit Delphi die Prozedur erstellt und du dann den Code einträgst. Wenn du die ganze Prozedur kopiert hast, musst du gucken, ob sie auch oben in TForm1 eingetragen wurde und sie über den OI dem Button zuweisen.

MfG Pr0g

thepaul 28. Dez 2004 12:35

Re: Snake (das Spiel)
 
Hiermit kann man dann auch die Pfeiltasten zur Steuerung benutzen:
Delphi-Quellcode:
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  case Key of
    VK_UP: Button2.Click;
    VK_DOWN: Button5.Click;
    VK_RIGHT: Button4.Click;
    VK_LEFT: Button3.Click;
  end;
end;

Marcel_Mars 6. Mär 2007 17:19

Re: Snake (das Spiel)
 
Also ich habe noch eine gute erweiterung des Codes. So kann man auch durch wände gehen wie im richtigen Snake.
Delphi-Quellcode:
if (kopf.X<0) then
 Kopf.X:= kopf.X + Breite + 1;
 if Kopf.Y < 0 then
 Kopf.Y := kopf.Y + Hoehe + 1;
 if Kopf.X > Breite then
 Kopf.X := Kopf.x - Breite - 1;
 if Kopf.Y > Hoehe then
 Kopf.Y := Kopf.Y - Hoehe - 1;
Das muss man einfach in calc_snake reinschreiben.

Freundliche Grüsse

Mars

Daniel G 9. Mai 2007 21:49

Re: Snake (das Spiel)
 
Schade, dass die Links ins Leere laufen... :(


Alle Zeitangaben in WEZ +1. Es ist jetzt 11:48 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz