Implementierung von Tetris Clearing-Zeilen

In meinem vorherigen Tetris-Tutorial habe ich Ihnen gezeigt, wie Sie die Kollisionserkennung in Tetris behandeln. Schauen wir uns jetzt den anderen wichtigen Aspekt des Spiels an: die Zeilen werden gelöscht.

Hinweis: Obwohl der Code in diesem Lernprogramm unter Verwendung von AS3 geschrieben wurde, sollten Sie in der Lage sein, in fast jeder Spieleentwicklungsumgebung dieselben Techniken und Konzepte zu verwenden.


Ermitteln einer abgeschlossenen Zeile

Das Erkennen einer Zeile ist eigentlich sehr einfach. Ein Blick auf das Array von Arrays macht es offensichtlich, was zu tun ist:

Die gefüllte Zeile ist die, die keine Nullen enthält - so können wir eine gegebene Zeile wie folgt überprüfen:

 Reihe = 4; // vierte Zeile prüfen isFilled = true; für (var col = 0; col < landed[row].length; col++)  if (landed[row][col] == 0)  isFilled = false;   //if isFilled is still true than row 4 is filled

Damit können wir natürlich jede Reihe durchlaufen und herausfinden, welche davon gefüllt ist:

 for (var Zeile = 0; Spalte < landed.length; row++)  isFilled = true; for (var col = 0; col < landed[row].length; col++)  if (landed[row][col] == 0)  isFilled = false;   //if isFilled is still true then current row is filled 

Okay, wir könnten dies optimieren, indem wir herausfinden, welche Linien es sind wahrscheinlich zu füllen, basierend auf welchen Zeilen der letzte Block belegt ist, aber warum die Mühe machen? Das Durchlaufen aller Elemente im 10x16-Raster ist keine prozessorintensive Aufgabe.

Die Frage ist jetzt: wie geht es uns? klar Die Linien?


Die naive Methode

Auf den ersten Blick scheint das einfach zu sein: Wir fügen nur die gefüllten Zeilen aus dem Array zusammen und fügen neue leere Zeilen an der Spitze ein.

 for (var Zeile = 0; Zeile < landed.length; row++)  isFilled = true; for (var col = 0; col < landed[row].length; col++)  if (landed[row][col] == 0)  isFilled = false;   //remove the filled line sub-array from the array landed.splice(row, 1); //add a new empty line sub-array to the start of the array landed.unshift([0,0,0,0,0,0,0,0,0,0]); 

Wenn wir dies mit dem obigen Array versuchen (und dann alles rendern), erhalten wir:

… Was würden wir erwarten, richtig? Es gibt immer noch 16 Reihen, aber die gefüllte wurde entfernt. Die neue Leerzeile hat alles nach unten gedrückt, um dies auszugleichen.

Hier ist ein einfacheres Beispiel mit den Vorher- und Nachher-Bildern nebeneinander:

Ein weiteres erwartetes Ergebnis. Und obwohl ich es hier nicht zeigen werde, behandelt derselbe Code auch Situationen, in denen mehr als eine Zeile gleichzeitig gefüllt ist (auch wenn diese Zeilen nicht nebeneinander liegen)..

Es gibt jedoch Fälle, in denen dies nicht den Erwartungen entspricht. Schau dir das an:

Es ist seltsam zu sehen, dass der blaue Block dort schwebt und an nichts gebunden ist. Es ist nicht falsch, genau - die meisten Versionen von Tetris machen dies, einschließlich der klassischen Game Boy-Edition -, also können Sie es dabei belassen.

Es gibt jedoch ein paar andere beliebte Wege, um damit umzugehen…


Die Big-Clump-Methode

Was wäre, wenn wir diesen einsamen blauen Block nach dem Löschen der Linie weiter fallen ließen?

Die große Schwierigkeit dabei ist, genau herauszufinden, was wir zu tun versuchen. Es ist schwieriger als es klingt!

Mein erster Instinkt wäre es, jeden einzelnen Block bis zur Landung herunterfallen zu lassen. Das würde zu solchen Situationen führen:

… Aber ich vermute, dass dies keinen Spaß machen würde, da alle Lücken schnell gefüllt werden würden. (Fühlen Sie sich jedoch frei, damit zu experimentieren - da könnte etwas drin sein!)

Ich möchte, dass die orangefarbenen Blöcke verbunden bleiben, aber der blaue Block fällt. Vielleicht könnten wir Blöcke fallen lassen, wenn sie keine anderen Blöcke links oder rechts von ihnen haben? Ah, aber schau dir diese Situation an:

Hier möchte ich, dass die blauen Blöcke alle in ihre jeweiligen "Löcher" fallen, nachdem die Linie gelöscht wurde - aber der mittlere Satz blauer Blöcke hat alle andere Blöcke neben sich: andere blaue Blöcke!

("Prüfen Sie also nur, ob sich die Blöcke neben roten Blöcken befinden", denken Sie, aber denken Sie daran, dass ich sie nur in Blau und Rot eingefärbt habe, um den Bezug auf andere Blöcke zu erleichtern. Es könnte sich um beliebige Farben handeln hätte jederzeit gelegt werden können.)

Wir können eine Sache identifizieren, die die blauen Blöcke im rechten Bild - und der einsame schwebende blaue Block von vorher - gemeinsam haben: Sie sind alle über die Zeile, die gelöscht wurde. Was also, wenn wir nicht versuchen, die einzelnen Blöcke fallen zu lassen, gruppieren wir alle diese blauen Blöcke zusammen und lassen sie als eins fallen?

Wir könnten sogar den gleichen Code wiederverwenden, der einen einzelnen Tetromino zum Absturz bringt. Hier ist eine Erinnerung aus dem vorherigen Tutorial:

 // setze tetromino.potentialTopLeft auf eine Zeile unterhalb von tetromino.topLeft, dann: for (var row = 0; row) < tetromino.shape.length; row++)  for (var col = 0; col < tetromino.shape[row].length; col++)  if (tetromino.shape[row][col] != 0)  if (row + tetromino.potentialTopLeft.row >= landed.length) // Dieser Block wäre unter dem Spielfeld. else if (gelandet [Reihe + Tetromino.potentialTopLeft.row]]! = 0 && landet [Col + Tetromino.potentialTopLeft.col]! = 0) / / der Speicherplatz ist belegt

Aber anstatt eine zu benutzen Tetromino Objekt erstellen wir ein neues Objekt, dessen gestalten enthält nur die blauen Blöcke - nennen wir dieses Objekt Büschel.

Das Übertragen der Blöcke ist nur eine Frage des Durchlaufens gelandet Array, jedes Element, das nicht Null ist, finden, das ausfüllen gleich Element in der clump.shape Array und Setzen des Elements der gelandet Array auf Null.

Wie üblich ist das mit einem Bild einfacher zu verstehen:

Links ist die clump.shape Array, und rechts ist das gelandet Array. Hier möchte ich keine leeren Zeilen ausfüllen clump.shape Um die Dinge sauberer zu halten, können Sie dies problemlos tun.

So unser Büschel Objekt sieht so aus:

 clump.shape = [[1,0,0,0,0,0,0,0,0,0], [1,0,0,1,1,0,0,0,0], [ 1,0,0,1,1,0,0,0,0,1]]; clump.topLeft = Zeile: 10, Spalte: 0;

… Und jetzt führen wir immer wieder denselben Code aus, den wir für den Fall eines Tetromino verwenden, bis der Büschel landet:

 // setze clump.potentialTopLeft so, dass es eine Zeile unter clump.topLeft ist, dann: for (var row = 0; row) < clump.shape.length; row++)  for (var col = 0; col < clump.shape[row].length; col++)  if (clump.shape[row][col] != 0)  if (row + clump.potentialTopLeft.row >= landed.length) // Dieser Block wäre unter dem Spielfeld. else if (gelandet [Reihe + Clump.potentialTopLeft.row]]! = 0 && landet [Col + Clump.potentialTopLeft.col]! = 0) / / der Speicherplatz ist belegt

Sobald der Büschel gelandet ist, kopieren wir die einzelnen Elemente zurück in die gelandet Array - wieder wie bei einem Tetromino. Anstatt dies jedoch jede halbe Sekunde auszuführen und alles zwischen jedem Fall erneut zu rendern, schlage ich vor, es immer und immer wieder auszuführen, bis der Büschel so schnell wie möglich landet dann Alles rendern, so dass es aussieht, als würde es sofort fallen.

Folgen Sie diesem durch, wenn Sie mögen; hier ist das Ergebnis:

Es ist möglich, dass hier eine weitere Linie gebildet wird, ohne dass der Spieler einen weiteren Block fallen muss, wodurch mögliche Spielerstrategien möglich werden, die mit der Naive-Methode nicht verfügbar sind. Daher müssen Sie sofort erneut nach gefüllten Linien suchen. In diesem Fall gibt es keine gefüllten Zeilen, sodass das Spiel fortgesetzt werden kann und Sie einen weiteren Block erzeugen können.

Alles scheint gut für die Clump-Methode zu sein, aber leider gibt es ein Problem, wie in diesem Vorher-Nachher-Beispiel gezeigt:


Nachdem die gefüllte Linie verschwindet, fallen beide blauen Blöcke zwei Quadrate und bleiben dann stehen.

Hier ist der blaue Block in der Mitte gelandet - und da er mit dem blauen Block auf der rechten Seite zusammengeballt ist, gilt dieser auch als "gelandet". Der nächste Block würde laichen, und wieder schweben wir in der Luft in einem blauen Block.

Die Big Clump-Methode ist aufgrund dieses nicht intuitiven Problems eigentlich keine effektive Methode, aber es ist ist auf halbem Weg zu einer guten Methode…


Die klebrige Methode

Sehen Sie sich noch einmal diese beiden Beispiele an:

In beiden Fällen gibt es eine offensichtliche Möglichkeit, die blauen Blöcke in getrennte Klumpen zu unterteilen - zwei Klumpen (jeweils ein Block) im ersten und drei Klumpen (drei, vier und ein Block) im zweiten.

Wenn wir die Blöcke so zusammenklumpen und dann jeden Klumpen einzeln fallen lassen, sollten wir das gewünschte Ergebnis erzielen! Darüber hinaus erscheint "Büschel" nicht länger als Wort.

Das meine ich damit:

Wir beginnen mit dieser Situation. Offensichtlich wird das zweite Lineup gelöscht.

Wir teilen die Blöcke oberhalb der gelöschten Linie in drei verschiedene Klumpen auf. (Ich habe verschiedene Farben verwendet, um zu ermitteln, welche Blöcke sich zusammenklumpen.)

Die Büschel fallen unabhängig voneinander - Sie bemerken, wie der grüne Büschel zwei Reihen fällt, während die blauen und violetten Büschel landen, nachdem Sie nur eine gestürzt haben. Die untere Zeile ist nun gefüllt, so dass auch diese gelöscht wird und die drei Klumpen fallen.

Wie ermitteln wir die Form der Klumpen? Wie Sie aus dem Bild sehen können, ist es eigentlich ganz einfach: Wir fassen alle Blöcke zu zusammenhängenden Formen zusammen - das heißt, für jeden Block fassen wir ihn mit allen Nachbarn und Nachbarn seiner Nachbarn zusammen Ein, bis jeder Block in einer Gruppe ist.

Anstatt genau zu erklären, wie man diese Gruppierung ausführt, weise ich Sie auf die Wikipedia-Seite zum Thema Flood-Fill hin. Auf dieser Seite werden mehrere Möglichkeiten erläutert, wie dies erreicht werden kann, und die Vor- und Nachteile der einzelnen Elemente.

Sobald Sie die Formen Ihrer Büschel haben, können Sie sie in ein Array einfügen:

 Klumpen = []; Klumpen [0] .Shape = [[3], [3]]; Klumpen [0] .topLeft = Zeile: 11, Spalte: 0; Klumpen [1] .Form = [[0,1,0], [0,1,1], [0,1,1], [1,1,1]]; Klumpen [1] .topLeft = Zeile: 9, Spalte: 3; Klumpen [2] .shape = [[1,1,1], [1,1,1], [0,1,1]]; Klumpen [2] .topLeft = Zeile: 10, Spalte: 7;

Dann iterieren Sie einfach jeden Klumpen im Array, der herunterfällt. Denken Sie daran, nach der Landung nach neuen gefüllten Linien zu suchen.

Dies wird als Sticky-Methode bezeichnet und wird in einigen Spielen wie Tetris Blast verwendet. Ich mag das; Es ist eine ordentliche Wendung für Tetris, die neue Strategien ermöglicht. Es gibt eine andere populäre Methode, die sich ziemlich unterscheidet ...


Herausforderung: Die Kaskadenmethode

Wenn Sie bisher den Konzepten gefolgt sind, lohnt es sich, die Cascade-Methode selbst als Übung zu implementieren.

Grundsätzlich erinnert sich jeder Block an das Tetromino, an dem er beteiligt war, auch wenn ein Segment dieses Tetrominos durch eine freie Zeile zerstört wird. Die Tetrominos - oder seltsame, zerhackte Teile von Tetrominos - fallen als Klumpen zusammen.

Wie immer helfen Bilder:

Ein T-Tetromino fällt ab und vervollständigt eine Linie. Beachten Sie, wie jeder Block mit seinem ursprünglichen Tetromino verbunden bleibt. (Wir gehen hier davon aus, dass bisher keine Zeilen gelöscht wurden.)

Die vervollständigte Linie wird gelöscht, wodurch das grüne Z-Tetromino in zwei separate Teile geteilt wird und andere Tetrominoes abgeschnitten werden.

Das T-Tetromino (oder was davon übrig ist) fällt weiter, weil es nicht von anderen Blöcken aufgehalten wird.

Der T-Tetromino landet und vervollständigt eine weitere Linie. Diese Linie wird gelöscht, wodurch noch mehr Tetrominos abgebrochen werden.

Wie Sie sehen, spielt die Cascade-Methode einiges anders als die beiden anderen Hauptmethoden. Wenn Sie noch nicht wissen, wie es funktioniert, sollten Sie eine Kopie von Quadra oder Tetris 2 finden (oder Videos auf YouTube nachschlagen), da beide diese Methode verwenden.

Viel Glück!


Fazit

Vielen Dank für das Lesen dieses Tutorials! Ich hoffe, dass Sie etwas gelernt haben (und nicht nur über Tetris), und dass Sie die Herausforderung anstreben. Wenn Sie Spiele mit diesen Techniken erstellen, würde ich sie gerne sehen! Bitte posten Sie sie in den Kommentaren unten oder twittern Sie mich an @MichaelJW.

.