Tetris - Grundlegende Ideen zur Vorgehensweise bezüglich Kollsionserkennung gesucht

Hi,

Ich überlege einen Tetris Ableger zu programmieren. Ich halte das ganze für machbar aber das einzige wo ich nicht weiß, wie ich das wirklich umsetzen soll, ist die Kollisionserkennung der Spielsteine.

Ein Tetris, welches nur diese Viereckigen Spielfiguren hat, wäre kein Problem. Wie aber setzt man das ganze mit den restlichen Spielfiguren wie z.B. dem “T” um? Spätestens hier fällt die Möglichkeit, die Spielfiguren als Bilddatei einzubauen weg, da Bilddateien ja letzendlich immer Rechteckig sind und keine “T” Form aufweisen. Somit muss ich also meine Spielfiguren selber zeichnen. Mein bisher einziger Ansatz ist, dass ich jede Spielfigur aus mehreren kleinen Viereckigen Blöcken zusammensetze.

Beispiel:

  • Die Viereckige Spielfigur würde aus 4 kleineren Vierecken bestehen.
  • Die lange Rechtekige Spielfigur “|” würde ich aus 8 übereinander zusammengesetzten kleineren Vierecken zusammenbauen.
  • Die “T” Figur und die restlichen entsprechend genauso.

Ich würde also eine abstrakte Klasse Spielfigur erstellen die eine Liste vom Typ Rectangle beinhaltet, welche z.b. von der Klasse “|-Spielfigur” erweitert wird und seine Liste dann 8 Rectangles beinhaltet, mit derartigen Koordinaten, dass diese eine “|”-Figur erzeugen.

Wäre das die richtige Vorgehensweise oder denke ich schon wieder viel zu kompliziert?

Es klingt zumindest zu kompliziert. Erstmal sollten die Blöcke als “Modell” des Spiels keinen Bezug zu Bilddateien haben. Aber wichtiger: Dort wird keine echte “Kollisionserkennung” gemacht. Es ist eben NICHT so, dass man einen Block hat, und dann eine Schleife wie


while (noOtherBlockIntersects(currentBlock)) currentBlock.y--;

mit der der Block nach unten bewegt wird, bis eine Überschneidung erkannt wird. Man hat ja von vornherein nur ein sehr grobes Gitter, und die Blöcke bewegen sich immer in Schritten (eben immer eine Zelle des Gitters). Auch wenn mein bisher einziger “Versuch”, mal was Tetris-artiges zu schreiben, sehr früh im Sand verlaufen ist, bei dem Versuch, es in bezug auf bestimmte Punkte besonders “flexibel” zu machen (speziell die Möglichen Formen der Steine, die Anzahl der Blöcke pro Stein, und die Frage, um welchen Punkt die Steine rotiert werden können), würde ich sagen, dass man eher sowas haben wird wie


solange (Stein nicht aufliegt)
{
    für (Jeden Block des Steines)
    {
        x = block.x;
        y = block.y;
        wenn (bei (x, y+1) ein schon "gefallener" Block liegt)
        {
            Dann liegt der Stein jetzt auf, d.h. alle seine Blöcke sind jetzt auch "gefallen"
            return;
        }
    }
    bewege den Stein 1 nach unten
}

Das ist NUR sehr suggestiver Pseudocode. Das Kritische (die Drehung) kommt da noch gar nicht vor, und ich denke, dass das frickelig werden kann, und man sich vorher genau überlegen sollte, wie man das umsetzt. Aber der entscheidende Punkt ist: Es werden keine Rechtecke auf Überschneidungen getestet oder so…

Bis auf die abstrakte Klasse ist das alles in Ordnung würde ich sagen.
Du hast halt ein Spielfeld, das in ein Raster aufgeteilt ist. Ein Rasterkästchen entspricht der Größe eines “Rectangle” deines Spielsteins (welche Form auch immer).
Wir nennen diese “Rectangle” mal Tiles.
Ich wüsste jetzt nicht, warum dein Spielstein eine Vererbung benötigt, eher sollte so ein Spielstein eine Liste von Tiles halten, die die Form des Steins representieren.
diese Steine dann ins Model und auf Basis des Models wird dann gezeichnet.

Gibt da sicherlich viele Philosophien, aber auf den ersten Blick würde ich in diese Richtung gehen.

Edit:

Die Abfrage der Kollision sollte der Spielstein dann übernehmen, wie in Marcos Beispiel schon angeschnitten.

Das Ganze ließe sich auch mit 'nem “BitSet-Image” und “2-Byte-Bitmaps” pro Spielstein und Lage realisieren, zumindest beim original Tetris-Spielstein-Set. Wenn ich noch wüsste wie das ging… zumindest bin ich durch so etwas darauf gekommen, Kollisionen direkt aus dem (eigenes implementierten) SampleModel eines BufferedImages per Event zu melden. Den Aufbau eines solchen Images (Playfield) hatte ich schon mal hier gepostet. Das müsste man nur noch so umbauen, dass nur eine Hintergrundfarbe übergeben wird und im SampleModel ein Event geworfen oder ein Observer informiert wird statt einen Zähler zu erhöhen.

Hallo,

ich habe als Übung auch mal Tetris programmiert. Bestimmt nicht sehr professionell, aber es hat alles funktioniert. Ich habe dazu ein 2-dimensionales Char-Array als “Spielfeld” genommen. Anhand des Chars wurde ermittelt, welche Farbe das Feld hat und dementsprechend dort gezeichnet werden muss (einfaches Rectangle). Dann gab es noch eine abstrakte Klasse Stein, deren Subklassen (SteinL, SteinI) getter und setter für Koordinaten (int), Farbe (= Char, z.B. L für den L-Stein) und Form (2-dim. Char-Array) implementieren mussten. Ein Thread hat dann immer dafür gesorgt, dass von einer Factory ein zufälliger Stein kommt, der eins nach unten gesetzt wird. Wenn Collision, dann wird Stein in das Spielfeld “persistiert” und durch einen neuen Stein aus der Factory ersetzt.

Ein Spielfeld könnte so Aussehen (X=leeres Feld):

XXXXXX
XXXXXX
XXXXXX
IXXXXJ
IXXXXJ
IXXXJJ
IXIIII

Der J-Stein:


XXXX
XXXJ
XXXJ
XXJJ

Das mal so als Anregung, der Rest fällt dir bestimmt einfacher. Ist natürlich nicht die beste Lösung, aber die erstbeste die mir eingefallen ist damals. Und als Anfänger fand ich das einfach umzusetzen. Logik und Oberfläche werden auch strikt getrennt auf die Weise.

[QUOTE=Marco13]Aber wichtiger: Dort wird keine echte “Kollisionserkennung” gemacht. Es ist eben NICHT so, dass man einen Block hat, und dann eine Schleife wie


while (noOtherBlockIntersects(currentBlock)) currentBlock.y--;

mit der der Block nach unten bewegt wird, bis eine Überschneidung erkannt wird.


solange (Stein nicht aufliegt)
{
    für (Jeden Block des Steines)
    {
        x = block.x;
        y = block.y;
        wenn (bei (x, y+1) ein schon "gefallener" Block liegt)
        {
            Dann liegt der Stein jetzt auf, d.h. alle seine Blöcke sind jetzt auch "gefallen"
            return;
        }
    }
    bewege den Stein 1 nach unten
}

[/QUOTE]

Du sagst am Anfang deines Posts, dass man keine (echte) Kollisionserkennung braucht, aber in deinem Pseudocode stehen dann Methoden wie “solange (Stein nicht aufliegt)” oder " wenn (bei (x, y+1) ein schon “gefallener” Block liegt)" wo man dann doch letzendlich aber doch um eine Kollsionserkennung nicht drum herum kommt, oder? Ich würde es vielleicht in etwa wie in deinem Pseudocode umsetzen, aber eben MIT Kollsionserkeuung, da ich nicht wüsste, wie sonst.

@ Rest:
Hier ist die Rede von einem Gitter. Meint ihr eher ein “virtuelles eigentlich nicht vorhandenes Gitter”, welches dadurch ensteht, dass ich die Spielfiguren alle um den selben Abstand verschiebe? Oder wollt ihr wirklich ein richtiges Gitter einbauen? Falls ja, wie?

@ peatear:
Ein Array will ich eher vermeiden. Das fände ich irgendwie zu statisch und festgefahren…

[QUOTE=Jack159]@ Rest:
Hier ist die Rede von einem Gitter. Meint ihr eher ein “virtuelles eigentlich nicht vorhandenes Gitter”, welches dadurch ensteht, dass ich die Spielfiguren alle um den selben Abstand verschiebe? Oder wollt ihr wirklich ein richtiges Gitter einbauen? Falls ja, wie?[/QUOTE]Ich für meinen Teil spreche eher von einem Raster und zwar von einem, welches definitiv auch als Klasse vorhanden ist. Wenn ich mehr Zeit hätte, würd’ ich auch ein weiteres Beispiel implementieren, aber ich häng’ zur Zeit relativ perplex an eigenen Problemen fest (JPEG-Image-Reader… OMG).

[QUOTE=Jack159]
Ein Array will ich eher vermeiden. Das fände ich irgendwie zu statisch und festgefahren…[/QUOTE]

o0 also Tetris ohne ein Array is iwie suspekt, findste nich!?
Was´n daran festgefahren? du hast ein festes Spielraster (oder wie auch immer du das nennen möchtest) x Reihen in die Breite und y Reihen in die Höhe.
Array[x][y] passt da wie die Faust auf´s Auge.
Ich würde an deiner Stelle das Rad nicht neu erfinden und wenn doch, dann wenigstens nich eckig::umleitung

Kollisionserkennung ist kein streng definierter Begriff, aber ich denke dabei eher an Überschneidungstests zwischen geometrischen Objekten, in 2D oder 3D, mit all den ausgefeilten Datenstrukturen, die es da rundherum so gibt, und nicht an die Abfrage „steht in diesem Array an dieser Stelle schon etwas“, aber … wenn man will, kann man das natürlich so nennen. War ja auch nur ein spontaner Gedanke.

Das sieht dann am Ende so aus:

https://blogs.oracle.com/mgorshenev/entry/broken_tetris

Jetzt habe ich verstanden, wie du das meintest :wink:

Natürlich ist eine echte Kollisionserkennung bei Tetris schon „zuviel“ Kollisionserkennung, denn eigentlich kollidiert ja nichts. Ich hätte es so wie in deinem Pseudocode gemeint.
Die Methode „wenn (bei (x, y+1) ein schon „gefallener“ Block liegt)“ wäre dann eine Kollisionsprüfung für den nächsten Schritt, bevor die Koordinaten der Spielfigur schon weitergesetzt werden.

Nur das du diese abfrage ja nur alle x pixel machen musst, wobei x die hoehe der bloecke ist