Spezifikation des Projekts (Tetris)
Dieses Dokument enthält die funktionale Spezifikation des Projekts. Bitte beachten Sie auch das Bewertungsschema für das Projekt, wo die technischen Anforderungen (Unit Tests, Doku, etc...) genauer aufgeführt sind.
Es gab im Laufe der Jahrzehnte viele Versionen von Tetris mit subtilen Unterschieden in der Spielmechanik. Sie sollen im Wesentlichen die Version für das Nintendo Entertainment System (NES) von 1989 nachimplementieren, siehe https://tetris.wiki/Tetris_(NES,_Nintendo). Von einigen sehr speziellen Besonderheiten weichen wir dabei ab, um die Implementierung nicht unnötig zu verkomplizieren.
Vorlagen
Wie für das Ü10, stellen wir Ihnen auch für das Projekt wieder eine geeignete Klasse TerminalManager zur Verfügung. Sie finden die Klasse unter https://daphne.informatik.uni-freiburg.de/ss2024/ProgrammierenCplusplus/svn/public/code/projekt . Achtung: Es ist eine leichte Variation der Klasse für das Ü10, die es Ihnen erlaubt, bei der Erzeugung eine (fast) beliebige Menge von Farben zu definieren. Ansonsten ermöglich es Ihnen die Klasse wie gehabt, einen einzelnen "Pixel" oder einen Text an eine beliebige Position auf den Bildschirm zu malen.
Detaillierte Anforderungen für volle Punktzahl
1. Es gibt sieben verschiedene Tetrominos, ein I-förmiges (genannt: I), zwei L-förmige (genannt: L und J), zwei Z-förmige (genannt: Z und S), ein quadratisches (genannt: O) und ein T-förmiges (genannt: T). Jedes davon besteht aus genau vier Quadraten. Jedes Tetromino sollte eine eigene Farbe haben. Welche, ist Ihnen überlassen.
2. Das Spielfeld (der Teil, in dem die Tetrominos fallen) hat 10 Spalten und 20 Zeilen. Jedes Tetromino startet so, dass sich das oberste Quadrat des Tetrominos in der obersten Zeile befindet. Die Startrotation ist immer gleich und im Nintendo Rotation System beschrieben (Die am weitesten Links stehende Rotation ist jeweils die Startrotation). Über der obersten Zeile gibt es zwei zusätzliche Zeilen für den Fall, dass ein Tetromino gleich beim Start roriert wird und über die oberste Zeile hinausragt. Im Originalspiel wir der herausragende Teil nicht gezeichnet. Sie können es in Ihrem Programm auch so machen, oder einfach zeichnen.
3. Es sollte einen Rand um das Spielfeld geben (zumindest links, rechts und unten; ob auch oben, ist Ihnen überlassen). Der Rand sollte eine andere Farbe haben als die Tetrominos.
4. Jedes neue Tetromino wird zufällig aus der Menge der sieben möglichen Tetrominos gewählt. Ist das Tetromino dabei das gleiche wie bei der vorherigen Wahl, wird noch einmal zufällig gewählt. Ist es dann wieder das gleiche, ist das halt so und es wird ausgewählt. Die Wahrscheinlichkeit, zweimal hintereinander das gleiche Tetromino zu bekommen ist also 1/49.
5. Ein Tetromino fällt mit einer bestimmten Geschwindigkeit, die vom Level abhängt (zu den Levels, siehe Punkt X). Mit der Pfeiltaste nach unten bewegt man das Tetromino unabhänig von der Geschwindigkeit eine Zeile weiter nach unten. Ein Tetromino fällt solange, bis es die tiefstmögliche Zeile erreicht (entweder die allerunterste Zeile oder die unterste Zeile, in der es nicht mit einem der bereits liegenden Tetrominos kollidiert). Wenn es diese Zeile erreicht, gilt es noch solange als fallend, bis es es gemäß seiner Fallgeschwindigkeit in die nächste Zeile fallen würde (wenn es ginge).
6. Solange ein Tetromino noch fällt, kann es nach links (mit der Pfeiltaste nach links) oder nach rechts (mit der Pfeiltaste nach rechts) verschoben werden. Voraussetzung ist, dass sich das Tetromino in der neuen Position weder mit dem Rand noch mit den schon liegenden Tetrominos überschneidet.
7. Solange ein Tetromino noch fällt, kann es außerdem nach links (mit Taste 'a') oder nach rechts (mit Taste 's') rotiert werden. Voraussetzung ist, dass sich das Tetromino in der neuen Position weder mit dem Rand noch mit den schon liegenden Tetrominos überschneidet. Wie genau jedes Tetromino von seiner bisherigen in die neue Position rotiert wird, ist im Nintendo Rotation System beschrieben. Sie sollen die right-handed Version implementieren (obere Tabelle). Für alle Tetrominos, die eine mittlere Zelle haben, ist das eine natürliche Rotation um 90 Grad, für das I-Tetromino beachten Sie bitte die Spezifikation genau.
8. Sobald eine Zeiles des Spielfeldes ganze mit Quadraten von Tetrominos gefüllt ist, wird sie gelöscht. Alle Quadrate von bereits liegenden Tetrominos darüber fallen dann um eins herunter. Es kann vorkommen, dass nach dem Untenankommen eines Tetrominos bis zu vier Zeilen auf einmal gefüllt sind und gelöscht werden.
9. Rechts vom Spielfeld wird, zusammen mit dem Schriftzug "Next", das Tetromino anzeigt, das als nächstes ausgewählt werden wird.
10. Es gibt verschiedene Level. Der erste Level ist Level 0. Der Level wird über dem Spielfeld angezeigt. Sobald zehn Zeilen oder mehr gelöscht sind, kommt man von Level i zu Level i+1. Falls mehr als zehn Zeilen gelöscht wurden, zählen die überschüssigen Zeilen schon für den nächsten Level. Die Level unterscheiden sich ausschließlich durch die Geschwindigkeit, mit der die Tetrominos fallen. Die Geschwindigkeiten sind in der ersten Tabelle im Abschnitt "Details" von https://tetris.wiki/Tetris_(NES,_Nintendo) definiert. Ein Frame ist dabei 1/60 Sekunde. Die Geschwindigkeit von 48 Frames per Gridcell für Level 0 entspricht also einer Fallgeschwindigkeit von 60/48 = 1.25 Zeilen pro Sekunde. Ab Level 29 sind es 60 Zeilen pro Sekunde, also ziemlich schnell.
11. Es gibt einen Punktestand, der rechts oben neben dem Spielfeld angezeigt wird. Zu Beginn ist der Punktestand 0. Wenn nach dem Fall eines Tetrominos k Zeilen gelöscht werden (für k = 1, 2, 3 oder 4) erhöht sich die Punktzahl auf Level i wie folgt: 40 * (i+1) für k = 1, 100 * (i+1) für k = 2, 300 * (i+1) für k = 3, 1200* (i+1) für k = 4. Jedes mal, wenn man ein Tetromino mit Pfeiltaste nach unten schneller fallen lässt, gibt es einen zusätzlichen Punkt (unabhängig vom Level).
12. Es sollte die folgenden Kommandozeilenparameter geben: für den Einstiegslevel (default: Level 0), für die Tasten zur Rotation des Tetrominos (default: a und s, siehe oben).
Tipps zum Testen von Zeit und Userinteraktion
Im Bewertungsschma für das Projekt steht "Es muss für jede nicht-triviale Funktion (außer Einlesen der Tasteneingabe) einen Unit-Test geben". Das heißt, Sie müssen auch testen, dass z.B. das Tetromino mit der korrekten Geschwindigkeit fällt und auf die entsprechenden Tastendrucke richtig reagiert wird. Das ist auf den ersten Blick etwas schwierig, deswegen gibt es hier ein paar Tipps:
0. Schreiben Sie zuerst die einfacheren Unit Tests und die Grundfunktionalität des Programms, bevor Sie sich hier verzetteln. 1. Fügen Sie ihren Klassen Hilfsfunktionen hinzu, die es erlauben, manuell einen genauen Zustand des Spiels herzustellen, sodass Sie einen wohldefinierten Startzustand haben.
2. Schreiben Sie die Hauptschleife in der 'play' Funktion so, dass se nur eine gewisse Zeit läuft (also die maximale Zeit als Argument bekommt). Diese können Sie dann testen, die eigentliche Spielfunktion ruft dann nur in Endlosschleife diese Implementierung auf, und ist damit trivial.
3. Schreiben Sie für den TerminalManager eine Funktion, die auf den nächsten UserInput wartet, aber höchstens für eine gewisse Zeit, und den UserInput(falls vorhanden) und die tatsächlich gewartete Zeit zurückgibt. Diese Funktion können Sie dann mocken (dem MockTerminalManager können Sie damit vorgeben, nach welcher "Zeit", er welche Tastendrücke simulieren soll.
4. Fügen Sie die Ergebnisse von 1. 2. und 3. so zusammen, dass Sie so Dinge testen können wie "Das Tetromino fällt mit der richtigen Geschwindigkeit, auch wenn mitten in einem Zeitschritt eine Rotation oder verschiebung durchgeführt wurde".
5. Bei Rückfragen wenden Sie sich gerne ans Forum, und beachten Sie bitte auch Punkt 0.
Ideen für optionale Zusatzfeatures
1. Ein Highscore, der persistent gespeichert wird. Das heißt, wenn man das Programm neu aufruft, kennt es den Highscore vom vorherigen Aufruf (durch Schreiben und Lesen einer geeigneten Datei).
2. Eine Statistik über die Tetrominos, die bereits ausgewählt wurden. Im Originalspiel wird diese Statistik links vom Spielfel angezeigt.
3. Ein sogenannter wall kick. Das heißt, wenn eine Rotation nach den oben beschrieben Regeln nicht ausgeführt wird, weil das Tetromino zu nah an einem der beiden Ränder ist, wird es automatisch so weit vom Rand wegbewegt, dass die Rotation ausgeführt werden kann. Das geht natürlich nur, wenn es bei dieser automatischen Bewegung nicht mit anderen, schon liegenden Tetrominos kollidiert.
4. Ein sogenannter hard drop, bei der das aktuelle Tetromino nicht nur schneller fällt, sondern sofort in unterstmögliche Zeile fällt. Dafür gibt es Extrapunkte, wie viele, können Sie sich selber überlegen.
5. Das Anzeigen sogenannter ghost pieces. Dabei wird, zusätzlich zum fallenden Tetromino, der "Umriss" des Tetrominos angezeigt in der Position, wenn man einen hard drop ausführen würden. Wie genau Sie diesen "Umriss" anzeigen (zum Beispiel durch heller malen) und wie Sie damit umgehen, wenn sich der Umriss mit der aktuellen Position des Tetrominos überschneidet, ist Ihnen überlassen.
5. Was immer Ihnen sonst noch so einfällt ...